wp/wp-includes/pomo/po.php
changeset 16 a86126ab1dd4
parent 9 177826044cd9
child 18 be944660c56a
equal deleted inserted replaced
15:3d4e9c994f10 16:a86126ab1dd4
     5  * @version $Id: po.php 1158 2015-11-20 04:31:23Z dd32 $
     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 __DIR__ . '/translations.php';
    11 
    11 
    12 if ( ! defined( 'PO_MAX_LINE_LEN' ) ) {
    12 if ( ! defined( 'PO_MAX_LINE_LEN' ) ) {
    13 	define( 'PO_MAX_LINE_LEN', 79 );
    13 	define( 'PO_MAX_LINE_LEN', 79 );
    14 }
    14 }
    15 
    15 
    46 		 * Exports all entries to PO format
    46 		 * Exports all entries to PO format
    47 		 *
    47 		 *
    48 		 * @return string sequence of mgsgid/msgstr PO strings, doesn't containt newline at the end
    48 		 * @return string sequence of mgsgid/msgstr PO strings, doesn't containt newline at the end
    49 		 */
    49 		 */
    50 		function export_entries() {
    50 		function export_entries() {
    51 			//TODO sorting
    51 			// TODO: Sorting.
    52 			return implode( "\n\n", array_map( array( 'PO', 'export_entry' ), $this->entries ) );
    52 			return implode( "\n\n", array_map( array( 'PO', 'export_entry' ), $this->entries ) );
    53 		}
    53 		}
    54 
    54 
    55 		/**
    55 		/**
    56 		 * Exports the whole PO file as a string
    56 		 * Exports the whole PO file as a string
    69 		}
    69 		}
    70 
    70 
    71 		/**
    71 		/**
    72 		 * Same as {@link export}, but writes the result to a file
    72 		 * Same as {@link export}, but writes the result to a file
    73 		 *
    73 		 *
    74 		 * @param string $filename where to write the PO string
    74 		 * @param string $filename        Where to write the PO string.
    75 		 * @param bool $include_headers whether to include tje headers in the export
    75 		 * @param bool   $include_headers Whether to include the headers in the export.
    76 		 * @return bool true on success, false on error
    76 		 * @return bool true on success, false on error
    77 		 */
    77 		 */
    78 		function export_to_file( $filename, $include_headers = true ) {
    78 		function export_to_file( $filename, $include_headers = true ) {
    79 			$fh = fopen( $filename, 'w' );
    79 			$fh = fopen( $filename, 'w' );
    80 			if ( false === $fh ) {
    80 			if ( false === $fh ) {
    90 
    90 
    91 		/**
    91 		/**
    92 		 * Text to include as a comment before the start of the PO contents
    92 		 * Text to include as a comment before the start of the PO contents
    93 		 *
    93 		 *
    94 		 * Doesn't need to include # in the beginning of lines, these are added automatically
    94 		 * Doesn't need to include # in the beginning of lines, these are added automatically
       
    95 		 *
       
    96 		 * @param string $text Text to include as a comment.
    95 		 */
    97 		 */
    96 		function set_comment_before_headers( $text ) {
    98 		function set_comment_before_headers( $text ) {
    97 			$this->comments_before_headers = $text;
    99 			$this->comments_before_headers = $text;
    98 		}
   100 		}
    99 
   101 
   115 			);
   117 			);
   116 
   118 
   117 			$string = str_replace( array_keys( $replaces ), array_values( $replaces ), $string );
   119 			$string = str_replace( array_keys( $replaces ), array_values( $replaces ), $string );
   118 
   120 
   119 			$po = $quote . implode( "${slash}n$quote$newline$quote", explode( $newline, $string ) ) . $quote;
   121 			$po = $quote . implode( "${slash}n$quote$newline$quote", explode( $newline, $string ) ) . $quote;
   120 			// add empty string on first line for readbility
   122 			// Add empty string on first line for readbility.
   121 			if ( false !== strpos( $string, $newline ) &&
   123 			if ( false !== strpos( $string, $newline ) &&
   122 				( substr_count( $string, $newline ) > 1 || ! ( $newline === substr( $string, -strlen( $newline ) ) ) ) ) {
   124 				( substr_count( $string, $newline ) > 1 || substr( $string, -strlen( $newline ) ) !== $newline ) ) {
   123 				$po = "$quote$quote$newline$po";
   125 				$po = "$quote$quote$newline$po";
   124 			}
   126 			}
   125 			// remove empty strings
   127 			// Remove empty strings.
   126 			$po = str_replace( "$newline$quote$quote", '', $po );
   128 			$po = str_replace( "$newline$quote$quote", '', $po );
   127 			return $po;
   129 			return $po;
   128 		}
   130 		}
   129 
   131 
   130 		/**
   132 		/**
   147 			foreach ( $lines as $line ) {
   149 			foreach ( $lines as $line ) {
   148 				preg_match_all( '/./u', $line, $chars );
   150 				preg_match_all( '/./u', $line, $chars );
   149 				$chars = $chars[0];
   151 				$chars = $chars[0];
   150 				foreach ( $chars as $char ) {
   152 				foreach ( $chars as $char ) {
   151 					if ( ! $previous_is_backslash ) {
   153 					if ( ! $previous_is_backslash ) {
   152 						if ( '\\' == $char ) {
   154 						if ( '\\' === $char ) {
   153 							$previous_is_backslash = true;
   155 							$previous_is_backslash = true;
   154 						} else {
   156 						} else {
   155 							$unpoified .= $char;
   157 							$unpoified .= $char;
   156 						}
   158 						}
   157 					} else {
   159 					} else {
   159 						$unpoified            .= isset( $escapes[ $char ] ) ? $escapes[ $char ] : $char;
   161 						$unpoified            .= isset( $escapes[ $char ] ) ? $escapes[ $char ] : $char;
   160 					}
   162 					}
   161 				}
   163 				}
   162 			}
   164 			}
   163 
   165 
   164 			// Standardise the line endings on imported content, technically PO files shouldn't contain \r
   166 			// Standardise the line endings on imported content, technically PO files shouldn't contain \r.
   165 			$unpoified = str_replace( array( "\r\n", "\r" ), "\n", $unpoified );
   167 			$unpoified = str_replace( array( "\r\n", "\r" ), "\n", $unpoified );
   166 
   168 
   167 			return $unpoified;
   169 			return $unpoified;
   168 		}
   170 		}
   169 
   171 
   176 		 */
   178 		 */
   177 		public static function prepend_each_line( $string, $with ) {
   179 		public static function prepend_each_line( $string, $with ) {
   178 			$lines  = explode( "\n", $string );
   180 			$lines  = explode( "\n", $string );
   179 			$append = '';
   181 			$append = '';
   180 			if ( "\n" === substr( $string, -1 ) && '' === end( $lines ) ) {
   182 			if ( "\n" === substr( $string, -1 ) && '' === end( $lines ) ) {
   181 				// Last line might be empty because $string was terminated
   183 				/*
   182 				// with a newline, remove it from the $lines array,
   184 				 * Last line might be empty because $string was terminated
   183 				// we'll restore state by re-terminating the string at the end
   185 				 * with a newline, remove it from the $lines array,
       
   186 				 * we'll restore state by re-terminating the string at the end.
       
   187 				 */
   184 				array_pop( $lines );
   188 				array_pop( $lines );
   185 				$append = "\n";
   189 				$append = "\n";
   186 			}
   190 			}
   187 			foreach ( $lines as &$line ) {
   191 			foreach ( $lines as &$line ) {
   188 				$line = $with . $line;
   192 				$line = $with . $line;
   207 
   211 
   208 		/**
   212 		/**
   209 		 * Builds a string from the entry for inclusion in PO file
   213 		 * Builds a string from the entry for inclusion in PO file
   210 		 *
   214 		 *
   211 		 * @param Translation_Entry $entry the entry to convert to po string (passed by reference).
   215 		 * @param Translation_Entry $entry the entry to convert to po string (passed by reference).
   212 		 * @return false|string PO-style formatted string for the entry or
   216 		 * @return string|false PO-style formatted string for the entry or
   213 		 *  false if the entry is empty
   217 		 *  false if the entry is empty
   214 		 */
   218 		 */
   215 		public static function export_entry( &$entry ) {
   219 		public static function export_entry( &$entry ) {
   216 			if ( null === $entry->singular || '' === $entry->singular ) {
   220 			if ( null === $entry->singular || '' === $entry->singular ) {
   217 				return false;
   221 				return false;
   290 			while ( true ) {
   294 			while ( true ) {
   291 				$res = $this->read_entry( $f, $lineno );
   295 				$res = $this->read_entry( $f, $lineno );
   292 				if ( ! $res ) {
   296 				if ( ! $res ) {
   293 					break;
   297 					break;
   294 				}
   298 				}
   295 				if ( $res['entry']->singular == '' ) {
   299 				if ( '' === $res['entry']->singular ) {
   296 					$this->set_headers( $this->make_headers( $res['entry']->translations[0] ) );
   300 					$this->set_headers( $this->make_headers( $res['entry']->translations[0] ) );
   297 				} else {
   301 				} else {
   298 					$this->add_entry( $res['entry'] );
   302 					$this->add_entry( $res['entry'] );
   299 				}
   303 				}
   300 			}
   304 			}
   313 		 *
   317 		 *
   314 		 * @param string $context
   318 		 * @param string $context
   315 		 * @return bool
   319 		 * @return bool
   316 		 */
   320 		 */
   317 		protected static function is_final( $context ) {
   321 		protected static function is_final( $context ) {
   318 			return ( $context === 'msgstr' ) || ( $context === 'msgstr_plural' );
   322 			return ( 'msgstr' === $context ) || ( 'msgstr_plural' === $context );
   319 		}
   323 		}
   320 
   324 
   321 		/**
   325 		/**
   322 		 * @param resource $f
   326 		 * @param resource $f
   323 		 * @param int      $lineno
   327 		 * @param int      $lineno
   324 		 * @return null|false|array
   328 		 * @return null|false|array
   325 		 */
   329 		 */
   326 		function read_entry( $f, $lineno = 0 ) {
   330 		function read_entry( $f, $lineno = 0 ) {
   327 			$entry = new Translation_Entry();
   331 			$entry = new Translation_Entry();
   328 			// where were we in the last step
   332 			// Where were we in the last step.
   329 			// can be: comment, msgctxt, msgid, msgid_plural, msgstr, msgstr_plural
   333 			// Can be: comment, msgctxt, msgid, msgid_plural, msgstr, msgstr_plural.
   330 			$context      = '';
   334 			$context      = '';
   331 			$msgstr_index = 0;
   335 			$msgstr_index = 0;
   332 			while ( true ) {
   336 			while ( true ) {
   333 				$lineno++;
   337 				$lineno++;
   334 				$line = PO::read_line( $f );
   338 				$line = PO::read_line( $f );
   335 				if ( ! $line ) {
   339 				if ( ! $line ) {
   336 					if ( feof( $f ) ) {
   340 					if ( feof( $f ) ) {
   337 						if ( self::is_final( $context ) ) {
   341 						if ( self::is_final( $context ) ) {
   338 							break;
   342 							break;
   339 						} elseif ( ! $context ) { // we haven't read a line and eof came
   343 						} elseif ( ! $context ) { // We haven't read a line and EOF came.
   340 							return null;
   344 							return null;
   341 						} else {
   345 						} else {
   342 							return false;
   346 							return false;
   343 						}
   347 						}
   344 					} else {
   348 					} else {
   345 						return false;
   349 						return false;
   346 					}
   350 					}
   347 				}
   351 				}
   348 				if ( $line == "\n" ) {
   352 				if ( "\n" === $line ) {
   349 					continue;
   353 					continue;
   350 				}
   354 				}
   351 				$line = trim( $line );
   355 				$line = trim( $line );
   352 				if ( preg_match( '/^#/', $line, $m ) ) {
   356 				if ( preg_match( '/^#/', $line, $m ) ) {
   353 					// the comment is the start of a new entry
   357 					// The comment is the start of a new entry.
   354 					if ( self::is_final( $context ) ) {
   358 					if ( self::is_final( $context ) ) {
   355 						PO::read_line( $f, 'put-back' );
   359 						PO::read_line( $f, 'put-back' );
   356 						$lineno--;
   360 						$lineno--;
   357 						break;
   361 						break;
   358 					}
   362 					}
   359 					// comments have to be at the beginning
   363 					// Comments have to be at the beginning.
   360 					if ( $context && $context != 'comment' ) {
   364 					if ( $context && 'comment' !== $context ) {
   361 						return false;
   365 						return false;
   362 					}
   366 					}
   363 					// add comment
   367 					// Add comment.
   364 					$this->add_comment_to_entry( $entry, $line );
   368 					$this->add_comment_to_entry( $entry, $line );
   365 				} elseif ( preg_match( '/^msgctxt\s+(".*")/', $line, $m ) ) {
   369 				} elseif ( preg_match( '/^msgctxt\s+(".*")/', $line, $m ) ) {
   366 					if ( self::is_final( $context ) ) {
   370 					if ( self::is_final( $context ) ) {
   367 						PO::read_line( $f, 'put-back' );
   371 						PO::read_line( $f, 'put-back' );
   368 						$lineno--;
   372 						$lineno--;
   369 						break;
   373 						break;
   370 					}
   374 					}
   371 					if ( $context && $context != 'comment' ) {
   375 					if ( $context && 'comment' !== $context ) {
   372 						return false;
   376 						return false;
   373 					}
   377 					}
   374 					$context         = 'msgctxt';
   378 					$context         = 'msgctxt';
   375 					$entry->context .= PO::unpoify( $m[1] );
   379 					$entry->context .= PO::unpoify( $m[1] );
   376 				} elseif ( preg_match( '/^msgid\s+(".*")/', $line, $m ) ) {
   380 				} elseif ( preg_match( '/^msgid\s+(".*")/', $line, $m ) ) {
   377 					if ( self::is_final( $context ) ) {
   381 					if ( self::is_final( $context ) ) {
   378 						PO::read_line( $f, 'put-back' );
   382 						PO::read_line( $f, 'put-back' );
   379 						$lineno--;
   383 						$lineno--;
   380 						break;
   384 						break;
   381 					}
   385 					}
   382 					if ( $context && $context != 'msgctxt' && $context != 'comment' ) {
   386 					if ( $context && 'msgctxt' !== $context && 'comment' !== $context ) {
   383 						return false;
   387 						return false;
   384 					}
   388 					}
   385 					$context          = 'msgid';
   389 					$context          = 'msgid';
   386 					$entry->singular .= PO::unpoify( $m[1] );
   390 					$entry->singular .= PO::unpoify( $m[1] );
   387 				} elseif ( preg_match( '/^msgid_plural\s+(".*")/', $line, $m ) ) {
   391 				} elseif ( preg_match( '/^msgid_plural\s+(".*")/', $line, $m ) ) {
   388 					if ( $context != 'msgid' ) {
   392 					if ( 'msgid' !== $context ) {
   389 						return false;
   393 						return false;
   390 					}
   394 					}
   391 					$context          = 'msgid_plural';
   395 					$context          = 'msgid_plural';
   392 					$entry->is_plural = true;
   396 					$entry->is_plural = true;
   393 					$entry->plural   .= PO::unpoify( $m[1] );
   397 					$entry->plural   .= PO::unpoify( $m[1] );
   394 				} elseif ( preg_match( '/^msgstr\s+(".*")/', $line, $m ) ) {
   398 				} elseif ( preg_match( '/^msgstr\s+(".*")/', $line, $m ) ) {
   395 					if ( $context != 'msgid' ) {
   399 					if ( 'msgid' !== $context ) {
   396 						return false;
   400 						return false;
   397 					}
   401 					}
   398 					$context             = 'msgstr';
   402 					$context             = 'msgstr';
   399 					$entry->translations = array( PO::unpoify( $m[1] ) );
   403 					$entry->translations = array( PO::unpoify( $m[1] ) );
   400 				} elseif ( preg_match( '/^msgstr\[(\d+)\]\s+(".*")/', $line, $m ) ) {
   404 				} elseif ( preg_match( '/^msgstr\[(\d+)\]\s+(".*")/', $line, $m ) ) {
   401 					if ( $context != 'msgid_plural' && $context != 'msgstr_plural' ) {
   405 					if ( 'msgid_plural' !== $context && 'msgstr_plural' !== $context ) {
   402 						return false;
   406 						return false;
   403 					}
   407 					}
   404 					$context                      = 'msgstr_plural';
   408 					$context                      = 'msgstr_plural';
   405 					$msgstr_index                 = $m[1];
   409 					$msgstr_index                 = $m[1];
   406 					$entry->translations[ $m[1] ] = PO::unpoify( $m[2] );
   410 					$entry->translations[ $m[1] ] = PO::unpoify( $m[2] );
   446 				'lineno' => $lineno,
   450 				'lineno' => $lineno,
   447 			);
   451 			);
   448 		}
   452 		}
   449 
   453 
   450 		/**
   454 		/**
   451 		 * @staticvar string   $last_line
   455 		 * @param resource $f
   452 		 * @staticvar boolean  $use_last_line
   456 		 * @param string   $action
   453 		 *
       
   454 		 * @param     resource $f
       
   455 		 * @param     string   $action
       
   456 		 * @return boolean
   457 		 * @return boolean
   457 		 */
   458 		 */
   458 		function read_line( $f, $action = 'read' ) {
   459 		function read_line( $f, $action = 'read' ) {
   459 			static $last_line     = '';
   460 			static $last_line     = '';
   460 			static $use_last_line = false;
   461 			static $use_last_line = false;
   461 			if ( 'clear' == $action ) {
   462 			if ( 'clear' === $action ) {
   462 				$last_line = '';
   463 				$last_line = '';
   463 				return true;
   464 				return true;
   464 			}
   465 			}
   465 			if ( 'put-back' == $action ) {
   466 			if ( 'put-back' === $action ) {
   466 				$use_last_line = true;
   467 				$use_last_line = true;
   467 				return true;
   468 				return true;
   468 			}
   469 			}
   469 			$line          = $use_last_line ? $last_line : fgets( $f );
   470 			$line          = $use_last_line ? $last_line : fgets( $f );
   470 			$line          = ( "\r\n" == substr( $line, -2 ) ) ? rtrim( $line, "\r\n" ) . "\n" : $line;
   471 			$line          = ( "\r\n" === substr( $line, -2 ) ) ? rtrim( $line, "\r\n" ) . "\n" : $line;
   471 			$last_line     = $line;
   472 			$last_line     = $line;
   472 			$use_last_line = false;
   473 			$use_last_line = false;
   473 			return $line;
   474 			return $line;
   474 		}
   475 		}
   475 
   476 
   478 		 * @param string            $po_comment_line
   479 		 * @param string            $po_comment_line
   479 		 */
   480 		 */
   480 		function add_comment_to_entry( &$entry, $po_comment_line ) {
   481 		function add_comment_to_entry( &$entry, $po_comment_line ) {
   481 			$first_two = substr( $po_comment_line, 0, 2 );
   482 			$first_two = substr( $po_comment_line, 0, 2 );
   482 			$comment   = trim( substr( $po_comment_line, 2 ) );
   483 			$comment   = trim( substr( $po_comment_line, 2 ) );
   483 			if ( '#:' == $first_two ) {
   484 			if ( '#:' === $first_two ) {
   484 				$entry->references = array_merge( $entry->references, preg_split( '/\s+/', $comment ) );
   485 				$entry->references = array_merge( $entry->references, preg_split( '/\s+/', $comment ) );
   485 			} elseif ( '#.' == $first_two ) {
   486 			} elseif ( '#.' === $first_two ) {
   486 				$entry->extracted_comments = trim( $entry->extracted_comments . "\n" . $comment );
   487 				$entry->extracted_comments = trim( $entry->extracted_comments . "\n" . $comment );
   487 			} elseif ( '#,' == $first_two ) {
   488 			} elseif ( '#,' === $first_two ) {
   488 				$entry->flags = array_merge( $entry->flags, preg_split( '/,\s*/', $comment ) );
   489 				$entry->flags = array_merge( $entry->flags, preg_split( '/,\s*/', $comment ) );
   489 			} else {
   490 			} else {
   490 				$entry->translator_comments = trim( $entry->translator_comments . "\n" . $comment );
   491 				$entry->translator_comments = trim( $entry->translator_comments . "\n" . $comment );
   491 			}
   492 			}
   492 		}
   493 		}
   493 
   494 
   494 		/**
   495 		/**
   495 		 * @param string $s
   496 		 * @param string $s
   496 		 * @return sring
   497 		 * @return string
   497 		 */
   498 		 */
   498 		public static function trim_quotes( $s ) {
   499 		public static function trim_quotes( $s ) {
   499 			if ( substr( $s, 0, 1 ) == '"' ) {
   500 			if ( '"' === substr( $s, 0, 1 ) ) {
   500 				$s = substr( $s, 1 );
   501 				$s = substr( $s, 1 );
   501 			}
   502 			}
   502 			if ( substr( $s, -1, 1 ) == '"' ) {
   503 			if ( '"' === substr( $s, -1, 1 ) ) {
   503 				$s = substr( $s, 0, -1 );
   504 				$s = substr( $s, 0, -1 );
   504 			}
   505 			}
   505 			return $s;
   506 			return $s;
   506 		}
   507 		}
   507 	}
   508 	}