4
* sfMessageSource_XLIFF class file.
6
* This program is free software; you can redistribute it and/or modify
7
* it under the terms of the BSD License.
9
* Copyright(c) 2004 by Qiang Xue. All rights reserved.
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/}
15
* @author Wei Zhuo <weizhuo[at]gmail[dot]com>
16
* @version $Id: sfMessageSource_XLIFF.class.php 23810 2009-11-12 11:07:44Z Kris.Wallsmith $
22
* sfMessageSource_XLIFF class.
24
* Using XML XLIFF format as the message source for translation.
25
* Details and example of XLIFF can be found in the following URLs.
27
* # http://www.opentag.com/xliff.htm
28
* # http://www-106.ibm.com/developerworks/xml/library/x-localis2/
30
* See the MessageSource::factory() method to instantiate this class.
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
37
class sfMessageSource_XLIFF extends sfMessageSource_File
40
* Message data filename extension.
43
protected $dataExt = '.xml';
46
* Loads the messages from a XLIFF file.
48
* @param string $filename XLIFF file.
49
* @return array|false An array of messages or false if there was a problem loading the file.
51
public function &loadData($filename)
53
libxml_use_internal_errors(true);
54
if (!$xml = simplexml_load_file($filename))
60
libxml_use_internal_errors(false);
62
$translationUnit = $xml->xpath('//trans-unit');
64
$translations = array();
66
foreach ($translationUnit as $unit)
68
$source = (string) $unit->source;
69
$translations[$source][] = (string) $unit->target;
70
$translations[$source][] = (string) $unit['id'];
71
$translations[$source][] = (string) $unit->note;
78
* Creates and returns a new DOMDocument instance
80
* @param string $xml XML string
84
protected function createDOMDocument($xml = null)
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;
92
if (null !== $xml && is_string($xml))
94
// Add header for XML with UTF-8
95
if (!preg_match('/<\?xml/', $xml))
97
$xml = '<?xml version="1.0" encoding="UTF-8"?>'."\n".$xml;
107
* Gets the variant for a catalogue depending on the current culture.
109
* @param string $catalogue catalogue
110
* @return string the variant.
115
protected function getVariants($catalogue = 'messages')
117
if (null === $catalogue)
119
$catalogue = 'messages';
122
foreach ($this->getCatalogueList($catalogue) as $variant)
124
$file = $this->getSource($variant);
127
return array($variant, $file);
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.
139
* @param string $catalogue the catalogue to add to
140
* @return boolean true if saved successfuly, false otherwise.
142
public function save($catalogue = 'messages')
144
$messages = $this->untranslated;
145
if (count($messages) <= 0)
150
$variants = $this->getVariants($catalogue);
153
list($variant, $filename) = $variants;
157
list($variant, $filename) = $this->createMessageTemplate($catalogue);
160
if (is_writable($filename) == false)
162
throw new sfException(sprintf("Unable to save to file %s, file must be writable.", $filename));
165
// create a new dom, import the existing xml
166
$dom = $this->createDOMDocument();
167
@$dom->load($filename);
169
// find the body element
170
$xpath = new DomXPath($dom);
171
$body = $xpath->query('//body')->item(0);
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);
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))
186
$count = intval($last->getAttribute('id'));
193
// for each message add it to the XML file using DOM
194
foreach ($messages as $message)
196
$unit = $dom->createElement('trans-unit');
197
$unit->setAttribute('id', ++$count);
199
$source = $dom->createElement('source');
200
$source->appendChild($dom->createTextNode($message));
201
$target = $dom->createElement('target');
202
$target->appendChild($dom->createTextNode(''));
204
$unit->appendChild($source);
205
$unit->appendChild($target);
207
$body->appendChild($unit);
210
$fileNode = $xpath->query('//file')->item(0);
211
$fileNode->setAttribute('date', @date('Y-m-d\TH:i:s\Z'));
213
$dom = $this->createDOMDocument($dom->saveXML());
215
// save it and clear the cache for this variant
216
$dom->save($filename);
219
$this->cache->remove($variant.':'.$this->culture);
226
* Updates the translation.
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.
234
public function update($text, $target, $comments, $catalogue = 'messages')
236
$variants = $this->getVariants($catalogue);
239
list($variant, $filename) = $variants;
246
if (is_writable($filename) == false)
248
throw new sfException(sprintf("Unable to update file %s, file must be writable.", $filename));
251
// create a new dom, import the existing xml
252
$dom = $this->createDOMDocument();
253
$dom->load($filename);
255
// find the body element
256
$xpath = new DomXPath($dom);
257
$units = $xpath->query('//trans-unit');
259
// for each of the existin units
260
foreach ($units as $unit)
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)
271
if ($node->nodeName == 'source' && $node->firstChild->wholeText == $text)
276
// found source, get the target and notes
279
// set the new translated string
280
if ($node->nodeName == 'target')
282
$node->nodeValue = $target;
287
if (!empty($comments) && $node->nodeName == 'note')
289
$node->nodeValue = $comments;
296
if ($found && !$targetted)
298
$targetNode = $dom->createElement('target');
299
$targetNode->appendChild($dom->createTextNode($target));
300
$unit->appendChild($targetNode);
304
if ($found && !$commented && !empty($comments))
306
$commentsNode = $dom->createElement('note');
307
$commentsNode->appendChild($dom->createTextNode($comments));
308
$unit->appendChild($commentsNode);
311
// finished searching
318
$fileNode = $xpath->query('//file')->item(0);
319
$fileNode->setAttribute('date', @date('Y-m-d\TH:i:s\Z'));
321
if ($dom->save($filename) > 0)
325
$this->cache->remove($variant.':'.$this->culture);
335
* Deletes a particular message from the specified catalogue.
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.
341
public function delete($message, $catalogue='messages')
343
$variants = $this->getVariants($catalogue);
346
list($variant, $filename) = $variants;
353
if (is_writable($filename) == false)
355
throw new sfException(sprintf("Unable to modify file %s, file must be writable.", $filename));
358
// create a new dom, import the existing xml
359
$dom = $this->createDOMDocument();
360
$dom->load($filename);
362
// find the body element
363
$xpath = new DomXPath($dom);
364
$units = $xpath->query('//trans-unit');
366
// for each of the existin units
367
foreach ($units as $unit)
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)
374
if ($node->nodeName == 'source' && $node->firstChild->wholeText == $message)
376
// we found it, remove and save the xml file.
377
$unit->parentNode->removeChild($unit);
379
$fileNode = $xpath->query('//file')->item(0);
380
$fileNode->setAttribute('date', @date('Y-m-d\TH:i:s\Z'));
382
if ($dom->save($filename) > 0)
384
if (!empty($this->cache))
386
$this->cache->remove($variant.':'.$this->culture);
402
protected function createMessageTemplate($catalogue)
404
if (null === $catalogue)
406
$catalogue = 'messages';
409
$variants = $this->getCatalogueList($catalogue);
410
$variant = array_shift($variants);
411
$file = $this->getSource($variant);
412
$dir = dirname($file);
421
throw new sfException(sprintf("Unable to create directory %s.", $dir));
424
$dom = $this->createDOMDocument($this->getTemplate($catalogue));
425
file_put_contents($file, $dom->saveXML());
428
return array($variant, $file);
431
protected function getTemplate($catalogue)
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">