PageRenderTime 72ms CodeModel.GetById 43ms RepoModel.GetById 0ms app.codeStats 1ms

/package/app/app/symfony/i18n/sfMessageSource_XLIFF.class.php

https://github.com/richhl/kalturaCE
PHP | 518 lines | 291 code | 71 blank | 156 comment | 49 complexity | e249e8de95359872767ec48f592576a8 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: sfMessageSource_XLIFF.class.php 2834 2006-11-27 14:09:05Z fabien $
  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 System.I18N.core
  33. */
  34. class sfMessageSource_XLIFF extends sfMessageSource
  35. {
  36. /**
  37. * Message data filename extension.
  38. * @var string
  39. */
  40. protected $dataExt = '.xml';
  41. /**
  42. * Separator between culture name and source.
  43. * @var string
  44. */
  45. protected $dataSeparator = '.';
  46. /**
  47. * Constructor.
  48. *
  49. * @param string the directory where the messages are stored.
  50. * @see MessageSource::factory();
  51. */
  52. function __construct($source)
  53. {
  54. $this->source = (string)$source;
  55. }
  56. /**
  57. * Load the messages from a XLIFF file.
  58. *
  59. * @param string XLIFF file.
  60. * @return array of messages.
  61. */
  62. protected function &loadData($filename)
  63. {
  64. //load it.
  65. $XML = simplexml_load_file($filename);
  66. if (!$XML)
  67. {
  68. return false;
  69. }
  70. $translationUnit = $XML->xpath('//trans-unit');
  71. $translations = array();
  72. foreach ($translationUnit as $unit)
  73. {
  74. $source = (string)$unit->source;
  75. $translations[$source][] = (string)$unit->target;
  76. $translations[$source][]= (string)$unit['id'];
  77. $translations[$source][]= (string)$unit->note;
  78. }
  79. return $translations;
  80. }
  81. /**
  82. * Get the last modified unix-time for this particular catalogue+variant.
  83. * Just use the file modified time.
  84. *
  85. * @param string catalogue+variant
  86. * @return int last modified in unix-time format.
  87. */
  88. protected function getLastModified($source)
  89. {
  90. if (is_file($source))
  91. {
  92. return filemtime($source);
  93. }
  94. else
  95. {
  96. return 0;
  97. }
  98. }
  99. /**
  100. * Get the XLIFF file for a specific message catalogue and cultural variant.
  101. *
  102. * @param string message catalogue
  103. * @return string full path to the XLIFF file.
  104. */
  105. protected function getSource($variant)
  106. {
  107. return $this->source.'/'.$variant;
  108. }
  109. /**
  110. * Determin if the XLIFF file source is valid.
  111. *
  112. * @param string XLIFF file
  113. * @return boolean true if valid, false otherwise.
  114. */
  115. protected function isValidSource($source)
  116. {
  117. return is_file($source);
  118. }
  119. /**
  120. * Get all the variants of a particular catalogue.
  121. *
  122. * @param string catalogue name
  123. * @return array list of all variants for this catalogue.
  124. */
  125. protected function getCatalogueList($catalogue)
  126. {
  127. $variants = explode('_', $this->culture);
  128. $source = $catalogue.$this->dataExt;
  129. $catalogues = array($source);
  130. $variant = null;
  131. for ($i = 0, $max = count($variants); $i < $max; $i++)
  132. {
  133. if (strlen($variants[$i]) > 0)
  134. {
  135. $variant .= ($variant) ? '_'.$variants[$i] : $variants[$i];
  136. $catalogues[] = $catalogue.$this->dataSeparator.$variant.$this->dataExt;
  137. }
  138. }
  139. $byDir = $this->getCatalogueByDir($catalogue);
  140. $catalogues = array_merge($byDir, array_reverse($catalogues));
  141. return $catalogues;
  142. }
  143. /**
  144. * Traverse through the directory structure to find the catalogues.
  145. * This should only be called by getCatalogueList()
  146. *
  147. * @param string a particular catalogue.
  148. * @return array a list of catalogues.
  149. * @see getCatalogueList()
  150. */
  151. protected function getCatalogueByDir($catalogue)
  152. {
  153. $variants = explode('_', $this->culture);
  154. $catalogues = array();
  155. $variant = null;
  156. for ($i = 0, $max = count($variants); $i < $max; $i++)
  157. {
  158. if (strlen($variants[$i]) > 0)
  159. {
  160. $variant .= ($variant) ? '_'.$variants[$i] : $variants[$i];
  161. $catalogues[] = $variant.'/'.$catalogue.$this->dataExt;
  162. }
  163. }
  164. return array_reverse($catalogues);
  165. }
  166. /**
  167. * Returns a list of catalogue and its culture ID.
  168. * E.g. array('messages','en_AU')
  169. *
  170. * @return array list of catalogues
  171. * @see getCatalogues()
  172. */
  173. public function catalogues()
  174. {
  175. return $this->getCatalogues();
  176. }
  177. /**
  178. * Returns a list of catalogue and its culture ID. This takes care
  179. * of directory structures.
  180. * E.g. array('messages','en_AU')
  181. *
  182. * @return array list of catalogues
  183. */
  184. protected function getCatalogues($dir = null, $variant = null)
  185. {
  186. $dir = $dir ? $dir : $this->source;
  187. $files = scandir($dir);
  188. $catalogue = array();
  189. foreach ($files as $file)
  190. {
  191. if (is_dir($dir.'/'.$file) && preg_match('/^[a-z]{2}(_[A-Z]{2,3})?$/', $file))
  192. {
  193. $catalogue = array_merge($catalogue, $this->getCatalogues($dir.'/'.$file, $file));
  194. }
  195. $pos = strpos($file,$this->dataExt);
  196. if ($pos > 0 && substr($file, -1 * strlen($this->dataExt)) == $this->dataExt)
  197. {
  198. $name = substr($file, 0, $pos);
  199. $dot = strrpos($name, $this->dataSeparator);
  200. $culture = $variant;
  201. $cat = $name;
  202. if (is_int($dot))
  203. {
  204. $culture = substr($name, $dot + 1,strlen($name));
  205. $cat = substr($name, 0, $dot);
  206. }
  207. $details[0] = $cat;
  208. $details[1] = $culture;
  209. $catalogue[] = $details;
  210. }
  211. }
  212. sort($catalogue);
  213. return $catalogue;
  214. }
  215. /**
  216. * Get the variant for a catalogue depending on the current culture.
  217. *
  218. * @param string catalogue
  219. * @return string the variant.
  220. * @see save()
  221. * @see update()
  222. * @see delete()
  223. */
  224. protected function getVariants($catalogue = 'messages')
  225. {
  226. if (is_null($catalogue))
  227. {
  228. $catalogue = 'messages';
  229. }
  230. foreach ($this->getCatalogueList($catalogue) as $variant)
  231. {
  232. $file = $this->getSource($variant);
  233. if (is_file($file))
  234. {
  235. return array($variant, $file);
  236. }
  237. }
  238. return false;
  239. }
  240. /**
  241. * Save the list of untranslated blocks to the translation source.
  242. * If the translation was not found, you should add those
  243. * strings to the translation source via the <b>append()</b> method.
  244. *
  245. * @param string the catalogue to add to
  246. * @return boolean true if saved successfuly, false otherwise.
  247. */
  248. public function save($catalogue = 'messages')
  249. {
  250. $messages = $this->untranslated;
  251. if (count($messages) <= 0)
  252. {
  253. return false;
  254. }
  255. $variants = $this->getVariants($catalogue);
  256. if ($variants)
  257. {
  258. list($variant, $filename) = $variants;
  259. }
  260. else
  261. {
  262. return false;
  263. }
  264. if (is_writable($filename) == false)
  265. {
  266. throw new sfException("Unable to save to file {$filename}, file must be writable.");
  267. }
  268. // create a new dom, import the existing xml
  269. $dom = DOMDocument::load($filename);
  270. // find the body element
  271. $xpath = new DomXPath($dom);
  272. $body = $xpath->query('//body')->item(0);
  273. $count = $xpath->query('//trans-unit')->length;
  274. // for each message add it to the XML file using DOM
  275. foreach ($messages as $message)
  276. {
  277. $unit = $dom->createElement('trans-unit');
  278. $unit->setAttribute('id', ++$count);
  279. $source = $dom->createElement('source', $message);
  280. $target = $dom->createElement('target', '');
  281. $unit->appendChild($dom->createTextNode("\n"));
  282. $unit->appendChild($source);
  283. $unit->appendChild($dom->createTextNode("\n"));
  284. $unit->appendChild($target);
  285. $unit->appendChild($dom->createTextNode("\n"));
  286. $body->appendChild($dom->createTextNode("\n"));
  287. $body->appendChild($unit);
  288. $body->appendChild($dom->createTextNode("\n"));
  289. }
  290. $fileNode = $xpath->query('//file')->item(0);
  291. $fileNode->setAttribute('date', @date('Y-m-d\TH:i:s\Z'));
  292. // save it and clear the cache for this variant
  293. $dom->save($filename);
  294. if (!empty($this->cache))
  295. {
  296. $this->cache->clean($variant, $this->culture);
  297. }
  298. return true;
  299. }
  300. /**
  301. * Update the translation.
  302. *
  303. * @param string the source string.
  304. * @param string the new translation string.
  305. * @param string comments
  306. * @param string the catalogue to save to.
  307. * @return boolean true if translation was updated, false otherwise.
  308. */
  309. public function update($text, $target, $comments, $catalogue = 'messages')
  310. {
  311. $variants = $this->getVariants($catalogue);
  312. if ($variants)
  313. {
  314. list($variant, $filename) = $variants;
  315. }
  316. else
  317. {
  318. return false;
  319. }
  320. if (is_writable($filename) == false)
  321. {
  322. throw new sfException("Unable to update file {$filename}, file must be writable.");
  323. }
  324. // create a new dom, import the existing xml
  325. $dom = DOMDocument::load($filename);
  326. // find the body element
  327. $xpath = new DomXPath($dom);
  328. $units = $xpath->query('//trans-unit');
  329. // for each of the existin units
  330. foreach ($units as $unit)
  331. {
  332. $found = false;
  333. $targetted = false;
  334. $commented = false;
  335. //in each unit, need to find the source, target and comment nodes
  336. //it will assume that the source is before the target.
  337. foreach ($unit->childNodes as $node)
  338. {
  339. // source node
  340. if ($node->nodeName == 'source' && $node->firstChild->wholeText == $text)
  341. {
  342. $found = true;
  343. }
  344. // found source, get the target and notes
  345. if ($found)
  346. {
  347. // set the new translated string
  348. if ($node->nodeName == 'target')
  349. {
  350. $node->nodeValue = $target;
  351. $targetted = true;
  352. }
  353. // set the notes
  354. if (!empty($comments) && $node->nodeName == 'note')
  355. {
  356. $node->nodeValue = $comments;
  357. $commented = true;
  358. }
  359. }
  360. }
  361. // append a target
  362. if ($found && !$targetted)
  363. {
  364. $unit->appendChild($dom->createElement('target',$target));
  365. }
  366. // append a note
  367. if ($found && !$commented && !empty($comments))
  368. {
  369. $unit->appendChild($dom->createElement('note',$comments));
  370. }
  371. // finished searching
  372. if ($found)
  373. {
  374. break;
  375. }
  376. }
  377. $fileNode = $xpath->query('//file')->item(0);
  378. $fileNode->setAttribute('date', @date('Y-m-d\TH:i:s\Z'));
  379. if ($dom->save($filename) > 0)
  380. {
  381. if (!empty($this->cache))
  382. {
  383. $this->cache->clean($variant, $this->culture);
  384. }
  385. return true;
  386. }
  387. return false;
  388. }
  389. /**
  390. * Delete a particular message from the specified catalogue.
  391. *
  392. * @param string the source message to delete.
  393. * @param string the catalogue to delete from.
  394. * @return boolean true if deleted, false otherwise.
  395. */
  396. public function delete($message, $catalogue='messages')
  397. {
  398. $variants = $this->getVariants($catalogue);
  399. if ($variants)
  400. {
  401. list($variant, $filename) = $variants;
  402. }
  403. else
  404. {
  405. return false;
  406. }
  407. if (is_writable($filename) == false)
  408. {
  409. throw new sfException("Unable to modify file {$filename}, file must be writable.");
  410. }
  411. // create a new dom, import the existing xml
  412. $dom = DOMDocument::load($filename);
  413. // find the body element
  414. $xpath = new DomXPath($dom);
  415. $units = $xpath->query('//trans-unit');
  416. // for each of the existin units
  417. foreach ($units as $unit)
  418. {
  419. //in each unit, need to find the source, target and comment nodes
  420. //it will assume that the source is before the target.
  421. foreach ($unit->childNodes as $node)
  422. {
  423. // source node
  424. if ($node->nodeName == 'source' && $node->firstChild->wholeText == $message)
  425. {
  426. // we found it, remove and save the xml file.
  427. $unit->parentNode->removeChild($unit);
  428. $fileNode = $xpath->query('//file')->item(0);
  429. $fileNode->setAttribute('date', @date('Y-m-d\TH:i:s\Z'));
  430. if ($dom->save($filename) > 0)
  431. {
  432. if (!empty($this->cache))
  433. {
  434. $this->cache->clean($variant, $this->culture);
  435. }
  436. return true;
  437. }
  438. else
  439. {
  440. return false;
  441. }
  442. }
  443. }
  444. }
  445. return false;
  446. }
  447. }