273 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			273 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| /**
 | |
|  * CGettextMoFile class file.
 | |
|  *
 | |
|  * @author Qiang Xue <qiang.xue@gmail.com>
 | |
|  * @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 <mike@iworks.at>.
 | |
|  * 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 <qiang.xue@gmail.com>
 | |
|  * @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");
 | |
| 	}
 | |
| }
 |