XMLReader - объявление XML разрешено только в начале документа - PullRequest
0 голосов
/ 08 ноября 2018

Я использую встроенный XMLReader в php для чтения данных из внешних каналов XML. Когда я пытаюсь прочитать канал, который начинается с новой строки, я получаю следующую ошибку:

ErrorException: XMLReader::read(): http://example.com/feeds/feed1.xml:2: parser error : XML declaration allowed only at the start of the document

Я думаю, это потому, что подача начинается с новой строки, но я не знаю, как решить проблему? Как я могу заставить его пропустить первую строку, если она содержит новую строку?

Не могу найти никого, кто бы мог решить эту проблему. У них есть обходной путь с использованием SimpleXMLElement, но я не могу загрузить весь документ в память.

Вот мой код:

$reader = new XMLReader;
$reader->open($linkToExternalFeed);

while ($reader->read() && $reader->name != 'item');

while ($reader->name == 'item')
{
    $node = new SimpleXMLElement($reader->readOuterXML());

    $this->doSomeParsing($node);

    unset($node);

    $reader->next($reader->name);
}

$reader->close();

Ответы [ 2 ]

0 голосов
/ 09 ноября 2018

Вы можете написать streamwrapper, который фильтрует поток.После того, как он найдет первый пробел, он удалит фильтр и начнет передавать данные в XMLWriter.

class ResourceWrapper {

    private $_stream;

    private $_filter;

    private $context;

    public static function createContext(
        $stream, callable $filter = NULL, string $protocol = 'myproject-resource'
    ): array {
        self::register($protocol);
        return [
            $protocol.'://context', 
            \stream_context_create(
                [
                    $protocol => [
                        'stream' => $stream,
                        'filter' => $filter
                    ]
                ]
            )
        ];
    }

    private static function register($protocol) {
        if (!\in_array($protocol, \stream_get_wrappers(), TRUE)) {
            \stream_wrapper_register($protocol, __CLASS__);
        }
    }

    public function removeFilter() {
        $this->_filter = NULL;
    }

    public function url_stat(string $path , int $flags): array {
        return [];
    }

    public function stream_open(
        string $path, string $mode, int $options, &$opened_path
    ): bool {
        list($protocol, $id) = \explode('://', $path);
        $context = \stream_context_get_options($this->context);
        if (
            isset($context[$protocol]['stream']) &&
            \is_resource($context[$protocol]['stream'])
        ) {
            $this->_stream = $context[$protocol]['stream'];
            $this->_filter = $context[$protocol]['filter'];
            return TRUE;
        }
        return FALSE;
    }

    public function stream_read(int $count) {
        if (NULL !== $this->_filter) {
            $filter = $this->_filter;
            return $filter(\fread($this->_stream, $count), $this);
        }
        return \fread($this->_stream, $count);
    }

    public function stream_eof(): bool {
        return \feof($this->_stream);
    }
}

Использование:

$xml = <<<'XML'


<?xml version="1.0"?>
<person><name>Alice</name></person>
XML;

// open the example XML string as a file stream
$resource = fopen('data://text/plain;base64,'.base64_encode($xml), 'rb');

$reader = new \XMLReader();
// create context for the stream and the filter
list($uri, $context) = \ResourceWrapper::createContext(
    $resource,
    function($data, \ResourceWrapper $wrapper) {
        // check for content after removing leading white space
        if (ltrim($data) !== '') {
            // found content, remove filter
            $wrapper->removeFilter();
            // return data without leading whitespace
            return ltrim($data);
        }
        return '';
    }
);
libxml_set_streams_context($context);
$reader->open($uri);

while ($foundNode = $reader->read()) {
    var_dump($reader->localName);
}

Выход:

string(6) "person" 
string(4) "name" 
string(5) "#text" 
string(4) "name" 
string(6) "person"
0 голосов
/ 08 ноября 2018

Не идеально, но это просто прочитает источник и ltrim() первую часть содержимого и запишет его во временный файл, после чего вы сможете прочитать файл с именем $tmpFile ...

$tmpFile = tempnam(".", "trx");
$fpIn = fopen($linkToExternalFeed,"r");
$fpOut = fopen($tmpFile, "w");
$buffer = fread($fpIn, 4096);
fwrite($fpOut, ltrim($buffer));
while ( $buffer = fread($fpIn, 4096))    {
    fwrite($fpOut, $buffer);
}
fclose($fpIn);
fclose($fpOut);

Я использую tmpname() для генерации уникального имени файла, вы можете установить для него все, что вам нравится. Также может быть полезно удалить этот файл после его обработки, чтобы сэкономить место и удалить потенциально конфиденциальную информацию.

...