parse(); } /** * Reads and parses a file * * @param string $filePath * * @throws \Exception. * @return Catalog */ public static function parseFile($filePath) { $parser = new Parser(new FileSystem($filePath)); return $parser->parse(); } public function __construct(SourceHandler $sourceHandler) { $this->sourceHandler = $sourceHandler; } /** * Reads and parses strings of a .po file. * * @param SourceHandler . Optional * * @throws \Exception, \InvalidArgumentException, ParseException * @return Catalog */ public function parse(Catalog $catalog = null) { $catalog = $catalog === null ? new CatalogArray() : $catalog; $this->lineNumber = 0; $entry = array(); $this->property = null; // current property // Flags $headersFound = false; while (!$this->sourceHandler->ended()) { $line = trim($this->sourceHandler->getNextLine()); if ($this->shouldIgnoreLine($line, $entry)) { $this->lineNumber++; continue; } if ($this->shouldCloseEntry($line, $entry)) { if (!$headersFound && $this->isHeader($entry)) { $headersFound = true; $catalog->addHeaders( $this->parseHeaders($entry['msgstr']) ); } else { $catalog->addEntry(EntryFactory::createFromArray($entry)); } $entry = array(); $this->property = null; if (empty($line)) { $this->lineNumber++; continue; } } $entry = $this->parseLine($line, $entry); $this->lineNumber++; continue; } $this->sourceHandler->close(); // add final entry if (count($entry)) { if ($this->isHeader($entry)) { $catalog->addHeaders( $this->parseHeaders($entry['msgstr']) ); } else { $catalog->addEntry(EntryFactory::createFromArray($entry)); } } return $catalog; } /** * @param string $line * @param array $entry * * @return array * @throws ParseException */ protected function parseLine($line, $entry) { $firstChar = strlen($line) > 0 ? $line[0] : ''; switch ($firstChar) { case '#': $entry = $this->parseComment($line, $entry); break; case 'm': $entry = $this->parseProperty($line, $entry); break; case '"': $entry = $this->parseMultiline($line, $entry); break; } return $entry; } /** * @param string $line * @param array $entry * * @return array * @throws ParseException */ protected function parseProperty($line, array $entry) { list($key, $value) = $this->getProperty($line); if (!isset($entry[$key])) { $entry[$key] = ''; } switch (true) { case $key === 'msgctxt': case $key === 'msgid': case $key === 'msgid_plural': case $key === 'msgstr': $entry[$key] .= $this->unquote($value); $this->property = $key; break; case strpos($key, 'msgstr[') !== false: $entry[$key] .= $this->unquote($value); $this->property = $key; break; default: throw new ParseException(sprintf('Could not parse %s at line %d', $key, $this->lineNumber)); } return $entry; } /** * @param string $line * @param array $entry * * @return array * @throws ParseException */ protected function parseMultiline($line, $entry) { switch (true) { case $this->property === 'msgctxt': case $this->property === 'msgid': case $this->property === 'msgid_plural': case $this->property === 'msgstr': case strpos($this->property, 'msgstr[') !== false: $entry[$this->property] .= $this->unquote($line); break; default: throw new ParseException( sprintf('Error parsing property %s as multiline.', $this->property) ); } return $entry; } /** * @param string $line * @param array $entry * * @return array * @throws ParseException */ protected function parseComment($line, $entry) { $comment = trim(substr($line, 0, 2)); switch ($comment) { case '#,': $line = trim(substr($line, 2)); $entry['flags'] = preg_split('/,\s*/', $line); break; case '#.': $entry['ccomment'] = !isset($entry['ccomment']) ? array() : $entry['ccomment']; $entry['ccomment'][] = trim(substr($line, 2)); break; case '#|': // Previous string case '#~': // Old entry case '#~|': // Previous string old $mode = array( '#|' => 'previous', '#~' => 'obsolete', '#~|' => 'previous-obsolete' ); $line = trim(substr($line, 2)); $property = $mode[$comment]; if ($property === 'previous') { if (!isset($entry[$property])) { $subEntry = array(); } else { $subEntry = $entry[$property]; } $subEntry = $this->parseLine($line, $subEntry); //$subEntry = $this->parseProperty($line, $subEntry); $entry[$property] = $subEntry; } else { $entry = $this->parseLine($line, $entry); $entry['obsolete'] = true; } break; // Reference case '#:': $entry['reference'][] = trim(substr($line, 2)); break; case '#': default: $entry['tcomment'] = !isset($entry['tcomment']) ? array() : $entry['tcomment']; $entry['tcomment'][] = trim(substr($line, 1)); break; } return $entry; } /** * @param string $msgstr * * @return Header */ protected function parseHeaders($msgstr) { $headers = array_filter(explode('\\n', $msgstr)); return new Header($headers); } /** * @param string $line * @param array $entry * * @return bool */ protected function shouldIgnoreLine($line, array $entry) { return empty($line) && count($entry) === 0; } /** * @param string $line * @param array $entry * * @return bool */ protected function shouldCloseEntry($line, array $entry) { $tokens = $this->getProperty($line); $property = $tokens[0]; return ($line === '' || ($property === 'msgid' && isset($entry['msgid']))); } /** * @param string $value * @return string */ protected function unquote($value) { return preg_replace('/^\"|\"$/', '', $value); } /** * Checks if entry is a header by * * @param array $entry * * @return bool */ protected function isHeader(array $entry) { if (empty($entry) || !isset($entry['msgstr'])) { return false; } if (!isset($entry['msgid']) || !empty($entry['msgid'])) { return false; } $standardHeaders = array( 'Project-Id-Version:', 'Report-Msgid-Bugs-To:', 'POT-Creation-Date:', 'PO-Revision-Date:', 'Last-Translator:', 'Language-Team:', 'MIME-Version:', 'Content-Type:', 'Content-Transfer-Encoding:', 'Plural-Forms:', ); $headers = explode('\n', $entry['msgstr']); // Remove text after double colon $headers = array_map( function ($header) { $pattern = '/(.*?:)(.*)/i'; $replace = '${1}'; return preg_replace($pattern, $replace, $header); }, $headers ); if (count(array_intersect($standardHeaders, $headers)) > 0) { return true; } // If it does not contain any of the standard headers // Let's see if it contains any custom header. $customHeaders = array_filter( $headers, function ($header) { return preg_match('/^X\-(.*):/i', $header) === 1; } ); return count($customHeaders) > 0; } /** * @param string $line * * @return array */ protected function getProperty($line) { $tokens = preg_split('/\s+/ ', $line, 2); return $tokens; } }