PageRenderTime 33ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/poedit-1.4.6.1/src/catalog.cpp

#
C++ | 1771 lines | 1379 code | 273 blank | 119 comment | 330 complexity | bd9b9aed6566bf77d07bd275d1cdb2f7 MD5 | raw file
Possible License(s): LGPL-2.0

Large files files are truncated, but you can click here to view the full file

  1. /*
  2. * This file is part of Poedit (http://www.poedit.net)
  3. *
  4. * Copyright (C) 1999-2007 Vaclav Slavik
  5. *
  6. * Permission is hereby granted, free of charge, to any person obtaining a
  7. * copy of this software and associated documentation files (the "Software"),
  8. * to deal in the Software without restriction, including without limitation
  9. * the rights to use, copy, modify, merge, publish, distribute, sublicense,
  10. * and/or sell copies of the Software, and to permit persons to whom the
  11. * Software is furnished to do so, subject to the following conditions:
  12. *
  13. * The above copyright notice and this permission notice shall be included in
  14. * all copies or substantial portions of the Software.
  15. *
  16. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  21. * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  22. * DEALINGS IN THE SOFTWARE.
  23. *
  24. * $Id$
  25. *
  26. * Translations catalog
  27. *
  28. */
  29. #include <wx/wxprec.h>
  30. #include <stdio.h>
  31. #include <wx/utils.h>
  32. #include <wx/tokenzr.h>
  33. #include <wx/log.h>
  34. #include <wx/intl.h>
  35. #include <wx/datetime.h>
  36. #include <wx/config.h>
  37. #include <wx/textfile.h>
  38. #include <wx/strconv.h>
  39. #include <wx/msgdlg.h>
  40. #include <wx/filename.h>
  41. #include <set>
  42. #include "catalog.h"
  43. #include "digger.h"
  44. #include "gexecute.h"
  45. #include "progressinfo.h"
  46. #include "summarydlg.h"
  47. #include "isocodes.h"
  48. // ----------------------------------------------------------------------
  49. // Textfile processing utilities:
  50. // ----------------------------------------------------------------------
  51. // Read one line from file, remove all \r and \n characters, ignore empty lines
  52. static wxString ReadTextLine(wxTextFile* f)
  53. {
  54. wxString s;
  55. while (s.empty())
  56. {
  57. if (f->Eof()) return wxEmptyString;
  58. // read next line and strip insignificant whitespace from it:
  59. s = f->GetNextLine().Strip(wxString::both);
  60. }
  61. return s;
  62. }
  63. // If input begins with pattern, fill output with end of input (without
  64. // pattern; strips trailing spaces) and return true. Return false otherwise
  65. // and don't touch output. Is permissive about whitespace in the input:
  66. // a space (' ') in pattern will match any number of any whitespace characters
  67. // on that position in input.
  68. static bool ReadParam(const wxString& input, const wxString& pattern, wxString& output)
  69. {
  70. if (input.size() < pattern.size())
  71. return false;
  72. unsigned in_pos = 0;
  73. unsigned pat_pos = 0;
  74. while (pat_pos < pattern.size() && in_pos < input.size())
  75. {
  76. const wxChar pat = pattern[pat_pos++];
  77. if (pat == _T(' '))
  78. {
  79. if (!wxIsspace(input[in_pos++]))
  80. return false;
  81. while (wxIsspace(input[in_pos]))
  82. {
  83. in_pos++;
  84. if (in_pos == input.size())
  85. return false;
  86. }
  87. }
  88. else
  89. {
  90. if (input[in_pos++] != pat)
  91. return false;
  92. }
  93. }
  94. if (pat_pos < pattern.size()) // pattern not fully matched
  95. return false;
  96. output = input.Mid(in_pos).Strip(wxString::trailing);
  97. return true;
  98. }
  99. static bool ReadParamIfNotSet(const wxString& input,
  100. const wxString& pattern, wxString& output)
  101. {
  102. wxString dummy;
  103. if (ReadParam(input, pattern, dummy))
  104. {
  105. if (output.empty())
  106. output = dummy;
  107. return true;
  108. }
  109. return false;
  110. }
  111. // Checks if the file was loaded correctly, i.e. that non-empty lines
  112. // ended up non-empty in memory, after doing charset conversion in
  113. // wxTextFile. This detects for example files that claim they are in UTF-8
  114. // while in fact they are not.
  115. bool VerifyFileCharset(const wxTextFile& f, const wxString& filename,
  116. const wxString& charset)
  117. {
  118. wxTextFile f2;
  119. if (!f2.Open(filename, wxConvISO8859_1))
  120. return false;
  121. if (f.GetLineCount() != f2.GetLineCount())
  122. {
  123. wxLogError(_("%i lines of file '%s' were not loaded correctly."),
  124. (int)f2.GetLineCount() - (int)f.GetLineCount(),
  125. filename.c_str());
  126. return false;
  127. }
  128. bool ok = true;
  129. size_t cnt = f.GetLineCount();
  130. for (size_t i = 0; i < cnt; i++)
  131. {
  132. if (f[i].empty() && !f2[i].empty()) // wxMBConv conversion failed
  133. {
  134. wxLogError(
  135. _("Line %u of file '%s' is corrupted (not valid %s data)."),
  136. i, filename.c_str(), charset.c_str());
  137. ok = false;
  138. }
  139. }
  140. return ok;
  141. }
  142. // converts \n into newline character and \\ into \:
  143. static wxString UnescapeCEscapes(const wxString& str)
  144. {
  145. wxString out;
  146. size_t len = str.size();
  147. size_t i;
  148. if ( len == 0 )
  149. return str;
  150. for (i = 0; i < len-1; i++)
  151. {
  152. if (str[i] == _T('\\'))
  153. {
  154. switch ((wxChar)str[i+1])
  155. {
  156. case _T('n'):
  157. out << _T('\n');
  158. i++;
  159. break;
  160. case _T('\\'):
  161. out << _T('\\');
  162. i++;
  163. break;
  164. case _T('"'):
  165. out << _T('"');
  166. i++;
  167. break;
  168. default:
  169. out << _T('\\');
  170. }
  171. }
  172. else
  173. {
  174. out << str[i];
  175. }
  176. }
  177. // last character:
  178. if (i < len)
  179. out << str[i];
  180. return out;
  181. }
  182. // ----------------------------------------------------------------------
  183. // Catalog::HeaderData
  184. // ----------------------------------------------------------------------
  185. void Catalog::HeaderData::FromString(const wxString& str)
  186. {
  187. wxString hdr(str);
  188. hdr = UnescapeCEscapes(hdr);
  189. wxStringTokenizer tkn(hdr, _T("\n"));
  190. wxString ln;
  191. m_entries.clear();
  192. while (tkn.HasMoreTokens())
  193. {
  194. ln = tkn.GetNextToken();
  195. size_t pos = ln.find(_T(':'));
  196. if (pos == wxString::npos)
  197. {
  198. wxLogError(_("Malformed header: '%s'"), ln.c_str());
  199. }
  200. else
  201. {
  202. Entry en;
  203. en.Key = wxString(ln.substr(0, pos)).Strip(wxString::both);
  204. en.Value = wxString(ln.substr(pos + 1)).Strip(wxString::both);
  205. m_entries.push_back(en);
  206. wxLogTrace(_T("poedit.header"),
  207. _T("%s='%s'"), en.Key.c_str(), en.Value.c_str());
  208. }
  209. }
  210. ParseDict();
  211. }
  212. wxString Catalog::HeaderData::ToString(const wxString& line_delim)
  213. {
  214. UpdateDict();
  215. wxString hdr;
  216. for (std::vector<Entry>::const_iterator i = m_entries.begin();
  217. i != m_entries.end(); i++)
  218. {
  219. wxString v(i->Value);
  220. v.Replace(_T("\\"), _T("\\\\"));
  221. v.Replace(_T("\""), _T("\\\""));
  222. hdr << i->Key << _T(": ") << v << _T("\\n") << line_delim;
  223. }
  224. return hdr;
  225. }
  226. void Catalog::HeaderData::UpdateDict()
  227. {
  228. SetHeader(_T("Project-Id-Version"), Project);
  229. SetHeader(_T("POT-Creation-Date"), CreationDate);
  230. SetHeader(_T("PO-Revision-Date"), RevisionDate);
  231. if (TranslatorEmail.empty())
  232. SetHeader(_T("Last-Translator"), Translator);
  233. else
  234. SetHeader(_T("Last-Translator"),
  235. Translator + _T(" <") + TranslatorEmail + _T(">"));
  236. if (TeamEmail.empty())
  237. SetHeader(_T("Language-Team"), Team);
  238. else
  239. SetHeader(_T("Language-Team"),
  240. Team + _T(" <") + TeamEmail + _T(">"));
  241. SetHeader(_T("MIME-Version"), _T("1.0"));
  242. SetHeader(_T("Content-Type"), _T("text/plain; charset=") + Charset);
  243. SetHeader(_T("Content-Transfer-Encoding"), _T("8bit"));
  244. // Set extended information:
  245. SetHeaderNotEmpty(_T("X-Poedit-Language"), Language);
  246. SetHeaderNotEmpty(_T("X-Poedit-Country"), Country);
  247. SetHeaderNotEmpty(_T("X-Poedit-SourceCharset"), SourceCodeCharset);
  248. if (!Keywords.empty())
  249. {
  250. wxString kw;
  251. for (size_t i = 0; i < Keywords.GetCount(); i++)
  252. kw += Keywords[i] + _T(';');
  253. kw.RemoveLast();
  254. SetHeader(_T("X-Poedit-KeywordsList"), kw);
  255. }
  256. unsigned i;
  257. bool noBookmarkSet = true;
  258. wxString bk;
  259. for (i = 0; i < BOOKMARK_LAST ; i++)
  260. {
  261. noBookmarkSet = noBookmarkSet && (Bookmarks[i] == NO_BOOKMARK);
  262. bk += wxString() << Bookmarks[i] << _T(',');
  263. }
  264. bk.RemoveLast();
  265. if (noBookmarkSet)
  266. DeleteHeader(_T("X-Poedit-Bookmarks"));
  267. else
  268. SetHeader(_T("X-Poedit-Bookmarks"), bk);
  269. SetHeaderNotEmpty(_T("X-Poedit-Basepath"), BasePath);
  270. i = 0;
  271. wxString path;
  272. while (true)
  273. {
  274. path.Printf(_T("X-Poedit-SearchPath-%i"), i);
  275. if (!HasHeader(path))
  276. break;
  277. DeleteHeader(path);
  278. i++;
  279. }
  280. for (i = 0; i < SearchPaths.GetCount(); i++)
  281. {
  282. path.Printf(_T("X-Poedit-SearchPath-%i"), i);
  283. SetHeader(path, SearchPaths[i]);
  284. }
  285. }
  286. void Catalog::HeaderData::ParseDict()
  287. {
  288. wxString dummy;
  289. Project = GetHeader(_T("Project-Id-Version"));
  290. CreationDate = GetHeader(_T("POT-Creation-Date"));
  291. RevisionDate = GetHeader(_T("PO-Revision-Date"));
  292. dummy = GetHeader(_T("Last-Translator"));
  293. if (!dummy.empty())
  294. {
  295. wxStringTokenizer tkn(dummy, _T("<>"));
  296. if (tkn.CountTokens() != 2)
  297. {
  298. Translator = dummy;
  299. TranslatorEmail = wxEmptyString;
  300. }
  301. else
  302. {
  303. Translator = tkn.GetNextToken().Strip(wxString::trailing);
  304. TranslatorEmail = tkn.GetNextToken();
  305. }
  306. }
  307. dummy = GetHeader(_T("Language-Team"));
  308. if (!dummy.empty())
  309. {
  310. wxStringTokenizer tkn(dummy, _T("<>"));
  311. if (tkn.CountTokens() != 2)
  312. {
  313. Team = dummy;
  314. TeamEmail = wxEmptyString;
  315. }
  316. else
  317. {
  318. Team = tkn.GetNextToken().Strip(wxString::trailing);
  319. TeamEmail = tkn.GetNextToken();
  320. }
  321. }
  322. wxString ctype = GetHeader(_T("Content-Type"));
  323. int charsetPos = ctype.Find(_T("; charset="));
  324. if (charsetPos != -1)
  325. {
  326. Charset =
  327. ctype.Mid(charsetPos + strlen("; charset=")).Strip(wxString::both);
  328. }
  329. else
  330. {
  331. Charset = _T("iso-8859-1");
  332. }
  333. // Parse extended information:
  334. Language = GetHeader(_T("X-Poedit-Language"));
  335. Country = GetHeader(_T("X-Poedit-Country"));
  336. SourceCodeCharset = GetHeader(_T("X-Poedit-SourceCharset"));
  337. BasePath = GetHeader(_T("X-Poedit-Basepath"));
  338. Keywords.Clear();
  339. wxString kw = GetHeader(_T("X-Poedit-KeywordsList"));
  340. if (!kw.empty())
  341. {
  342. wxStringTokenizer tkn(kw, _T(";"));
  343. while (tkn.HasMoreTokens())
  344. Keywords.Add(tkn.GetNextToken());
  345. }
  346. else
  347. {
  348. // try backward-compatibility version X-Poedit-Keywords. The difference
  349. // is the separator used, see
  350. // http://sourceforge.net/tracker/index.php?func=detail&aid=1206579&group_id=27043&atid=389153
  351. wxString kw = GetHeader(_T("X-Poedit-Keywords"));
  352. if (!kw.empty())
  353. {
  354. wxStringTokenizer tkn(kw, _T(","));
  355. while (tkn.HasMoreTokens())
  356. Keywords.Add(tkn.GetNextToken());
  357. // and remove it, it's not for newer versions:
  358. DeleteHeader(_T("X-Poedit-Keywords"));
  359. }
  360. }
  361. int i;
  362. for(i = 0; i < BOOKMARK_LAST; i++)
  363. {
  364. Bookmarks[i] = NO_BOOKMARK;
  365. }
  366. wxString bk = GetHeader(_T("X-Poedit-Bookmarks"));
  367. if (!bk.empty())
  368. {
  369. wxStringTokenizer tkn(bk, _T(","));
  370. i=0;
  371. long int val;
  372. while (tkn.HasMoreTokens() && i<BOOKMARK_LAST)
  373. {
  374. tkn.GetNextToken().ToLong(&val);
  375. Bookmarks[i] = val;
  376. i++;
  377. }
  378. }
  379. i = 0;
  380. wxString path;
  381. SearchPaths.Clear();
  382. while (true)
  383. {
  384. path.Printf(_T("X-Poedit-SearchPath-%i"), i);
  385. if (!HasHeader(path))
  386. break;
  387. SearchPaths.Add(GetHeader(path));
  388. i++;
  389. }
  390. }
  391. wxString Catalog::HeaderData::GetHeader(const wxString& key) const
  392. {
  393. const Entry *e = Find(key);
  394. if (e)
  395. return e->Value;
  396. else
  397. return wxEmptyString;
  398. }
  399. bool Catalog::HeaderData::HasHeader(const wxString& key) const
  400. {
  401. return Find(key) != NULL;
  402. }
  403. void Catalog::HeaderData::SetHeader(const wxString& key, const wxString& value)
  404. {
  405. Entry *e = (Entry*) Find(key);
  406. if (e)
  407. {
  408. e->Value = value;
  409. }
  410. else
  411. {
  412. Entry en;
  413. en.Key = key;
  414. en.Value = value;
  415. m_entries.push_back(en);
  416. }
  417. }
  418. void Catalog::HeaderData::SetHeaderNotEmpty(const wxString& key,
  419. const wxString& value)
  420. {
  421. if (value.empty())
  422. DeleteHeader(key);
  423. else
  424. SetHeader(key, value);
  425. }
  426. void Catalog::HeaderData::DeleteHeader(const wxString& key)
  427. {
  428. if (HasHeader(key))
  429. {
  430. Entries enew;
  431. for (Entries::const_iterator i = m_entries.begin();
  432. i != m_entries.end(); i++)
  433. {
  434. if (i->Key != key)
  435. enew.push_back(*i);
  436. }
  437. m_entries = enew;
  438. }
  439. }
  440. const Catalog::HeaderData::Entry *
  441. Catalog::HeaderData::Find(const wxString& key) const
  442. {
  443. size_t size = m_entries.size();
  444. for (size_t i = 0; i < size; i++)
  445. {
  446. if (m_entries[i].Key == key)
  447. return &(m_entries[i]);
  448. }
  449. return NULL;
  450. }
  451. // ----------------------------------------------------------------------
  452. // Parsers
  453. // ----------------------------------------------------------------------
  454. bool CatalogParser::Parse()
  455. {
  456. if (m_textFile->GetLineCount() == 0)
  457. return false;
  458. wxString line, dummy;
  459. wxString mflags, mstr, msgid_plural, mcomment;
  460. wxArrayString mrefs, mautocomments, mtranslations;
  461. wxArrayString msgid_old;
  462. bool has_plural = false;
  463. bool has_context = false;
  464. wxString msgctxt;
  465. unsigned mlinenum;
  466. line = m_textFile->GetFirstLine();
  467. if (line.empty()) line = ReadTextLine(m_textFile);
  468. while (!line.empty())
  469. {
  470. // ignore empty special tags (except for automatic comments which we
  471. // DO want to preserve):
  472. while (line == _T("#,") || line == _T("#:") || line == _T("#|"))
  473. line = ReadTextLine(m_textFile);
  474. // flags:
  475. // Can't we have more than one flag, now only the last is kept ...
  476. if (ReadParam(line, _T("#, "), dummy))
  477. {
  478. mflags = _T("#, ") + dummy;
  479. line = ReadTextLine(m_textFile);
  480. }
  481. // auto comments:
  482. if (ReadParam(line, _T("#. "), dummy) || ReadParam(line, _T("#."), dummy)) // second one to account for empty auto comments
  483. {
  484. mautocomments.Add(dummy);
  485. line = ReadTextLine(m_textFile);
  486. }
  487. // references:
  488. else if (ReadParam(line, _T("#: "), dummy))
  489. {
  490. // A line may contain several references, separated by white-space.
  491. // Each reference is in the form "path_name:line_number"
  492. // (path_name may contain spaces)
  493. dummy = dummy.Strip(wxString::both);
  494. while (!dummy.empty())
  495. {
  496. size_t i = 0;
  497. while (i < dummy.length() && dummy[i] != _T(':')) { i++; }
  498. while (i < dummy.length() && !wxIsspace(dummy[i])) { i++; }
  499. mrefs.Add(dummy.Left(i));
  500. dummy = dummy.Mid(i).Strip(wxString::both);
  501. }
  502. line = ReadTextLine(m_textFile);
  503. }
  504. // previous msgid value:
  505. else if (ReadParam(line, _T("#| "), dummy))
  506. {
  507. msgid_old.Add(dummy);
  508. line = ReadTextLine(m_textFile);
  509. }
  510. // msgctxt:
  511. else if (ReadParam(line, _T("msgctxt \""), dummy))
  512. {
  513. has_context = true;
  514. msgctxt = dummy.RemoveLast();
  515. while (!(line = ReadTextLine(m_textFile)).empty())
  516. {
  517. if (line[0u] == _T('\t'))
  518. line.Remove(0, 1);
  519. if (line[0u] == _T('"') && line.Last() == _T('"'))
  520. msgctxt += line.Mid(1, line.Length() - 2);
  521. else
  522. break;
  523. }
  524. }
  525. // msgid:
  526. else if (ReadParam(line, _T("msgid \""), dummy))
  527. {
  528. mstr = dummy.RemoveLast();
  529. mlinenum = m_textFile->GetCurrentLine() + 1;
  530. while (!(line = ReadTextLine(m_textFile)).empty())
  531. {
  532. if (line[0u] == _T('\t'))
  533. line.Remove(0, 1);
  534. if (line[0u] == _T('"') && line.Last() == _T('"'))
  535. mstr += line.Mid(1, line.Length() - 2);
  536. else
  537. break;
  538. }
  539. }
  540. // msgid_plural:
  541. else if (ReadParam(line, _T("msgid_plural \""), dummy))
  542. {
  543. msgid_plural = dummy.RemoveLast();
  544. has_plural = true;
  545. mlinenum = m_textFile->GetCurrentLine() + 1;
  546. while (!(line = ReadTextLine(m_textFile)).empty())
  547. {
  548. if (line[0u] == _T('\t'))
  549. line.Remove(0, 1);
  550. if (line[0u] == _T('"') && line.Last() == _T('"'))
  551. msgid_plural += line.Mid(1, line.Length() - 2);
  552. else
  553. break;
  554. }
  555. }
  556. // msgstr:
  557. else if (ReadParam(line, _T("msgstr \""), dummy))
  558. {
  559. if (has_plural)
  560. {
  561. wxLogError(_("Broken catalog file: singular form msgstr used together with msgid_plural"));
  562. return false;
  563. }
  564. wxString str = dummy.RemoveLast();
  565. while (!(line = ReadTextLine(m_textFile)).empty())
  566. {
  567. if (line[0u] == _T('\t'))
  568. line.Remove(0, 1);
  569. if (line[0u] == _T('"') && line.Last() == _T('"'))
  570. str += line.Mid(1, line.Length() - 2);
  571. else
  572. break;
  573. }
  574. mtranslations.Add(str);
  575. if (!OnEntry(mstr, wxEmptyString, false,
  576. has_context, msgctxt,
  577. mtranslations,
  578. mflags, mrefs, mcomment, mautocomments, msgid_old,
  579. mlinenum))
  580. {
  581. return false;
  582. }
  583. mcomment = mstr = msgid_plural = msgctxt = mflags = wxEmptyString;
  584. has_plural = has_context = false;
  585. mrefs.Clear();
  586. mautocomments.Clear();
  587. mtranslations.Clear();
  588. msgid_old.Clear();
  589. }
  590. // msgstr[i]:
  591. else if (ReadParam(line, _T("msgstr["), dummy))
  592. {
  593. if (!has_plural)
  594. {
  595. wxLogError(_("Broken catalog file: plural form msgstr used without msgid_plural"));
  596. return false;
  597. }
  598. wxString idx = dummy.BeforeFirst(_T(']'));
  599. wxString label = _T("msgstr[") + idx + _T("]");
  600. while (ReadParam(line, label + _T(" \""), dummy))
  601. {
  602. wxString str = dummy.RemoveLast();
  603. while (!(line=ReadTextLine(m_textFile)).empty())
  604. {
  605. line.Trim(/*fromRight=*/false);
  606. if (line[0u] == _T('"') && line.Last() == _T('"'))
  607. str += line.Mid(1, line.Length() - 2);
  608. else
  609. {
  610. if (ReadParam(line, _T("msgstr["), dummy))
  611. {
  612. idx = dummy.BeforeFirst(_T(']'));
  613. label = _T("msgstr[") + idx + _T("]");
  614. }
  615. break;
  616. }
  617. }
  618. mtranslations.Add(str);
  619. }
  620. if (!OnEntry(mstr, msgid_plural, true,
  621. has_context, msgctxt,
  622. mtranslations,
  623. mflags, mrefs, mcomment, mautocomments, msgid_old,
  624. mlinenum))
  625. {
  626. return false;
  627. }
  628. mcomment = mstr = msgid_plural = msgctxt = mflags = wxEmptyString;
  629. has_plural = has_context = false;
  630. mrefs.Clear();
  631. mautocomments.Clear();
  632. mtranslations.Clear();
  633. msgid_old.Clear();
  634. }
  635. // deleted lines:
  636. else if (ReadParam(line, _T("#~ "), dummy))
  637. {
  638. wxArrayString deletedLines;
  639. deletedLines.Add(line);
  640. mlinenum = m_textFile->GetCurrentLine() + 1;
  641. while (!(line = ReadTextLine(m_textFile)).empty())
  642. {
  643. // if line does not start with "#~ " anymore, stop reading
  644. if (!ReadParam(line, _T("#~ "), dummy))
  645. break;
  646. // if the line starts with "#~ msgid", we skipped an empty line
  647. // and it's a new entry, so stop reading too (see bug #329)
  648. if (ReadParam(line, _T("#~ msgid"), dummy))
  649. break;
  650. deletedLines.Add(line);
  651. }
  652. if (!OnDeletedEntry(deletedLines,
  653. mflags, mrefs, mcomment, mautocomments, mlinenum))
  654. {
  655. return false;
  656. }
  657. mcomment = mstr = msgid_plural = mflags = wxEmptyString;
  658. has_plural = false;
  659. mrefs.Clear();
  660. mautocomments.Clear();
  661. mtranslations.Clear();
  662. msgid_old.Clear();
  663. }
  664. // comment:
  665. else if (line[0u] == _T('#'))
  666. {
  667. bool readNewLine = false;
  668. while (!line.empty() &&
  669. line[0u] == _T('#') &&
  670. (line.Length() < 2 || (line[1u] != _T(',') && line[1u] != _T(':') && line[1u] != _T('.') )))
  671. {
  672. mcomment << line << _T('\n');
  673. readNewLine = true;
  674. line = ReadTextLine(m_textFile);
  675. }
  676. if (!readNewLine)
  677. line = ReadTextLine(m_textFile);
  678. }
  679. else
  680. {
  681. line = ReadTextLine(m_textFile);
  682. }
  683. }
  684. return true;
  685. }
  686. class CharsetInfoFinder : public CatalogParser
  687. {
  688. public:
  689. CharsetInfoFinder(wxTextFile *f)
  690. : CatalogParser(f), m_charset(_T("iso-8859-1")) {}
  691. wxString GetCharset() const { return m_charset; }
  692. protected:
  693. wxString m_charset;
  694. virtual bool OnEntry(const wxString& msgid,
  695. const wxString& msgid_plural,
  696. bool has_plural,
  697. bool has_context,
  698. const wxString& context,
  699. const wxArrayString& mtranslations,
  700. const wxString& flags,
  701. const wxArrayString& references,
  702. const wxString& comment,
  703. const wxArrayString& autocomments,
  704. const wxArrayString& msgid_old,
  705. unsigned lineNumber)
  706. {
  707. if (msgid.empty())
  708. {
  709. // gettext header:
  710. Catalog::HeaderData hdr;
  711. hdr.FromString(mtranslations[0]);
  712. m_charset = hdr.Charset;
  713. if (m_charset == _T("CHARSET"))
  714. m_charset = _T("iso-8859-1");
  715. return false; // stop parsing
  716. }
  717. return true;
  718. }
  719. };
  720. class LoadParser : public CatalogParser
  721. {
  722. public:
  723. LoadParser(Catalog *c, wxTextFile *f)
  724. : CatalogParser(f), m_catalog(c) {}
  725. protected:
  726. Catalog *m_catalog;
  727. virtual bool OnEntry(const wxString& msgid,
  728. const wxString& msgid_plural,
  729. bool has_plural,
  730. bool has_context,
  731. const wxString& context,
  732. const wxArrayString& mtranslations,
  733. const wxString& flags,
  734. const wxArrayString& references,
  735. const wxString& comment,
  736. const wxArrayString& autocomments,
  737. const wxArrayString& msgid_old,
  738. unsigned lineNumber);
  739. virtual bool OnDeletedEntry(const wxArrayString& deletedLines,
  740. const wxString& flags,
  741. const wxArrayString& references,
  742. const wxString& comment,
  743. const wxArrayString& autocomments,
  744. unsigned lineNumber);
  745. };
  746. bool LoadParser::OnEntry(const wxString& msgid,
  747. const wxString& msgid_plural,
  748. bool has_plural,
  749. bool has_context,
  750. const wxString& context,
  751. const wxArrayString& mtranslations,
  752. const wxString& flags,
  753. const wxArrayString& references,
  754. const wxString& comment,
  755. const wxArrayString& autocomments,
  756. const wxArrayString& msgid_old,
  757. unsigned lineNumber)
  758. {
  759. if (msgid.empty())
  760. {
  761. // gettext header:
  762. m_catalog->m_header.FromString(mtranslations[0]);
  763. m_catalog->m_header.Comment = comment;
  764. }
  765. else
  766. {
  767. CatalogItem d;
  768. if (!flags.empty()) d.SetFlags(flags);
  769. d.SetString(msgid);
  770. if (has_plural)
  771. d.SetPluralString(msgid_plural);
  772. if (has_context)
  773. d.SetContext(context);
  774. d.SetTranslations(mtranslations);
  775. d.SetComment(comment);
  776. d.SetLineNumber(lineNumber);
  777. for (size_t i = 0; i < references.GetCount(); i++)
  778. d.AddReference(references[i]);
  779. for (size_t i = 0; i < autocomments.GetCount(); i++)
  780. d.AddAutoComments(autocomments[i]);
  781. d.SetOldMsgid(msgid_old);
  782. m_catalog->AddItem(d);
  783. }
  784. return true;
  785. }
  786. bool LoadParser::OnDeletedEntry(const wxArrayString& deletedLines,
  787. const wxString& flags,
  788. const wxArrayString& references,
  789. const wxString& comment,
  790. const wxArrayString& autocomments,
  791. unsigned lineNumber)
  792. {
  793. CatalogDeletedData d;
  794. if (!flags.empty()) d.SetFlags(flags);
  795. d.SetDeletedLines(deletedLines);
  796. d.SetComment(comment);
  797. d.SetLineNumber(lineNumber);
  798. for (size_t i = 0; i < autocomments.GetCount(); i++)
  799. d.AddAutoComments(autocomments[i]);
  800. m_catalog->AddDeletedItem(d);
  801. return true;
  802. }
  803. // ----------------------------------------------------------------------
  804. // Catalog class
  805. // ----------------------------------------------------------------------
  806. Catalog::Catalog()
  807. {
  808. m_isOk = true;
  809. m_header.BasePath = wxEmptyString;
  810. for(int i = BOOKMARK_0; i < BOOKMARK_LAST; i++)
  811. {
  812. m_header.Bookmarks[i] = -1;
  813. }
  814. }
  815. Catalog::~Catalog()
  816. {
  817. Clear();
  818. }
  819. Catalog::Catalog(const wxString& po_file)
  820. {
  821. m_isOk = Load(po_file);
  822. }
  823. static wxString GetCurrentTimeRFC822()
  824. {
  825. wxDateTime timenow = wxDateTime::Now();
  826. int offs = wxDateTime::TimeZone(wxDateTime::Local).GetOffset();
  827. wxString s;
  828. s.Printf(_T("%s%s%02i%02i"),
  829. timenow.Format(_T("%Y-%m-%d %H:%M")).c_str(),
  830. (offs > 0) ? _T("+") : _T("-"),
  831. abs(offs) / 3600, (abs(offs) / 60) % 60);
  832. return s;
  833. }
  834. void Catalog::CreateNewHeader()
  835. {
  836. HeaderData &dt = Header();
  837. dt.CreationDate = GetCurrentTimeRFC822();
  838. dt.RevisionDate = dt.CreationDate;
  839. dt.Language = wxEmptyString;
  840. dt.Country = wxEmptyString;
  841. dt.Project = wxEmptyString;
  842. dt.Team = wxEmptyString;
  843. dt.TeamEmail = wxEmptyString;
  844. dt.Charset = _T("UTF-8");
  845. dt.Translator = wxConfig::Get()->Read(_T("translator_name"), wxEmptyString);
  846. dt.TranslatorEmail = wxConfig::Get()->Read(_T("translator_email"), wxEmptyString);
  847. dt.SourceCodeCharset = wxEmptyString;
  848. // NB: keep in sync with Catalog::Update!
  849. dt.Keywords.Add(_T("_"));
  850. dt.Keywords.Add(_T("gettext"));
  851. dt.Keywords.Add(_T("gettext_noop"));
  852. dt.BasePath = _T(".");
  853. dt.UpdateDict();
  854. }
  855. void Catalog::CreateNewHeader(const Catalog::HeaderData& pot_header)
  856. {
  857. HeaderData &dt = Header();
  858. dt = pot_header;
  859. // UTF-8 should be used by default no matter what the POT uses
  860. dt.Charset = _T("UTF-8");
  861. // clear the fields that are translation-specific:
  862. dt.Language.clear();
  863. dt.Country.clear();
  864. dt.Team.clear();
  865. dt.TeamEmail.clear();
  866. // translator should be pre-filled
  867. dt.Translator = wxConfig::Get()->Read(_T("translator_name"), wxEmptyString);
  868. dt.TranslatorEmail = wxConfig::Get()->Read(_T("translator_email"), wxEmptyString);
  869. dt.UpdateDict();
  870. }
  871. bool Catalog::Load(const wxString& po_file)
  872. {
  873. wxTextFile f;
  874. Clear();
  875. m_isOk = false;
  876. m_fileName = po_file;
  877. m_header.BasePath = wxEmptyString;
  878. /* Load the .po file: */
  879. if (!f.Open(po_file, wxConvISO8859_1))
  880. return false;
  881. CharsetInfoFinder charsetFinder(&f);
  882. charsetFinder.Parse();
  883. m_header.Charset = charsetFinder.GetCharset();
  884. f.Close();
  885. wxCSConv encConv(m_header.Charset);
  886. if (!f.Open(po_file, encConv))
  887. return false;
  888. if (!VerifyFileCharset(f, po_file, m_header.Charset))
  889. {
  890. wxLogError(_("There were errors when loading the catalog. Some data may be missing or corrupted as the result."));
  891. }
  892. LoadParser parser(this, &f);
  893. if (!parser.Parse())
  894. {
  895. wxLogError(
  896. wxString::Format(
  897. _("Couldn't load file %s, it is probably corrupted."),
  898. po_file.c_str()));
  899. return false;
  900. }
  901. // now that the catalog is loaded, update its items with the bookmarks
  902. for (unsigned i = BOOKMARK_0; i < BOOKMARK_LAST; i++)
  903. {
  904. if (m_header.Bookmarks[i] != -1 &&
  905. m_header.Bookmarks[i] < (int)m_items.size())
  906. {
  907. m_items[m_header.Bookmarks[i]].SetBookmark(
  908. static_cast<Bookmark>(i));
  909. }
  910. }
  911. m_isOk = true;
  912. f.Close();
  913. /* Load extended information from .po.poedit file, if present:
  914. (NB: this is deprecated, poedit >= 1.3.0 stores the data in
  915. .po file's header as X-Poedit-Foo) */
  916. if (wxFileExists(po_file + _T(".poedit")) &&
  917. f.Open(po_file + _T(".poedit"), wxConvISO8859_1))
  918. {
  919. wxString dummy;
  920. // poedit header (optional, we should be able to read any catalog):
  921. f.GetFirstLine();
  922. if (ReadParam(ReadTextLine(&f),
  923. _T("#. Number of items: "), dummy))
  924. {
  925. // not used anymore
  926. }
  927. ReadParamIfNotSet(ReadTextLine(&f),
  928. _T("#. Language: "), m_header.Language);
  929. dummy = ReadTextLine(&f);
  930. if (ReadParamIfNotSet(dummy, _T("#. Country: "), m_header.Country))
  931. dummy = ReadTextLine(&f);
  932. if (ReadParamIfNotSet(dummy, _T("#. Basepath: "), m_header.BasePath))
  933. dummy = ReadTextLine(&f);
  934. ReadParamIfNotSet(dummy, _T("#. SourceCodeCharSet: "), m_header.SourceCodeCharset);
  935. if (ReadParam(ReadTextLine(&f), _T("#. Paths: "), dummy))
  936. {
  937. bool setPaths = m_header.SearchPaths.empty();
  938. long sz;
  939. dummy.ToLong(&sz);
  940. for (; sz > 0; sz--)
  941. {
  942. if (ReadParam(ReadTextLine(&f), _T("#. "), dummy))
  943. {
  944. if (setPaths)
  945. m_header.SearchPaths.Add(dummy);
  946. }
  947. }
  948. }
  949. if (ReadParam(ReadTextLine(&f), _T("#. Keywords: "), dummy))
  950. {
  951. bool setKeyw = m_header.Keywords.empty();
  952. long sz;
  953. dummy.ToLong(&sz);
  954. for (; sz > 0; sz--)
  955. {
  956. if (ReadParam(ReadTextLine(&f), _T("#. "), dummy))
  957. {
  958. if (setKeyw)
  959. m_header.Keywords.Add(dummy);
  960. }
  961. }
  962. }
  963. f.Close();
  964. }
  965. return true;
  966. }
  967. void Catalog::AddItem(const CatalogItem& data)
  968. {
  969. m_items.push_back(data);
  970. }
  971. void Catalog::AddDeletedItem(const CatalogDeletedData& data)
  972. {
  973. m_deletedItems.push_back(data);
  974. }
  975. bool Catalog::HasDeletedItems()
  976. {
  977. return !m_deletedItems.empty();
  978. }
  979. void Catalog::RemoveDeletedItems()
  980. {
  981. m_deletedItems.clear();
  982. }
  983. void Catalog::Clear()
  984. {
  985. m_items.clear();
  986. m_deletedItems.clear();
  987. m_isOk = true;
  988. for(int i = BOOKMARK_0; i < BOOKMARK_LAST; i++)
  989. {
  990. m_header.Bookmarks[i] = -1;
  991. }
  992. }
  993. int Catalog::SetBookmark(int id, Bookmark bookmark)
  994. {
  995. int result = (bookmark==NO_BOOKMARK)?-1:m_header.Bookmarks[bookmark];
  996. // unset previous bookmarks, if any
  997. Bookmark bk = m_items[id].GetBookmark();
  998. if (bk != NO_BOOKMARK)
  999. m_header.Bookmarks[bk] = -1;
  1000. if (result > -1)
  1001. m_items[result].SetBookmark(NO_BOOKMARK);
  1002. // set new bookmark
  1003. m_items[id].SetBookmark(bookmark);
  1004. if (bookmark != NO_BOOKMARK)
  1005. m_header.Bookmarks[bookmark] = id;
  1006. // return id of previous item for that bookmark
  1007. return result;
  1008. }
  1009. static bool CanEncodeStringToCharset(const wxString& s, wxMBConv& conv)
  1010. {
  1011. if (s.empty())
  1012. return true;
  1013. if (!s.mb_str(conv))
  1014. return false;
  1015. return true;
  1016. }
  1017. static bool CanEncodeToCharset(Catalog& catalog, const wxString& charset)
  1018. {
  1019. if (charset.Lower() == _T("utf-8") || charset.Lower() == _T("utf8"))
  1020. return true;
  1021. wxCSConv conv(charset);
  1022. catalog.Header().UpdateDict();
  1023. const Catalog::HeaderData::Entries& hdr(catalog.Header().GetAllHeaders());
  1024. for (Catalog::HeaderData::Entries::const_iterator i = hdr.begin();
  1025. i != hdr.end(); i++)
  1026. {
  1027. if (!CanEncodeStringToCharset(i->Value, conv))
  1028. return false;
  1029. }
  1030. size_t cnt = catalog.GetCount();
  1031. for (size_t i = 0; i < cnt; i++)
  1032. {
  1033. if (!CanEncodeStringToCharset(catalog[i].GetTranslation(), conv) ||
  1034. !CanEncodeStringToCharset(catalog[i].GetString(), conv))
  1035. {
  1036. return false;
  1037. }
  1038. }
  1039. return true;
  1040. }
  1041. static void GetCRLFBehaviour(wxTextFileType& type, bool& preserve)
  1042. {
  1043. wxString format = wxConfigBase::Get()->Read(_T("crlf_format"), _T("unix"));
  1044. if (format == _T("win")) type = wxTextFileType_Dos;
  1045. else if (format == _T("mac")) type = wxTextFileType_Mac;
  1046. else if (format == _T("native")) type = wxTextFile::typeDefault;
  1047. else /* _T("unix") */ type = wxTextFileType_Unix;
  1048. preserve = (bool)(wxConfigBase::Get()->Read(_T("keep_crlf"), true));
  1049. }
  1050. static void SaveMultiLines(wxTextFile &f, const wxString& text)
  1051. {
  1052. wxStringTokenizer tkn(text, _T('\n'));
  1053. while (tkn.HasMoreTokens())
  1054. f.AddLine(tkn.GetNextToken());
  1055. }
  1056. /** Adds \n characters as neccesary for good-looking output
  1057. */
  1058. static wxString FormatStringForFile(const wxString& text)
  1059. {
  1060. wxString s;
  1061. unsigned n_cnt = 0;
  1062. int len = text.length();
  1063. s.Alloc(len + 16);
  1064. // Scan the string up to len-2 because we don't want to account for the
  1065. // very last \n on the line:
  1066. // "some\n string \n"
  1067. // ^
  1068. // |
  1069. // \--- = len-2
  1070. int i;
  1071. for (i = 0; i < len-2; i++)
  1072. {
  1073. if (text[i] == _T('\\') && text[i+1] == _T('n'))
  1074. {
  1075. n_cnt++;
  1076. s << _T("\\n\"\n\"");
  1077. i++;
  1078. }
  1079. else
  1080. s << text[i];
  1081. }
  1082. // ...and add not yet processed characters to the string...
  1083. for (; i < len; i++)
  1084. s << text[i];
  1085. if (n_cnt >= 1)
  1086. return _T("\"\n\"") + s;
  1087. else
  1088. return s;
  1089. }
  1090. bool Catalog::Save(const wxString& po_file, bool save_mo)
  1091. {
  1092. wxTextFileType crlfOrig, crlf;
  1093. bool crlfPreserve;
  1094. wxTextFile f;
  1095. if ( wxFileExists(po_file) && !wxFile::Access(po_file, wxFile::write) )
  1096. {
  1097. wxLogError(_("File '%s' is read-only and cannot be saved.\nPlease save it under different name."),
  1098. po_file.c_str());
  1099. return false;
  1100. }
  1101. GetCRLFBehaviour(crlfOrig, crlfPreserve);
  1102. // Update information about last modification time. But if the header
  1103. // was empty previously, the author apparently doesn't want this header
  1104. // set, so don't mess with it. See https://sourceforge.net/tracker/?func=detail&atid=389156&aid=1900298&group_id=27043
  1105. // for motivation:
  1106. if ( !m_header.RevisionDate.empty() )
  1107. m_header.RevisionDate = GetCurrentTimeRFC822();
  1108. /* Detect CRLF format: */
  1109. if ( crlfPreserve && wxFileExists(po_file) &&
  1110. f.Open(po_file, wxConvISO8859_1) )
  1111. {
  1112. {
  1113. wxLogNull null;
  1114. crlf = f.GuessType();
  1115. }
  1116. if (crlf == wxTextFileType_None || crlf == wxTextFile::typeDefault)
  1117. crlf = crlfOrig;
  1118. f.Close();
  1119. }
  1120. else
  1121. crlf = crlfOrig;
  1122. /* Save .po file: */
  1123. wxString charset(m_header.Charset);
  1124. if (!charset || charset == _T("CHARSET"))
  1125. charset = _T("UTF-8");
  1126. if (!CanEncodeToCharset(*this, charset))
  1127. {
  1128. wxString msg;
  1129. msg.Printf(_("The catalog couldn't be saved in '%s' charset as\nspecified in catalog settings. It was saved in UTF-8 instead\nand the setting was modified accordingly."), charset.c_str());
  1130. wxMessageBox(msg, _("Error saving catalog"),
  1131. wxOK | wxICON_EXCLAMATION);
  1132. charset = _T("UTF-8");
  1133. }
  1134. m_header.Charset = charset;
  1135. if (!wxFileExists(po_file) || !f.Open(po_file, wxConvISO8859_1))
  1136. if (!f.Create(po_file))
  1137. return false;
  1138. for (int j = f.GetLineCount() - 1; j >= 0; j--)
  1139. f.RemoveLine(j);
  1140. wxCSConv encConv(charset);
  1141. SaveMultiLines(f, m_header.Comment);
  1142. f.AddLine(_T("msgid \"\""));
  1143. f.AddLine(_T("msgstr \"\""));
  1144. wxString pohdr = wxString(_T("\"")) + m_header.ToString(_T("\"\n\""));
  1145. pohdr.RemoveLast();
  1146. SaveMultiLines(f, pohdr);
  1147. f.AddLine(wxEmptyString);
  1148. for (unsigned i = 0; i < m_items.size(); i++)
  1149. {
  1150. CatalogItem& data = m_items[i];
  1151. SaveMultiLines(f, data.GetComment());
  1152. for (unsigned i = 0; i < data.GetAutoComments().GetCount(); i++)
  1153. {
  1154. if (data.GetAutoComments()[i].empty())
  1155. f.AddLine(_T("#."));
  1156. else
  1157. f.AddLine(_T("#. ") + data.GetAutoComments()[i]);
  1158. }
  1159. for (unsigned i = 0; i < data.GetReferences().GetCount(); i++)
  1160. f.AddLine(_T("#: ") + data.GetReferences()[i]);
  1161. wxString dummy = data.GetFlags();
  1162. if (!dummy.empty())
  1163. f.AddLine(dummy);
  1164. for (unsigned i = 0; i < data.GetOldMsgid().GetCount(); i++)
  1165. f.AddLine(_T("#| ") + data.GetOldMsgid()[i]);
  1166. if ( data.HasContext() )
  1167. {
  1168. SaveMultiLines(f, _T("msgctxt \"") + FormatStringForFile(data.GetContext()) + _T("\""));
  1169. }
  1170. dummy = FormatStringForFile(data.GetString());
  1171. data.SetLineNumber(f.GetLineCount()+1);
  1172. SaveMultiLines(f, _T("msgid \"") + dummy + _T("\""));
  1173. if (data.HasPlural())
  1174. {
  1175. dummy = FormatStringForFile(data.GetPluralString());
  1176. SaveMultiLines(f, _T("msgid_plural \"") + dummy + _T("\""));
  1177. for (size_t i = 0; i < data.GetNumberOfTranslations(); i++)
  1178. {
  1179. dummy = FormatStringForFile(data.GetTranslation(i));
  1180. wxString hdr = wxString::Format(_T("msgstr[%u] \""), i);
  1181. SaveMultiLines(f, hdr + dummy + _T("\""));
  1182. }
  1183. }
  1184. else
  1185. {
  1186. dummy = FormatStringForFile(data.GetTranslation());
  1187. SaveMultiLines(f, _T("msgstr \"") + dummy + _T("\""));
  1188. }
  1189. f.AddLine(wxEmptyString);
  1190. }
  1191. // Write back deleted items in the file so that they're not lost
  1192. for (unsigned i = 0; i < m_deletedItems.size(); i++)
  1193. {
  1194. if ( i != 0 )
  1195. f.AddLine(wxEmptyString);
  1196. CatalogDeletedData& deletedItem = m_deletedItems[i];
  1197. SaveMultiLines(f, deletedItem.GetComment());
  1198. for (unsigned i = 0; i < deletedItem.GetAutoComments().GetCount(); i++)
  1199. f.AddLine(_T("#. ") + deletedItem.GetAutoComments()[i]);
  1200. for (unsigned i = 0; i < deletedItem.GetReferences().GetCount(); i++)
  1201. f.AddLine(_T("#: ") + deletedItem.GetReferences()[i]);
  1202. wxString dummy = deletedItem.GetFlags();
  1203. if (!dummy.empty())
  1204. f.AddLine(dummy);
  1205. deletedItem.SetLineNumber(f.GetLineCount()+1);
  1206. for (size_t j = 0; j < deletedItem.GetDeletedLines().GetCount(); j++)
  1207. f.AddLine(deletedItem.GetDeletedLines()[j]);
  1208. }
  1209. f.Write(crlf, encConv);
  1210. f.Close();
  1211. /* Poedit < 1.3.0 used to save additional info in .po.poedit file. It's
  1212. not used anymore, so delete the file if it exists: */
  1213. if (wxFileExists(po_file + _T(".poedit")))
  1214. {
  1215. wxRemoveFile(po_file + _T(".poedit"));
  1216. }
  1217. /* If the user wants it, compile .mo file right now: */
  1218. if (save_mo && wxConfig::Get()->Read(_T("compile_mo"), true))
  1219. ExecuteGettext(_T("msgfmt -c -o \"") +
  1220. po_file.BeforeLast(_T('.')) + _T(".mo\" \"") + po_file + _T("\""));
  1221. m_fileName = po_file;
  1222. return true;
  1223. }
  1224. bool Catalog::Update(bool summary)
  1225. {
  1226. if (!m_isOk) return false;
  1227. ProgressInfo pinfo;
  1228. pinfo.SetTitle(_("Updating catalog..."));
  1229. wxString cwd = wxGetCwd();
  1230. if (m_fileName != wxEmptyString)
  1231. {
  1232. wxString path;
  1233. if (wxIsAbsolutePath(m_header.BasePath))
  1234. path = m_header.BasePath;
  1235. else
  1236. path = wxPathOnly(m_fileName) + _T("/") + m_header.BasePath;
  1237. if (wxIsAbsolutePath(path))
  1238. wxSetWorkingDirectory(path);
  1239. else
  1240. wxSetWorkingDirectory(cwd + _T("/") + path);
  1241. }
  1242. SourceDigger dig(&pinfo);
  1243. wxArrayString keywords;
  1244. if (m_header.Keywords.empty())
  1245. {
  1246. // NB: keep in sync with Catalog::CreateNewHeader!
  1247. keywords.Add(_T("_"));
  1248. keywords.Add(_T("gettext"));
  1249. keywords.Add(_T("gettext_noop"));
  1250. }
  1251. else
  1252. {
  1253. WX_APPEND_ARRAY(keywords, m_header.Keywords);
  1254. }
  1255. Catalog *newcat = dig.Dig(m_header.SearchPaths,
  1256. keywords,
  1257. m_header.SourceCodeCharset);
  1258. if (newcat != NULL)
  1259. {
  1260. bool succ = false;
  1261. pinfo.UpdateMessage(_("Merging differences..."));
  1262. if (!summary || ShowMergeSummary(newcat))
  1263. succ = Merge(newcat);
  1264. if (!succ)
  1265. {
  1266. delete newcat;
  1267. newcat = NULL;
  1268. }
  1269. }
  1270. wxSetWorkingDirectory(cwd);
  1271. if (newcat == NULL) return false;
  1272. delete newcat;
  1273. return true;
  1274. }
  1275. bool Catalog::UpdateFromPOT(const wxString& pot_file, bool summary,
  1276. bool replace_header)
  1277. {
  1278. if (!m_isOk) return false;
  1279. Catalog newcat(pot_file);
  1280. if (!newcat.IsOk())
  1281. {
  1282. wxLogError(_("'%s' is not a valid POT file."), pot_file.c_str());
  1283. return false;
  1284. }
  1285. if (!summary || ShowMergeSummary(&newcat))
  1286. {
  1287. if ( !Merge(&newcat) )
  1288. return false;
  1289. if ( replace_header )
  1290. CreateNewHeader(newcat.Header());
  1291. return true;
  1292. }
  1293. else
  1294. {
  1295. return false;
  1296. }
  1297. }
  1298. bool Catalog::Merge(Catalog *refcat)
  1299. {
  1300. wxString oldname = m_fileName;
  1301. wxString tmpdir = wxGetTempFileName(_T("poedit"));
  1302. wxRemoveFile(tmpdir);
  1303. if (!wxMkdir(tmpdir, 0700))
  1304. return false;
  1305. wxString tmp1 = tmpdir + wxFILE_SEP_PATH + _T("ref.pot");
  1306. wxString tmp2 = tmpdir + wxFILE_SEP_PATH + _T("input.po");
  1307. wxString tmp3 = tmpdir + wxFILE_SEP_PATH + _T("output.po");
  1308. refcat->Save(tmp1, false);
  1309. Save(tmp2, false);
  1310. bool succ =
  1311. ExecuteGettext(_T("msgmerge --force-po -o \"") + tmp3 + _T("\" \"") +
  1312. tmp2 + _T("\" \"") + tmp1 + _T("\""));
  1313. if (succ)
  1314. {
  1315. Load(tmp3);
  1316. }
  1317. wxRemoveFile(tmp1);
  1318. wxRemoveFile(tmp2);
  1319. wxRemoveFile(tmp3);
  1320. wxRmdir(tmpdir);
  1321. m_fileName = oldname;
  1322. return succ;
  1323. }
  1324. void Catalog::GetMergeSummary(Catalog *refcat,
  1325. wxArrayString& snew, wxArrayString& sobsolete)
  1326. {
  1327. wxASSERT( snew.empty() );
  1328. wxASSERT( sobsolete.empty() );
  1329. std::set<wxString> strsThis, strsRef;
  1330. for ( unsigned i = 0; i < GetCount(); i++ )
  1331. strsThis.insert((*this)[i].GetString());
  1332. for ( unsigned i = 0; i < refcat->GetCount(); i++ )
  1333. strsRef.insert((*refcat)[i].GetString());
  1334. unsigned i;
  1335. for (i = 0; i < GetCount(); i++)
  1336. {
  1337. if (strsRef.find((*this)[i].GetString()) == strsRef.end())
  1338. sobsolete.Add((*this)[i].GetString());
  1339. }
  1340. for (i = 0; i < refcat->GetCount(); i++)
  1341. {
  1342. if (strsThis.find((*refcat)[i].GetString()) == strsThis.end())
  1343. snew.Add((*refcat)[i].GetString());
  1344. }
  1345. }
  1346. bool Catalog::ShowMergeSummary(Catalog *refcat)
  1347. {
  1348. if (wxConfig::Get()->Read(_T("show_summary"), true))
  1349. {
  1350. wxArrayString snew, sobsolete;
  1351. GetMergeSummary(refcat, snew, sobsolete);
  1352. MergeSummaryDialog sdlg;
  1353. sdlg.TransferTo(snew, sobsolete);
  1354. return (sdlg.ShowModal() == wxID_OK);
  1355. }
  1356. else
  1357. return true;
  1358. }
  1359. unsigned Catalog::GetPluralFormsCount() const
  1360. {
  1361. if (m_header.HasHeader(_T("Plural-Forms")))
  1362. {
  1363. // e.g. "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ?
  1364. // 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
  1365. wxString form = m_header.GetHeader(_T("Plural-Forms"));
  1366. form = form.BeforeFirst(_T(';'));
  1367. if (form.BeforeFirst(_T('=')) == _T("nplurals"))
  1368. {
  1369. long val;
  1370. if (form.AfterFirst(_T('=')).ToLong(&val))
  1371. return val;
  1372. }
  1373. }
  1374. return 0;
  1375. }
  1376. void Catalog::GetStatistics(int *all, int *fuzzy, int *badtokens, int *untranslated)
  1377. {
  1378. if (all) *all = 0;
  1379. if (fuzzy) *fuzzy = 0;
  1380. if (badtokens) *badtokens = 0;
  1381. if (untranslated) *untranslated = 0;
  1382. for (size_t i = 0; i < GetCount(); i++)
  1383. {
  1384. if (all) (*all)++;
  1385. if ((*this)[i].IsFuzzy())
  1386. (*fuzzy)++;
  1387. if ((*this)[i].GetValidity() == CatalogItem::Val_Invalid)
  1388. (*badtokens)++;
  1389. if (!(*this)[i].IsTranslated())
  1390. (*untranslated)++;
  1391. }
  1392. }
  1393. void CatalogItem::SetFlags(const wxString& flags)
  1394. {
  1395. m_isFuzzy = false;
  1396. m_moreFlags.Empty();
  1397. if (flags.empty()) return;
  1398. wxStringTokenizer tkn(flags.Mid(1), _T(" ,"), wxTOKEN_STRTOK);
  1399. wxString s;
  1400. while (tkn.HasMoreTokens())
  1401. {
  1402. s = tkn.GetNextToken();
  1403. if (s == _T("fuzzy")) m_isFuzzy = true;
  1404. else m_moreFlags << _T(", ") << s;
  1405. }
  1406. }
  1407. wxString CatalogItem::GetFlags() const
  1408. {
  1409. wxString f;
  1410. if (m_isFuzzy) f << _T(", fuzzy");
  1411. f << m_moreFlags;
  1412. if (!f.empty())
  1413. return _T("#") + f;
  1414. else
  1415. return wxEmptyString;
  1416. }
  1417. bool CatalogItem::IsInFormat(const wxString& format)
  1418. {
  1419. wxString lookingFor;
  1420. lookingFor.Printf(_T("%s-format"), format.c_str());
  1421. wxStringTokenizer tkn(m_moreFlags, _T(" ,"), wxTOKEN_STRTOK);
  1422. while (tkn.HasMoreTokens())
  1423. {
  1424. if (tkn.GetNextToken() == lookingFor)
  1425. return true;
  1426. }
  1427. return false;
  1428. }
  1429. wxString CatalogItem::GetTranslation(unsigned idx) const
  1430. {
  1431. if (idx >= GetNumberOfTranslations())
  1432. return wxEmptyString;
  1433. else
  1434. return m_translations[idx];
  1435. }
  1436. void CatalogItem::SetTranslation(const wxString &t, unsigned idx)
  1437. {
  1438. while (idx >= m_translations.GetCount())
  1439. m_translations.Add(wxEmptyString);
  1440. m_translations[idx] = t;
  1441. m_validity = Val_Unknown;
  1442. m_isTranslated = true;
  1443. for (size_t i = 0; i < m_translations.GetCount(); i++)
  1444. {
  1445. if (m_translations[i].empty())
  1446. {
  1447. m_isTranslated = false;
  1448. break;
  1449. }
  1450. }
  1451. }
  1452. void CatalogItem::SetTranslations(const wxArrayString &t)
  1453. {
  1454. m_translations = t;
  1455. m_validity = Val_Unknown;
  1456. m_isTranslated = true;
  1457. for (size_t i = 0; i < m_translations.GetCount(); i++)
  1458. {
  1459. if (m_translations[i].empty())
  1460. {
  1461. m_isTranslated = false;
  1462. break;
  1463. }
  1464. }
  1465. }
  1466. wxString Catalog::GetLocaleCode() const
  1467. {
  1468. wxString lang;
  1469. // was the language explicitly specified?
  1470. if (!m_header.Language.empty())
  1471. {
  1472. lang = LookupLanguageCode(m_header.Language.c_str());
  1473. if (!m_header.Country.empty())
  1474. {
  1475. lang += _T('_');
  1476. lang += LookupCountryCode(m_header.Country.c_str());
  1477. }
  1478. }
  1479. // if not, can we deduce it from filename?
  1480. if (lang.empty() && !m_fileName.empty())
  1481. {
  1482. wxString name;
  1483. wxFileName::SplitPath(m_fileName, NULL, &name, NULL);
  1484. if (name.length() == 2)
  1485. {
  1486. if (IsKnownLanguageCode(name))
  1487. lang = name;
  1488. }
  1489. else if (name.length() == 5 && name[2u] == _T('_'))
  1490. {
  1491. if (IsKnownLanguageCode(name.Mid(0, 2)) &&
  1492. IsKnownCountryCode(name.Mid(3, 2)))
  1493. lang = name;
  1494. }
  1495. }
  1496. wxLo

Large files files are truncated, but you can click here to view the full file