home *** CD-ROM | disk | FTP | other *** search
/ Cricao de Sites - 650 Layouts Prontos / WebMasters.iso / Servidores / xampp-win32-1.6.7-installer.exe / php / PEAR / Translation2 / Container / xml.php < prev   
Encoding:
PHP Script  |  2008-07-02  |  16.1 KB  |  500 lines

  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. 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. ?>