~canonical-sysadmins/wordpress/4.7.4

« back to all changes in this revision

Viewing changes to wp-includes/pomo/po.php

  • Committer: Jacek Nykis
  • Date: 2015-01-05 16:17:05 UTC
  • Revision ID: jacek.nykis@canonical.com-20150105161705-w544l1h5mcg7u4w9
Initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<?php
 
2
/**
 
3
 * Class for working with PO files
 
4
 *
 
5
 * @version $Id: po.php 718 2012-10-31 00:32:02Z nbachiyski $
 
6
 * @package pomo
 
7
 * @subpackage po
 
8
 */
 
9
 
 
10
require_once dirname(__FILE__) . '/translations.php';
 
11
 
 
12
define('PO_MAX_LINE_LEN', 79);
 
13
 
 
14
ini_set('auto_detect_line_endings', 1);
 
15
 
 
16
/**
 
17
 * Routines for working with PO files
 
18
 */
 
19
if ( !class_exists( 'PO' ) ):
 
20
class PO extends Gettext_Translations {
 
21
 
 
22
        var $comments_before_headers = '';
 
23
 
 
24
        /**
 
25
         * Exports headers to a PO entry
 
26
         *
 
27
         * @return string msgid/msgstr PO entry for this PO file headers, doesn't contain newline at the end
 
28
         */
 
29
        function export_headers() {
 
30
                $header_string = '';
 
31
                foreach($this->headers as $header => $value) {
 
32
                        $header_string.= "$header: $value\n";
 
33
                }
 
34
                $poified = PO::poify($header_string);
 
35
                if ($this->comments_before_headers)
 
36
                        $before_headers = $this->prepend_each_line(rtrim($this->comments_before_headers)."\n", '# ');
 
37
                else
 
38
                        $before_headers = '';
 
39
                return rtrim("{$before_headers}msgid \"\"\nmsgstr $poified");
 
40
        }
 
41
 
 
42
        /**
 
43
         * Exports all entries to PO format
 
44
         *
 
45
         * @return string sequence of mgsgid/msgstr PO strings, doesn't containt newline at the end
 
46
         */
 
47
        function export_entries() {
 
48
                //TODO sorting
 
49
                return implode("\n\n", array_map(array('PO', 'export_entry'), $this->entries));
 
50
        }
 
51
 
 
52
        /**
 
53
         * Exports the whole PO file as a string
 
54
         *
 
55
         * @param bool $include_headers whether to include the headers in the export
 
56
         * @return string ready for inclusion in PO file string for headers and all the enrtries
 
57
         */
 
58
        function export($include_headers = true) {
 
59
                $res = '';
 
60
                if ($include_headers) {
 
61
                        $res .= $this->export_headers();
 
62
                        $res .= "\n\n";
 
63
                }
 
64
                $res .= $this->export_entries();
 
65
                return $res;
 
66
        }
 
67
 
 
68
        /**
 
69
         * Same as {@link export}, but writes the result to a file
 
70
         *
 
71
         * @param string $filename where to write the PO string
 
72
         * @param bool $include_headers whether to include tje headers in the export
 
73
         * @return bool true on success, false on error
 
74
         */
 
75
        function export_to_file($filename, $include_headers = true) {
 
76
                $fh = fopen($filename, 'w');
 
77
                if (false === $fh) return false;
 
78
                $export = $this->export($include_headers);
 
79
                $res = fwrite($fh, $export);
 
80
                if (false === $res) return false;
 
81
                return fclose($fh);
 
82
        }
 
83
 
 
84
        /**
 
85
         * Text to include as a comment before the start of the PO contents
 
86
         *
 
87
         * Doesn't need to include # in the beginning of lines, these are added automatically
 
88
         */
 
89
        function set_comment_before_headers( $text ) {
 
90
                $this->comments_before_headers = $text;
 
91
        }
 
92
 
 
93
        /**
 
94
         * Formats a string in PO-style
 
95
         *
 
96
         * @static
 
97
         * @param string $string the string to format
 
98
         * @return string the poified string
 
99
         */
 
100
        function poify($string) {
 
101
                $quote = '"';
 
102
                $slash = '\\';
 
103
                $newline = "\n";
 
104
 
 
105
                $replaces = array(
 
106
                        "$slash"        => "$slash$slash",
 
107
                        "$quote"        => "$slash$quote",
 
108
                        "\t"            => '\t',
 
109
                );
 
110
 
 
111
                $string = str_replace(array_keys($replaces), array_values($replaces), $string);
 
112
 
 
113
                $po = $quote.implode("${slash}n$quote$newline$quote", explode($newline, $string)).$quote;
 
114
                // add empty string on first line for readbility
 
115
                if (false !== strpos($string, $newline) &&
 
116
                                (substr_count($string, $newline) > 1 || !($newline === substr($string, -strlen($newline))))) {
 
117
                        $po = "$quote$quote$newline$po";
 
118
                }
 
119
                // remove empty strings
 
120
                $po = str_replace("$newline$quote$quote", '', $po);
 
121
                return $po;
 
122
        }
 
123
 
 
124
        /**
 
125
         * Gives back the original string from a PO-formatted string
 
126
         *
 
127
         * @static
 
128
         * @param string $string PO-formatted string
 
129
         * @return string enascaped string
 
130
         */
 
131
        function unpoify($string) {
 
132
                $escapes = array('t' => "\t", 'n' => "\n", '\\' => '\\');
 
133
                $lines = array_map('trim', explode("\n", $string));
 
134
                $lines = array_map(array('PO', 'trim_quotes'), $lines);
 
135
                $unpoified = '';
 
136
                $previous_is_backslash = false;
 
137
                foreach($lines as $line) {
 
138
                        preg_match_all('/./u', $line, $chars);
 
139
                        $chars = $chars[0];
 
140
                        foreach($chars as $char) {
 
141
                                if (!$previous_is_backslash) {
 
142
                                        if ('\\' == $char)
 
143
                                                $previous_is_backslash = true;
 
144
                                        else
 
145
                                                $unpoified .= $char;
 
146
                                } else {
 
147
                                        $previous_is_backslash = false;
 
148
                                        $unpoified .= isset($escapes[$char])? $escapes[$char] : $char;
 
149
                                }
 
150
                        }
 
151
                }
 
152
                return $unpoified;
 
153
        }
 
154
 
 
155
        /**
 
156
         * Inserts $with in the beginning of every new line of $string and
 
157
         * returns the modified string
 
158
         *
 
159
         * @static
 
160
         * @param string $string prepend lines in this string
 
161
         * @param string $with prepend lines with this string
 
162
         */
 
163
        function prepend_each_line($string, $with) {
 
164
                $php_with = var_export($with, true);
 
165
                $lines = explode("\n", $string);
 
166
                // do not prepend the string on the last empty line, artefact by explode
 
167
                if ("\n" == substr($string, -1)) unset($lines[count($lines) - 1]);
 
168
                $res = implode("\n", array_map(create_function('$x', "return $php_with.\$x;"), $lines));
 
169
                // give back the empty line, we ignored above
 
170
                if ("\n" == substr($string, -1)) $res .= "\n";
 
171
                return $res;
 
172
        }
 
173
 
 
174
        /**
 
175
         * Prepare a text as a comment -- wraps the lines and prepends #
 
176
         * and a special character to each line
 
177
         *
 
178
         * @access private
 
179
         * @param string $text the comment text
 
180
         * @param string $char character to denote a special PO comment,
 
181
         *      like :, default is a space
 
182
         */
 
183
        function comment_block($text, $char=' ') {
 
184
                $text = wordwrap($text, PO_MAX_LINE_LEN - 3);
 
185
                return PO::prepend_each_line($text, "#$char ");
 
186
        }
 
187
 
 
188
        /**
 
189
         * Builds a string from the entry for inclusion in PO file
 
190
         *
 
191
         * @static
 
192
         * @param object &$entry the entry to convert to po string
 
193
         * @return string|bool PO-style formatted string for the entry or
 
194
         *      false if the entry is empty
 
195
         */
 
196
        function export_entry(&$entry) {
 
197
                if (is_null($entry->singular)) return false;
 
198
                $po = array();
 
199
                if (!empty($entry->translator_comments)) $po[] = PO::comment_block($entry->translator_comments);
 
200
                if (!empty($entry->extracted_comments)) $po[] = PO::comment_block($entry->extracted_comments, '.');
 
201
                if (!empty($entry->references)) $po[] = PO::comment_block(implode(' ', $entry->references), ':');
 
202
                if (!empty($entry->flags)) $po[] = PO::comment_block(implode(", ", $entry->flags), ',');
 
203
                if (!is_null($entry->context)) $po[] = 'msgctxt '.PO::poify($entry->context);
 
204
                $po[] = 'msgid '.PO::poify($entry->singular);
 
205
                if (!$entry->is_plural) {
 
206
                        $translation = empty($entry->translations)? '' : $entry->translations[0];
 
207
                        $po[] = 'msgstr '.PO::poify($translation);
 
208
                } else {
 
209
                        $po[] = 'msgid_plural '.PO::poify($entry->plural);
 
210
                        $translations = empty($entry->translations)? array('', '') : $entry->translations;
 
211
                        foreach($translations as $i => $translation) {
 
212
                                $po[] = "msgstr[$i] ".PO::poify($translation);
 
213
                        }
 
214
                }
 
215
                return implode("\n", $po);
 
216
        }
 
217
 
 
218
        function import_from_file($filename) {
 
219
                $f = fopen($filename, 'r');
 
220
                if (!$f) return false;
 
221
                $lineno = 0;
 
222
                while (true) {
 
223
                        $res = $this->read_entry($f, $lineno);
 
224
                        if (!$res) break;
 
225
                        if ($res['entry']->singular == '') {
 
226
                                $this->set_headers($this->make_headers($res['entry']->translations[0]));
 
227
                        } else {
 
228
                                $this->add_entry($res['entry']);
 
229
                        }
 
230
                }
 
231
                PO::read_line($f, 'clear');
 
232
                if ( false === $res ) {
 
233
                        return false;
 
234
                }
 
235
                if ( ! $this->headers && ! $this->entries ) {
 
236
                        return false;
 
237
                }
 
238
                return true;
 
239
        }
 
240
 
 
241
        function read_entry($f, $lineno = 0) {
 
242
                $entry = new Translation_Entry();
 
243
                // where were we in the last step
 
244
                // can be: comment, msgctxt, msgid, msgid_plural, msgstr, msgstr_plural
 
245
                $context = '';
 
246
                $msgstr_index = 0;
 
247
                $is_final = create_function('$context', 'return $context == "msgstr" || $context == "msgstr_plural";');
 
248
                while (true) {
 
249
                        $lineno++;
 
250
                        $line = PO::read_line($f);
 
251
                        if (!$line)  {
 
252
                                if (feof($f)) {
 
253
                                        if ($is_final($context))
 
254
                                                break;
 
255
                                        elseif (!$context) // we haven't read a line and eof came
 
256
                                                return null;
 
257
                                        else
 
258
                                                return false;
 
259
                                } else {
 
260
                                        return false;
 
261
                                }
 
262
                        }
 
263
                        if ($line == "\n") continue;
 
264
                        $line = trim($line);
 
265
                        if (preg_match('/^#/', $line, $m)) {
 
266
                                // the comment is the start of a new entry
 
267
                                if ($is_final($context)) {
 
268
                                        PO::read_line($f, 'put-back');
 
269
                                        $lineno--;
 
270
                                        break;
 
271
                                }
 
272
                                // comments have to be at the beginning
 
273
                                if ($context && $context != 'comment') {
 
274
                                        return false;
 
275
                                }
 
276
                                // add comment
 
277
                                $this->add_comment_to_entry($entry, $line);
 
278
                        } elseif (preg_match('/^msgctxt\s+(".*")/', $line, $m)) {
 
279
                                if ($is_final($context)) {
 
280
                                        PO::read_line($f, 'put-back');
 
281
                                        $lineno--;
 
282
                                        break;
 
283
                                }
 
284
                                if ($context && $context != 'comment') {
 
285
                                        return false;
 
286
                                }
 
287
                                $context = 'msgctxt';
 
288
                                $entry->context .= PO::unpoify($m[1]);
 
289
                        } elseif (preg_match('/^msgid\s+(".*")/', $line, $m)) {
 
290
                                if ($is_final($context)) {
 
291
                                        PO::read_line($f, 'put-back');
 
292
                                        $lineno--;
 
293
                                        break;
 
294
                                }
 
295
                                if ($context && $context != 'msgctxt' && $context != 'comment') {
 
296
                                        return false;
 
297
                                }
 
298
                                $context = 'msgid';
 
299
                                $entry->singular .= PO::unpoify($m[1]);
 
300
                        } elseif (preg_match('/^msgid_plural\s+(".*")/', $line, $m)) {
 
301
                                if ($context != 'msgid') {
 
302
                                        return false;
 
303
                                }
 
304
                                $context = 'msgid_plural';
 
305
                                $entry->is_plural = true;
 
306
                                $entry->plural .= PO::unpoify($m[1]);
 
307
                        } elseif (preg_match('/^msgstr\s+(".*")/', $line, $m)) {
 
308
                                if ($context != 'msgid') {
 
309
                                        return false;
 
310
                                }
 
311
                                $context = 'msgstr';
 
312
                                $entry->translations = array(PO::unpoify($m[1]));
 
313
                        } elseif (preg_match('/^msgstr\[(\d+)\]\s+(".*")/', $line, $m)) {
 
314
                                if ($context != 'msgid_plural' && $context != 'msgstr_plural') {
 
315
                                        return false;
 
316
                                }
 
317
                                $context = 'msgstr_plural';
 
318
                                $msgstr_index = $m[1];
 
319
                                $entry->translations[$m[1]] = PO::unpoify($m[2]);
 
320
                        } elseif (preg_match('/^".*"$/', $line)) {
 
321
                                $unpoified = PO::unpoify($line);
 
322
                                switch ($context) {
 
323
                                        case 'msgid':
 
324
                                                $entry->singular .= $unpoified; break;
 
325
                                        case 'msgctxt':
 
326
                                                $entry->context .= $unpoified; break;
 
327
                                        case 'msgid_plural':
 
328
                                                $entry->plural .= $unpoified; break;
 
329
                                        case 'msgstr':
 
330
                                                $entry->translations[0] .= $unpoified; break;
 
331
                                        case 'msgstr_plural':
 
332
                                                $entry->translations[$msgstr_index] .= $unpoified; break;
 
333
                                        default:
 
334
                                                return false;
 
335
                                }
 
336
                        } else {
 
337
                                return false;
 
338
                        }
 
339
                }
 
340
                if (array() == array_filter($entry->translations, create_function('$t', 'return $t || "0" === $t;'))) {
 
341
                        $entry->translations = array();
 
342
                }
 
343
                return array('entry' => $entry, 'lineno' => $lineno);
 
344
        }
 
345
 
 
346
        function read_line($f, $action = 'read') {
 
347
                static $last_line = '';
 
348
                static $use_last_line = false;
 
349
                if ('clear' == $action) {
 
350
                        $last_line = '';
 
351
                        return true;
 
352
                }
 
353
                if ('put-back' == $action) {
 
354
                        $use_last_line = true;
 
355
                        return true;
 
356
                }
 
357
                $line = $use_last_line? $last_line : fgets($f);
 
358
                $line = ( "\r\n" == substr( $line, -2 ) ) ? rtrim( $line, "\r\n" ) . "\n" : $line;
 
359
                $last_line = $line;
 
360
                $use_last_line = false;
 
361
                return $line;
 
362
        }
 
363
 
 
364
        function add_comment_to_entry(&$entry, $po_comment_line) {
 
365
                $first_two = substr($po_comment_line, 0, 2);
 
366
                $comment = trim(substr($po_comment_line, 2));
 
367
                if ('#:' == $first_two) {
 
368
                        $entry->references = array_merge($entry->references, preg_split('/\s+/', $comment));
 
369
                } elseif ('#.' == $first_two) {
 
370
                        $entry->extracted_comments = trim($entry->extracted_comments . "\n" . $comment);
 
371
                } elseif ('#,' == $first_two) {
 
372
                        $entry->flags = array_merge($entry->flags, preg_split('/,\s*/', $comment));
 
373
                } else {
 
374
                        $entry->translator_comments = trim($entry->translator_comments . "\n" . $comment);
 
375
                }
 
376
        }
 
377
 
 
378
        function trim_quotes($s) {
 
379
                if ( substr($s, 0, 1) == '"') $s = substr($s, 1);
 
380
                if ( substr($s, -1, 1) == '"') $s = substr($s, 0, -1);
 
381
                return $s;
 
382
        }
 
383
}
 
384
endif;