## Apache Zeta Components Manual :: File Source for ts_backend.php

## Source for file ts_backend.php

Documentation is available at ts_backend.php

1. <?php
2. /**
3. *
4. * Licensed to the Apache Software Foundation (ASF) under one
5. * or more contributor license agreements. See the NOTICE file
6. * distributed with this work for additional information
7. * regarding copyright ownership. The ASF licenses this file
8. * to you under the Apache License, Version 2.0 (the
9. * "License"); you may not use this file except in compliance
10. * with the License. You may obtain a copy of the License at
11. *
12. * http://www.apache.org/licenses/LICENSE-2.0
13. *
14. * Unless required by applicable law or agreed to in writing,
15. * software distributed under the License is distributed on an
16. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17. * KIND, either express or implied. See the License for the
18. * specific language governing permissions and limitations
19. * under the License.
20. *
21. * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
22. * @version //autogentag//
23. * @filesource
24. * @package Translation
25. */
27. /**
28. * Translation backend that reads Qt's Linguist TS files.
29. *
30. * This class is the backend that can read Qt's Linguist files as source for a
31. * translation. The format is an XML file, which contains contexts and all the
32. * translatable strings.
33. *
34. * Example:
35. * <code>
36. * <?php
37. * $a = new ezcTranslationTsBackend(
38. * 'tests/translations',
39. * array ( 'format' => '[LOCALE].xml' )
40. * );
41. *
42. * $b = new ezcTranslationManager( $a );
43. * ?>
44. * </code>
45. *
46. * The reader capabilities of this class (the implementation of
47. * ezcTranslationContextRead) can be used it two different ways, where the
48. * second one is the more elegant approach.
49. *
50. * Reader Example 1:
51. * <code>
52. * <?php
53. * $backend = new ezcTranslationTsBackend( "files/translations" );
54. * $backend->options->format = '[LOCALE].xml';
55. * $backend->initReader( 'nb-no' );
56. * $backend->next();
57. *
58. * $contexts = array();
59. * while ( $backend->valid() )
60. * {
61. * $contextName = $backend->key();
62. * $contextData = $backend->current();
63. * // do something with the data
64. * $backend->next();
65. * }
66. * ?>
67. * </code>
68. *
69. * Reader Example 2:
70. * <code>
71. * <?php
72. * $backend = new ezcTranslationTsBackend( "{$currentDir}/files/translations" );
73. * $backend->options->format = '[LOCALE].xml';
74. * $backend->initReader( 'nb-no' );
75. *
76. * $contexts = array();
77. * foreach ( $backend as $contextName => $contextData )
78. * {
79. * // do something with the data
80. * }
81. * ?>
82. * </code>
83. *
84. * For a more extensive example see {@link ezcTranslationManager}.
85. *
86. * @property ezcTranslationTsBackendOptions $options
87. * Contains the options for this class.
88. *
89. * @package Translation
90. * @version //autogentag//
91. * @mainclass
92. */
93. class ezcTranslationTsBackend implements ezcTranslationBackend, ezcTranslationContextRead, ezcTranslationContextWrite
94. {
95. /**
96. * The last read context, as read by next() method.
97. *
98. * The next() method is a part of the {@link ezcTranslationContextRead}
99. * interface. The first element is the name, the second an array with
100. * {@link ezcTranslationData} objects. An example of such an array is:
101. *
102. * <code>
103. * array(
104. * 'design/admin/class/classlist',
105. * array(
106. * new ezcTranslationData( 'Edit', 'Rediger', false, ezcTranslationData::TRANSLATED ),
107. * new ezcTranslationData( 'Create a copy of the <%class_name> class.', 'Lag en kopi av klassen <%class_name>.', false, ezcTranslationData::TRANSLATED ),
108. * ),
109. * );
110. * </code>
111. *
112. * @var array
113. */
114. private $currentContext = null;
116. /**
117. * Handle for the XML parser used as part of the ezcTranslationContextRead interface.
118. *
119. * @var resource
120. */
121. private $xmlParser = null;
123. /**
124. * Contains the DOM tree for modifications while writing a new translation file
125. *
126. * @var DomDocument
127. */
128. private $dom;
130. /**
131. * Contains the locale used for writing a new translation file
132. *
133. * @var string
134. */
135. private $writeLocale = null;
137. /**
138. * Container to hold the properties
139. *
140. * @var array(string=>mixed)
141. */
142. protected $properties ;
144. /**
145. * Constructs a new ezcTranslationTsBackend that will use the file specified by $location.
146. *
147. * You can specify additional options through the $options parameter. See
148. * the documentation for the {@link ezcTranslationTsBackend::setOptions()}
149. * method for supported options.
150. *
151. * @throws ezcTranslationNotConfiguredException if $location is not set or is empty.
152. * @param string $location
153. * @param array(string=>mixed) $options
154. */
155. function __construct ( $location, array $options = array() )
156. {
157. if ( !$location || ! strlen ( $location ) )
158. {
159. throw new ezcTranslationNotConfiguredException ( $location );
160. }
161. $this-> properties ['options'] = new ezcTranslationTsBackendOptions ( $options );
162. $this-> properties ['options']->location = $location;
163. }
165. /**
166. * Set new options.
167. * This method allows you to change the options of the translation backend.
168. *
169. * @param ezcTranslationTsBackendOptions $options The options to set.
170. *
171. * @throws ezcBaseSettingNotFoundException
172. * If you tried to set a non-existent option value.
173. * @throws ezcBaseSettingValueException
174. * If the value is not valid for the desired option.
175. * @throws ezcBaseValueException
176. * If you submit neither an array nor an instance of
177. * ezcTranslationTsBackendOptions.
178. */
179. public function setOptions ( $options )
180. {
181. if ( is_array ( $options ) )
182. {
183. $this->options->merge( $options );
184. }
185. else if ( $options instanceof ezcTranslationTsBackendOptions )
186. {
187. $this->options = $options;
188. }
189. else
190. {
191. throw new ezcBaseValueException ( "options", $options, "instance of ezcTranslationTsBackendOptions" );
192. }
193. }
195. /**
196. * Returns the current options.
197. * Returns the options currently set for this backend.
198. *
199. * @return ezcTranslationTsBackendOptions The current options.
200. */
201. public function getOptions ()
202. {
203. return $this->options;
204. }
206. /**
207. * Returns the filename for the translation file using the locale $locale.
208. *
209. * This function uses the <i>location</i> and <i>format</i> options,
210. * combined with the $locale parameter to form a filename that contains the
211. * translation belonging to the specified locale.
212. *
213. * @param string $locale
214. * @return string
215. */
216. public function buildTranslationFileName ( $locale )
217. {
218. $filename = $this->options->location . $this->options->format;
219. $filename = str_replace ( '[LOCALE]', $locale, $filename );
220. return $filename;
221. }
223. /**
224. * Creates an SimpleXML parser object for the locale $locale.
225. *
226. * You can set the class of the returned object through the $returnClass
227. * parameter. That class should extend the SimpleXMLElement class.
228. *
229. * This function checks if the <i>location</i> setting is made, if the file
230. * with the filename as returned by buildTranslationFileName() exists and
231. * creates a SimpleXML parser object for this file. If either the setting
232. * is not made, or the file doesn't exists it throws an exception.
233. *
234. * @throws ezcTranslationMissingTranslationFileException if the translation
235. * could not be opened.
236. * @param string $locale
237. * @param string $returnClass The class of the returned XML Parser Object.
238. * @return object The created parser. The parameter $returnClass
239. * determines what type of class gets returned. The
240. * classname that you specify should be inherited from
241. * SimpleXMLElement.
242. */
243. public function openTranslationFile ( $locale, $returnClass = 'SimpleXMLElement' )
244. {
245. $filename = $this-> buildTranslationFileName ( $locale );
246. if ( ! file_exists ( $filename ) )
247. {
248. throw new ezcTranslationMissingTranslationFileException ( $filename );
249. }
250. return simplexml_load_file ( $filename, $returnClass );
251. }
253. /**
254. * Creates a DOM parser object for the locale $locale.
255. *
256. * This function checks if the <i>location</i> setting is made, if the file
257. * with the filename as returned by buildTranslationFileName() exists and
258. * creates a DOM parser object for this file. If the setting
259. * is not made, it throws an exception. IF the file does not exist, a new
260. * DomDocument is created.
261. *
262. * @param string $locale
263. * @return object The created parser.
264. */
265. public function openTranslationFileForWriting ( $locale )
266. {
267. $filename = $this-> buildTranslationFileName ( $locale );
268. if ( ! file_exists ( $filename ) )
269. {
270. $dom = new DOMDocument( '1.0', 'UTF-8' );
271. $dom->formatOutput = true;
272. $root = $dom->createElement( 'TS' );
273. $dom->appendChild( $root );
274. }
275. else
276. {
277. $dom = new DOMDocument( '1.0', 'UTF-8' );
278. $dom->preserveWhiteSpace = false;
279. $dom->load( $filename );
280. $dom->formatOutput = true;
281. $root = $dom->getElementsByTagName( 'TS' )->item( 0 );
282. }
284. return $dom;
285. }
287. /**
288. * Returns the data from the XML element $message as an
289. * ezcTranslationData object.
290. *
291. * @param SimpleXMLElement $message
292. * @return ezcTranslationData
293. */
294. private function parseSimpleXMLMessage( SimpleXMLElement $message )
295. {
296. $status = ezcTranslationData ::TRANSLATED;
297. if ( $message->translation['type'] == 'unfinished' )
298. {
299. $status = ezcTranslationData ::UNFINISHED;
300. }
301. else if ( $message->translation['type'] == 'obsolete' )
302. {
303. if ( $this->options->keepObsolete )
304. {
305. $status = ezcTranslationData ::OBSOLETE;
306. }
307. else
308. {
309. return null;
310. }
311. }
313. $source = trim ( (string) $message->source );
314. $translation = trim ( (string) $message->translation );
315. $comment = trim ( (string) $message->comment );
317. $source = strlen ( $source ) ? $source : false;
318. $translation = strlen ( $translation ) ? $translation : false;
319. $comment = strlen ( $comment ) ? $comment : false;
321. $location = $message->location;
322. $file = $line = false;
323. if ( $location )
324. {
325. $file = trim ( (string) $location['filename'] );
326. $line = trim ( (string) $location['line'] );
327. }
329. return new ezcTranslationData ( $source, $translation, $comment, $status, $file, $line );
330. }
333. /**
334. * Returns a array containing a translation map for the locale $locale
335. * and the context $context.
336. *
337. * This method returns an array containing the translation map for the
338. * specified $locale and $context. It uses the location and
339. * format options to locate the file, unless caching is
340. * enabled.
341. *
342. * @throws ezcTranslationContextNotAvailableException if a context is not
343. * available
344. * @throws ezcTranslationMissingTranslationFileException if the translation
345. * file does not exist.
346. * @throws ezcTranslationNotConfiguredException if the option <i>format</i>
347. * is not set before this method is called.
348. * @param string $locale
349. * @param string $context
350. * @return array(ezcTranslationData)
351. */
352. public function getContext ( $locale, $context )
353. {
354. $ts = $this-> openTranslationFile ( $locale );
355. $contextElements = array();
356. foreach ( $ts as $trContext )
357. {
358. if ( (string) $trContext->name == $context )
359. {
360. foreach ( $trContext as $message )
361. {
362. if ( $message->source != '' )
363. {
364. $element = $this->parseSimpleXMLMessage( $message );
365. if ( is_null ( $element ) )
366. {
367. continue;
368. }
369. $contextElements[] = $element;
370. }
371. }
372. return $contextElements;
373. }
374. }
375. throw new ezcTranslationContextNotAvailableException ( $context );
376. }
378. /**
379. * Returns a list with all context names for the locale $locale.
380. *
381. * @throws ezcTranslationMissingTranslationFileException if the translation
382. * file does not exist.
383. * @throws ezcTranslationNotConfiguredException if the option <i>format</i>
384. * is not set before this method is called.
385. * @param string $locale
386. * @return array(string)
387. */
388. public function getContextNames ( $locale )
389. {
390. $ts = $this-> openTranslationFile ( $locale );
391. $contextNames = array();
392. foreach ( $ts as $trContext )
393. {
394. $contextNames[] = (string) $trContext->name;
395. }
396. return $contextNames;
397. }
399. /**
400. * Initializes the reader to read from locale $locale.
401. *
402. * Opens the translation file.
403. *
404. * @throws ezcTranslationNotConfiguredException if the option <i>format</i>
405. * is not set before this method is called.
406. *
407. * @param string $locale
408. * @return void
409. */
410. public function initReader ( $locale )
411. {
412. $this->xmlParser = $this-> openTranslationFile ( $locale, 'SimpleXMLIterator' );
413. $this->xmlParser->rewind();
414. }
416. /**
417. * Deinitializes the reader
418. *
419. * This method should be called after the haveMore() method returns false
420. * to cleanup resources.
421. *
422. * @throws ezcTranslationException when the reader is not initialized with
423. * initReader().
424. * @return void
425. */
426. public function deinitReader ()
427. {
428. $this->xmlParser = null;
429. }
431. /**
432. * Stores a context.
433. *
434. * This method stores the context that it received to the backend specified
435. * storage place.
436. *
437. * @throws ezcTranslationWriterNotInitializedException when the writer is
438. * not initialized with initWriter().
439. * @param string $context The context's name
440. * @param array(ezcTranslationData) $data The context's translation map
441. * @return void
442. */
443. public function storeContext ( $context, array $data )
444. {
445. if ( is_null( $this->dom ) )
446. {
447. throw new ezcTranslationWriterNotInitializedException ();
448. }
450. $dom = $this->dom;
451. $root = $dom->getElementsByTagName( 'TS' )->item( 0 );
453. // find the context element
454. $xpath = new DOMXPath( $dom );
455. $result = $xpath->query( '//context/name[text()="' . htmlspecialchars( $context ) . '"]' );
457. // If the context does not exist, we create a node for it; otherwise we just use it.
458. if ( !$result->length )
459. {
460. $contextNode = $dom->createElement( 'context' );
461. $nameNode = $dom->createElement( 'name', htmlspecialchars( $context ) );
462. $contextNode->appendChild( $nameNode );
463. $root->appendChild( $contextNode );
464. }
465. else
466. {
467. $contextNode = $result->item( 0 )->parentNode;
468. }
470. // for every entry, we add a new message
471. foreach ( $data as $info )
472. {
473. // check if the string is already there, if so, remove it first
474. $xpath = new DOMXPath( $dom );
475. $xpathString = str_replace ( '"', '",\'"\',"', $info->original );
477. $result = $xpath->query( 'message/source[text()=concat("' . $xpathString . '","")]', $contextNode );
478. if ( $result->length )
479. {
480. $node = $result->item( 0 )->parentNode;
481. $contextNode->removeChild( $node );
482. }
484. // create the new element
485. $message = $dom->createElement( 'message' );
486. $source = $dom->createElement( 'source', htmlspecialchars ( $info->original ) );
487. $message->appendChild( $source );
489. $translation = $dom->createElement( 'translation', htmlspecialchars ( $info->translation ) );
490. switch ( $info->status )
491. {
492. case ezcTranslationData ::UNFINISHED:
493. $translation->setAttribute( 'type', 'unfinished' );
494. break;
495. case ezcTranslationData ::OBSOLETE:
496. $translation->setAttribute( 'type', 'obsolete' );
497. break;
498. }
499. $message->appendChild( $translation );
501. if ( $info->comment )
502. {
503. $comment = $dom->createElement( 'comment', htmlspecialchars ( $info->comment ) );
504. $message->appendChild( $comment );
505. }
507. if ( $info->filename && $info->line )
508. {
509. $location = $dom->createElement( 'location' );
510. $location->setAttribute( 'filename', $info->filename );
511. $location->setAttribute( 'line', $info->line );
512. $message->appendChild( $location );
513. }
515. $contextNode->appendChild( $message );
516. }
517. }
519. /**
520. * Initializes the writer to write to locale $locale.
521. *
522. * Opens the translation file.
523. *
524. * @throws ezcTranslationNotConfiguredException if the option <i>format</i>
525. * is not set before this method is called.
526. *
527. * @param string $locale
528. * @return void
529. */
530. public function initWriter ( $locale )
531. {
532. $this->dom = $this-> openTranslationFileForWriting ( $locale );
533. $this->writeLocale = $locale;
534. }
536. /**
537. * Deinitializes the writer
538. *
539. * @return void
540. */
541. public function deinitWriter ()
542. {
543. if ( is_null ( $this->dom ) )
544. {
545. throw new ezcTranslationWriterNotInitializedException ();
546. }
547. $filename = $this-> buildTranslationFileName ( $this->writeLocale );
548. $this->dom->save( $filename );
549. }
551. /**
552. * Advanced to the next context.
553. *
554. * This method parses a little bit more of the XML file to be able to
555. * return the next context. If no more contexts are available it sets the
556. * $currentContext member variable to null, so that the valid() method can
557. * pick this up. If there are more contexts available it reads the context
558. * from the file and stores it into the $currentContext member variable.
559. * This method is used for iteration as part of the Iterator interface.
560. *
561. * @throws ezcTranslationReaderNotInitializedException when the reader is
562. * not initialized with initReader().
563. * @return void
564. */
565. public function next ()
566. {
567. if ( is_null ( $this->xmlParser ) )
568. {
569. throw new ezcTranslationReaderNotInitializedException ();
570. }
571. $valid = $this->xmlParser-> valid ();
573. if ( $valid )
574. {
575. $newContext = array( trim ( $this->xmlParser->getChildren()->name), array() );
577. foreach ( $this->xmlParser->getChildren()->message as $data )
578. {
579. $translationItem = $this->parseSimpleXMLMessage( $data );
581. if ( ! is_null ( $translationItem ) )
582. {
583. $newContext[1][] = $translationItem;
584. }
585. }
587. $this-> currentContext = $newContext;
588. $this->xmlParser-> next ();
589. }
590. else
591. {
592. $this-> currentContext = null;
593. }
594. }
596. /**
597. * Returns whether there is a new context available.
598. *
599. * This method checks whether a valid context was read. It checks the
600. * $currentContext member variable for the status.
601. * This method is used for iteration as part of the Iterator interface.
602. *
603. * @throws ezcTranslationReaderNotInitializedException when the reader is
604. * not initialized with initReader().
605. * @return bool
606. */
607. public function valid ()
608. {
609. return $this-> currentContext != null;
610. }
612. /**
613. * Returns the current context
614. *
615. * This method returns the latest read context, that the haveMore() method
616. * put into the $currentContext property. See
617. * {@link ezcTranslationTsBackend::$currentContext} for the format of this
618. * array.
619. * This method is used for iteration as part of the Iterator interface.
620. *
621. * @throws ezcTranslationReaderNotInitializedException when the reader is
622. * not initialized with initReader().
623. * @return array The current context's translation map
624. */
625. public function currentContext ()
626. {
627. if ( is_null ( $this->xmlParser ) )
628. {
629. throw new ezcTranslationReaderNotInitializedException ();
630. }
631. return $this-> currentContext ;
632. }
634. /**
635. * Returns the current context's data.
636. *
637. * This method returns the latest read context, that the next() method
638. * put into the $currentContext property. See
639. * {@link ezcTranslationTsBackend::$currentContext} for the format of this
640. * array.
641. * This method is used for iteration as part of the Iterator interface.
642. *
643. * @throws ezcTranslationReaderNotInitializedException when the reader is
644. * not initialized with initReader().
645. * @return array The current context's translation map
646. */
647. public function current ()
648. {
649. $context = $this-> currentContext ();
650. return $context[1];
651. }
653. /**
654. * Returns the current context's name.
655. *
656. * This method returns the latest read context, that the next() method
657. * put into the $currentContext property. See
658. * {@link ezcTranslationTsBackend::$currentContext} for the format of this
659. * array.
660. * This method is used for iteration as part of the Iterator interface.
661. *
662. * @throws ezcTranslationReaderNotInitializedException when the reader is
663. * not initialized with initReader().
664. * @return string The current context's name
665. */
666. public function key ()
667. {
668. $context = $this-> currentContext ();
669. return $context[0];
670. }
672. /**
673. * Empty function to satisfy the Iterator interface.
674. *
675. * The iterator interface expects this method to rewind to the start of
676. * the array. As we do not support rewinding actually, the only thing that
677. * the rewind() implementation does is reading the first element from the
678. * translation file. There are no side effects either if you just use the
679. * foreach or while methods. (See class introduction for an example).
680. * This method is used for iteration as part of the Iterator interface.
681. *
682. * @throws ezcTranslationReaderNotInitializedException when the reader is
683. * not initialized with initReader().
684. * @return void
685. */
686. public function rewind ()
687. {
688. $this-> next ();
689. }
691. /**
692. * Property read access.
693. *
694. * @throws ezcBasePropertyNotFoundException
695. * If the the desired property is not found.
696. * @param string $propertyName Name of the property.
697. * @return mixed Value of the property or null.
698. * @ignore
699. */
700. public function __get( $propertyName )
701. {
702. switch ( $propertyName )
703. {
704. case 'options':
705. return $this-> properties ['options'];
706. default:
707. break;
708. }
709. throw new ezcBasePropertyNotFoundException ( $propertyName );
710. }
712. /**
713. * Property write access.
714. *
715. * @throws ezcBaseValueException
716. * If a the value for the property options is not an instance of
717. * ezcTranslationTsBackendOptions.
718. * @throws ezcBasePropertyNotFoundException
719. * If the the desired property is not found.
720. * @param string $propertyName Name of the property.
721. * @param mixed $val The value for the property.
722. * @ignore
723. */
724. public function __set( $propertyName, $val )
725. {
726. switch ( $propertyName )
727. {
728. case 'options':
729. if ( !( $val instanceof ezcTranslationTsBackendOptions ) )
730. {
731. throw new ezcBaseValueException ( $propertyName, $val, 'instance of ezcTranslationTsBackendOptions' );
732. }
733. $this-> properties ['options'] = $val;
734. return;
735. default:
736. break;
737. }
738. throw new ezcBasePropertyNotFoundException ( $propertyName );
739. }
741. /**
742. * Property isset access.
743. *
744. * @param string $propertyName Name of the property.
745. * @return bool True is the property is set, otherwise false.
746. * @ignore
747. */
748. public function __isset( $propertyName )
749. {
750. switch ( $propertyName )
751. {
752. case 'options':
753. return true;
754. }
755. return false;
756. }
758. }
759. ?>

Documentation generated by phpDocumentor 1.4.3