Subversion Repositories PHPX

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
39 PointedEar 1
<?php
2
/**
3
 * Zend Framework (http://framework.zend.com/)
4
 *
5
 * @link      http://github.com/zendframework/zf2 for the canonical source repository
6
 * @copyright Copyright (c) 2005-2013 Zend Technologies USA Inc. (http://www.zend.com)
7
 * @license   http://framework.zend.com/license/new-bsd New BSD License
8
 */
9
 
10
namespace Zend\I18n\Translator\Loader;
11
 
12
use Zend\I18n\Exception;
13
use Zend\I18n\Translator\Plural\Rule as PluralRule;
14
use Zend\I18n\Translator\TextDomain;
15
use Zend\Stdlib\ErrorHandler;
16
 
17
/**
18
 * Gettext loader.
19
 */
20
class Gettext implements FileLoaderInterface
21
{
22
    /**
23
     * Current file pointer.
24
     *
25
     * @var resource
26
     */
27
    protected $file;
28
 
29
    /**
30
     * Whether the current file is little endian.
31
     *
32
     * @var bool
33
     */
34
    protected $littleEndian;
35
 
36
    /**
37
     * load(): defined by FileLoaderInterface.
38
     *
39
     * @see    FileLoaderInterface::load()
40
     * @param  string $locale
41
     * @param  string $filename
42
     * @return TextDomain
43
     * @throws Exception\InvalidArgumentException
44
     */
45
    public function load($locale, $filename)
46
    {
47
        if (!is_file($filename) || !is_readable($filename)) {
48
            throw new Exception\InvalidArgumentException(sprintf(
49
                'Could not open file %s for reading',
50
                $filename
51
            ));
52
        }
53
 
54
        $textDomain = new TextDomain();
55
 
56
        ErrorHandler::start();
57
        $this->file = fopen($filename, 'rb');
58
        $error = ErrorHandler::stop();
59
        if (false === $this->file) {
60
            throw new Exception\InvalidArgumentException(sprintf(
61
                'Could not open file %s for reading',
62
                $filename
63
            ), 0, $error);
64
        }
65
 
66
        // Verify magic number
67
        $magic = fread($this->file, 4);
68
 
69
        if ($magic == "\x95\x04\x12\xde") {
70
            $this->littleEndian = false;
71
        } elseif ($magic == "\xde\x12\x04\x95") {
72
            $this->littleEndian = true;
73
        } else {
74
            fclose($this->file);
75
            throw new Exception\InvalidArgumentException(sprintf(
76
                '%s is not a valid gettext file',
77
                $filename
78
            ));
79
        }
80
 
81
        // Verify major revision (only 0 and 1 supported)
82
        $majorRevision = ($this->readInteger() >> 16);
83
 
84
        if ($majorRevision !== 0 && $majorRevision !== 1) {
85
            fclose($this->file);
86
            throw new Exception\InvalidArgumentException(sprintf(
87
                '%s has an unknown major revision',
88
                $filename
89
            ));
90
        }
91
 
92
        // Gather main information
93
        $numStrings                   = $this->readInteger();
94
        $originalStringTableOffset    = $this->readInteger();
95
        $translationStringTableOffset = $this->readInteger();
96
 
97
        // Usually there follow size and offset of the hash table, but we have
98
        // no need for it, so we skip them.
99
        fseek($this->file, $originalStringTableOffset);
100
        $originalStringTable = $this->readIntegerList(2 * $numStrings);
101
 
102
        fseek($this->file, $translationStringTableOffset);
103
        $translationStringTable = $this->readIntegerList(2 * $numStrings);
104
 
105
        // Read in all translations
106
        for ($current = 0; $current < $numStrings; $current++) {
107
            $sizeKey                 = $current * 2 + 1;
108
            $offsetKey               = $current * 2 + 2;
109
            $originalStringSize      = $originalStringTable[$sizeKey];
110
            $originalStringOffset    = $originalStringTable[$offsetKey];
111
            $translationStringSize   = $translationStringTable[$sizeKey];
112
            $translationStringOffset = $translationStringTable[$offsetKey];
113
 
114
            $originalString = array('');
115
            if ($originalStringSize > 0) {
116
                fseek($this->file, $originalStringOffset);
117
                $originalString = explode("\0", fread($this->file, $originalStringSize));
118
            }
119
 
120
            if ($translationStringSize > 0) {
121
                fseek($this->file, $translationStringOffset);
122
                $translationString = explode("\0", fread($this->file, $translationStringSize));
123
 
124
                if (count($originalString) > 1 && count($translationString) > 1) {
125
                    $textDomain[$originalString[0]] = $translationString;
126
 
127
                    array_shift($originalString);
128
 
129
                    foreach ($originalString as $string) {
130
                        $textDomain[$string] = '';
131
                    }
132
                } else {
133
                    $textDomain[$originalString[0]] = $translationString[0];
134
                }
135
            }
136
        }
137
 
138
        // Read header entries
139
        if (array_key_exists('', $textDomain)) {
140
            $rawHeaders = explode("\n", trim($textDomain['']));
141
 
142
            foreach ($rawHeaders as $rawHeader) {
143
                list($header, $content) = explode(':', $rawHeader, 2);
144
 
145
                if (trim(strtolower($header)) === 'plural-forms') {
146
                    $textDomain->setPluralRule(PluralRule::fromString($content));
147
                }
148
            }
149
 
150
            unset($textDomain['']);
151
        }
152
 
153
        fclose($this->file);
154
 
155
        return $textDomain;
156
    }
157
 
158
    /**
159
     * Read a single integer from the current file.
160
     *
161
     * @return integer
162
     */
163
    protected function readInteger()
164
    {
165
        if ($this->littleEndian) {
166
            $result = unpack('Vint', fread($this->file, 4));
167
        } else {
168
            $result = unpack('Nint', fread($this->file, 4));
169
        }
170
 
171
        return $result['int'];
172
    }
173
 
174
    /**
175
     * Read an integer from the current file.
176
     *
177
     * @param  integer $num
178
     * @return integer
179
     */
180
    protected function readIntegerList($num)
181
    {
182
        if ($this->littleEndian) {
183
            return unpack('V' . $num, fread($this->file, 4 * $num));
184
        }
185
 
186
        return unpack('N' . $num, fread($this->file, 4 * $num));
187
    }
188
}