private function PoStreamReader::readLine

private PoStreamReader::readLine()

Reads a line from the PO stream and stores data internally.

Expands $this->_current_item based on new data for the current item. If this line ends the current item, it is saved with setItemFromArray() with data from $this->_current_item.

An internal state machine is maintained in this reader using $this->_context as the reading state. PO items are in between COMMENT states (when items have at least one line or comment in between them) or indicated by MSGSTR or MSGSTR_ARR followed immediately by an MSGID or MSGCTXT (when items closely follow each other).

Return value

FALSE if an error was logged, NULL otherwise. The errors are considered non-blocking, so reading can continue, while the errors are collected for later presentation.

File

core/lib/Drupal/Component/Gettext/PoStreamReader.php, line 245

Class

PoStreamReader
Implements Gettext PO stream reader.

Namespace

Drupal\Component\Gettext

Code

private function readLine() {
  // Read a line and set the stream finished indicator if it was not
  // possible anymore.
  $line = fgets($this->_fd);
  $this->_finished = ($line === FALSE);

  if (!$this->_finished) {

    if ($this->_line_number == 0) {
      // The first line might come with a UTF-8 BOM, which should be removed.
      $line = str_replace("\xEF\xBB\xBF", '', $line);
      // Current plurality for 'msgstr[]'.
      $this->_current_plural_index = 0;
    }

    // Track the line number for error reporting.
    $this->_line_number++;

    // Initialize common values for error logging.
    $log_vars = array(
      '%uri' => $this->getURI(),
      '%line' => $this->_line_number,
    );

    // Trim away the linefeed. \\n might appear at the end of the string if
    // another line continuing the same string follows. We can remove that.
    $line = trim(strtr($line, array("\\\n" => "")));

    if (!strncmp('#', $line, 1)) {
      // Lines starting with '#' are comments.

      if ($this->_context == 'COMMENT') {
        // Already in comment context, add to current comment.
        $this->_current_item['#'][] = substr($line, 1);
      }
      elseif (($this->_context == 'MSGSTR') || ($this->_context == 'MSGSTR_ARR')) {
        // We are currently in string context, save current item.
        $this->setItemFromArray($this->_current_item);

        // Start a new entry for the comment.
        $this->_current_item = array();
        $this->_current_item['#'][] = substr($line, 1);

        $this->_context = 'COMMENT';
        return;
      }
      else {
        // A comment following any other context is a syntax error.
        $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: "msgstr" was expected but not found on line %line.', $log_vars);
        return FALSE;
      }
      return;
    }
    elseif (!strncmp('msgid_plural', $line, 12)) {
      // A plural form for the current source string.

      if ($this->_context != 'MSGID') {
        // A plural form can only be added to an msgid directly.
        $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: "msgid_plural" was expected but not found on line %line.', $log_vars);
        return FALSE;
      }

      // Remove 'msgid_plural' and trim away whitespace.
      $line = trim(substr($line, 12));

      // Only the plural source string is left, parse it.
      $quoted = $this->parseQuoted($line);
      if ($quoted === FALSE) {
        // The plural form must be wrapped in quotes.
        $this->_errors[] = SafeMarkup::format('The translation stream %uri contains a syntax error on line %line.', $log_vars);
        return FALSE;
      }

      // Append the plural source to the current entry.
      if (is_string($this->_current_item['msgid'])) {
        // The first value was stored as string. Now we know the context is
        // plural, it is converted to array.
        $this->_current_item['msgid'] = array($this->_current_item['msgid']);
      }
      $this->_current_item['msgid'][] = $quoted;

      $this->_context = 'MSGID_PLURAL';
      return;
    }
    elseif (!strncmp('msgid', $line, 5)) {
      // Starting a new message.

      if (($this->_context == 'MSGSTR') || ($this->_context == 'MSGSTR_ARR')) {
        // We are currently in string context, save current item.
        $this->setItemFromArray($this->_current_item);

        // Start a new context for the msgid.
        $this->_current_item = array();
      }
      elseif ($this->_context == 'MSGID') {
        // We are currently already in the context, meaning we passed an id with no data.
        $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: "msgid" is unexpected on line %line.', $log_vars);
        return FALSE;
      }

      // Remove 'msgid' and trim away whitespace.
      $line = trim(substr($line, 5));

      // Only the message id string is left, parse it.
      $quoted = $this->parseQuoted($line);
      if ($quoted === FALSE) {
        // The message id must be wrapped in quotes.
        $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: invalid format for "msgid" on line %line.', $log_vars, $log_vars);
        return FALSE;
      }

      $this->_current_item['msgid'] = $quoted;
      $this->_context = 'MSGID';
      return;
    }
    elseif (!strncmp('msgctxt', $line, 7)) {
      // Starting a new context.

      if (($this->_context == 'MSGSTR') || ($this->_context == 'MSGSTR_ARR')) {
        // We are currently in string context, save current item.
        $this->setItemFromArray($this->_current_item);
        $this->_current_item = array();
      }
      elseif (!empty($this->_current_item['msgctxt'])) {
        // A context cannot apply to another context.
        $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: "msgctxt" is unexpected on line %line.', $log_vars);
        return FALSE;
      }

      // Remove 'msgctxt' and trim away whitespaces.
      $line = trim(substr($line, 7));

      // Only the msgctxt string is left, parse it.
      $quoted = $this->parseQuoted($line);
      if ($quoted === FALSE) {
        // The context string must be quoted.
        $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: invalid format for "msgctxt" on line %line.', $log_vars);
        return FALSE;
      }

      $this->_current_item['msgctxt'] = $quoted;

      $this->_context = 'MSGCTXT';
      return;
    }
    elseif (!strncmp('msgstr[', $line, 7)) {
      // A message string for a specific plurality.

      if (($this->_context != 'MSGID') && 
        ($this->_context != 'MSGCTXT') && 
        ($this->_context != 'MSGID_PLURAL') && 
        ($this->_context != 'MSGSTR_ARR')) {
        // Plural message strings must come after msgid, msgxtxt,
        // msgid_plural, or other msgstr[] entries.
        $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: "msgstr[]" is unexpected on line %line.', $log_vars);
        return FALSE;
      }

      // Ensure the plurality is terminated.
      if (strpos($line, ']') === FALSE) {
        $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: invalid format for "msgstr[]" on line %line.', $log_vars);
        return FALSE;
      }

      // Extract the plurality.
      $frombracket = strstr($line, '[');
      $this->_current_plural_index = substr($frombracket, 1, strpos($frombracket, ']') - 1);

      // Skip to the next whitespace and trim away any further whitespace,
      // bringing $line to the message text only.
      $line = trim(strstr($line, " "));

      $quoted = $this->parseQuoted($line);
      if ($quoted === FALSE) {
        // The string must be quoted.
        $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: invalid format for "msgstr[]" on line %line.', $log_vars);
        return FALSE;
      }
      if (!isset($this->_current_item['msgstr']) || !is_array($this->_current_item['msgstr'])) {
        $this->_current_item['msgstr'] = array();
      }

      $this->_current_item['msgstr'][$this->_current_plural_index] = $quoted;

      $this->_context = 'MSGSTR_ARR';
      return;
    }
    elseif (!strncmp("msgstr", $line, 6)) {
      // A string pair for an msgid (with optional context).

      if (($this->_context != 'MSGID') && ($this->_context != 'MSGCTXT')) {
        // Strings are only valid within an id or context scope.
        $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: "msgstr" is unexpected on line %line.', $log_vars);
        return FALSE;
      }

      // Remove 'msgstr' and trim away away whitespaces.
      $line = trim(substr($line, 6));

      // Only the msgstr string is left, parse it.
      $quoted = $this->parseQuoted($line);
      if ($quoted === FALSE) {
        // The string must be quoted.
        $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: invalid format for "msgstr" on line %line.', $log_vars);
        return FALSE;
      }

      $this->_current_item['msgstr'] = $quoted;

      $this->_context = 'MSGSTR';
      return;
    }
    elseif ($line != '') {
      // Anything that is not a token may be a continuation of a previous token.

      $quoted = $this->parseQuoted($line);
      if ($quoted === FALSE) {
        // This string must be quoted.
        $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: string continuation expected on line %line.', $log_vars);
        return FALSE;
      }

      // Append the string to the current item.
      if (($this->_context == 'MSGID') || ($this->_context == 'MSGID_PLURAL')) {
        if (is_array($this->_current_item['msgid'])) {
          // Add string to last array element for plural sources.
          $last_index = count($this->_current_item['msgid']) - 1;
          $this->_current_item['msgid'][$last_index] .= $quoted;
        }
        else {
          // Singular source, just append the string.
          $this->_current_item['msgid'] .= $quoted;
        }
      }
      elseif ($this->_context == 'MSGCTXT') {
        // Multiline context name.
        $this->_current_item['msgctxt'] .= $quoted;
      }
      elseif ($this->_context == 'MSGSTR') {
        // Multiline translation string.
        $this->_current_item['msgstr'] .= $quoted;
      }
      elseif ($this->_context == 'MSGSTR_ARR') {
        // Multiline plural translation string.
        $this->_current_item['msgstr'][$this->_current_plural_index] .= $quoted;
      }
      else {
        // No valid context to append to.
        $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: unexpected string on line %line.', $log_vars);
        return FALSE;
      }
      return;
    }
  }

  // Empty line read or EOF of PO stream, close out the last entry.
  if (($this->_context == 'MSGSTR') || ($this->_context == 'MSGSTR_ARR')) {
    $this->setItemFromArray($this->_current_item);
    $this->_current_item = array();
  }
  elseif ($this->_context != 'COMMENT') {
    $this->_errors[] = SafeMarkup::format('The translation stream %uri ended unexpectedly at line %line.', $log_vars);
    return FALSE;
  }
}

© 2001–2016 by the original authors
Licensed under the GNU General Public License, version 2 and later.
Drupal is a registered trademark of Dries Buytaert.
https://api.drupal.org/api/drupal/core!lib!Drupal!Component!Gettext!PoStreamReader.php/function/PoStreamReader::readLine/8.1.x