/** * Replace any current contents with header and entries from PO souce string * * @param string $source po formatted string to parse * * @return void * * @throws UnrecognizedInputException */ public function parsePoSource($source) { /** * This is an incredibly ugly regex pattern that breaks a line of a po file into * pieces that can be analyzed and acted upon. * * The matches array in preg_match will break out like this: * [0] full string * [1] mostly useless broad match of initial token, including trailing space * [2] bare token, or full msgstr[n] clause * [3] 'n' of a msgstr[n] line * [4] '"' if a data line * [5] remaining line * [6] a bare or malformed comment */ $pattern = '/(^(#|#.|#;|#,|#\\||msgid|msgid_plural|msgctxt|msgstr|msgstr\\[([0-9]+)\\])\\s|(^"))(.+)|(^#.*)/'; $source_lines = explode("\n", $source); $wsBreak = false; $inHeader = true; $headerEntry = new PoHeader(); $entry = $headerEntry; $unrecognized = array(); $lastKey = ''; $currentPlural = 0; foreach ($source_lines as $line => $s) { $result = preg_match($pattern, $s, $matches); if (!$result) { $lastKey = ''; if ($s == '' || ctype_space($s)) { if ($inHeader) { $this->setHeaderEntry($headerEntry); $entry = null; $inHeader = false; } if (!$wsBreak) { if (!($entry === null)) { $this->addEntry($entry); } $entry = null; $wsBreak = true; } } else { $wsBreak = false; $unrecognized[$line + 1] = $s; } } else { if ($entry === null) { $entry = new PoEntry(); } $wsBreak = false; $currentKey = $matches[2]; // will be used to set last key switch ($matches[2]) { case PoTokens::TRANSLATOR_COMMENTS: case PoTokens::EXTRACTED_COMMENTS: case PoTokens::REFERENCE: case PoTokens::FLAG: case PoTokens::OBSOLETE: case PoTokens::PREVIOUS: $entry->add($matches[2], $matches[5]); break; case PoTokens::CONTEXT: case PoTokens::MESSAGE: case PoTokens::PLURAL: case PoTokens::TRANSLATED: $entry->addQuoted($matches[2], $matches[5]); break; default: if ($matches[4] == PoTokens::CONTINUED_DATA) { $currentKey = $lastKey; // keep the previous key if ($currentKey == PoTokens::TRANSLATED_PLURAL) { $entry->addQuotedAtPosition(PoTokens::TRANSLATED, $currentPlural, '"' . $matches[5]); } else { $entry->addQuoted($currentKey, '"' . $matches[5]); } } elseif (substr($matches[2], 0, 7) == PoTokens::TRANSLATED_PLURAL) { $currentKey = PoTokens::TRANSLATED_PLURAL; $currentPlural = $matches[3]; $entry->addQuotedAtPosition(PoTokens::TRANSLATED, $currentPlural, $matches[5]); } elseif (isset($matches[6][0]) && $matches[6][0] == PoTokens::TRANSLATOR_COMMENTS) { $value = substr($matches[6], 1); $value = empty($value) ? '' : $value; $entry->add(PoTokens::TRANSLATOR_COMMENTS, $value); } else { $unrecognized[$line + 1] = $s; } break; } $lastKey = $currentKey; } } if (!($entry === null)) { $this->addEntry($entry); } // throw at the very end, anything recognized has been processed $this->unrecognizedInput = $unrecognized; if (count($unrecognized)) { throw new UnrecognizedInputException(); } }
/** * Check the supplied entry for sprintf directives and set php-format flag if found * * @param PoEntry $entry entry to check * * @return void */ public function checkPhpFormatFlag(PoEntry $entry) { if (preg_match('#(?<!%)%(?:\\d+\\$)?[+-]?(?:[ 0]|\'.{1})?-?\\d*(?:\\.\\d+)?[bcdeEufFgGosxX]#', $entry->get(PoTokens::MESSAGE) . $entry->get(PoTokens::PLURAL))) { $entry->addFlag('php-format'); } }