home *** CD-ROM | disk | FTP | other *** search
- <?php
- /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
-
- /**
- * Contains the Translation2_Container_xml class
- *
- * PHP versions 4 and 5
- *
- * LICENSE: Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. The name of the author may not be used to endorse or promote products
- * derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- * @category Internationalization
- * @package Translation2
- * @author Lorenzo Alberton <l.alberton@quipo.it>
- * @author Olivier Guilyardi <olivier@samalyse.com>
- * @copyright 2004-2008 Lorenzo Alberton, Olivier Guilyardi
- * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause)
- * @version CVS: $Id: xml.php,v 1.17 2008/02/02 18:05:06 quipo Exp $
- * @link http://pear.php.net/package/Translation2
- */
-
- /**
- * require Translation2_Container class
- */
- require_once 'Translation2/Container.php';
- /**
- * require XML_Unserializer class
- */
- require_once 'XML/Unserializer.php';
- /**
- * Document Type Definition
- */
- define('TRANSLATION2_DTD',
- "<!ELEMENT translation2 (languages,pages)>\n" .
- "<!ELEMENT languages (lang*)>\n" .
- "<!ELEMENT lang (name?,meta?,error_text?,encoding?)>\n" .
- "<!ATTLIST lang id ID #REQUIRED>\n" .
- "<!ELEMENT name (#PCDATA)>\n" .
- "<!ELEMENT meta (#PCDATA)>\n" .
- "<!ELEMENT error_text (#PCDATA)>\n" .
- "<!ELEMENT encoding (#PCDATA)>\n" .
- "<!ELEMENT pages (page*)>\n" .
- "<!ELEMENT page (string*)>\n" .
- "<!ATTLIST page key CDATA #REQUIRED>\n" .
- "<!ELEMENT string (tr*)>\n" .
- "<!ATTLIST string key CDATA #REQUIRED>\n" .
- "<!ELEMENT tr (#PCDATA)>\n" .
- "<!ATTLIST tr lang IDREF #REQUIRED>\n"
- );
-
- /**
- * Storage driver for fetching data from a XML file
- *
- * Example file :
- * <pre>
- * <?xml version="1.0" encoding="iso-8859-1"?>
- * <translation2>
- * <languages>
- * <lang id='fr_FR'>
- * <name> English </name>
- * <meta> Custom meta data</meta>
- * <error_text> Non disponible en franĪais </error_text>
- * <encoding> iso-8859-1 </encoding>
- * </lang>
- * <!-- some more <lang>...</lang> -->
- * </languages>
- * <pages>
- * <page key='pets'>
- * <string key='cat'>
- * <tr lang='fr_FR'> Chat </tr>
- * <!-- some more <tr>...</tr> -->
- * </string>
- * <!-- some more <string>...</string> -->
- * </page>
- * <!-- some more <page>...</page> -->
- * </pages>
- * </translation2>
- * </pre>
- *
- * @category Internationalization
- * @package Translation2
- * @author Lorenzo Alberton <l.alberton@quipo.it>
- * @author Olivier Guilyardi <olivier@samalyse.com>
- * @copyright 2004-2008 Lorenzo Alberton, Olivier Guilyardi
- * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause)
- * @link http://pear.php.net/package/Translation2
- */
- class Translation2_Container_xml extends Translation2_Container
- {
- // {{{ class vars
-
- /**
- * Unserialized XML data
- * @var object
- */
- var $_data = null;
-
- /**
- * XML file name
- * @var string
- */
- var $_filename;
-
- // }}}
- // {{{ init
-
- /**
- * Initialize the container
- *
- * @param array $options - 'filename': Path to the XML file
- *
- * @return boolean|PEAR_Error object if something went wrong
- */
- function init($options)
- {
- $this->_filename = $options['filename'];
- unset($options['filename']);
- $this->_setDefaultOptions();
- $this->_parseOptions($options);
-
- return $this->_loadFile();
- }
-
- // }}}
- // {{{ _loadFile()
-
- /**
- * Load an XML file into memory, and eventually decode the strings from UTF-8
- *
- * @return boolean|PEAR_Error
- * @access private
- */
- function _loadFile()
- {
- $keyAttr = array (
- 'lang' => 'id',
- 'page' => 'key',
- 'string' => 'key',
- 'tr' => 'lang'
- );
- if (!$fp = @fopen($this->_filename, 'r')) {
- return new PEAR_Error ("Can\'t read from the XML source: {$this->_filename}");
- }
- @flock($fp, LOCK_SH);
- $unserializer = &new XML_Unserializer (array('keyAttribute' => $keyAttr));
- if (PEAR::isError($status = $unserializer->unserialize($this->_filename, true))) {
- fclose($fp);
- return $status;
- }
- fclose($fp);
-
- // unserialize data
- $this->_data = $unserializer->getUnserializedData();
- $this->fixEmptySets($this->_data);
- $this->_fixDuplicateEntries();
-
- // Handle default language settings.
- // This allows, for example, to rapidly write the meta data as:
- //
- // <lang key="fr"/>
- // <lang key="en"/>
-
- $defaults = array(
- 'name' => '',
- 'meta' => '',
- 'error_text' => '',
- 'encoding' => 'iso-8859-1'
- );
-
- foreach ($this->_data['languages'] as $lang_id => $settings) {
- if (empty($settings)) {
- $this->_data['languages'][$lang_id] = $defaults;
- } else {
- $this->_data['languages'][$lang_id] =
- array_merge($defaults, $this->_data['languages'][$lang_id]);
- }
- }
-
- // convert lang metadata from UTF-8
- if (PEAR::isError($e = $this->_convertLangEncodings('from_xml', $this->_data))) {
- return $e;
- }
-
- // convert encodings of the translated strings from xml (somehow heavy)
- return $this->_convertEncodings('from_xml', $this->_data);
- }
-
- // }}}
- // {{{ _convertEncodings()
-
- /**
- * Convert strings to/from XML unique charset (UTF-8)
- *
- * @param string $direction ['from_xml' | 'to_xml']
- * @param array &$data Data buffer to operate on
- *
- * @return boolean|PEAR_Error
- */
- function _convertEncodings($direction, &$data)
- {
- if ($direction == 'from_xml') {
- $source_encoding = 'UTF-8';
- } else {
- $target_encoding = 'UTF-8';
- }
-
- foreach ($data['pages'] as $page_id => $page_content) {
- foreach ($page_content as $str_id => $translations) {
- foreach ($translations as $lang => $str) {
- if ($direction == 'from_xml') {
- $target_encoding =
- strtoupper($data['languages'][$lang]['encoding']);
- } else {
- $source_encoding =
- strtoupper($data['languages'][$lang]['encoding']);
- }
- if ($target_encoding != $source_encoding) {
- $res = iconv($source_encoding, $target_encoding, $str);
- if ($res === false) {
- $msg = 'Encoding conversion error ' .
- "(source encoding: $source_encoding, ".
- "target encoding: $target_encoding, ".
- "processed string: \"$str\"";
- return $this->raiseError($msg,
- TRANSLATION2_ERROR_ENCODING_CONVERSION,
- PEAR_ERROR_RETURN,
- E_USER_WARNING);
- }
- $data['pages'][$page_id][$str_id][$lang] = $res;
- }
- }
- }
- }
- return true;
- }
-
- // }}}
- // {{{ _convertLangEncodings()
-
- /**
- * Convert lang data to/from XML unique charset (UTF-8)
- *
- * @param string $direction ['from_xml' | 'to_xml']
- * @param array &$data Data buffer to operate on
- *
- * @return boolean|PEAR_Error
- */
- function _convertLangEncodings($direction, &$data)
- {
- static $fields = array('name', 'meta', 'error_text');
-
- if ($direction == 'from_xml') {
- $source_encoding = 'UTF-8';
- } else {
- $target_encoding = 'UTF-8';
- }
-
- foreach ($data['languages'] as $lang_id => $lang) {
- if ($direction == 'from_xml') {
- $target_encoding = strtoupper($lang['encoding']);
- } else {
- $source_encoding = strtoupper($lang['encoding']);
- }
- //foreach (array_keys($lang) as $field) {
- foreach ($fields as $field) {
- if ($target_encoding != $source_encoding && !empty($lang[$field])) {
- $res = iconv($source_encoding, $target_encoding, $lang[$field]);
- if ($res === false) {
- $msg = 'Encoding conversion error ' .
- "(source encoding: $source_encoding, ".
- "target encoding: $target_encoding, ".
- "processed string: \"$lang[$field]\"";
- return $this->raiseError($msg,
- TRANSLATION2_ERROR_ENCODING_CONVERSION,
- PEAR_ERROR_RETURN,
- E_USER_WARNING);
- }
- $data['languages'][$lang_id][$field] = $res;
- }
- }
- }
- return true;
- }
-
- // }}}
- // {{{ _fixDuplicateEntries()
-
- /**
- * Remove duplicate entries from the xml data
- *
- * @return void
- */
- function _fixDuplicateEntries()
- {
- foreach ($this->_data['pages'] as $pagename => $pagedata) {
- foreach ($pagedata as $stringname => $stringvalues) {
- if (is_array(array_pop($stringvalues))) {
- $this->_data['pages'][$pagename][$stringname] =
- call_user_func_array(array($this, '_merge'), $stringvalues);
- }
- }
- }
- }
-
- // }}}
- // {{{ fixEmptySets()
-
- /**
- * Turn empty strings returned by XML_Unserializer into empty arrays
- *
- * Note: this method is public because called statically by the t2xmlchk.php
- * script. It is not meant to be called by user-space code.
- *
- * @param array &$data array of languages/pages
- *
- * @return void
- * @access public
- * @static
- */
- function fixEmptySets(&$data)
- {
- if (PEAR::isError($this->_data) && ($this->_data->code == XML_UNSERIALIZER_ERROR_NO_UNSERIALIZATION)) {
- //empty file... create skeleton
- $this->_data = array(
- 'languages' => array(),
- 'pages' => array(),
- );
- }
- if (is_string($data['languages']) and trim($data['languages']) == '') {
- $data['languages'] = array();
- }
- if (is_string($data['pages']) and trim($data['pages']) == '') {
- $data['pages'] = array();
- } else {
- foreach ($data['pages'] as $pageName => $strings) {
- //if (is_string($strings) and trim($strings) == '') {
- if (is_string($strings)) {
- $data['pages'][$pageName] = array();
- } else {
- foreach ($strings as $stringName => $translations) {
- if (is_string($translations) and trim($translations) == '') {
- $data['pages'][$pageName][$stringName] = array();
- }
- }
- }
- }
- }
- }
-
- // }}}
- // {{{ _merge()
-
- /**
- * Wrapper for array_merge()
- *
- * @param array $arr1 reference
- *
- * @return array
- */
- function _merge()
- {
- $return = array();
- foreach (func_get_args() as $arg) {
- $return = array_merge($return, $arg);
- }
- return $return;
- }
-
- // }}}
- // {{{ _setDefaultOptions()
-
- /**
- * Set some default options
- *
- * @return void
- * @access private
- */
- function _setDefaultOptions()
- {
- //save changes on shutdown or in real time?
- $this->options['save_on_shutdown'] = true;
- }
-
- // }}}
- // {{{ fetchLangs()
-
- /**
- * Fetch the available langs
- *
- * @return void
- */
- function fetchLangs()
- {
- $res = array();
- foreach ($this->_data['languages'] as $id => $spec) {
- $spec['id'] = $id;
- $res[$id] = $spec;
- }
- $this->langs = $res;
- }
-
- // }}}
- // {{{ getPage()
-
- /**
- * Returns an array of the strings in the selected page
- *
- * @param string $pageID page/group ID
- * @param string $langID language ID
- *
- * @return array
- */
- function getPage($pageID = null, $langID = null)
- {
- $langID = $this->_getLangID($langID);
- if (PEAR::isError($langID)) {
- return $langID;
- }
- $pageID = (is_null($pageID)) ? '#NULL' : $pageID;
- $pageID = (empty($pageID) && (0 !== $pageID)) ? '#EMPTY' : $pageID;
-
- $result = array();
- foreach ($this->_data['pages'][$pageID] as $str_id => $translations) {
- $result[$str_id] = isset($translations[$langID])
- ? $translations[$langID]
- : null;
- }
-
- return $result;
- }
-
- // }}}
- // {{{ getOne()
-
- /**
- * Get a single item from the container
- *
- * @param string $stringID string ID
- * @param string $pageID page/group ID
- * @param string $langID language ID
- *
- * @return string
- */
- function getOne($stringID, $pageID = null, $langID = null)
- {
- $langID = $this->_getLangID($langID);
- if (PEAR::isError($langID)) {
- return $langID;
- }
- $pageID = (is_null($pageID)) ? '#NULL' : $pageID;
- return isset($this->_data['pages'][$pageID][$stringID][$langID])
- ? $this->_data['pages'][$pageID][$stringID][$langID]
- : null;
- }
-
- // }}}
- // {{{ getStringID()
-
- /**
- * Get the stringID for the given string
- *
- * @param string $string string
- * @param string $pageID page/group ID
- *
- * @return string
- */
- function getStringID($string, $pageID = null)
- {
- $pageID = (is_null($pageID)) ? '#NULL' : $pageID;
-
- foreach ($this->_data['pages'][$pageID] as $stringID => $translations) {
- if (array_search($string, $translations) !== false) {
- return $stringID;
- }
- }
-
- return '';
- }
-
- // }}}
- }
- ?>