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