[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/pomo/ -> po.php (source)

   1  <?php
   2  /**
   3   * Class for working with PO files
   4   *
   5   * @version $Id: po.php 1158 2015-11-20 04:31:23Z dd32 $
   6   * @package pomo
   7   * @subpackage po
   8   */
   9  
  10  require_once  __DIR__ . '/translations.php';
  11  
  12  if ( ! defined( 'PO_MAX_LINE_LEN' ) ) {
  13      define( 'PO_MAX_LINE_LEN', 79 );
  14  }
  15  
  16  ini_set( 'auto_detect_line_endings', 1 );
  17  
  18  /**
  19   * Routines for working with PO files
  20   */
  21  if ( ! class_exists( 'PO', false ) ) :
  22      class PO extends Gettext_Translations {
  23  
  24          var $comments_before_headers = '';
  25  
  26          /**
  27           * Exports headers to a PO entry
  28           *
  29           * @return string msgid/msgstr PO entry for this PO file headers, doesn't contain newline at the end
  30           */
  31  		function export_headers() {
  32              $header_string = '';
  33              foreach ( $this->headers as $header => $value ) {
  34                  $header_string .= "$header: $value\n";
  35              }
  36              $poified = PO::poify( $header_string );
  37              if ( $this->comments_before_headers ) {
  38                  $before_headers = $this->prepend_each_line( rtrim( $this->comments_before_headers ) . "\n", '# ' );
  39              } else {
  40                  $before_headers = '';
  41              }
  42              return rtrim( "{$before_headers}msgid \"\"\nmsgstr $poified" );
  43          }
  44  
  45          /**
  46           * Exports all entries to PO format
  47           *
  48           * @return string sequence of mgsgid/msgstr PO strings, doesn't containt newline at the end
  49           */
  50  		function export_entries() {
  51              // TODO: Sorting.
  52              return implode( "\n\n", array_map( array( 'PO', 'export_entry' ), $this->entries ) );
  53          }
  54  
  55          /**
  56           * Exports the whole PO file as a string
  57           *
  58           * @param bool $include_headers whether to include the headers in the export
  59           * @return string ready for inclusion in PO file string for headers and all the enrtries
  60           */
  61  		function export( $include_headers = true ) {
  62              $res = '';
  63              if ( $include_headers ) {
  64                  $res .= $this->export_headers();
  65                  $res .= "\n\n";
  66              }
  67              $res .= $this->export_entries();
  68              return $res;
  69          }
  70  
  71          /**
  72           * Same as {@link export}, but writes the result to a file
  73           *
  74           * @param string $filename where to write the PO string
  75           * @param bool $include_headers whether to include tje headers in the export
  76           * @return bool true on success, false on error
  77           */
  78  		function export_to_file( $filename, $include_headers = true ) {
  79              $fh = fopen( $filename, 'w' );
  80              if ( false === $fh ) {
  81                  return false;
  82              }
  83              $export = $this->export( $include_headers );
  84              $res    = fwrite( $fh, $export );
  85              if ( false === $res ) {
  86                  return false;
  87              }
  88              return fclose( $fh );
  89          }
  90  
  91          /**
  92           * Text to include as a comment before the start of the PO contents
  93           *
  94           * Doesn't need to include # in the beginning of lines, these are added automatically
  95           */
  96  		function set_comment_before_headers( $text ) {
  97              $this->comments_before_headers = $text;
  98          }
  99  
 100          /**
 101           * Formats a string in PO-style
 102           *
 103           * @param string $string the string to format
 104           * @return string the poified string
 105           */
 106  		public static function poify( $string ) {
 107              $quote   = '"';
 108              $slash   = '\\';
 109              $newline = "\n";
 110  
 111              $replaces = array(
 112                  "$slash" => "$slash$slash",
 113                  "$quote" => "$slash$quote",
 114                  "\t"     => '\t',
 115              );
 116  
 117              $string = str_replace( array_keys( $replaces ), array_values( $replaces ), $string );
 118  
 119              $po = $quote . implode( "$slash}n$quote$newline$quote", explode( $newline, $string ) ) . $quote;
 120              // Add empty string on first line for readbility.
 121              if ( false !== strpos( $string, $newline ) &&
 122                  ( substr_count( $string, $newline ) > 1 || substr( $string, -strlen( $newline ) ) !== $newline ) ) {
 123                  $po = "$quote$quote$newline$po";
 124              }
 125              // Remove empty strings.
 126              $po = str_replace( "$newline$quote$quote", '', $po );
 127              return $po;
 128          }
 129  
 130          /**
 131           * Gives back the original string from a PO-formatted string
 132           *
 133           * @param string $string PO-formatted string
 134           * @return string enascaped string
 135           */
 136  		public static function unpoify( $string ) {
 137              $escapes               = array(
 138                  't'  => "\t",
 139                  'n'  => "\n",
 140                  'r'  => "\r",
 141                  '\\' => '\\',
 142              );
 143              $lines                 = array_map( 'trim', explode( "\n", $string ) );
 144              $lines                 = array_map( array( 'PO', 'trim_quotes' ), $lines );
 145              $unpoified             = '';
 146              $previous_is_backslash = false;
 147              foreach ( $lines as $line ) {
 148                  preg_match_all( '/./u', $line, $chars );
 149                  $chars = $chars[0];
 150                  foreach ( $chars as $char ) {
 151                      if ( ! $previous_is_backslash ) {
 152                          if ( '\\' === $char ) {
 153                              $previous_is_backslash = true;
 154                          } else {
 155                              $unpoified .= $char;
 156                          }
 157                      } else {
 158                          $previous_is_backslash = false;
 159                          $unpoified            .= isset( $escapes[ $char ] ) ? $escapes[ $char ] : $char;
 160                      }
 161                  }
 162              }
 163  
 164              // Standardise the line endings on imported content, technically PO files shouldn't contain \r.
 165              $unpoified = str_replace( array( "\r\n", "\r" ), "\n", $unpoified );
 166  
 167              return $unpoified;
 168          }
 169  
 170          /**
 171           * Inserts $with in the beginning of every new line of $string and
 172           * returns the modified string
 173           *
 174           * @param string $string prepend lines in this string
 175           * @param string $with prepend lines with this string
 176           */
 177  		public static function prepend_each_line( $string, $with ) {
 178              $lines  = explode( "\n", $string );
 179              $append = '';
 180              if ( "\n" === substr( $string, -1 ) && '' === end( $lines ) ) {
 181                  /*
 182                   * Last line might be empty because $string was terminated
 183                   * with a newline, remove it from the $lines array,
 184                   * we'll restore state by re-terminating the string at the end.
 185                   */
 186                  array_pop( $lines );
 187                  $append = "\n";
 188              }
 189              foreach ( $lines as &$line ) {
 190                  $line = $with . $line;
 191              }
 192              unset( $line );
 193              return implode( "\n", $lines ) . $append;
 194          }
 195  
 196          /**
 197           * Prepare a text as a comment -- wraps the lines and prepends #
 198           * and a special character to each line
 199           *
 200           * @access private
 201           * @param string $text the comment text
 202           * @param string $char character to denote a special PO comment,
 203           *  like :, default is a space
 204           */
 205  		public static function comment_block( $text, $char = ' ' ) {
 206              $text = wordwrap( $text, PO_MAX_LINE_LEN - 3 );
 207              return PO::prepend_each_line( $text, "#$char " );
 208          }
 209  
 210          /**
 211           * Builds a string from the entry for inclusion in PO file
 212           *
 213           * @param Translation_Entry $entry the entry to convert to po string (passed by reference).
 214           * @return string|false PO-style formatted string for the entry or
 215           *  false if the entry is empty
 216           */
 217  		public static function export_entry( &$entry ) {
 218              if ( null === $entry->singular || '' === $entry->singular ) {
 219                  return false;
 220              }
 221              $po = array();
 222              if ( ! empty( $entry->translator_comments ) ) {
 223                  $po[] = PO::comment_block( $entry->translator_comments );
 224              }
 225              if ( ! empty( $entry->extracted_comments ) ) {
 226                  $po[] = PO::comment_block( $entry->extracted_comments, '.' );
 227              }
 228              if ( ! empty( $entry->references ) ) {
 229                  $po[] = PO::comment_block( implode( ' ', $entry->references ), ':' );
 230              }
 231              if ( ! empty( $entry->flags ) ) {
 232                  $po[] = PO::comment_block( implode( ', ', $entry->flags ), ',' );
 233              }
 234              if ( $entry->context ) {
 235                  $po[] = 'msgctxt ' . PO::poify( $entry->context );
 236              }
 237              $po[] = 'msgid ' . PO::poify( $entry->singular );
 238              if ( ! $entry->is_plural ) {
 239                  $translation = empty( $entry->translations ) ? '' : $entry->translations[0];
 240                  $translation = PO::match_begin_and_end_newlines( $translation, $entry->singular );
 241                  $po[]        = 'msgstr ' . PO::poify( $translation );
 242              } else {
 243                  $po[]         = 'msgid_plural ' . PO::poify( $entry->plural );
 244                  $translations = empty( $entry->translations ) ? array( '', '' ) : $entry->translations;
 245                  foreach ( $translations as $i => $translation ) {
 246                      $translation = PO::match_begin_and_end_newlines( $translation, $entry->plural );
 247                      $po[]        = "msgstr[$i] " . PO::poify( $translation );
 248                  }
 249              }
 250              return implode( "\n", $po );
 251          }
 252  
 253  		public static function match_begin_and_end_newlines( $translation, $original ) {
 254              if ( '' === $translation ) {
 255                  return $translation;
 256              }
 257  
 258              $original_begin    = "\n" === substr( $original, 0, 1 );
 259              $original_end      = "\n" === substr( $original, -1 );
 260              $translation_begin = "\n" === substr( $translation, 0, 1 );
 261              $translation_end   = "\n" === substr( $translation, -1 );
 262  
 263              if ( $original_begin ) {
 264                  if ( ! $translation_begin ) {
 265                      $translation = "\n" . $translation;
 266                  }
 267              } elseif ( $translation_begin ) {
 268                  $translation = ltrim( $translation, "\n" );
 269              }
 270  
 271              if ( $original_end ) {
 272                  if ( ! $translation_end ) {
 273                      $translation .= "\n";
 274                  }
 275              } elseif ( $translation_end ) {
 276                  $translation = rtrim( $translation, "\n" );
 277              }
 278  
 279              return $translation;
 280          }
 281  
 282          /**
 283           * @param string $filename
 284           * @return boolean
 285           */
 286  		function import_from_file( $filename ) {
 287              $f = fopen( $filename, 'r' );
 288              if ( ! $f ) {
 289                  return false;
 290              }
 291              $lineno = 0;
 292              while ( true ) {
 293                  $res = $this->read_entry( $f, $lineno );
 294                  if ( ! $res ) {
 295                      break;
 296                  }
 297                  if ( '' === $res['entry']->singular ) {
 298                      $this->set_headers( $this->make_headers( $res['entry']->translations[0] ) );
 299                  } else {
 300                      $this->add_entry( $res['entry'] );
 301                  }
 302              }
 303              PO::read_line( $f, 'clear' );
 304              if ( false === $res ) {
 305                  return false;
 306              }
 307              if ( ! $this->headers && ! $this->entries ) {
 308                  return false;
 309              }
 310              return true;
 311          }
 312  
 313          /**
 314           * Helper function for read_entry
 315           *
 316           * @param string $context
 317           * @return bool
 318           */
 319  		protected static function is_final( $context ) {
 320              return ( 'msgstr' === $context ) || ( 'msgstr_plural' === $context );
 321          }
 322  
 323          /**
 324           * @param resource $f
 325           * @param int      $lineno
 326           * @return null|false|array
 327           */
 328  		function read_entry( $f, $lineno = 0 ) {
 329              $entry = new Translation_Entry();
 330              // Where were we in the last step.
 331              // Can be: comment, msgctxt, msgid, msgid_plural, msgstr, msgstr_plural.
 332              $context      = '';
 333              $msgstr_index = 0;
 334              while ( true ) {
 335                  $lineno++;
 336                  $line = PO::read_line( $f );
 337                  if ( ! $line ) {
 338                      if ( feof( $f ) ) {
 339                          if ( self::is_final( $context ) ) {
 340                              break;
 341                          } elseif ( ! $context ) { // We haven't read a line and EOF came.
 342                              return null;
 343                          } else {
 344                              return false;
 345                          }
 346                      } else {
 347                          return false;
 348                      }
 349                  }
 350                  if ( "\n" === $line ) {
 351                      continue;
 352                  }
 353                  $line = trim( $line );
 354                  if ( preg_match( '/^#/', $line, $m ) ) {
 355                      // The comment is the start of a new entry.
 356                      if ( self::is_final( $context ) ) {
 357                          PO::read_line( $f, 'put-back' );
 358                          $lineno--;
 359                          break;
 360                      }
 361                      // Comments have to be at the beginning.
 362                      if ( $context && 'comment' !== $context ) {
 363                          return false;
 364                      }
 365                      // Add comment.
 366                      $this->add_comment_to_entry( $entry, $line );
 367                  } elseif ( preg_match( '/^msgctxt\s+(".*")/', $line, $m ) ) {
 368                      if ( self::is_final( $context ) ) {
 369                          PO::read_line( $f, 'put-back' );
 370                          $lineno--;
 371                          break;
 372                      }
 373                      if ( $context && 'comment' !== $context ) {
 374                          return false;
 375                      }
 376                      $context         = 'msgctxt';
 377                      $entry->context .= PO::unpoify( $m[1] );
 378                  } elseif ( preg_match( '/^msgid\s+(".*")/', $line, $m ) ) {
 379                      if ( self::is_final( $context ) ) {
 380                          PO::read_line( $f, 'put-back' );
 381                          $lineno--;
 382                          break;
 383                      }
 384                      if ( $context && 'msgctxt' !== $context && 'comment' !== $context ) {
 385                          return false;
 386                      }
 387                      $context          = 'msgid';
 388                      $entry->singular .= PO::unpoify( $m[1] );
 389                  } elseif ( preg_match( '/^msgid_plural\s+(".*")/', $line, $m ) ) {
 390                      if ( 'msgid' !== $context ) {
 391                          return false;
 392                      }
 393                      $context          = 'msgid_plural';
 394                      $entry->is_plural = true;
 395                      $entry->plural   .= PO::unpoify( $m[1] );
 396                  } elseif ( preg_match( '/^msgstr\s+(".*")/', $line, $m ) ) {
 397                      if ( 'msgid' !== $context ) {
 398                          return false;
 399                      }
 400                      $context             = 'msgstr';
 401                      $entry->translations = array( PO::unpoify( $m[1] ) );
 402                  } elseif ( preg_match( '/^msgstr\[(\d+)\]\s+(".*")/', $line, $m ) ) {
 403                      if ( 'msgid_plural' !== $context && 'msgstr_plural' !== $context ) {
 404                          return false;
 405                      }
 406                      $context                      = 'msgstr_plural';
 407                      $msgstr_index                 = $m[1];
 408                      $entry->translations[ $m[1] ] = PO::unpoify( $m[2] );
 409                  } elseif ( preg_match( '/^".*"$/', $line ) ) {
 410                      $unpoified = PO::unpoify( $line );
 411                      switch ( $context ) {
 412                          case 'msgid':
 413                              $entry->singular .= $unpoified;
 414                              break;
 415                          case 'msgctxt':
 416                              $entry->context .= $unpoified;
 417                              break;
 418                          case 'msgid_plural':
 419                              $entry->plural .= $unpoified;
 420                              break;
 421                          case 'msgstr':
 422                              $entry->translations[0] .= $unpoified;
 423                              break;
 424                          case 'msgstr_plural':
 425                              $entry->translations[ $msgstr_index ] .= $unpoified;
 426                              break;
 427                          default:
 428                              return false;
 429                      }
 430                  } else {
 431                      return false;
 432                  }
 433              }
 434  
 435              $have_translations = false;
 436              foreach ( $entry->translations as $t ) {
 437                  if ( $t || ( '0' === $t ) ) {
 438                      $have_translations = true;
 439                      break;
 440                  }
 441              }
 442              if ( false === $have_translations ) {
 443                  $entry->translations = array();
 444              }
 445  
 446              return array(
 447                  'entry'  => $entry,
 448                  'lineno' => $lineno,
 449              );
 450          }
 451  
 452          /**
 453           * @staticvar string   $last_line
 454           * @staticvar boolean  $use_last_line
 455           *
 456           * @param     resource $f
 457           * @param     string   $action
 458           * @return boolean
 459           */
 460  		function read_line( $f, $action = 'read' ) {
 461              static $last_line     = '';
 462              static $use_last_line = false;
 463              if ( 'clear' === $action ) {
 464                  $last_line = '';
 465                  return true;
 466              }
 467              if ( 'put-back' === $action ) {
 468                  $use_last_line = true;
 469                  return true;
 470              }
 471              $line          = $use_last_line ? $last_line : fgets( $f );
 472              $line          = ( "\r\n" === substr( $line, -2 ) ) ? rtrim( $line, "\r\n" ) . "\n" : $line;
 473              $last_line     = $line;
 474              $use_last_line = false;
 475              return $line;
 476          }
 477  
 478          /**
 479           * @param Translation_Entry $entry
 480           * @param string            $po_comment_line
 481           */
 482  		function add_comment_to_entry( &$entry, $po_comment_line ) {
 483              $first_two = substr( $po_comment_line, 0, 2 );
 484              $comment   = trim( substr( $po_comment_line, 2 ) );
 485              if ( '#:' === $first_two ) {
 486                  $entry->references = array_merge( $entry->references, preg_split( '/\s+/', $comment ) );
 487              } elseif ( '#.' === $first_two ) {
 488                  $entry->extracted_comments = trim( $entry->extracted_comments . "\n" . $comment );
 489              } elseif ( '#,' === $first_two ) {
 490                  $entry->flags = array_merge( $entry->flags, preg_split( '/,\s*/', $comment ) );
 491              } else {
 492                  $entry->translator_comments = trim( $entry->translator_comments . "\n" . $comment );
 493              }
 494          }
 495  
 496          /**
 497           * @param string $s
 498           * @return string
 499           */
 500  		public static function trim_quotes( $s ) {
 501              if ( '"' === substr( $s, 0, 1 ) ) {
 502                  $s = substr( $s, 1 );
 503              }
 504              if ( '"' === substr( $s, -1, 1 ) ) {
 505                  $s = substr( $s, 0, -1 );
 506              }
 507              return $s;
 508          }
 509      }
 510  endif;


Generated : Fri May 29 08:20:01 2020 Cross-referenced by PHPXref