Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 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: sfMessageSource_XLIFF.class.php 23810 2009-11-12 11:07:44Z Kris.Wallsmith $
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
}