Development

/branches/1.4/lib/i18n/sfMessageSource_XLIFF.class.php

You must first sign up to be able to contribute.

root/branches/1.4/lib/i18n/sfMessageSource_XLIFF.class.php

Revision 23810, 11.3 kB (checked in by Kris.Wallsmith, 4 years ago)

[1.3] set svn:eol-style property to native and svn:keywords property to Id on all .php files

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