[ Index ]

PHP Cross Reference of WordPress

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 dirname( __FILE__ ) . '/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 || ! ( $newline === substr( $string, -strlen( $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                  // Last line might be empty because $string was terminated
 182                  // with a newline, remove it from the $lines array,
 183                  // we'll restore state by re-terminating the string at the end
 184                  array_pop( $lines );
 185                  $append = "\n";
 186              }
 187              foreach ( $lines as &$line ) {
 188                  $line = $with . $line;
 189              }
 190              unset( $line );
 191              return implode( "\n", $lines ) . $append;
 192          }
 193  
 194          /**
 195           * Prepare a text as a comment -- wraps the lines and prepends #
 196           * and a special character to each line
 197           *
 198           * @access private
 199           * @param string $text the comment text
 200           * @param string $char character to denote a special PO comment,
 201           *  like :, default is a space
 202           */
 203  		public static function comment_block( $text, $char = ' ' ) {
 204              $text = wordwrap( $text, PO_MAX_LINE_LEN - 3 );
 205              return PO::prepend_each_line( $text, "#$char " );
 206          }
 207  
 208          /**
 209           * Builds a string from the entry for inclusion in PO file
 210           *
 211           * @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
 213           *  false if the entry is empty
 214           */
 215  		public static function export_entry( &$entry ) {
 216              if ( null === $entry->singular || '' === $entry->singular ) {
 217                  return false;
 218              }
 219              $po = array();
 220              if ( ! empty( $entry->translator_comments ) ) {
 221                  $po[] = PO::comment_block( $entry->translator_comments );
 222              }
 223              if ( ! empty( $entry->extracted_comments ) ) {
 224                  $po[] = PO::comment_block( $entry->extracted_comments, '.' );
 225              }
 226              if ( ! empty( $entry->references ) ) {
 227                  $po[] = PO::comment_block( implode( ' ', $entry->references ), ':' );
 228              }
 229              if ( ! empty( $entry->flags ) ) {
 230                  $po[] = PO::comment_block( implode( ', ', $entry->flags ), ',' );
 231              }
 232              if ( $entry->context ) {
 233                  $po[] = 'msgctxt ' . PO::poify( $entry->context );
 234              }
 235              $po[] = 'msgid ' . PO::poify( $entry->singular );
 236              if ( ! $entry->is_plural ) {
 237                  $translation = empty( $entry->translations ) ? '' : $entry->translations[0];
 238                  $translation = PO::match_begin_and_end_newlines( $translation, $entry->singular );
 239                  $po[]        = 'msgstr ' . PO::poify( $translation );
 240              } else {
 241                  $po[]         = 'msgid_plural ' . PO::poify( $entry->plural );
 242                  $translations = empty( $entry->translations ) ? array( '', '' ) : $entry->translations;
 243                  foreach ( $translations as $i => $translation ) {
 244                      $translation = PO::match_begin_and_end_newlines( $translation, $entry->plural );
 245                      $po[]        = "msgstr[$i] " . PO::poify( $translation );
 246                  }
 247              }
 248              return implode( "\n", $po );
 249          }
 250  
 251  		public static function match_begin_and_end_newlines( $translation, $original ) {
 252              if ( '' === $translation ) {
 253                  return $translation;
 254              }
 255  
 256              $original_begin    = "\n" === substr( $original, 0, 1 );
 257              $original_end      = "\n" === substr( $original, -1 );
 258              $translation_begin = "\n" === substr( $translation, 0, 1 );
 259              $translation_end   = "\n" === substr( $translation, -1 );
 260  
 261              if ( $original_begin ) {
 262                  if ( ! $translation_begin ) {
 263                      $translation = "\n" . $translation;
 264                  }
 265              } elseif ( $translation_begin ) {
 266                  $translation = ltrim( $translation, "\n" );
 267              }
 268  
 269              if ( $original_end ) {
 270                  if ( ! $translation_end ) {
 271                      $translation .= "\n";
 272                  }
 273              } elseif ( $translation_end ) {
 274                  $translation = rtrim( $translation, "\n" );
 275              }
 276  
 277              return $translation;
 278          }
 279  
 280          /**
 281           * @param string $filename
 282           * @return boolean
 283           */
 284  		function import_from_file( $filename ) {
 285              $f = fopen( $filename, 'r' );
 286              if ( ! $f ) {
 287                  return false;
 288              }
 289              $lineno = 0;
 290              while ( true ) {
 291                  $res = $this->read_entry( $f, $lineno );
 292                  if ( ! $res ) {
 293                      break;
 294                  }
 295                  if ( $res['entry']->singular == '' ) {
 296                      $this->set_headers( $this->make_headers( $res['entry']->translations[0] ) );
 297                  } else {
 298                      $this->add_entry( $res['entry'] );
 299                  }
 300              }
 301              PO::read_line( $f, 'clear' );
 302              if ( false === $res ) {
 303                  return false;
 304              }
 305              if ( ! $this->headers && ! $this->entries ) {
 306                  return false;
 307              }
 308              return true;
 309          }
 310  
 311          /**
 312           * Helper function for read_entry
 313           *
 314           * @param string $context
 315           * @return bool
 316           */
 317  		protected static function is_final( $context ) {
 318              return ( $context === 'msgstr' ) || ( $context === 'msgstr_plural' );
 319          }
 320  
 321          /**
 322           * @param resource $f
 323           * @param int      $lineno
 324           * @return null|false|array
 325           */
 326  		function read_entry( $f, $lineno = 0 ) {
 327              $entry = new Translation_Entry();
 328              // where were we in the last step
 329              // can be: comment, msgctxt, msgid, msgid_plural, msgstr, msgstr_plural
 330              $context      = '';
 331              $msgstr_index = 0;
 332              while ( true ) {
 333                  $lineno++;
 334                  $line = PO::read_line( $f );
 335                  if ( ! $line ) {
 336                      if ( feof( $f ) ) {
 337                          if ( self::is_final( $context ) ) {
 338                              break;
 339                          } elseif ( ! $context ) { // we haven't read a line and eof came
 340                              return null;
 341                          } else {
 342                              return false;
 343                          }
 344                      } else {
 345                          return false;
 346                      }
 347                  }
 348                  if ( $line == "\n" ) {
 349                      continue;
 350                  }
 351                  $line = trim( $line );
 352                  if ( preg_match( '/^#/', $line, $m ) ) {
 353                      // the comment is the start of a new entry
 354                      if ( self::is_final( $context ) ) {
 355                          PO::read_line( $f, 'put-back' );
 356                          $lineno--;
 357                          break;
 358                      }
 359                      // comments have to be at the beginning
 360                      if ( $context && $context != 'comment' ) {
 361                          return false;
 362                      }
 363                      // add comment
 364                      $this->add_comment_to_entry( $entry, $line );
 365                  } elseif ( preg_match( '/^msgctxt\s+(".*")/', $line, $m ) ) {
 366                      if ( self::is_final( $context ) ) {
 367                          PO::read_line( $f, 'put-back' );
 368                          $lineno--;
 369                          break;
 370                      }
 371                      if ( $context && $context != 'comment' ) {
 372                          return false;
 373                      }
 374                      $context         = 'msgctxt';
 375                      $entry->context .= PO::unpoify( $m[1] );
 376                  } elseif ( preg_match( '/^msgid\s+(".*")/', $line, $m ) ) {
 377                      if ( self::is_final( $context ) ) {
 378                          PO::read_line( $f, 'put-back' );
 379                          $lineno--;
 380                          break;
 381                      }
 382                      if ( $context && $context != 'msgctxt' && $context != 'comment' ) {
 383                          return false;
 384                      }
 385                      $context          = 'msgid';
 386                      $entry->singular .= PO::unpoify( $m[1] );
 387                  } elseif ( preg_match( '/^msgid_plural\s+(".*")/', $line, $m ) ) {
 388                      if ( $context != 'msgid' ) {
 389                          return false;
 390                      }
 391                      $context          = 'msgid_plural';
 392                      $entry->is_plural = true;
 393                      $entry->plural   .= PO::unpoify( $m[1] );
 394                  } elseif ( preg_match( '/^msgstr\s+(".*")/', $line, $m ) ) {
 395                      if ( $context != 'msgid' ) {
 396                          return false;
 397                      }
 398                      $context             = 'msgstr';
 399                      $entry->translations = array( PO::unpoify( $m[1] ) );
 400                  } elseif ( preg_match( '/^msgstr\[(\d+)\]\s+(".*")/', $line, $m ) ) {
 401                      if ( $context != 'msgid_plural' && $context != 'msgstr_plural' ) {
 402                          return false;
 403                      }
 404                      $context                      = 'msgstr_plural';
 405                      $msgstr_index                 = $m[1];
 406                      $entry->translations[ $m[1] ] = PO::unpoify( $m[2] );
 407                  } elseif ( preg_match( '/^".*"$/', $line ) ) {
 408                      $unpoified = PO::unpoify( $line );
 409                      switch ( $context ) {
 410                          case 'msgid':
 411                              $entry->singular .= $unpoified;
 412                              break;
 413                          case 'msgctxt':
 414                              $entry->context .= $unpoified;
 415                              break;
 416                          case 'msgid_plural':
 417                              $entry->plural .= $unpoified;
 418                              break;
 419                          case 'msgstr':
 420                              $entry->translations[0] .= $unpoified;
 421                              break;
 422                          case 'msgstr_plural':
 423                              $entry->translations[ $msgstr_index ] .= $unpoified;
 424                              break;
 425                          default:
 426                              return false;
 427                      }
 428                  } else {
 429                      return false;
 430                  }
 431              }
 432  
 433              $have_translations = false;
 434              foreach ( $entry->translations as $t ) {
 435                  if ( $t || ( '0' === $t ) ) {
 436                      $have_translations = true;
 437                      break;
 438                  }
 439              }
 440              if ( false === $have_translations ) {
 441                  $entry->translations = array();
 442              }
 443  
 444              return array(
 445                  'entry'  => $entry,
 446                  'lineno' => $lineno,
 447              );
 448          }
 449  
 450          /**
 451           * @staticvar string   $last_line
 452           * @staticvar boolean  $use_last_line
 453           *
 454           * @param     resource $f
 455           * @param     string   $action
 456           * @return boolean
 457           */
 458  		function read_line( $f, $action = 'read' ) {
 459              static $last_line     = '';
 460              static $use_last_line = false;
 461              if ( 'clear' == $action ) {
 462                  $last_line = '';
 463                  return true;
 464              }
 465              if ( 'put-back' == $action ) {
 466                  $use_last_line = true;
 467                  return true;
 468              }
 469              $line          = $use_last_line ? $last_line : fgets( $f );
 470              $line          = ( "\r\n" == substr( $line, -2 ) ) ? rtrim( $line, "\r\n" ) . "\n" : $line;
 471              $last_line     = $line;
 472              $use_last_line = false;
 473              return $line;
 474          }
 475  
 476          /**
 477           * @param Translation_Entry $entry
 478           * @param string            $po_comment_line
 479           */
 480  		function add_comment_to_entry( &$entry, $po_comment_line ) {
 481              $first_two = substr( $po_comment_line, 0, 2 );
 482              $comment   = trim( substr( $po_comment_line, 2 ) );
 483              if ( '#:' == $first_two ) {
 484                  $entry->references = array_merge( $entry->references, preg_split( '/\s+/', $comment ) );
 485              } elseif ( '#.' == $first_two ) {
 486                  $entry->extracted_comments = trim( $entry->extracted_comments . "\n" . $comment );
 487              } elseif ( '#,' == $first_two ) {
 488                  $entry->flags = array_merge( $entry->flags, preg_split( '/,\s*/', $comment ) );
 489              } else {
 490                  $entry->translator_comments = trim( $entry->translator_comments . "\n" . $comment );
 491              }
 492          }
 493  
 494          /**
 495           * @param string $s
 496           * @return sring
 497           */
 498  		public static function trim_quotes( $s ) {
 499              if ( substr( $s, 0, 1 ) == '"' ) {
 500                  $s = substr( $s, 1 );
 501              }
 502              if ( substr( $s, -1, 1 ) == '"' ) {
 503                  $s = substr( $s, 0, -1 );
 504              }
 505              return $s;
 506          }
 507      }
 508  endif;


Generated: Tue Sep 17 01:00:03 2019 Cross-referenced by PHPXref 0.7.1