XML::LibXML findnodes() не возвращает результаты при наличии xmlns

Я использую XML::LibXML::Reader для анализа большого документа и столкнулся с проблемой, из-за которой атрибут xmlns приводит к сбою findnodes(). Я исправил это, добавив регулярное выражение для удаления атрибута xmls, но мне было интересно, есть ли более элегантное решение без регулярных выражений. Если вы удалите строку регулярного выражения ($xml =~ s{xmlns...), вы увидите, что фраза «Loc = $loc» не дает результатов.

Вот код:

use strict;
use warnings;
use feature qw( say );
use XML::LibXML::Reader qw( XML_READER_TYPE_ELEMENT );

my $xml = <<'__EOI__';
<url xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    <loc>http://example.com</loc>
    <lastmod>2018-10-19</lastmod>
</url>
__EOI__


$xml =~ s{xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"}{};

my $reader = XML::LibXML::Reader->new( string => $xml);
while ( $reader->read ) {
    next unless $reader->nodeType == XML_READER_TYPE_ELEMENT;
    next unless $reader->name eq 'url';
    my $xml = $reader->readOuterXml;
    my $doc = XML::LibXML->load_xml(string => $xml);
    say "Doc = $doc";
    my ($loc) = $doc->findnodes('//loc');
    say "Loc = $loc";
}

person dgate    schedule 20.10.2018    source источник


Ответы (2)


Вы просите найти узлы с пространством имен null и с именем loc. В документе таких узлов нет, поэтому findnodes корректно ничего не возвращает.

Вы хотите найти узлы с пространством имен http://www.sitemaps.org/schemas/sitemap/0.9 и именем loc. Для этого вы можете использовать следующее:

my $doc = XML::LibXML->load_xml( string => $xml );

my $xpc = XML::LibXML::XPathContext->new();
$xpc->registerNs( sm => 'http://www.sitemaps.org/schemas/sitemap/0.9' );

my ($loc) = $xpc->findnodes('//sm:loc', $doc);
person ikegami    schedule 20.10.2018
comment
Примечание. Для более старых версий XML::LibXML требуется явная загрузка XML::LibXML::XPathContext. - person ikegami; 20.10.2018
comment
Замечательно, спасибо. Я должен признать, что совершенно не знал о необходимости использования пространств имен с документами, в которых они есть, поэтому я ценю новое окно обработки XML, которое вы открыли для меня. - person dgate; 22.10.2018

Ваш код начинается с использования XML::LibXML::Reader API, а затем использует XML::LibXML->load_xml для создания DOM из части документа. XML::LibXML::Reader API обычно используется только с огромными XML-документами, которые требуют больших объемов памяти при загрузке в виде модели DOM. Если ваш XML-документ невелик, гораздо проще использовать такой подход, как ответ Икегами, который просто использует DOM API для загрузите весь документ, а затем запросите его с помощью XPath.

Однако, если у вас действительно есть огромный XML-документ, вам может быть интересно решить проблему с помощью Reader API:

my $sitemap_uri = 'http://www.sitemaps.org/schemas/sitemap/0.9';
my $xpc = XML::LibXML::XPathContext->new();
$xpc->registerNs(sm => $sitemap_uri);

my $reader = XML::LibXML::Reader->new(location => './sitemap.xml');
while ($reader->read) {
    $reader->nextElement('url', $sitemap_uri) or last;
    my $doc = $reader->copyCurrentNode(1);
    say "Doc = $doc";
    my ($loc) = $xpc->findnodes('//sm:loc', $doc);
    say "Loc = $loc";
}

Вызов $reader->nextElement — это быстрый способ перейти к следующему вхождению определенного элемента. В этом примере я сопоставил как имя элемента, так и его пространство имен.

Вызов $reader->copyCurrentNode(1) — это удобный метод, который возвращает этот узел и все его дочерние узлы в виде фрагмента DOM. Вам нужно будет использовать XML::LibXML::XPathContext для запроса этой DOM с помощью операторов XPath с учетом пространства имен.

Мое руководство по XML::LibXML включает описание работы с пространствами имен XML а также работа с большими документами.

person Grant McLean    schedule 21.10.2018
comment
Я довольно часто пользовался вашим сайтом, изучая обработку Perl XML, и не могу поверить, что раньше не видел страницу работы с пространствами имен XML! Большое спасибо за большую работу, которую вы делаете. - person dgate; 22.10.2018