PageRenderTime 29ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/i18n/sfMessageSource_XLIFF.class.php

https://github.com/bheneka/gitta
PHP | 447 lines | 277 code | 61 blank | 109 comment | 46 complexity | 5fd0ff3b6a0de39d672a99c48435f935 MD5 | raw file
  1. <?php
  2. /**
  3. * sfMessageSource_XLIFF class file.
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the BSD License.
  7. *
  8. * Copyright(c) 2004 by Qiang Xue. All rights reserved.
  9. *
  10. * To contact the author write to {@link mailto:qiang.xue@gmail.com Qiang Xue}
  11. * The latest version of PRADO can be obtained from:
  12. * {@link http://prado.sourceforge.net/}
  13. *
  14. * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
  15. * @version $Id$
  16. * @package symfony
  17. * @subpackage i18n
  18. */
  19. /**
  20. * sfMessageSource_XLIFF class.
  21. *
  22. * Using XML XLIFF format as the message source for translation.
  23. * Details and example of XLIFF can be found in the following URLs.
  24. *
  25. * # http://www.opentag.com/xliff.htm
  26. * # http://www-106.ibm.com/developerworks/xml/library/x-localis2/
  27. *
  28. * See the MessageSource::factory() method to instantiate this class.
  29. *
  30. * @author Xiang Wei Zhuo <weizhuo[at]gmail[dot]com>
  31. * @version v1.0, last update on Fri Dec 24 16:18:44 EST 2004
  32. * @package symfony
  33. * @subpackage i18n
  34. */
  35. class sfMessageSource_XLIFF extends sfMessageSource_File
  36. {
  37. /**
  38. * Message data filename extension.
  39. * @var string
  40. */
  41. protected $dataExt = '.xml';
  42. /**
  43. * Loads the messages from a XLIFF file.
  44. *
  45. * @param string $filename XLIFF file.
  46. * @return array|false An array of messages or false if there was a problem loading the file.
  47. */
  48. public function &loadData($filename)
  49. {
  50. libxml_use_internal_errors(true);
  51. if (!$xml = simplexml_load_file($filename))
  52. {
  53. $error = false;
  54. return $error;
  55. }
  56. libxml_use_internal_errors(false);
  57. $translationUnit = $xml->xpath('//trans-unit');
  58. $translations = array();
  59. foreach ($translationUnit as $unit)
  60. {
  61. $source = (string) $unit->source;
  62. $translations[$source][] = (string) $unit->target;
  63. $translations[$source][] = (string) $unit['id'];
  64. $translations[$source][] = (string) $unit->note;
  65. }
  66. return $translations;
  67. }
  68. /**
  69. * Creates and returns a new DOMDocument instance
  70. *
  71. * @param string $xml XML string
  72. *
  73. * @return DOMDocument
  74. */
  75. protected function createDOMDocument($xml = null)
  76. {
  77. $domimp = new DOMImplementation();
  78. $doctype = $domimp->createDocumentType('xliff', '-//XLIFF//DTD XLIFF//EN', 'http://www.oasis-open.org/committees/xliff/documents/xliff.dtd');
  79. $dom = $domimp->createDocument('', '', $doctype);
  80. $dom->formatOutput = true;
  81. $dom->preserveWhiteSpace = false;
  82. if (null !== $xml && is_string($xml))
  83. {
  84. // Add header for XML with UTF-8
  85. if (!preg_match('/<\?xml/', $xml))
  86. {
  87. $xml = '<?xml version="1.0" encoding="UTF-8"?>'."\n".$xml;
  88. }
  89. $dom->loadXML($xml);
  90. }
  91. return $dom;
  92. }
  93. /**
  94. * Gets the variant for a catalogue depending on the current culture.
  95. *
  96. * @param string $catalogue catalogue
  97. * @return string the variant.
  98. * @see save()
  99. * @see update()
  100. * @see delete()
  101. */
  102. protected function getVariants($catalogue = 'messages')
  103. {
  104. if (null === $catalogue)
  105. {
  106. $catalogue = 'messages';
  107. }
  108. foreach ($this->getCatalogueList($catalogue) as $variant)
  109. {
  110. $file = $this->getSource($variant);
  111. if (is_file($file))
  112. {
  113. return array($variant, $file);
  114. }
  115. }
  116. return false;
  117. }
  118. /**
  119. * Saves the list of untranslated blocks to the translation source.
  120. * If the translation was not found, you should add those
  121. * strings to the translation source via the <b>append()</b> method.
  122. *
  123. * @param string $catalogue the catalogue to add to
  124. * @return boolean true if saved successfuly, false otherwise.
  125. */
  126. public function save($catalogue = 'messages')
  127. {
  128. $messages = $this->untranslated;
  129. if (count($messages) <= 0)
  130. {
  131. return false;
  132. }
  133. $variants = $this->getVariants($catalogue);
  134. if ($variants)
  135. {
  136. list($variant, $filename) = $variants;
  137. }
  138. else
  139. {
  140. list($variant, $filename) = $this->createMessageTemplate($catalogue);
  141. }
  142. if (is_writable($filename) == false)
  143. {
  144. throw new sfException(sprintf("Unable to save to file %s, file must be writable.", $filename));
  145. }
  146. // create a new dom, import the existing xml
  147. $dom = $this->createDOMDocument();
  148. @$dom->load($filename);
  149. // find the body element
  150. $xpath = new DomXPath($dom);
  151. $body = $xpath->query('//body')->item(0);
  152. if (null === $body)
  153. {
  154. //create and try again
  155. $this->createMessageTemplate($catalogue);
  156. $dom->load($filename);
  157. $xpath = new DomXPath($dom);
  158. $body = $xpath->query('//body')->item(0);
  159. }
  160. // find the biggest "id" used
  161. $lastNodes = $xpath->query('//trans-unit[not(@id <= preceding-sibling::trans-unit/@id) and not(@id <= following-sibling::trans-unit/@id)]');
  162. if (null !== $last = $lastNodes->item(0))
  163. {
  164. $count = intval($last->getAttribute('id'));
  165. }
  166. else
  167. {
  168. $count = 0;
  169. }
  170. // for each message add it to the XML file using DOM
  171. foreach ($messages as $message)
  172. {
  173. $unit = $dom->createElement('trans-unit');
  174. $unit->setAttribute('id', ++$count);
  175. $source = $dom->createElement('source');
  176. $source->appendChild($dom->createTextNode($message));
  177. $target = $dom->createElement('target');
  178. $target->appendChild($dom->createTextNode(''));
  179. $unit->appendChild($source);
  180. $unit->appendChild($target);
  181. $body->appendChild($unit);
  182. }
  183. $fileNode = $xpath->query('//file')->item(0);
  184. $fileNode->setAttribute('date', @date('Y-m-d\TH:i:s\Z'));
  185. $dom = $this->createDOMDocument($dom->saveXML());
  186. // save it and clear the cache for this variant
  187. $dom->save($filename);
  188. if ($this->cache)
  189. {
  190. $this->cache->remove($variant.':'.$this->culture);
  191. }
  192. return true;
  193. }
  194. /**
  195. * Updates the translation.
  196. *
  197. * @param string $text the source string.
  198. * @param string $target the new translation string.
  199. * @param string $comments comments
  200. * @param string $catalogue the catalogue to save to.
  201. * @return boolean true if translation was updated, false otherwise.
  202. */
  203. public function update($text, $target, $comments, $catalogue = 'messages')
  204. {
  205. $variants = $this->getVariants($catalogue);
  206. if ($variants)
  207. {
  208. list($variant, $filename) = $variants;
  209. }
  210. else
  211. {
  212. return false;
  213. }
  214. if (is_writable($filename) == false)
  215. {
  216. throw new sfException(sprintf("Unable to update file %s, file must be writable.", $filename));
  217. }
  218. // create a new dom, import the existing xml
  219. $dom = $this->createDOMDocument();
  220. $dom->load($filename);
  221. // find the body element
  222. $xpath = new DomXPath($dom);
  223. $units = $xpath->query('//trans-unit');
  224. // for each of the existin units
  225. foreach ($units as $unit)
  226. {
  227. $found = false;
  228. $targetted = false;
  229. $commented = false;
  230. //in each unit, need to find the source, target and comment nodes
  231. //it will assume that the source is before the target.
  232. foreach ($unit->childNodes as $node)
  233. {
  234. // source node
  235. if ($node->nodeName == 'source' && $node->firstChild->wholeText == $text)
  236. {
  237. $found = true;
  238. }
  239. // found source, get the target and notes
  240. if ($found)
  241. {
  242. // set the new translated string
  243. if ($node->nodeName == 'target')
  244. {
  245. $node->nodeValue = $target;
  246. $targetted = true;
  247. }
  248. // set the notes
  249. if (!empty($comments) && $node->nodeName == 'note')
  250. {
  251. $node->nodeValue = $comments;
  252. $commented = true;
  253. }
  254. }
  255. }
  256. // append a target
  257. if ($found && !$targetted)
  258. {
  259. $targetNode = $dom->createElement('target');
  260. $targetNode->appendChild($dom->createTextNode($target));
  261. $unit->appendChild($targetNode);
  262. }
  263. // append a note
  264. if ($found && !$commented && !empty($comments))
  265. {
  266. $commentsNode = $dom->createElement('note');
  267. $commentsNode->appendChild($dom->createTextNode($comments));
  268. $unit->appendChild($commentsNode);
  269. }
  270. // finished searching
  271. if ($found)
  272. {
  273. break;
  274. }
  275. }
  276. $fileNode = $xpath->query('//file')->item(0);
  277. $fileNode->setAttribute('date', @date('Y-m-d\TH:i:s\Z'));
  278. if ($dom->save($filename) > 0)
  279. {
  280. if ($this->cache)
  281. {
  282. $this->cache->remove($variant.':'.$this->culture);
  283. }
  284. return true;
  285. }
  286. return false;
  287. }
  288. /**
  289. * Deletes a particular message from the specified catalogue.
  290. *
  291. * @param string $message the source message to delete.
  292. * @param string $catalogue the catalogue to delete from.
  293. * @return boolean true if deleted, false otherwise.
  294. */
  295. public function delete($message, $catalogue='messages')
  296. {
  297. $variants = $this->getVariants($catalogue);
  298. if ($variants)
  299. {
  300. list($variant, $filename) = $variants;
  301. }
  302. else
  303. {
  304. return false;
  305. }
  306. if (is_writable($filename) == false)
  307. {
  308. throw new sfException(sprintf("Unable to modify file %s, file must be writable.", $filename));
  309. }
  310. // create a new dom, import the existing xml
  311. $dom = $this->createDOMDocument();
  312. $dom->load($filename);
  313. // find the body element
  314. $xpath = new DomXPath($dom);
  315. $units = $xpath->query('//trans-unit');
  316. // for each of the existin units
  317. foreach ($units as $unit)
  318. {
  319. //in each unit, need to find the source, target and comment nodes
  320. //it will assume that the source is before the target.
  321. foreach ($unit->childNodes as $node)
  322. {
  323. // source node
  324. if ($node->nodeName == 'source' && $node->firstChild->wholeText == $message)
  325. {
  326. // we found it, remove and save the xml file.
  327. $unit->parentNode->removeChild($unit);
  328. $fileNode = $xpath->query('//file')->item(0);
  329. $fileNode->setAttribute('date', @date('Y-m-d\TH:i:s\Z'));
  330. if ($dom->save($filename) > 0)
  331. {
  332. if (!empty($this->cache))
  333. {
  334. $this->cache->remove($variant.':'.$this->culture);
  335. }
  336. return true;
  337. }
  338. else
  339. {
  340. return false;
  341. }
  342. }
  343. }
  344. }
  345. return false;
  346. }
  347. protected function createMessageTemplate($catalogue)
  348. {
  349. if (null === $catalogue)
  350. {
  351. $catalogue = 'messages';
  352. }
  353. $variants = $this->getCatalogueList($catalogue);
  354. $variant = array_shift($variants);
  355. $file = $this->getSource($variant);
  356. $dir = dirname($file);
  357. if (!is_dir($dir))
  358. {
  359. @mkdir($dir);
  360. @chmod($dir, 0777);
  361. }
  362. if (!is_dir($dir))
  363. {
  364. throw new sfException(sprintf("Unable to create directory %s.", $dir));
  365. }
  366. $dom = $this->createDOMDocument($this->getTemplate($catalogue));
  367. file_put_contents($file, $dom->saveXML());
  368. chmod($file, 0777);
  369. return array($variant, $file);
  370. }
  371. protected function getTemplate($catalogue)
  372. {
  373. $date = date('c');
  374. return <<<EOD
  375. <?xml version="1.0" encoding="UTF-8"?>
  376. <!DOCTYPE xliff PUBLIC "-//XLIFF//DTD XLIFF//EN" "http://www.oasis-open.org/committees/xliff/documents/xliff.dtd" >
  377. <xliff version="1.0">
  378. <file source-language="EN" target-language="{$this->culture}" datatype="plaintext" original="$catalogue" date="$date" product-name="$catalogue">
  379. <header />
  380. <body>
  381. </body>
  382. </file>
  383. </xliff>
  384. EOD;
  385. }
  386. }