function PoStreamReader::readLine

Same name in other branches
  1. 9 core/lib/Drupal/Component/Gettext/PoStreamReader.php \Drupal\Component\Gettext\PoStreamReader::readLine()
  2. 8.9.x core/lib/Drupal/Component/Gettext/PoStreamReader.php \Drupal\Component\Gettext\PoStreamReader::readLine()
  3. 11.x core/lib/Drupal/Component/Gettext/PoStreamReader.php \Drupal\Component\Gettext\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

bool|null 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.

1 call to PoStreamReader::readLine()
PoStreamReader::readItem in core/lib/Drupal/Component/Gettext/PoStreamReader.php
Reads and returns a PoItem (source/translation pair).

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->lineNumber == 0) {
            // The first line might come with a UTF-8 BOM, which should be removed.
            $line = str_replace("", '', $line);
            // Current plurality for 'msgstr[]'.
            $this->currentPluralIndex = 0;
        }
        // Track the line number for error reporting.
        $this->lineNumber++;
        // Initialize common values for error logging.
        $log_vars = [
            '%uri' => $this->getURI(),
            '%line' => $this->lineNumber,
        ];
        // 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, [
            "\\\n" => "",
        ]));
        if (!strncmp('#', $line, 1)) {
            // Lines starting with '#' are comments.
            if ($this->context == 'COMMENT') {
                // Already in comment context, add to current comment.
                $this->currentItem['#'][] = substr($line, 1);
            }
            elseif ($this->context == 'MSGSTR' || $this->context == 'MSGSTR_ARR') {
                // We are currently in string context, save current item.
                $this->setItemFromArray($this->currentItem);
                // Start a new entry for the comment.
                $this->currentItem = [];
                $this->currentItem['#'][] = substr($line, 1);
                $this->context = 'COMMENT';
                return;
            }
            else {
                // A comment following any other context is a syntax error.
                $this->errors[] = new FormattableMarkup('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[] = new FormattableMarkup('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[] = new FormattableMarkup('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->currentItem['msgid'])) {
                // The first value was stored as string. Now we know the context is
                // plural, it is converted to array.
                $this->currentItem['msgid'] = [
                    $this->currentItem['msgid'],
                ];
            }
            $this->currentItem['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->currentItem);
                // Start a new context for the msgid.
                $this->currentItem = [];
            }
            elseif ($this->context == 'MSGID') {
                // We are currently already in the context, meaning we passed an id with no data.
                $this->errors[] = new FormattableMarkup('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[] = new FormattableMarkup('The translation stream %uri contains an error: invalid format for "msgid" on line %line.', $log_vars);
                return FALSE;
            }
            $this->currentItem['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->currentItem);
                $this->currentItem = [];
            }
            elseif (!empty($this->currentItem['msgctxt'])) {
                // A context cannot apply to another context.
                $this->errors[] = new FormattableMarkup('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[] = new FormattableMarkup('The translation stream %uri contains an error: invalid format for "msgctxt" on line %line.', $log_vars);
                return FALSE;
            }
            $this->currentItem['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, msgctxt,
                // msgid_plural, or other msgstr[] entries.
                $this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: "msgstr[]" is unexpected on line %line.', $log_vars);
                return FALSE;
            }
            // Ensure the plurality is terminated.
            if (!str_contains($line, ']')) {
                $this->errors[] = new FormattableMarkup('The translation stream %uri contains an error: invalid format for "msgstr[]" on line %line.', $log_vars);
                return FALSE;
            }
            // Extract the plurality.
            $from_bracket = strstr($line, '[');
            $this->currentPluralIndex = substr($from_bracket, 1, strpos($from_bracket, ']') - 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[] = new FormattableMarkup('The translation stream %uri contains an error: invalid format for "msgstr[]" on line %line.', $log_vars);
                return FALSE;
            }
            if (!isset($this->currentItem['msgstr']) || !is_array($this->currentItem['msgstr'])) {
                $this->currentItem['msgstr'] = [];
            }
            $this->currentItem['msgstr'][$this->currentPluralIndex] = $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[] = new FormattableMarkup('The translation stream %uri contains an error: "msgstr" is unexpected on line %line.', $log_vars);
                return FALSE;
            }
            // Remove 'msgstr' and trim 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[] = new FormattableMarkup('The translation stream %uri contains an error: invalid format for "msgstr" on line %line.', $log_vars);
                return FALSE;
            }
            $this->currentItem['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[] = new FormattableMarkup('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->currentItem['msgid'])) {
                    // Add string to last array element for plural sources.
                    $last_index = count($this->currentItem['msgid']) - 1;
                    $this->currentItem['msgid'][$last_index] .= $quoted;
                }
                else {
                    // Singular source, just append the string.
                    $this->currentItem['msgid'] .= $quoted;
                }
            }
            elseif ($this->context == 'MSGCTXT') {
                // Multiline context name.
                $this->currentItem['msgctxt'] .= $quoted;
            }
            elseif ($this->context == 'MSGSTR') {
                // Multiline translation string.
                $this->currentItem['msgstr'] .= $quoted;
            }
            elseif ($this->context == 'MSGSTR_ARR') {
                // Multiline plural translation string.
                $this->currentItem['msgstr'][$this->currentPluralIndex] .= $quoted;
            }
            else {
                // No valid context to append to.
                $this->errors[] = new FormattableMarkup('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->currentItem);
        $this->currentItem = [];
    }
    elseif ($this->context != 'COMMENT') {
        $this->errors[] = new FormattableMarkup('The translation stream %uri ended unexpectedly at line %line.', $log_vars);
        return FALSE;
    }
}

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.