1: <?php
2: /**
3: * CGettextMoFile class file.
4: *
5: * @author Qiang Xue <qiang.xue@gmail.com>
6: * @link http://www.yiiframework.com/
7: * @copyright 2008-2013 Yii Software LLC
8: * @license http://www.yiiframework.com/license/
9: */
10:
11: /**
12: * CGettextMoFile represents an MO Gettext message file.
13: *
14: * This class is written by adapting Michael's Gettext_MO class in PEAR.
15: * Please refer to the following license terms.
16: *
17: * Copyright (c) 2004-2005, Michael Wallner <mike@iworks.at>.
18: * All rights reserved.
19: *
20: * Redistribution and use in source and binary forms, with or without
21: * modification, are permitted provided that the following conditions are met:
22: *
23: * * Redistributions of source code must retain the above copyright notice,
24: * this list of conditions and the following disclaimer.
25: * * Redistributions in binary form must reproduce the above copyright
26: * notice, this list of conditions and the following disclaimer in the
27: * documentation and/or other materials provided with the distribution.
28: *
29: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
30: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
31: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
32: * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
33: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
34: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
35: * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
36: * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
37: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
38: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39: *
40: * @author Qiang Xue <qiang.xue@gmail.com>
41: * @package system.i18n.gettext
42: * @since 1.0
43: */
44: class CGettextMoFile extends CGettextFile
45: {
46: /**
47: * @var boolean whether to use Big Endian when reading and writing an integer.
48: */
49: public $useBigEndian=false;
50:
51: /**
52: * Constructor.
53: * @param boolean $useBigEndian whether to use Big Endian when reading and writing an integer.
54: */
55: public function __construct($useBigEndian=false)
56: {
57: $this->useBigEndian=$useBigEndian;
58: }
59:
60: /**
61: * Loads messages from an MO file.
62: * @param string $file file path
63: * @param string $context message context
64: * @return array message translations (source message => translated message)
65: */
66: public function load($file,$context)
67: {
68: if(!($fr=@fopen($file,'rb')))
69: throw new CException(Yii::t('yii','Unable to read file "{file}".',
70: array('{file}'=>$file)));
71:
72: if(!@flock($fr,LOCK_SH))
73: throw new CException(Yii::t('yii','Unable to lock file "{file}" for reading.',
74: array('{file}'=>$file)));
75:
76: $magic=current($array=unpack('c',$this->readByte($fr,4)));
77: if($magic==-34)
78: $this->useBigEndian=false;
79: elseif($magic==-107)
80: $this->useBigEndian=true;
81: else
82: throw new CException(Yii::t('yii','Invalid MO file: {file} (magic: {magic}).',
83: array('{file}'=>$file,'{magic}'=>$magic)));
84:
85: if(($revision=$this->readInteger($fr))!=0)
86: throw new CException(Yii::t('yii','Invalid MO file revision: {revision}.',
87: array('{revision}'=>$revision)));
88:
89: $count=$this->readInteger($fr);
90: $sourceOffset=$this->readInteger($fr);
91: $targetOffset=$this->readInteger($fr);
92:
93: $sourceLengths=array();
94: $sourceOffsets=array();
95: fseek($fr,$sourceOffset);
96: for($i=0;$i<$count;++$i)
97: {
98: $sourceLengths[]=$this->readInteger($fr);
99: $sourceOffsets[]=$this->readInteger($fr);
100: }
101:
102: $targetLengths=array();
103: $targetOffsets=array();
104: fseek($fr,$targetOffset);
105: for($i=0;$i<$count;++$i)
106: {
107: $targetLengths[]=$this->readInteger($fr);
108: $targetOffsets[]=$this->readInteger($fr);
109: }
110:
111: $messages=array();
112: for($i=0;$i<$count;++$i)
113: {
114: $id=$this->readString($fr,$sourceLengths[$i],$sourceOffsets[$i]);
115: $pos = strpos($id,chr(4));
116:
117: if(($context && $pos!==false && substr($id,0,$pos)===$context) || (!$context && $pos===false))
118: {
119: if($pos !== false)
120: $id=substr($id,$pos+1);
121:
122: $message=$this->readString($fr,$targetLengths[$i],$targetOffsets[$i]);
123: $messages[$id]=$message;
124: }
125: }
126:
127: @flock($fr,LOCK_UN);
128: @fclose($fr);
129:
130: return $messages;
131: }
132:
133: /**
134: * Saves messages to an MO file.
135: * @param string $file file path
136: * @param array $messages message translations (message id => translated message).
137: * Note if the message has a context, the message id must be prefixed with
138: * the context with chr(4) as the separator.
139: */
140: public function save($file,$messages)
141: {
142: if(!($fw=@fopen($file,'wb')))
143: throw new CException(Yii::t('yii','Unable to write file "{file}".',
144: array('{file}'=>$file)));
145:
146: if(!@flock($fw,LOCK_EX))
147: throw new CException(Yii::t('yii','Unable to lock file "{file}" for writing.',
148: array('{file}'=>$file)));
149:
150: // magic
151: if($this->useBigEndian)
152: $this->writeByte($fw,pack('c*', 0x95, 0x04, 0x12, 0xde));
153: else
154: $this->writeByte($fw,pack('c*', 0xde, 0x12, 0x04, 0x95));
155:
156: // revision
157: $this->writeInteger($fw,0);
158:
159: // message count
160: $n=count($messages);
161: $this->writeInteger($fw,$n);
162:
163: // offset of source message table
164: $offset=28;
165: $this->writeInteger($fw,$offset);
166: $offset+=($n*8);
167: $this->writeInteger($fw,$offset);
168: // hashtable size, omitted
169: $this->writeInteger($fw,0);
170: $offset+=($n*8);
171: $this->writeInteger($fw,$offset);
172:
173: // length and offsets for source messagess
174: foreach(array_keys($messages) as $id)
175: {
176: $len=strlen($id);
177: $this->writeInteger($fw,$len);
178: $this->writeInteger($fw,$offset);
179: $offset+=$len+1;
180: }
181:
182: // length and offsets for target messagess
183: foreach($messages as $message)
184: {
185: $len=strlen($message);
186: $this->writeInteger($fw,$len);
187: $this->writeInteger($fw,$offset);
188: $offset+=$len+1;
189: }
190:
191: // source messages
192: foreach(array_keys($messages) as $id)
193: $this->writeString($fw,$id);
194:
195: // target messages
196: foreach($messages as $message)
197: $this->writeString($fw,$message);
198:
199: @flock($fw,LOCK_UN);
200: @fclose($fw);
201: }
202:
203: /**
204: * Reads one or several bytes.
205: * @param resource $fr file handle
206: * @param integer $n number of bytes to read
207: * @return string bytes
208: */
209: protected function readByte($fr,$n=1)
210: {
211: if($n>0)
212: return fread($fr,$n);
213: }
214:
215: /**
216: * Writes bytes.
217: * @param resource $fw file handle
218: * @param string $data the data
219: * @return integer how many bytes are written
220: */
221: protected function writeByte($fw,$data)
222: {
223: return fwrite($fw,$data);
224: }
225:
226: /**
227: * Reads a 4-byte integer.
228: * @param resource $fr file handle
229: * @return integer the result
230: * @see useBigEndian
231: */
232: protected function readInteger($fr)
233: {
234: return current($array=unpack($this->useBigEndian ? 'N' : 'V', $this->readByte($fr,4)));
235: }
236:
237: /**
238: * Writes a 4-byte integer.
239: * @param resource $fw file handle
240: * @param integer $data the data
241: * @return integer how many bytes are written
242: */
243: protected function writeInteger($fw,$data)
244: {
245: return $this->writeByte($fw,pack($this->useBigEndian ? 'N' : 'V', (int)$data));
246: }
247:
248: /**
249: * Reads a string.
250: * @param resource $fr file handle
251: * @param integer $length string length
252: * @param integer $offset offset of the string in the file. If null, it reads from the current position.
253: * @return string the result
254: */
255: protected function readString($fr,$length,$offset=null)
256: {
257: if($offset!==null)
258: fseek($fr,$offset);
259: return $this->readByte($fr,$length);
260: }
261:
262: /**
263: * Writes a string.
264: * @param resource $fw file handle
265: * @param string $data the string
266: * @return integer how many bytes are written
267: */
268: protected function writeString($fw,$data)
269: {
270: return $this->writeByte($fw,$data."\0");
271: }
272: }
273: