* @link http://www.yiiframework.com/ * @copyright 2008-2013 Yii Software LLC * @license http://www.yiiframework.com/license/ */ /** * CGettextMoFile represents an MO Gettext message file. * * This class is written by adapting Michael's Gettext_MO class in PEAR. * Please refer to the following license terms. * * Copyright (c) 2004-2005, Michael Wallner . * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER 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. * * @author Qiang Xue * @package system.i18n.gettext * @since 1.0 */ class CGettextMoFile extends CGettextFile { /** * @var boolean whether to use Big Endian when reading and writing an integer. */ public $useBigEndian=false; /** * Constructor. * @param boolean $useBigEndian whether to use Big Endian when reading and writing an integer. */ public function __construct($useBigEndian=false) { $this->useBigEndian=$useBigEndian; } /** * Loads messages from an MO file. * @param string $file file path * @param string $context message context * @return array message translations (source message => translated message) */ public function load($file,$context) { if(!($fr=@fopen($file,'rb'))) throw new CException(Yii::t('yii','Unable to read file "{file}".', array('{file}'=>$file))); if(!@flock($fr,LOCK_SH)) throw new CException(Yii::t('yii','Unable to lock file "{file}" for reading.', array('{file}'=>$file))); $magic=current($array=unpack('c',$this->readByte($fr,4))); if($magic==-34) $this->useBigEndian=false; elseif($magic==-107) $this->useBigEndian=true; else throw new CException(Yii::t('yii','Invalid MO file: {file} (magic: {magic}).', array('{file}'=>$file,'{magic}'=>$magic))); if(($revision=$this->readInteger($fr))!=0) throw new CException(Yii::t('yii','Invalid MO file revision: {revision}.', array('{revision}'=>$revision))); $count=$this->readInteger($fr); $sourceOffset=$this->readInteger($fr); $targetOffset=$this->readInteger($fr); $sourceLengths=array(); $sourceOffsets=array(); fseek($fr,$sourceOffset); for($i=0;$i<$count;++$i) { $sourceLengths[]=$this->readInteger($fr); $sourceOffsets[]=$this->readInteger($fr); } $targetLengths=array(); $targetOffsets=array(); fseek($fr,$targetOffset); for($i=0;$i<$count;++$i) { $targetLengths[]=$this->readInteger($fr); $targetOffsets[]=$this->readInteger($fr); } $messages=array(); for($i=0;$i<$count;++$i) { $id=$this->readString($fr,$sourceLengths[$i],$sourceOffsets[$i]); $pos = strpos($id,chr(4)); if(($context && $pos!==false && substr($id,0,$pos)===$context) || (!$context && $pos===false)) { if($pos !== false) $id=substr($id,$pos+1); $message=$this->readString($fr,$targetLengths[$i],$targetOffsets[$i]); $messages[$id]=$message; } } @flock($fr,LOCK_UN); @fclose($fr); return $messages; } /** * Saves messages to an MO file. * @param string $file file path * @param array $messages message translations (message id => translated message). * Note if the message has a context, the message id must be prefixed with * the context with chr(4) as the separator. */ public function save($file,$messages) { if(!($fw=@fopen($file,'wb'))) throw new CException(Yii::t('yii','Unable to write file "{file}".', array('{file}'=>$file))); if(!@flock($fw,LOCK_EX)) throw new CException(Yii::t('yii','Unable to lock file "{file}" for writing.', array('{file}'=>$file))); // magic if($this->useBigEndian) $this->writeByte($fw,pack('c*', 0x95, 0x04, 0x12, 0xde)); else $this->writeByte($fw,pack('c*', 0xde, 0x12, 0x04, 0x95)); // revision $this->writeInteger($fw,0); // message count $n=count($messages); $this->writeInteger($fw,$n); // offset of source message table $offset=28; $this->writeInteger($fw,$offset); $offset+=($n*8); $this->writeInteger($fw,$offset); // hashtable size, omitted $this->writeInteger($fw,0); $offset+=($n*8); $this->writeInteger($fw,$offset); // length and offsets for source messagess foreach(array_keys($messages) as $id) { $len=strlen($id); $this->writeInteger($fw,$len); $this->writeInteger($fw,$offset); $offset+=$len+1; } // length and offsets for target messagess foreach($messages as $message) { $len=strlen($message); $this->writeInteger($fw,$len); $this->writeInteger($fw,$offset); $offset+=$len+1; } // source messages foreach(array_keys($messages) as $id) $this->writeString($fw,$id); // target messages foreach($messages as $message) $this->writeString($fw,$message); @flock($fw,LOCK_UN); @fclose($fw); } /** * Reads one or several bytes. * @param resource $fr file handle * @param integer $n number of bytes to read * @return string bytes */ protected function readByte($fr,$n=1) { if($n>0) return fread($fr,$n); } /** * Writes bytes. * @param resource $fw file handle * @param string $data the data * @return integer how many bytes are written */ protected function writeByte($fw,$data) { return fwrite($fw,$data); } /** * Reads a 4-byte integer. * @param resource $fr file handle * @return integer the result * @see useBigEndian */ protected function readInteger($fr) { return current($array=unpack($this->useBigEndian ? 'N' : 'V', $this->readByte($fr,4))); } /** * Writes a 4-byte integer. * @param resource $fw file handle * @param integer $data the data * @return integer how many bytes are written */ protected function writeInteger($fw,$data) { return $this->writeByte($fw,pack($this->useBigEndian ? 'N' : 'V', (int)$data)); } /** * Reads a string. * @param resource $fr file handle * @param integer $length string length * @param integer $offset offset of the string in the file. If null, it reads from the current position. * @return string the result */ protected function readString($fr,$length,$offset=null) { if($offset!==null) fseek($fr,$offset); return $this->readByte($fr,$length); } /** * Writes a string. * @param resource $fw file handle * @param string $data the string * @return integer how many bytes are written */ protected function writeString($fw,$data) { return $this->writeByte($fw,$data."\0"); } }