You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							267 lines
						
					
					
						
							8.1 KiB
						
					
					
				
			
		
		
	
	
							267 lines
						
					
					
						
							8.1 KiB
						
					
					
				<?php | 
						|
/** | 
						|
 * @link http://www.yiiframework.com/ | 
						|
 * @copyright Copyright (c) 2008 Yii Software LLC | 
						|
 * @license http://www.yiiframework.com/license/ | 
						|
 */ | 
						|
 | 
						|
namespace yii\i18n; | 
						|
 | 
						|
use yii\base\Exception; | 
						|
 | 
						|
/** | 
						|
 * GettextMoFile 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> | 
						|
 * @since 2.0 | 
						|
 */ | 
						|
class GettextMoFile extends GettextFile | 
						|
{ | 
						|
	/** | 
						|
	 * @var boolean whether to use big-endian when reading and writing an integer. | 
						|
	 */ | 
						|
	public $useBigEndian = false; | 
						|
 | 
						|
	/** | 
						|
	 * Loads messages from an MO file. | 
						|
	 * @param string $filePath file path | 
						|
	 * @param string $context message context | 
						|
	 * @return array message translations. Array keys are source messages and array values are translated messages: | 
						|
	 * source message => translated message. | 
						|
	 */ | 
						|
	public function load($filePath, $context) | 
						|
	{ | 
						|
		if (false === ($fileHandle = @fopen($filePath, 'rb'))) { | 
						|
			throw new Exception('Unable to read file "' . $filePath . '".'); | 
						|
		} | 
						|
		if (false === @flock($fileHandle, LOCK_SH)) { | 
						|
			throw new Exception('Unable to lock file "' . $filePath . '" for reading.'); | 
						|
		} | 
						|
 | 
						|
		// magic | 
						|
		$array = unpack('c', $this->readBytes($fileHandle, 4)); | 
						|
		$magic = current($array); | 
						|
		if ($magic == -34) { | 
						|
			$this->useBigEndian = false; | 
						|
		} elseif ($magic == -107) { | 
						|
			$this->useBigEndian = true; | 
						|
		} else { | 
						|
			throw new Exception('Invalid MO file: ' . $filePath . ' (magic: ' . $magic . ').'); | 
						|
		} | 
						|
 | 
						|
		// revision | 
						|
		$revision = $this->readInteger($fileHandle); | 
						|
		if ($revision != 0) { | 
						|
			throw new Exception('Invalid MO file revision: ' . $revision . '.'); | 
						|
		} | 
						|
 | 
						|
		$count = $this->readInteger($fileHandle); | 
						|
		$sourceOffset = $this->readInteger($fileHandle); | 
						|
		$targetOffset = $this->readInteger($fileHandle); | 
						|
 | 
						|
		$sourceLengths = array(); | 
						|
		$sourceOffsets = array(); | 
						|
		fseek($fileHandle, $sourceOffset); | 
						|
		for ($i = 0; $i < $count; ++$i) { | 
						|
			$sourceLengths[] = $this->readInteger($fileHandle); | 
						|
			$sourceOffsets[] = $this->readInteger($fileHandle); | 
						|
		} | 
						|
 | 
						|
		$targetLengths = array(); | 
						|
		$targetOffsets = array(); | 
						|
		fseek($fileHandle, $targetOffset); | 
						|
		for ($i = 0; $i < $count; ++$i) { | 
						|
			$targetLengths[] = $this->readInteger($fileHandle); | 
						|
			$targetOffsets[] = $this->readInteger($fileHandle); | 
						|
		} | 
						|
 | 
						|
		$messages = array(); | 
						|
		for ($i = 0; $i < $count; ++$i) { | 
						|
			$id = $this->readString($fileHandle, $sourceLengths[$i], $sourceOffsets[$i]); | 
						|
			$separatorPosition = strpos($id, chr(4)); | 
						|
 | 
						|
			if (($context && $separatorPosition !== false && substr($id, 0, $separatorPosition) === $context) || | 
						|
				(!$context && $separatorPosition === false)) { | 
						|
				if ($separatorPosition !== false) { | 
						|
					$id = substr($id, $separatorPosition+1); | 
						|
				} | 
						|
 | 
						|
				$message = $this->readString($fileHandle, $targetLengths[$i], $targetOffsets[$i]); | 
						|
				$messages[$id] = $message; | 
						|
			} | 
						|
		} | 
						|
 | 
						|
		@flock($fileHandle, LOCK_UN); | 
						|
		@fclose($fileHandle); | 
						|
		return $messages; | 
						|
	} | 
						|
 | 
						|
	/** | 
						|
	 * Saves messages to an MO file. | 
						|
	 * @param string $filePath file path | 
						|
	 * @param array $messages message translations. Array keys are source messages and array values are | 
						|
	 * translated messages: source message => 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($filePath, $messages) | 
						|
	{ | 
						|
		if (false === ($fileHandle = @fopen($filePath, 'wb'))) { | 
						|
			throw new Exception('Unable to write file "' . $filePath . '".'); | 
						|
		} | 
						|
		if (false === @flock($fileHandle, LOCK_EX)) { | 
						|
			throw new Exception('Unable to lock file "' . $filePath . '" for reading.'); | 
						|
		} | 
						|
 | 
						|
		// magic | 
						|
		if ($this->useBigEndian) { | 
						|
			$this->writeBytes($fileHandle, pack('c*', 0x95, 0x04, 0x12, 0xde)); // -107 | 
						|
		} else { | 
						|
			$this->writeBytes($fileHandle, pack('c*', 0xde, 0x12, 0x04, 0x95)); // -34 | 
						|
		} | 
						|
 | 
						|
		// revision | 
						|
		$this->writeInteger($fileHandle, 0); | 
						|
 | 
						|
		// message count | 
						|
		$messageCount = count($messages); | 
						|
		$this->writeInteger($fileHandle, $messageCount); | 
						|
 | 
						|
		// offset of source message table | 
						|
		$offset = 28; | 
						|
		$this->writeInteger($fileHandle, $offset); | 
						|
		$offset += $messageCount * 8; | 
						|
		$this->writeInteger($fileHandle, $offset); | 
						|
 | 
						|
		// hashtable size, omitted | 
						|
		$this->writeInteger($fileHandle, 0); | 
						|
		$offset += $messageCount * 8; | 
						|
		$this->writeInteger($fileHandle, $offset); | 
						|
 | 
						|
		// length and offsets for source messages | 
						|
		foreach (array_keys($messages) as $id) { | 
						|
			$length = strlen($id); | 
						|
			$this->writeInteger($fileHandle, $length); | 
						|
			$this->writeInteger($fileHandle, $offset); | 
						|
			$offset += $length + 1; | 
						|
		} | 
						|
 | 
						|
		// length and offsets for target messages | 
						|
		foreach ($messages as $message) { | 
						|
			$length = strlen($message); | 
						|
			$this->writeInteger($fileHandle, $length); | 
						|
			$this->writeInteger($fileHandle, $offset); | 
						|
			$offset += $length + 1; | 
						|
		} | 
						|
 | 
						|
		// source messages | 
						|
		foreach (array_keys($messages) as $id) { | 
						|
			$this->writeString($fileHandle, $id); | 
						|
		} | 
						|
 | 
						|
		// target messages | 
						|
		foreach ($messages as $message) { | 
						|
			$this->writeString($fileHandle, $message); | 
						|
		} | 
						|
 | 
						|
		@flock($fileHandle, LOCK_UN); | 
						|
		@fclose($fileHandle); | 
						|
	} | 
						|
 | 
						|
	/** | 
						|
	 * Reads one or several bytes. | 
						|
	 * @param resource $fileHandle to read from | 
						|
	 * @param integer $byteCount to be read | 
						|
	 * @return string bytes | 
						|
	 */ | 
						|
	protected function readBytes($fileHandle, $byteCount = 1) | 
						|
	{ | 
						|
		if ($byteCount > 0) { | 
						|
			return fread($fileHandle, $byteCount); | 
						|
		} | 
						|
	} | 
						|
 | 
						|
	/** | 
						|
	 * Write bytes. | 
						|
	 * @param resource $fileHandle to write to | 
						|
	 * @param string $bytes to be written | 
						|
	 * @return integer how many bytes are written | 
						|
	 */ | 
						|
	protected function writeBytes($fileHandle, $bytes) | 
						|
	{ | 
						|
		return fwrite($fileHandle, $bytes); | 
						|
	} | 
						|
 | 
						|
	/** | 
						|
	 * Reads a 4-byte integer. | 
						|
	 * @param resource $fileHandle to read from | 
						|
	 * @return integer the result | 
						|
	 */ | 
						|
	protected function readInteger($fileHandle) | 
						|
	{ | 
						|
		$array = unpack($this->useBigEndian ? 'N' : 'V', $this->readBytes($fileHandle, 4)); | 
						|
		return current($array); | 
						|
	} | 
						|
 | 
						|
	/** | 
						|
	 * Writes a 4-byte integer. | 
						|
	 * @param resource $fileHandle to write to | 
						|
	 * @param integer $integer to be written | 
						|
	 * @return integer how many bytes are written | 
						|
	 */ | 
						|
	protected function writeInteger($fileHandle, $integer) | 
						|
	{ | 
						|
		return $this->writeBytes($fileHandle, pack($this->useBigEndian ? 'N' : 'V', (int)$integer)); | 
						|
	} | 
						|
 | 
						|
	/** | 
						|
	 * Reads a string. | 
						|
	 * @param resource $fileHandle file handle | 
						|
	 * @param integer $length of the string | 
						|
	 * @param integer $offset of the string in the file. If null, it reads from the current position. | 
						|
	 * @return string the result | 
						|
	 */ | 
						|
	protected function readString($fileHandle, $length, $offset = null) | 
						|
	{ | 
						|
		if ($offset !== null) { | 
						|
			fseek($fileHandle, $offset); | 
						|
		} | 
						|
		return $this->readBytes($fileHandle, $length); | 
						|
	} | 
						|
 | 
						|
	/** | 
						|
	 * Writes a string. | 
						|
	 * @param resource $fileHandle to write to | 
						|
	 * @param string $string to be written | 
						|
	 * @return integer how many bytes are written | 
						|
	 */ | 
						|
	protected function writeString($fileHandle, $string) | 
						|
	{ | 
						|
		return $this->writeBytes($fileHandle, $string. "\0"); | 
						|
	} | 
						|
}
 | 
						|
 |