wp/wp-includes/pomo/po.php
changeset 7 cf61fcea0001
parent 5 5e2f62d02dcd
child 9 177826044cd9
equal deleted inserted replaced
6:490d5cc509ed 7:cf61fcea0001
     1 <?php
     1 <?php
     2 /**
     2 /**
     3  * Class for working with PO files
     3  * Class for working with PO files
     4  *
     4  *
     5  * @version $Id: po.php 718 2012-10-31 00:32:02Z nbachiyski $
     5  * @version $Id: po.php 1158 2015-11-20 04:31:23Z dd32 $
     6  * @package pomo
     6  * @package pomo
     7  * @subpackage po
     7  * @subpackage po
     8  */
     8  */
     9 
     9 
    10 require_once dirname(__FILE__) . '/translations.php';
    10 require_once dirname(__FILE__) . '/translations.php';
    11 
    11 
    12 define('PO_MAX_LINE_LEN', 79);
    12 if ( ! defined( 'PO_MAX_LINE_LEN' ) ) {
       
    13 	define('PO_MAX_LINE_LEN', 79);
       
    14 }
    13 
    15 
    14 ini_set('auto_detect_line_endings', 1);
    16 ini_set('auto_detect_line_endings', 1);
    15 
    17 
    16 /**
    18 /**
    17  * Routines for working with PO files
    19  * Routines for working with PO files
    18  */
    20  */
    19 if ( !class_exists( 'PO' ) ):
    21 if ( ! class_exists( 'PO', false ) ):
    20 class PO extends Gettext_Translations {
    22 class PO extends Gettext_Translations {
    21 
    23 
    22 	var $comments_before_headers = '';
    24 	var $comments_before_headers = '';
    23 
    25 
    24 	/**
    26 	/**
    95 	 *
    97 	 *
    96 	 * @static
    98 	 * @static
    97 	 * @param string $string the string to format
    99 	 * @param string $string the string to format
    98 	 * @return string the poified string
   100 	 * @return string the poified string
    99 	 */
   101 	 */
   100 	function poify($string) {
   102 	public static function poify($string) {
   101 		$quote = '"';
   103 		$quote = '"';
   102 		$slash = '\\';
   104 		$slash = '\\';
   103 		$newline = "\n";
   105 		$newline = "\n";
   104 
   106 
   105 		$replaces = array(
   107 		$replaces = array(
   126 	 *
   128 	 *
   127 	 * @static
   129 	 * @static
   128 	 * @param string $string PO-formatted string
   130 	 * @param string $string PO-formatted string
   129 	 * @return string enascaped string
   131 	 * @return string enascaped string
   130 	 */
   132 	 */
   131 	function unpoify($string) {
   133 	public static function unpoify($string) {
   132 		$escapes = array('t' => "\t", 'n' => "\n", '\\' => '\\');
   134 		$escapes = array('t' => "\t", 'n' => "\n", 'r' => "\r", '\\' => '\\');
   133 		$lines = array_map('trim', explode("\n", $string));
   135 		$lines = array_map('trim', explode("\n", $string));
   134 		$lines = array_map(array('PO', 'trim_quotes'), $lines);
   136 		$lines = array_map(array('PO', 'trim_quotes'), $lines);
   135 		$unpoified = '';
   137 		$unpoified = '';
   136 		$previous_is_backslash = false;
   138 		$previous_is_backslash = false;
   137 		foreach($lines as $line) {
   139 		foreach($lines as $line) {
   147 					$previous_is_backslash = false;
   149 					$previous_is_backslash = false;
   148 					$unpoified .= isset($escapes[$char])? $escapes[$char] : $char;
   150 					$unpoified .= isset($escapes[$char])? $escapes[$char] : $char;
   149 				}
   151 				}
   150 			}
   152 			}
   151 		}
   153 		}
       
   154 
       
   155 		// Standardise the line endings on imported content, technically PO files shouldn't contain \r
       
   156 		$unpoified = str_replace( array( "\r\n", "\r" ), "\n", $unpoified );
       
   157 
   152 		return $unpoified;
   158 		return $unpoified;
   153 	}
   159 	}
   154 
   160 
   155 	/**
   161 	/**
   156 	 * Inserts $with in the beginning of every new line of $string and
   162 	 * Inserts $with in the beginning of every new line of $string and
   158 	 *
   164 	 *
   159 	 * @static
   165 	 * @static
   160 	 * @param string $string prepend lines in this string
   166 	 * @param string $string prepend lines in this string
   161 	 * @param string $with prepend lines with this string
   167 	 * @param string $with prepend lines with this string
   162 	 */
   168 	 */
   163 	function prepend_each_line($string, $with) {
   169 	public static function prepend_each_line($string, $with) {
   164 		$php_with = var_export($with, true);
       
   165 		$lines = explode("\n", $string);
   170 		$lines = explode("\n", $string);
   166 		// do not prepend the string on the last empty line, artefact by explode
   171 		$append = '';
   167 		if ("\n" == substr($string, -1)) unset($lines[count($lines) - 1]);
   172 		if ("\n" === substr($string, -1) && '' === end($lines)) {
   168 		$res = implode("\n", array_map(create_function('$x', "return $php_with.\$x;"), $lines));
   173 			// Last line might be empty because $string was terminated
   169 		// give back the empty line, we ignored above
   174 			// with a newline, remove it from the $lines array,
   170 		if ("\n" == substr($string, -1)) $res .= "\n";
   175 			// we'll restore state by re-terminating the string at the end
   171 		return $res;
   176 			array_pop($lines);
       
   177 			$append = "\n";
       
   178 		}
       
   179 		foreach ($lines as &$line) {
       
   180 			$line = $with . $line;
       
   181 		}
       
   182 		unset($line);
       
   183 		return implode("\n", $lines) . $append;
   172 	}
   184 	}
   173 
   185 
   174 	/**
   186 	/**
   175 	 * Prepare a text as a comment -- wraps the lines and prepends #
   187 	 * Prepare a text as a comment -- wraps the lines and prepends #
   176 	 * and a special character to each line
   188 	 * and a special character to each line
   178 	 * @access private
   190 	 * @access private
   179 	 * @param string $text the comment text
   191 	 * @param string $text the comment text
   180 	 * @param string $char character to denote a special PO comment,
   192 	 * @param string $char character to denote a special PO comment,
   181 	 * 	like :, default is a space
   193 	 * 	like :, default is a space
   182 	 */
   194 	 */
   183 	function comment_block($text, $char=' ') {
   195 	public static function comment_block($text, $char=' ') {
   184 		$text = wordwrap($text, PO_MAX_LINE_LEN - 3);
   196 		$text = wordwrap($text, PO_MAX_LINE_LEN - 3);
   185 		return PO::prepend_each_line($text, "#$char ");
   197 		return PO::prepend_each_line($text, "#$char ");
   186 	}
   198 	}
   187 
   199 
   188 	/**
   200 	/**
   189 	 * Builds a string from the entry for inclusion in PO file
   201 	 * Builds a string from the entry for inclusion in PO file
   190 	 *
   202 	 *
   191 	 * @static
   203 	 * @static
   192 	 * @param Translation_Entry &$entry the entry to convert to po string
   204 	 * @param Translation_Entry $entry the entry to convert to po string (passed by reference).
   193 	 * @return false|string PO-style formatted string for the entry or
   205 	 * @return false|string PO-style formatted string for the entry or
   194 	 * 	false if the entry is empty
   206 	 * 	false if the entry is empty
   195 	 */
   207 	 */
   196 	function export_entry(&$entry) {
   208 	public static function export_entry(&$entry) {
   197 		if (is_null($entry->singular)) return false;
   209 		if ( null === $entry->singular || '' === $entry->singular ) return false;
   198 		$po = array();
   210 		$po = array();
   199 		if (!empty($entry->translator_comments)) $po[] = PO::comment_block($entry->translator_comments);
   211 		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, '.');
   212 		if (!empty($entry->extracted_comments)) $po[] = PO::comment_block($entry->extracted_comments, '.');
   201 		if (!empty($entry->references)) $po[] = PO::comment_block(implode(' ', $entry->references), ':');
   213 		if (!empty($entry->references)) $po[] = PO::comment_block(implode(' ', $entry->references), ':');
   202 		if (!empty($entry->flags)) $po[] = PO::comment_block(implode(", ", $entry->flags), ',');
   214 		if (!empty($entry->flags)) $po[] = PO::comment_block(implode(", ", $entry->flags), ',');
   203 		if (!is_null($entry->context)) $po[] = 'msgctxt '.PO::poify($entry->context);
   215 		if ($entry->context) $po[] = 'msgctxt '.PO::poify($entry->context);
   204 		$po[] = 'msgid '.PO::poify($entry->singular);
   216 		$po[] = 'msgid '.PO::poify($entry->singular);
   205 		if (!$entry->is_plural) {
   217 		if (!$entry->is_plural) {
   206 			$translation = empty($entry->translations)? '' : $entry->translations[0];
   218 			$translation = empty($entry->translations)? '' : $entry->translations[0];
       
   219 			$translation = PO::match_begin_and_end_newlines( $translation, $entry->singular );
   207 			$po[] = 'msgstr '.PO::poify($translation);
   220 			$po[] = 'msgstr '.PO::poify($translation);
   208 		} else {
   221 		} else {
   209 			$po[] = 'msgid_plural '.PO::poify($entry->plural);
   222 			$po[] = 'msgid_plural '.PO::poify($entry->plural);
   210 			$translations = empty($entry->translations)? array('', '') : $entry->translations;
   223 			$translations = empty($entry->translations)? array('', '') : $entry->translations;
   211 			foreach($translations as $i => $translation) {
   224 			foreach($translations as $i => $translation) {
       
   225 				$translation = PO::match_begin_and_end_newlines( $translation, $entry->plural );
   212 				$po[] = "msgstr[$i] ".PO::poify($translation);
   226 				$po[] = "msgstr[$i] ".PO::poify($translation);
   213 			}
   227 			}
   214 		}
   228 		}
   215 		return implode("\n", $po);
   229 		return implode("\n", $po);
       
   230 	}
       
   231 
       
   232 	public static function match_begin_and_end_newlines( $translation, $original ) {
       
   233 		if ( '' === $translation ) {
       
   234 			return $translation;
       
   235 		}
       
   236 
       
   237 		$original_begin = "\n" === substr( $original, 0, 1 );
       
   238 		$original_end = "\n" === substr( $original, -1 );
       
   239 		$translation_begin = "\n" === substr( $translation, 0, 1 );
       
   240 		$translation_end = "\n" === substr( $translation, -1 );
       
   241 
       
   242 		if ( $original_begin ) {
       
   243 			if ( ! $translation_begin ) {
       
   244 				$translation = "\n" . $translation;
       
   245 			}
       
   246 		} elseif ( $translation_begin ) {
       
   247 			$translation = ltrim( $translation, "\n" );
       
   248 		}
       
   249 
       
   250 		if ( $original_end ) {
       
   251 			if ( ! $translation_end ) {
       
   252 				$translation .= "\n";
       
   253 			}
       
   254 		} elseif ( $translation_end ) {
       
   255 			$translation = rtrim( $translation, "\n" );
       
   256 		}
       
   257 
       
   258 		return $translation;
   216 	}
   259 	}
   217 
   260 
   218 	/**
   261 	/**
   219 	 * @param string $filename
   262 	 * @param string $filename
   220 	 * @return boolean
   263 	 * @return boolean
   241 		}
   284 		}
   242 		return true;
   285 		return true;
   243 	}
   286 	}
   244 
   287 
   245 	/**
   288 	/**
       
   289 	 * Helper function for read_entry
       
   290 	 * @param string $context
       
   291 	 * @return bool
       
   292 	 */
       
   293 	protected static function is_final($context) {
       
   294 		return ($context === 'msgstr') || ($context === 'msgstr_plural');
       
   295 	}
       
   296 
       
   297 	/**
   246 	 * @param resource $f
   298 	 * @param resource $f
   247 	 * @param int      $lineno
   299 	 * @param int      $lineno
   248 	 * @return null|false|array
   300 	 * @return null|false|array
   249 	 */
   301 	 */
   250 	function read_entry($f, $lineno = 0) {
   302 	function read_entry($f, $lineno = 0) {
   251 		$entry = new Translation_Entry();
   303 		$entry = new Translation_Entry();
   252 		// where were we in the last step
   304 		// where were we in the last step
   253 		// can be: comment, msgctxt, msgid, msgid_plural, msgstr, msgstr_plural
   305 		// can be: comment, msgctxt, msgid, msgid_plural, msgstr, msgstr_plural
   254 		$context = '';
   306 		$context = '';
   255 		$msgstr_index = 0;
   307 		$msgstr_index = 0;
   256 		$is_final = create_function('$context', 'return $context == "msgstr" || $context == "msgstr_plural";');
       
   257 		while (true) {
   308 		while (true) {
   258 			$lineno++;
   309 			$lineno++;
   259 			$line = PO::read_line($f);
   310 			$line = PO::read_line($f);
   260 			if (!$line)  {
   311 			if (!$line)  {
   261 				if (feof($f)) {
   312 				if (feof($f)) {
   262 					if ($is_final($context))
   313 					if (self::is_final($context))
   263 						break;
   314 						break;
   264 					elseif (!$context) // we haven't read a line and eof came
   315 					elseif (!$context) // we haven't read a line and eof came
   265 						return null;
   316 						return null;
   266 					else
   317 					else
   267 						return false;
   318 						return false;
   271 			}
   322 			}
   272 			if ($line == "\n") continue;
   323 			if ($line == "\n") continue;
   273 			$line = trim($line);
   324 			$line = trim($line);
   274 			if (preg_match('/^#/', $line, $m)) {
   325 			if (preg_match('/^#/', $line, $m)) {
   275 				// the comment is the start of a new entry
   326 				// the comment is the start of a new entry
   276 				if ($is_final($context)) {
   327 				if (self::is_final($context)) {
   277 					PO::read_line($f, 'put-back');
   328 					PO::read_line($f, 'put-back');
   278 					$lineno--;
   329 					$lineno--;
   279 					break;
   330 					break;
   280 				}
   331 				}
   281 				// comments have to be at the beginning
   332 				// comments have to be at the beginning
   283 					return false;
   334 					return false;
   284 				}
   335 				}
   285 				// add comment
   336 				// add comment
   286 				$this->add_comment_to_entry($entry, $line);
   337 				$this->add_comment_to_entry($entry, $line);
   287 			} elseif (preg_match('/^msgctxt\s+(".*")/', $line, $m)) {
   338 			} elseif (preg_match('/^msgctxt\s+(".*")/', $line, $m)) {
   288 				if ($is_final($context)) {
   339 				if (self::is_final($context)) {
   289 					PO::read_line($f, 'put-back');
   340 					PO::read_line($f, 'put-back');
   290 					$lineno--;
   341 					$lineno--;
   291 					break;
   342 					break;
   292 				}
   343 				}
   293 				if ($context && $context != 'comment') {
   344 				if ($context && $context != 'comment') {
   294 					return false;
   345 					return false;
   295 				}
   346 				}
   296 				$context = 'msgctxt';
   347 				$context = 'msgctxt';
   297 				$entry->context .= PO::unpoify($m[1]);
   348 				$entry->context .= PO::unpoify($m[1]);
   298 			} elseif (preg_match('/^msgid\s+(".*")/', $line, $m)) {
   349 			} elseif (preg_match('/^msgid\s+(".*")/', $line, $m)) {
   299 				if ($is_final($context)) {
   350 				if (self::is_final($context)) {
   300 					PO::read_line($f, 'put-back');
   351 					PO::read_line($f, 'put-back');
   301 					$lineno--;
   352 					$lineno--;
   302 					break;
   353 					break;
   303 				}
   354 				}
   304 				if ($context && $context != 'msgctxt' && $context != 'comment') {
   355 				if ($context && $context != 'msgctxt' && $context != 'comment') {
   344 				}
   395 				}
   345 			} else {
   396 			} else {
   346 				return false;
   397 				return false;
   347 			}
   398 			}
   348 		}
   399 		}
   349 		if (array() == array_filter($entry->translations, create_function('$t', 'return $t || "0" === $t;'))) {
   400 
       
   401 		$have_translations = false;
       
   402 		foreach ( $entry->translations as $t ) {
       
   403 			if ( $t || ('0' === $t) ) {
       
   404 				$have_translations = true;
       
   405 				break;
       
   406 			}
       
   407 		}
       
   408 		if ( false === $have_translations ) {
   350 			$entry->translations = array();
   409 			$entry->translations = array();
   351 		}
   410 		}
       
   411 
   352 		return array('entry' => $entry, 'lineno' => $lineno);
   412 		return array('entry' => $entry, 'lineno' => $lineno);
   353 	}
   413 	}
   354 
   414 
   355 	/**
   415 	/**
   356 	 * @staticvar string   $last_line
   416 	 * @staticvar string   $last_line
   357 	 * @staticvar boolean  $use_last_line
   417 	 * @staticvar boolean  $use_last_line
       
   418 	 *
   358 	 * @param     resource $f
   419 	 * @param     resource $f
   359 	 * @param     string   $action
   420 	 * @param     string   $action
   360 	 * @return boolean
   421 	 * @return boolean
   361 	 */
   422 	 */
   362 	function read_line($f, $action = 'read') {
   423 	function read_line($f, $action = 'read') {
   397 
   458 
   398 	/**
   459 	/**
   399 	 * @param string $s
   460 	 * @param string $s
   400 	 * @return sring
   461 	 * @return sring
   401 	 */
   462 	 */
   402 	function trim_quotes($s) {
   463 	public static function trim_quotes($s) {
   403 		if ( substr($s, 0, 1) == '"') $s = substr($s, 1);
   464 		if ( substr($s, 0, 1) == '"') $s = substr($s, 1);
   404 		if ( substr($s, -1, 1) == '"') $s = substr($s, 0, -1);
   465 		if ( substr($s, -1, 1) == '"') $s = substr($s, 0, -1);
   405 		return $s;
   466 		return $s;
   406 	}
   467 	}
   407 }
   468 }