Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
3
 
4
/**
5
 * Contains the Translation2_Container_xml class
6
 *
7
 * PHP versions 4 and 5
8
 *
9
 * LICENSE: Redistribution and use in source and binary forms, with or without
10
 * modification, are permitted provided that the following conditions are met:
11
 * 1. Redistributions of source code must retain the above copyright
12
 *    notice, this list of conditions and the following disclaimer.
13
 * 2. Redistributions in binary form must reproduce the above copyright
14
 *    notice, this list of conditions and the following disclaimer in the
15
 *    documentation and/or other materials provided with the distribution.
16
 * 3. The name of the author may not be used to endorse or promote products
17
 *    derived from this software without specific prior written permission.
18
 *
19
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
20
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
21
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22
 * IN NO EVENT SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY
23
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
 *
30
 * @category  Internationalization
31
 * @package   Translation2
32
 * @author    Lorenzo Alberton <l.alberton@quipo.it>
33
 * @author    Olivier Guilyardi <olivier@samalyse.com>
34
 * @copyright 2004-2008 Lorenzo Alberton, Olivier Guilyardi
35
 * @license   http://www.debian.org/misc/bsd.license  BSD License (3 Clause)
36
 * @version   CVS: $Id: xml.php 305985 2010-12-05 22:55:33Z clockwerx $
37
 * @link      http://pear.php.net/package/Translation2
38
 */
39
 
40
/**
41
 * require Translation2_Container class
42
 */
43
require_once 'Translation2/Container.php';
44
/**
45
 * require XML_Unserializer class
46
 */
47
require_once 'XML/Unserializer.php';
48
/**
49
 * Document Type Definition
50
 */
51
define('TRANSLATION2_DTD',
52
    "<!ELEMENT translation2 (languages,pages)>\n" .
53
    "<!ELEMENT languages (lang*)>\n" .
54
    "<!ELEMENT lang (name?,meta?,error_text?,encoding?)>\n" .
55
    "<!ATTLIST lang id ID #REQUIRED>\n" .
56
    "<!ELEMENT name (#PCDATA)>\n" .
57
    "<!ELEMENT meta (#PCDATA)>\n" .
58
    "<!ELEMENT error_text (#PCDATA)>\n" .
59
    "<!ELEMENT encoding (#PCDATA)>\n" .
60
    "<!ELEMENT pages (page*)>\n" .
61
    "<!ELEMENT page (string*)>\n" .
62
    "<!ATTLIST page key CDATA #REQUIRED>\n" .
63
    "<!ELEMENT string (tr*)>\n" .
64
    "<!ATTLIST string key CDATA #REQUIRED>\n" .
65
    "<!ELEMENT tr (#PCDATA)>\n" .
66
    "<!ATTLIST tr lang IDREF #REQUIRED>\n"
67
);
68
 
69
/**
70
 * Storage driver for fetching data from a XML file
71
 *
72
 * Example file :
73
 * <pre>
74
 * <?xml version="1.0" encoding="iso-8859-1"?>
75
 * <translation2>
76
 *     <languages>
77
 *         <lang id='fr_FR'>
78
 *             <name> English </name>
79
 *             <meta> Custom meta data</meta>
80
 *             <error_text> Non disponible en français </error_text>
81
 *             <encoding> iso-8859-1 </encoding>
82
 *         </lang>
83
 *         <!-- some more <lang>...</lang> -->
84
 *     </languages>
85
 *     <pages>
86
 *         <page key='pets'>
87
 *             <string key='cat'>
88
 *                 <tr lang='fr_FR'> Chat </tr>
89
 *                 <!-- some more <tr>...</tr> -->
90
 *             </string>
91
 *             <!-- some more <string>...</string> -->
92
 *         </page>
93
 *         <!-- some more <page>...</page> -->
94
 *     </pages>
95
 * </translation2>
96
 * </pre>
97
 *
98
 * @category  Internationalization
99
 * @package   Translation2
100
 * @author    Lorenzo Alberton <l.alberton@quipo.it>
101
 * @author    Olivier Guilyardi <olivier@samalyse.com>
102
 * @copyright 2004-2008 Lorenzo Alberton, Olivier Guilyardi
103
 * @license   http://www.debian.org/misc/bsd.license  BSD License (3 Clause)
104
 * @link      http://pear.php.net/package/Translation2
105
 */
106
class Translation2_Container_xml extends Translation2_Container
107
{
108
    // {{{ class vars
109
 
110
    /**
111
     * Unserialized XML data
112
     * @var object
113
     */
114
    var $_data = null;
115
 
116
    /**
117
     * XML file name
118
     * @var string
119
     */
120
    var $_filename;
121
 
122
    // }}}
123
    // {{{ init
124
 
125
    /**
126
     * Initialize the container
127
     *
128
     * @param array $options - 'filename': Path to the XML file
129
     *
130
     * @return boolean|PEAR_Error object if something went wrong
131
     */
132
    function init($options)
133
    {
134
        $this->_filename = $options['filename'];
135
        unset($options['filename']);
136
        $this->_setDefaultOptions();
137
        $this->_parseOptions($options);
138
 
139
        return $this->_loadFile();
140
    }
141
 
142
    // }}}
143
    // {{{ _loadFile()
144
 
145
    /**
146
     * Load an XML file into memory, and eventually decode the strings from UTF-8
147
     *
148
     * @return boolean|PEAR_Error
149
     * @access private
150
     */
151
    function _loadFile()
152
    {
153
        $keyAttr = array (
154
            'lang'   => 'id',
155
            'page'   => 'key',
156
            'string' => 'key',
157
            'tr'     => 'lang'
158
        );
159
        if (!$fp = @fopen($this->_filename, 'r')) {
160
            return new PEAR_Error ("Can\'t read from the XML source: {$this->_filename}");
161
        }
162
        @flock($fp, LOCK_SH);
163
        $unserializer = &new XML_Unserializer (array('keyAttribute' => $keyAttr));
164
        if (PEAR::isError($status = $unserializer->unserialize($this->_filename, true))) {
165
            fclose($fp);
166
            return $status;
167
        }
168
        fclose($fp);
169
 
170
        // unserialize data
171
        $this->_data = $unserializer->getUnserializedData();
172
        $this->fixEmptySets($this->_data);
173
        $this->_fixDuplicateEntries();
174
 
175
        // Handle default language settings.
176
        // This allows, for example, to rapidly write the meta data as:
177
        //
178
        // <lang key="fr"/>
179
        // <lang key="en"/>
180
 
181
        $defaults = array(
182
            'name'       => '',
183
            'meta'       => '',
184
            'error_text' => '',
185
            'encoding'   => 'iso-8859-1'
186
        );
187
 
188
        foreach ($this->_data['languages'] as $lang_id => $settings) {
189
            if (empty($settings)) {
190
                $this->_data['languages'][$lang_id] = $defaults;
191
            } else {
192
                $this->_data['languages'][$lang_id] =
193
                    array_merge($defaults, $this->_data['languages'][$lang_id]);
194
            }
195
        }
196
 
197
        // convert lang metadata from UTF-8
198
        if (PEAR::isError($e = $this->_convertLangEncodings('from_xml', $this->_data))) {
199
            return $e;
200
        }
201
 
202
        // convert encodings of the translated strings from xml (somehow heavy)
203
        return $this->_convertEncodings('from_xml', $this->_data);
204
    }
205
 
206
    // }}}
207
    // {{{ _convertEncodings()
208
 
209
    /**
210
     * Convert strings to/from XML unique charset (UTF-8)
211
     *
212
     * @param string $direction ['from_xml' | 'to_xml']
213
     * @param array  &$data     Data buffer to operate on
214
     *
215
     * @return boolean|PEAR_Error
216
     */
217
    function _convertEncodings($direction, &$data)
218
    {
219
        if ($direction == 'from_xml') {
220
            $source_encoding = 'UTF-8';
221
        } else {
222
            $target_encoding = 'UTF-8';
223
        }
224
 
225
        foreach ($data['pages'] as $page_id => $page_content) {
226
            foreach ($page_content as $str_id => $translations) {
227
                foreach ($translations as $lang => $str) {
228
                    if ($direction == 'from_xml') {
229
                        $target_encoding =
230
                            strtoupper($data['languages'][$lang]['encoding']);
231
                    } else {
232
                        $source_encoding =
233
                            strtoupper($data['languages'][$lang]['encoding']);
234
                    }
235
                    if ($target_encoding != $source_encoding) {
236
                        $res = iconv($source_encoding, $target_encoding, $str);
237
                        if ($res === false) {
238
                            $msg = 'Encoding conversion error ' .
239
                                   "(source encoding: $source_encoding, ".
240
                                   "target encoding: $target_encoding, ".
241
                                   "processed string: \"$str\"";
242
                            return $this->raiseError($msg,
243
                                    TRANSLATION2_ERROR_ENCODING_CONVERSION,
244
                                    PEAR_ERROR_RETURN,
245
                                    E_USER_WARNING);
246
                        }
247
                        $data['pages'][$page_id][$str_id][$lang] = $res;
248
                    }
249
                }
250
            }
251
        }
252
        return true;
253
    }
254
 
255
    // }}}
256
    // {{{ _convertLangEncodings()
257
 
258
    /**
259
     * Convert lang data to/from XML unique charset (UTF-8)
260
     *
261
     * @param string $direction ['from_xml' | 'to_xml']
262
     * @param array  &$data     Data buffer to operate on
263
     *
264
     * @return boolean|PEAR_Error
265
     */
266
    function _convertLangEncodings($direction, &$data)
267
    {
268
        static $fields = array('name', 'meta', 'error_text');
269
 
270
        if ($direction == 'from_xml') {
271
            $source_encoding = 'UTF-8';
272
        } else {
273
            $target_encoding = 'UTF-8';
274
        }
275
 
276
        foreach ($data['languages'] as $lang_id => $lang) {
277
            if ($direction == 'from_xml') {
278
                $target_encoding = strtoupper($lang['encoding']);
279
            } else {
280
                $source_encoding = strtoupper($lang['encoding']);
281
            }
282
            //foreach (array_keys($lang) as $field) {
283
            foreach ($fields as $field) {
284
                if ($target_encoding != $source_encoding && !empty($lang[$field])) {
285
                    $res = iconv($source_encoding, $target_encoding, $lang[$field]);
286
                    if ($res === false) {
287
                        $msg = 'Encoding conversion error ' .
288
                               "(source encoding: $source_encoding, ".
289
                               "target encoding: $target_encoding, ".
290
                               "processed string: \"$lang[$field]\"";
291
                        return $this->raiseError($msg,
292
                                TRANSLATION2_ERROR_ENCODING_CONVERSION,
293
                                PEAR_ERROR_RETURN,
294
                                E_USER_WARNING);
295
                    }
296
                    $data['languages'][$lang_id][$field] = $res;
297
                }
298
            }
299
        }
300
        return true;
301
    }
302
 
303
    // }}}
304
    // {{{ _fixDuplicateEntries()
305
 
306
    /**
307
     * Remove duplicate entries from the xml data
308
     *
309
     * @return void
310
     */
311
    function _fixDuplicateEntries()
312
    {
313
        foreach ($this->_data['pages'] as $pagename => $pagedata) {
314
            foreach ($pagedata as $stringname => $stringvalues) {
315
                if (is_array(array_pop($stringvalues))) {
316
                    $this->_data['pages'][$pagename][$stringname] =
317
                        call_user_func_array(array($this, '_merge'), $stringvalues);
318
                }
319
            }
320
        }
321
    }
322
 
323
    // }}}
324
    // {{{ fixEmptySets()
325
 
326
    /**
327
     * Turn empty strings returned by XML_Unserializer into empty arrays
328
     *
329
     * Note: this method is public because called statically by the t2xmlchk.php
330
     * script. It is not meant to be called by user-space code.
331
     *
332
     * @param array &$data array of languages/pages
333
     *
334
     * @return void
335
     * @access public
336
     * @static
337
     */
338
    function fixEmptySets(&$data)
339
    {
340
        if (PEAR::isError($this->_data) && ($this->_data->code == XML_UNSERIALIZER_ERROR_NO_UNSERIALIZATION)) {
341
            //empty file... create skeleton
342
            $this->_data = array(
343
                'languages' => array(),
344
                'pages'     => array(),
345
            );
346
        }
347
        if (is_string($data['languages']) and trim($data['languages']) == '') {
348
            $data['languages'] = array();
349
        }
350
        if (is_string($data['pages']) and trim($data['pages']) == '') {
351
            $data['pages'] = array();
352
        } else {
353
            foreach ($data['pages'] as $pageName => $strings) {
354
                //if (is_string($strings) and trim($strings) == '') {
355
                if (is_string($strings)) {
356
                    $data['pages'][$pageName] = array();
357
                } else {
358
                    foreach ($strings as $stringName => $translations) {
359
                        if (is_string($translations) and trim($translations) == '') {
360
                            $data['pages'][$pageName][$stringName] = array();
361
                        }
362
                    }
363
                }
364
            }
365
        }
366
    }
367
 
368
    // }}}
369
    // {{{ _merge()
370
 
371
    /**
372
     * Wrapper for array_merge()
373
     *
374
     * @param array $arr1 reference
375
     *
376
     * @return array
377
     */
378
    function _merge()
379
    {
380
        $return = array();
381
        foreach (func_get_args() as $arg) {
382
            $return = array_merge($return, $arg);
383
        }
384
        return $return;
385
    }
386
 
387
    // }}}
388
    // {{{ _setDefaultOptions()
389
 
390
    /**
391
     * Set some default options
392
     *
393
     * @return void
394
     * @access private
395
     */
396
    function _setDefaultOptions()
397
    {
398
        //save changes on shutdown or in real time?
399
        $this->options['save_on_shutdown']  = true;
400
    }
401
 
402
    // }}}
403
    // {{{ fetchLangs()
404
 
405
    /**
406
     * Fetch the available langs
407
     *
408
     * @return void
409
     */
410
    function fetchLangs()
411
    {
412
        $res = array();
413
        foreach ($this->_data['languages'] as $id => $spec) {
414
            $spec['id'] = $id;
415
            $res[$id] = $spec;
416
        }
417
        $this->langs = $res;
418
    }
419
 
420
    // }}}
421
    // {{{ getPage()
422
 
423
    /**
424
     * Returns an array of the strings in the selected page
425
     *
426
     * @param string $pageID page/group ID
427
     * @param string $langID language ID
428
     *
429
     * @return array
430
     */
431
    function getPage($pageID = null, $langID = null)
432
    {
433
        $langID = $this->_getLangID($langID);
434
        if (PEAR::isError($langID)) {
435
            return $langID;
436
        }
437
        $pageID = (is_null($pageID)) ? '#NULL' : $pageID;
438
        $pageID = (empty($pageID) && (0 !== $pageID)) ? '#EMPTY' : $pageID;
439
 
440
        $result = array();
441
        foreach ($this->_data['pages'][$pageID] as $str_id => $translations) {
442
            $result[$str_id]  = isset($translations[$langID])
443
                                ? $translations[$langID]
444
                                : null;
445
        }
446
 
447
        return $result;
448
    }
449
 
450
    // }}}
451
    // {{{ getOne()
452
 
453
    /**
454
     * Get a single item from the container
455
     *
456
     * @param string $stringID string ID
457
     * @param string $pageID   page/group ID
458
     * @param string $langID   language ID
459
     *
460
     * @return string
461
     */
462
    function getOne($stringID, $pageID = null, $langID = null)
463
    {
464
        $langID = $this->_getLangID($langID);
465
        if (PEAR::isError($langID)) {
466
            return $langID;
467
        }
468
        $pageID = (is_null($pageID)) ? '#NULL' : $pageID;
469
        return isset($this->_data['pages'][$pageID][$stringID][$langID])
470
               ? $this->_data['pages'][$pageID][$stringID][$langID]
471
               : null;
472
    }
473
 
474
    // }}}
475
    // {{{ getStringID()
476
 
477
    /**
478
     * Get the stringID for the given string
479
     *
480
     * @param string $string string
481
     * @param string $pageID page/group ID
482
     *
483
     * @return string
484
     */
485
    function getStringID($string, $pageID = null)
486
    {
487
        $pageID = (is_null($pageID)) ? '#NULL' : $pageID;
488
 
489
        foreach ($this->_data['pages'][$pageID] as $stringID => $translations) {
490
            if (array_search($string, $translations) !== false) {
491
                return $stringID;
492
            }
493
        }
494
 
495
        return '';
496
    }
497
 
498
    // }}}
499
}
500
?>