[ Index ]

PHP Cross Reference of BackPress

title

Body

[close]

/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          return rtrim("{$before_headers}msgid \"\"\nmsgstr $poified");
  42      }
  43  
  44      /**
  45       * Exports all entries to PO format
  46       *
  47       * @return string sequence of mgsgid/msgstr PO strings, doesn't containt newline at the end
  48       */
  49  	function export_entries() {
  50          //TODO sorting
  51          return implode("\n\n", array_map(array('PO', 'export_entry'), $this->entries));
  52      }
  53  
  54      /**
  55       * Exports the whole PO file as a string
  56       *
  57       * @param bool $include_headers whether to include the headers in the export
  58       * @return string ready for inclusion in PO file string for headers and all the enrtries
  59       */
  60  	function export($include_headers = true) {
  61          $res = '';
  62          if ($include_headers) {
  63              $res .= $this->export_headers();
  64              $res .= "\n\n";
  65          }
  66          $res .= $this->export_entries();
  67          return $res;
  68      }
  69  
  70      /**
  71       * Same as {@link export}, but writes the result to a file
  72       *
  73       * @param string $filename where to write the PO string
  74       * @param bool $include_headers whether to include tje headers in the export
  75       * @return bool true on success, false on error
  76       */
  77  	function export_to_file($filename, $include_headers = true) {
  78          $fh = fopen($filename, 'w');
  79          if (false === $fh) return false;
  80          $export = $this->export($include_headers);
  81          $res = fwrite($fh, $export);
  82          if (false === $res) return false;
  83          return fclose($fh);
  84      }
  85  
  86      /**
  87       * Text to include as a comment before the start of the PO contents
  88       *
  89       * Doesn't need to include # in the beginning of lines, these are added automatically
  90       */
  91  	function set_comment_before_headers( $text ) {
  92          $this->comments_before_headers = $text;
  93      }
  94  
  95      /**
  96       * Formats a string in PO-style
  97       *
  98       * @static
  99       * @param string $string the string to format
 100       * @return string the poified string
 101       */
 102  	public static function poify($string) {
 103          $quote = '"';
 104          $slash = '\\';
 105          $newline = "\n";
 106  
 107          $replaces = array(
 108              "$slash"     => "$slash$slash",
 109              "$quote"    => "$slash$quote",
 110              "\t"         => '\t',
 111          );
 112  
 113          $string = str_replace(array_keys($replaces), array_values($replaces), $string);
 114  
 115          $po = $quote.implode("$slash}n$quote$newline$quote", explode($newline, $string)).$quote;
 116          // add empty string on first line for readbility
 117          if (false !== strpos($string, $newline) &&
 118                  (substr_count($string, $newline) > 1 || !($newline === substr($string, -strlen($newline))))) {
 119              $po = "$quote$quote$newline$po";
 120          }
 121          // remove empty strings
 122          $po = str_replace("$newline$quote$quote", '', $po);
 123          return $po;
 124      }
 125  
 126      /**
 127       * Gives back the original string from a PO-formatted string
 128       *
 129       * @static
 130       * @param string $string PO-formatted string
 131       * @return string enascaped string
 132       */
 133  	public static function unpoify($string) {
 134          $escapes = array('t' => "\t", 'n' => "\n", 'r' => "\r", '\\' => '\\');
 135          $lines = array_map('trim', explode("\n", $string));
 136          $lines = array_map(array('PO', 'trim_quotes'), $lines);
 137          $unpoified = '';
 138          $previous_is_backslash = false;
 139          foreach($lines as $line) {
 140              preg_match_all('/./u', $line, $chars);
 141              $chars = $chars[0];
 142              foreach($chars as $char) {
 143                  if (!$previous_is_backslash) {
 144                      if ('\\' == $char)
 145                          $previous_is_backslash = true;
 146                      else
 147                          $unpoified .= $char;
 148                  } else {
 149                      $previous_is_backslash = false;
 150                      $unpoified .= isset($escapes[$char])? $escapes[$char] : $char;
 151                  }
 152              }
 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  
 158          return $unpoified;
 159      }
 160  
 161      /**
 162       * Inserts $with in the beginning of every new line of $string and
 163       * returns the modified string
 164       *
 165       * @static
 166       * @param string $string prepend lines in this string
 167       * @param string $with prepend lines with this string
 168       */
 169  	public static function prepend_each_line($string, $with) {
 170          $php_with = var_export($with, true);
 171          $lines = explode("\n", $string);
 172          // do not prepend the string on the last empty line, artefact by explode
 173          if ("\n" == substr($string, -1)) unset($lines[count($lines) - 1]);
 174          $res = implode("\n", array_map(create_function('$x', "return $php_with.\$x;"), $lines));
 175          // give back the empty line, we ignored above
 176          if ("\n" == substr($string, -1)) $res .= "\n";
 177          return $res;
 178      }
 179  
 180      /**
 181       * Prepare a text as a comment -- wraps the lines and prepends #
 182       * and a special character to each line
 183       *
 184       * @access private
 185       * @param string $text the comment text
 186       * @param string $char character to denote a special PO comment,
 187       *     like :, default is a space
 188       */
 189  	public static function comment_block($text, $char=' ') {
 190          $text = wordwrap($text, PO_MAX_LINE_LEN - 3);
 191          return PO::prepend_each_line($text, "#$char ");
 192      }
 193  
 194      /**
 195       * Builds a string from the entry for inclusion in PO file
 196       *
 197       * @static
 198       * @param Translation_Entry &$entry the entry to convert to po string
 199       * @return false|string PO-style formatted string for the entry or
 200       *     false if the entry is empty
 201       */
 202  	public static function export_entry(&$entry) {
 203          if ( null === $entry->singular || '' === $entry->singular ) return false;
 204          $po = array();
 205          if (!empty($entry->translator_comments)) $po[] = PO::comment_block($entry->translator_comments);
 206          if (!empty($entry->extracted_comments)) $po[] = PO::comment_block($entry->extracted_comments, '.');
 207          if (!empty($entry->references)) $po[] = PO::comment_block(implode(' ', $entry->references), ':');
 208          if (!empty($entry->flags)) $po[] = PO::comment_block(implode(", ", $entry->flags), ',');
 209          if ($entry->context) $po[] = 'msgctxt '.PO::poify($entry->context);
 210          $po[] = 'msgid '.PO::poify($entry->singular);
 211          if (!$entry->is_plural) {
 212              $translation = empty($entry->translations)? '' : $entry->translations[0];
 213              $translation = PO::match_begin_and_end_newlines( $translation, $entry->singular );
 214              $po[] = 'msgstr '.PO::poify($translation);
 215          } else {
 216              $po[] = 'msgid_plural '.PO::poify($entry->plural);
 217              $translations = empty($entry->translations)? array('', '') : $entry->translations;
 218              foreach($translations as $i => $translation) {
 219                  $translation = PO::match_begin_and_end_newlines( $translation, $entry->plural );
 220                  $po[] = "msgstr[$i] ".PO::poify($translation);
 221              }
 222          }
 223          return implode("\n", $po);
 224      }
 225  
 226  	public static function match_begin_and_end_newlines( $translation, $original ) {
 227          if ( '' === $translation ) {
 228              return $translation;
 229          }
 230  
 231          $original_begin = "\n" === substr( $original, 0, 1 );
 232          $original_end = "\n" === substr( $original, -1 );
 233          $translation_begin = "\n" === substr( $translation, 0, 1 );
 234          $translation_end = "\n" === substr( $translation, -1 );
 235  
 236          if ( $original_begin ) {
 237              if ( ! $translation_begin ) {
 238                  $translation = "\n" . $translation;
 239              }
 240          } elseif ( $translation_begin ) {
 241              $translation = ltrim( $translation, "\n" );
 242          }
 243  
 244          if ( $original_end ) {
 245              if ( ! $translation_end ) {
 246                  $translation .= "\n";
 247              }
 248          } elseif ( $translation_end ) {
 249              $translation = rtrim( $translation, "\n" );
 250          }
 251  
 252          return $translation;
 253      }
 254  
 255      /**
 256       * @param string $filename
 257       * @return boolean
 258       */
 259  	function import_from_file($filename) {
 260          $f = fopen($filename, 'r');
 261          if (!$f) return false;
 262          $lineno = 0;
 263          while (true) {
 264              $res = $this->read_entry($f, $lineno);
 265              if (!$res) break;
 266              if ($res['entry']->singular == '') {
 267                  $this->set_headers($this->make_headers($res['entry']->translations[0]));
 268              } else {
 269                  $this->add_entry($res['entry']);
 270              }
 271          }
 272          PO::read_line($f, 'clear');
 273          if ( false === $res ) {
 274              return false;
 275          }
 276          if ( ! $this->headers && ! $this->entries ) {
 277              return false;
 278          }
 279          return true;
 280      }
 281  
 282      /**
 283       * @param resource $f
 284       * @param int      $lineno
 285       * @return null|false|array
 286       */
 287  	function read_entry($f, $lineno = 0) {
 288          $entry = new Translation_Entry();
 289          // where were we in the last step
 290          // can be: comment, msgctxt, msgid, msgid_plural, msgstr, msgstr_plural
 291          $context = '';
 292          $msgstr_index = 0;
 293          $is_final = create_function('$context', 'return $context == "msgstr" || $context == "msgstr_plural";');
 294          while (true) {
 295              $lineno++;
 296              $line = PO::read_line($f);
 297              if (!$line)  {
 298                  if (feof($f)) {
 299                      if ($is_final($context))
 300                          break;
 301                      elseif (!$context) // we haven't read a line and eof came
 302                          return null;
 303                      else
 304                          return false;
 305                  } else {
 306                      return false;
 307                  }
 308              }
 309              if ($line == "\n") continue;
 310              $line = trim($line);
 311              if (preg_match('/^#/', $line, $m)) {
 312                  // the comment is the start of a new entry
 313                  if ($is_final($context)) {
 314                      PO::read_line($f, 'put-back');
 315                      $lineno--;
 316                      break;
 317                  }
 318                  // comments have to be at the beginning
 319                  if ($context && $context != 'comment') {
 320                      return false;
 321                  }
 322                  // add comment
 323                  $this->add_comment_to_entry($entry, $line);
 324              } elseif (preg_match('/^msgctxt\s+(".*")/', $line, $m)) {
 325                  if ($is_final($context)) {
 326                      PO::read_line($f, 'put-back');
 327                      $lineno--;
 328                      break;
 329                  }
 330                  if ($context && $context != 'comment') {
 331                      return false;
 332                  }
 333                  $context = 'msgctxt';
 334                  $entry->context .= PO::unpoify($m[1]);
 335              } elseif (preg_match('/^msgid\s+(".*")/', $line, $m)) {
 336                  if ($is_final($context)) {
 337                      PO::read_line($f, 'put-back');
 338                      $lineno--;
 339                      break;
 340                  }
 341                  if ($context && $context != 'msgctxt' && $context != 'comment') {
 342                      return false;
 343                  }
 344                  $context = 'msgid';
 345                  $entry->singular .= PO::unpoify($m[1]);
 346              } elseif (preg_match('/^msgid_plural\s+(".*")/', $line, $m)) {
 347                  if ($context != 'msgid') {
 348                      return false;
 349                  }
 350                  $context = 'msgid_plural';
 351                  $entry->is_plural = true;
 352                  $entry->plural .= PO::unpoify($m[1]);
 353              } elseif (preg_match('/^msgstr\s+(".*")/', $line, $m)) {
 354                  if ($context != 'msgid') {
 355                      return false;
 356                  }
 357                  $context = 'msgstr';
 358                  $entry->translations = array(PO::unpoify($m[1]));
 359              } elseif (preg_match('/^msgstr\[(\d+)\]\s+(".*")/', $line, $m)) {
 360                  if ($context != 'msgid_plural' && $context != 'msgstr_plural') {
 361                      return false;
 362                  }
 363                  $context = 'msgstr_plural';
 364                  $msgstr_index = $m[1];
 365                  $entry->translations[$m[1]] = PO::unpoify($m[2]);
 366              } elseif (preg_match('/^".*"$/', $line)) {
 367                  $unpoified = PO::unpoify($line);
 368                  switch ($context) {
 369                      case 'msgid':
 370                          $entry->singular .= $unpoified; break;
 371                      case 'msgctxt':
 372                          $entry->context .= $unpoified; break;
 373                      case 'msgid_plural':
 374                          $entry->plural .= $unpoified; break;
 375                      case 'msgstr':
 376                          $entry->translations[0] .= $unpoified; break;
 377                      case 'msgstr_plural':
 378                          $entry->translations[$msgstr_index] .= $unpoified; break;
 379                      default:
 380                          return false;
 381                  }
 382              } else {
 383                  return false;
 384              }
 385          }
 386          if (array() == array_filter($entry->translations, create_function('$t', 'return $t || "0" === $t;'))) {
 387              $entry->translations = array();
 388          }
 389          return array('entry' => $entry, 'lineno' => $lineno);
 390      }
 391  
 392      /**
 393       * @staticvar string   $last_line
 394       * @staticvar boolean  $use_last_line
 395       *
 396       * @param     resource $f
 397       * @param     string   $action
 398       * @return boolean
 399       */
 400  	function read_line($f, $action = 'read') {
 401          static $last_line = '';
 402          static $use_last_line = false;
 403          if ('clear' == $action) {
 404              $last_line = '';
 405              return true;
 406          }
 407          if ('put-back' == $action) {
 408              $use_last_line = true;
 409              return true;
 410          }
 411          $line = $use_last_line? $last_line : fgets($f);
 412          $line = ( "\r\n" == substr( $line, -2 ) ) ? rtrim( $line, "\r\n" ) . "\n" : $line;
 413          $last_line = $line;
 414          $use_last_line = false;
 415          return $line;
 416      }
 417  
 418      /**
 419       * @param Translation_Entry $entry
 420       * @param string            $po_comment_line
 421       */
 422  	function add_comment_to_entry(&$entry, $po_comment_line) {
 423          $first_two = substr($po_comment_line, 0, 2);
 424          $comment = trim(substr($po_comment_line, 2));
 425          if ('#:' == $first_two) {
 426              $entry->references = array_merge($entry->references, preg_split('/\s+/', $comment));
 427          } elseif ('#.' == $first_two) {
 428              $entry->extracted_comments = trim($entry->extracted_comments . "\n" . $comment);
 429          } elseif ('#,' == $first_two) {
 430              $entry->flags = array_merge($entry->flags, preg_split('/,\s*/', $comment));
 431          } else {
 432              $entry->translator_comments = trim($entry->translator_comments . "\n" . $comment);
 433          }
 434      }
 435  
 436      /**
 437       * @param string $s
 438       * @return sring
 439       */
 440  	public static function trim_quotes($s) {
 441          if ( substr($s, 0, 1) == '"') $s = substr($s, 1);
 442          if ( substr($s, -1, 1) == '"') $s = substr($s, 0, -1);
 443          return $s;
 444      }
 445  }
 446  endif;


Generated: Sun Nov 17 01:01:34 2019 Cross-referenced by PHPXref 0.7.1