Translation2
[ class tree: Translation2 ] [ index: Translation2 ] [ all elements ]

Source for file xml.php

Documentation is available at xml.php

  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,v 1.17 2008/02/02 18:05:06 quipo Exp $
  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. {
  107.     // {{{ class vars
  108.  
  109.     /**
  110.      * Unserialized XML data
  111.      * @var object 
  112.      */
  113.     var $_data = null;
  114.  
  115.     /**
  116.      * XML file name
  117.      * @var string 
  118.      */
  119.     var $_filename;
  120.     
  121.     // }}}
  122.     // {{{ init
  123.  
  124.     /**
  125.      * Initialize the container
  126.      *
  127.      * @param array $options - 'filename': Path to the XML file
  128.      *
  129.      * @return boolean|PEAR_Errorobject if something went wrong
  130.      */
  131.     function init($options)
  132.     {
  133.         $this->_filename $options['filename'];
  134.         unset($options['filename']);
  135.         $this->_setDefaultOptions();
  136.         $this->_parseOptions($options);
  137.  
  138.         return $this->_loadFile();
  139.     }
  140.  
  141.     // }}}
  142.     // {{{ _loadFile()
  143.     
  144.     /**
  145.      * Load an XML file into memory, and eventually decode the strings from UTF-8
  146.      *
  147.      * @return boolean|PEAR_Error
  148.      * @access private
  149.      */
  150.     function _loadFile()
  151.     {
  152.         $keyAttr = array (
  153.             'lang'   => 'id',
  154.             'page'   => 'key',
  155.             'string' => 'key',
  156.             'tr'     => 'lang'
  157.         );
  158.         if (!$fp @fopen($this->_filename'r')) {
  159.             return new PEAR_Error ("Can\'t read from the XML source: {$this->_filename}");
  160.         }
  161.         @flock($fp, LOCK_SH);
  162.         $unserializer = &new XML_Unserializer (array('keyAttribute' => $keyAttr));
  163.         if (PEAR::isError($status = $unserializer->unserialize($this->_filenametrue))) {
  164.             fclose($fp);
  165.             return $status;
  166.         }
  167.         fclose($fp);
  168.  
  169.         // unserialize data
  170.         $this->_data $unserializer->getUnserializedData();
  171.         $this->fixEmptySets($this->_data);
  172.         $this->_fixDuplicateEntries();
  173.  
  174.         // Handle default language settings.
  175.         // This allows, for example, to rapidly write the meta data as:
  176.         //
  177.         // <lang key="fr"/>
  178.         // <lang key="en"/>
  179.  
  180.         $defaults = array(
  181.             'name'       => '',
  182.             'meta'       => '',
  183.             'error_text' => '',
  184.             'encoding'   => 'iso-8859-1'
  185.         );
  186.  
  187.         foreach ($this->_data['languages'as $lang_id => $settings{
  188.             if (empty($settings)) {
  189.                 $this->_data['languages'][$lang_id$defaults;
  190.             } else {
  191.                 $this->_data['languages'][$lang_id=
  192.                     array_merge($defaults$this->_data['languages'][$lang_id]);
  193.             }
  194.         }
  195.  
  196.         // convert lang metadata from UTF-8
  197.         if (PEAR::isError($e = $this->_convertLangEncodings('from_xml'$this->_data))) {
  198.             return $e;
  199.         }
  200.  
  201.         // convert encodings of the translated strings from xml (somehow heavy)
  202.         return $this->_convertEncodings('from_xml'$this->_data);
  203.     }
  204.     
  205.     // }}}
  206.     // {{{ _convertEncodings()
  207.     /** 
  208.      * Convert strings to/from XML unique charset (UTF-8)
  209.      *
  210.      * @param string $direction ['from_xml' | 'to_xml']
  211.      * @param array  &$data     Data buffer to operate on
  212.      *
  213.      * @return boolean|PEAR_Error
  214.      */
  215.     function _convertEncodings($direction, &$data)
  216.     {
  217.         if ($direction == 'from_xml') {
  218.             $source_encoding = 'UTF-8';
  219.         } else {
  220.             $target_encoding = 'UTF-8';
  221.         }
  222.         
  223.         foreach ($data['pages'] as $page_id => $page_content) {
  224.             foreach ($page_content as $str_id => $translations) {
  225.                 foreach ($translations as $lang => $str) {
  226.                     if ($direction == 'from_xml') {
  227.                         $target_encoding =
  228.                             strtoupper($data['languages'][$lang]['encoding']);
  229.                     } else {
  230.                         $source_encoding =
  231.                             strtoupper($data['languages'][$lang]['encoding']);
  232.                     }
  233.                     if ($target_encoding != $source_encoding) {
  234.                         $res = iconv($source_encoding, $target_encoding, $str);
  235.                         if ($res === false) {
  236.                             $msg = 'Encoding conversion error ' .
  237.                                    "(source encoding: $source_encoding, ".
  238.                                    "target encoding: $target_encoding, ".
  239.                                    "processed string: \"$str\"";
  240.                             return $this->raiseError($msg,
  241.                                     TRANSLATION2_ERROR_ENCODING_CONVERSION,
  242.                                     PEAR_ERROR_RETURN,
  243.                                     E_USER_WARNING);
  244.                         }
  245.                         $data['pages'][$page_id][$str_id][$lang] = $res;
  246.                     }
  247.                 }
  248.             }
  249.         }
  250.         return true;
  251.     }
  252.          
  253.     // }}}
  254.     // {{{ _convertLangEncodings()
  255.     /**
  256.      * Convert lang data to/from XML unique charset (UTF-8)
  257.      *
  258.      * @param string $direction ['from_xml' | 'to_xml']
  259.      * @param array  &$data     Data buffer to operate on
  260.      *
  261.      * @return boolean|PEAR_Error
  262.      */
  263.     function _convertLangEncodings($direction, &$data)
  264.     {
  265.         static $fields = array('name', 'meta', 'error_text');
  266.  
  267.         if ($direction == 'from_xml') {
  268.             $source_encoding = 'UTF-8';
  269.         } else {
  270.             $target_encoding = 'UTF-8';
  271.         }
  272.         
  273.         foreach ($data['languages'] as $lang_id => $lang) {
  274.             if ($direction == 'from_xml') {
  275.                 $target_encoding = strtoupper($lang['encoding']);
  276.             } else {
  277.                 $source_encoding = strtoupper($lang['encoding']);
  278.             }
  279.             //foreach (array_keys($lang) as $field) {
  280.             foreach ($fields as $field) {
  281.                 if ($target_encoding != $source_encoding && !empty($lang[$field])) {
  282.                     $res = iconv($source_encoding, $target_encoding, $lang[$field]);
  283.                     if ($res === false) {
  284.                         $msg = 'Encoding conversion error ' .
  285.                                "(source encoding: $source_encoding, ".
  286.                                "target encoding: $target_encoding, ".
  287.                                "processed string: \"$lang[$field]\"";
  288.                         return $this->raiseError($msg,
  289.                                 TRANSLATION2_ERROR_ENCODING_CONVERSION,
  290.                                 PEAR_ERROR_RETURN,
  291.                                 E_USER_WARNING);
  292.                     }
  293.                     $data['languages'][$lang_id][$field] = $res;
  294.                 }
  295.             }
  296.         }
  297.         return true;
  298.     }
  299.  
  300.     // }}}
  301.     // {{{ _fixDuplicateEntries()
  302.     
  303. /**    
  304.      * Remove duplicate entries from the xml data
  305.      *
  306.      * @return void 
  307.      */
  308.     function _fixDuplicateEntries()
  309.     {
  310.         foreach ($this->_data['pages'] as $pagename => $pagedata) {
  311.             foreach ($pagedata as $stringname => $stringvalues) {
  312.                 if (is_array(array_pop($stringvalues))) {
  313.                     $this->_data['pages'][$pagename][$stringname] =
  314.                         call_user_func_array(array($this, '_merge'), $stringvalues);
  315.                 }
  316.             }
  317.         }
  318.     }
  319.     
  320.     // }}}
  321.     // {{{ fixEmptySets()
  322. /**    
  323.      * Turn empty strings returned by XML_Unserializer into empty arrays
  324.      *
  325.      * Note: this method is public because called statically by the t2xmlchk.php
  326.      * script. It is not meant to be called by user-space code.
  327.      *
  328.      * @param array &$data array of languages/pages
  329.      *
  330.      * @return void 
  331.      * @access public
  332.      * @static
  333.      */
  334.     function fixEmptySets(&$data)
  335.     {
  336.         if (PEAR::isError($this->_data) && ($this->_data->code == XML_UNSERIALIZER_ERROR_NO_UNSERIALIZATION)) {
  337.             //empty file... create skeleton
  338.             $this->_data = array(
  339.                 'languages' => array(),
  340.                 'pages'     => array(),
  341.             );
  342.         }
  343.         if (is_string($data['languages']) and trim($data['languages']) == '') {
  344.             $data['languages'] = array();
  345.         }
  346.         if (is_string($data['pages']) and trim($data['pages']) == '') {
  347.             $data['pages'] = array();
  348.         } else {
  349.             foreach ($data['pages'] as $pageName => $strings) {
  350.                 //if (is_string($strings) and trim($strings) == '') {
  351.                 if (is_string($strings)) {
  352.                     $data['pages'][$pageName] = array();
  353.                 } else {
  354.                     foreach ($strings as $stringName => $translations) {
  355.                         if (is_string($translations) and trim($translations) == '') {
  356.                             $data['pages'][$pageName][$stringName] = array();
  357.                         }
  358.                     }
  359.                 }
  360.             }
  361.         }
  362.     }
  363.  
  364.     // }}}
  365.     // {{{ _merge()
  366. /**    
  367.      * Wrapper for array_merge()
  368.      *
  369.      * @param array $arr1 reference
  370.      *
  371.      * @return array 
  372.      */
  373.     function _merge()
  374.     {
  375.         $return = array();
  376.         foreach (func_get_args() as $arg) {
  377.             $return = array_merge($return, $arg);
  378.         }
  379.         return $return;
  380.     }
  381.     
  382.     // }}}
  383.     // {{{ _setDefaultOptions()
  384. /**    
  385.      * Set some default options
  386.      *
  387.      * @return void 
  388.      * @access private
  389.      */
  390.     function _setDefaultOptions()
  391.     {
  392.         //save changes on shutdown or in real time?
  393.         $this->options['save_on_shutdown']  = true;
  394.     }
  395.  
  396.     // }}}
  397.     // {{{ fetchLangs()
  398. /**    
  399.      * Fetch the available langs
  400.      *
  401.      * @return void 
  402.      */
  403.     function fetchLangs()
  404.     {
  405.         $res = array();
  406.         foreach ($this->_data['languages'] as $id => $spec) {
  407.             $spec['id'] = $id;
  408.             $res[$id] = $spec;
  409.         }
  410.         $this->langs = $res;
  411.     }
  412.  
  413.     // }}}
  414.     // {{{ getPage()
  415. /**    
  416.      * Returns an array of the strings in the selected page
  417.      *
  418.      * @param string $pageID page/group ID
  419.      * @param string $langID language ID
  420.      *
  421.      * @return array 
  422.      */
  423.     function getPage($pageID = null, $langID = null)
  424.     {
  425.         $langID = $this->_getLangID($langID);
  426.         if (PEAR::isError($langID)) {
  427.             return $langID;
  428.         }
  429.         $pageID = (is_null($pageID)) ? '#NULL' : $pageID;
  430.         $pageID = (empty($pageID) && (0 !== $pageID)) ? '#EMPTY' : $pageID;
  431.  
  432.         $result = array();
  433.         foreach ($this->_data['pages'][$pageID] as $str_id => $translations) {
  434.             $result[$str_id]  = isset($translations[$langID]) 
  435.                                 ? $translations[$langID] 
  436.                                 : null;
  437.         }
  438.         
  439.         return $result;
  440.     }
  441.  
  442.     // }}}
  443.     // {{{ getOne()
  444. /**    
  445.      * Get a single item from the container
  446.      *
  447.      * @param string $stringID string ID
  448.      * @param string $pageID   page/group ID
  449.      * @param string $langID   language ID
  450.      *
  451.      * @return string 
  452.      */
  453.     function getOne($stringID, $pageID = null, $langID = null)
  454.     {
  455.         $langID = $this->_getLangID($langID);
  456.         if (PEAR::isError($langID)) {
  457.             return $langID;
  458.         }
  459.         $pageID = (is_null($pageID)) ? '#NULL' : $pageID;                         
  460.         return isset($this->_data['pages'][$pageID][$stringID][$langID])
  461.                ? $this->_data['pages'][$pageID][$stringID][$langID]
  462.                : null;
  463.     }
  464.  
  465.     // }}}
  466.     // {{{ getStringID()
  467. /**    
  468.      * Get the stringID for the given string
  469.      *
  470.      * @param string $string string
  471.      * @param string $pageID page/group ID
  472.      *
  473.      * @return string 
  474.      */
  475.     function getStringID($string, $pageID = null)
  476.     {
  477.         $pageID = (is_null($pageID)) ? '#NULL' : $pageID;                        
  478.         
  479.         foreach ($this->_data['pages'][$pageID] as $stringID => $translations) {
  480.             if (array_search($string, $translations) !== false) {
  481.                 return $stringID;
  482.             }
  483.         }
  484.  
  485.         return '';
  486.     }
  487.     
  488.     // }}}
  489. }

Documentation generated on Fri, 14 Nov 2008 11:30:38 -0500 by phpDocumentor 1.4.0. PEAR Logo Copyright © PHP Group 2004.