mirror of
https://github.com/fruithost/Panel.git
synced 2026-10-30 16:46:49 +09:00
431 lines
12 KiB
PHP
Executable File
431 lines
12 KiB
PHP
Executable File
<?php
|
||
|
||
namespace Sepia\PoParser;
|
||
|
||
use Sepia\PoParser\Catalog\Catalog;
|
||
use Sepia\PoParser\Catalog\CatalogArray;
|
||
use Sepia\PoParser\Catalog\EntryFactory;
|
||
use Sepia\PoParser\Catalog\Header;
|
||
use Sepia\PoParser\Exception\ParseException;
|
||
use Sepia\PoParser\SourceHandler\FileSystem;
|
||
use Sepia\PoParser\SourceHandler\SourceHandler;
|
||
use Sepia\PoParser\SourceHandler\StringSource;
|
||
|
||
/**
|
||
* Copyright (c) 2012 Raúl Ferràs raul.ferras@gmail.com
|
||
* All rights reserved.
|
||
*
|
||
* Redistribution and use in source and binary forms, with or without
|
||
* modification, are permitted provided that the following conditions
|
||
* are met:
|
||
* 1. Redistributions of source code must retain the above copyright
|
||
* notice, this list of conditions and the following disclaimer.
|
||
* 2. Redistributions in binary form must reproduce the above copyright
|
||
* notice, this list of conditions and the following disclaimer in the
|
||
* documentation and/or other materials provided with the distribution.
|
||
* 3. Neither the name of copyright holders nor the names of its
|
||
* contributors may be used to endorse or promote products derived
|
||
* from this software without specific prior written permission.
|
||
*
|
||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||
* ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS OR CONTRIBUTORS
|
||
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||
* POSSIBILITY OF SUCH DAMAGE.
|
||
*
|
||
* https://github.com/raulferras/PHP-po-parser
|
||
*
|
||
* Class to parse .po file and extract its strings.
|
||
*
|
||
* @version 5.0
|
||
*/
|
||
class Parser
|
||
{
|
||
/** @var SourceHandler */
|
||
protected $sourceHandler;
|
||
|
||
/** @var int */
|
||
protected $lineNumber;
|
||
|
||
/** @var string */
|
||
protected $property;
|
||
|
||
/**
|
||
* Reads and parses a string
|
||
*
|
||
* @param string $string po content
|
||
*
|
||
* @throws \Exception.
|
||
* @return Catalog
|
||
*/
|
||
public static function parseString($string)
|
||
{
|
||
$parser = new Parser(new StringSource($string));
|
||
|
||
return $parser->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;
|
||
}
|
||
}
|