у меня есть пример xml
<?xml version = "1.0" ?>
<Details date = "2022-02-09" ver = "1">
<VerNum>/14</VerNum>
<Info>
<model>S22</model>
<branch name = "city_1">
<prevstock>10000</prevstock>
<def>1</def>
</branch>
<branch name = "city_2">
<presstock>2000</presstock>
<def>2</def>
</branch>
<branch name = "city_3">
<futstock>3000</futstock>
<def>0.3</def>
</branch>
</Info>
</Details>
Мне нужно получить доступ к запасу, имя тега не всегда согласовано, и я также не могу зависеть от положения узла, я не понимаю правильное использование функции XPath/ends-with.
use warnings;
use strict;
use feature 'say';
use Data::Dumper;
use XML::LibXML;
my $file = "ex.xml";#// die "Usage: $0 filename\n";
my $parser = XML::LibXML->load_xml(location => $file);
my %branch_stock;
foreach my $sec ($parser->findnodes('/Details/Info')) {
for my $branch ($sec->findnodes('./branch')) {
my $branch_name = $branch->getAttribute('name');
my $stock_value = $branch->findnodes('*[ends-with(name(),"stock")]')->[0]->textContent;
#say "$branch_name --> $stock_value";
$branch_stock{$branch_name} = $stock_value;
}
}
say Dumper \%branch_stock;
Это дает мне ошибку,
error : xmlXPathCompOpEval: function ends-with not found
XPath error : Unregistered function
XPath error : Stack usage errror
error : xmlXPathCompiledEval: 2 objects left on the stack.
Может ли кто-нибудь помочь понять проблему и помочь преодолеть, пожалуйста? Заранее большое спасибо.
Я не понял, XPath1 и XPath2? Не могли бы вы уточнить, пожалуйста? :) Пожалуйста, предложите способ исправить это.
@GillesQuenot На самом деле у меня есть открытый запрос на извлечение для XML::XPath, чтобы добавить ends-with()
и другие, но сопровождающий еще не объединил его. (Но код OP не использует этот модуль)
Хотелось бы иметь XPath2 с HTML::Tree builder::XPath! Может быть, однажды. Грустно, что мы застряли с XPath1. Вот почему я паршу с нодами и pptr.dev
Используйте это выражение XPath1
, которое имитирует функцию fn:ends-with()¹:
//branch/node()[substring(local-name(),
string-length(local-name()) - string-length("stock") + 1) = "stock"]
Так:
my $stock_value = $branch
->findnodes('node()[substring(local-name(),
string-length(
local-name()) - string-length("stock") + 1) = "stock"]'
)->[0]->textContent;
Из википедии (к сожалению...)
Существует несколько версий XPath. XPath 1.0 был опубликован в 1999 г., XPath 2.0 — в 2007 г. (второе издание — в 2010 г.), XPath 3.0 — в 2014 г., а XPath 3.1 — в 2017 г. Однако XPath 1.0 по-прежнему остается наиболее широко доступной версией.
Связанный: https://stackoverflow.com/a/40935676/465183
Этот приятный ends-with()
, наряду со многими другими функциями, есть в XPath2 (и позже ). В XML::LibXML мы ограничены XPath 1.0, так как это лежит в основе libxml2
.
Одной из работающих функций для запроса по частичному тексту является contains
use warnings;
use strict;
use feature 'say';
use Data::Dumper;
use XML::LibXML;
my $file = shift // die "Usage: $0 file\n";
my $parser = XML::LibXML->load_xml(location => $file);
my %branch_stock;
for my $sec ($parser->findnodes('/Details/Info')) {
for my $branch ($sec->findnodes('./branch')) {
my $branch_name = $branch->getAttribute('name');
for my $stock ($branch->findnodes('*[contains(name(),"stock")]')) {
say "$branch_name --> $stock";
$branch_stock{$branch_name} = $stock->textContent;
# No "ends-with" in XPath1, what we have here
# $branch->findnodes('*[ends-with(name(),"stock")]')
}
}
}
print Dumper \%branch_stock;
Это печатает
city_1 --> <prevstock>10000</prevstock>
city_2 --> <presstock>2000</presstock>
city_3 --> <futstock>3000</futstock>
$VAR1 = {
'city_3' => '3000',
'city_1' => '10000',
'city_2' => '2000'
};
Несколько слов об исходниках и документации, которые, как мне кажется, не так уж и просты для XPath.
Обзор в perl-libxml-by-example . Хотя в XPath 1.0 отсутствуют мощные функции более поздних версий, в нем есть несколько функций . Для этого также можно создавать собственные функции, используя Perl API . Библиотека XML::LibXML
использует XML::LibXML::XPathContext.
Если действительно есть один stock
под каждым branch
, что здесь явно ожидается, нам не нужен цикл, но мы можем выбрать «первый» (единственный) элемент
for my $sec ($parser->findnodes('/Details/Info')) {
for my $branch ($sec->findnodes('./branch')) {
my $branch_name = $branch->getAttribute('name');
my $stock = $branch->findnodes('*[contains(name(),"stock")]')->[0];
say "$branch_name --> $stock";
$branch_stock{$branch_name} = $stock->textContent;
}
}
И для этого есть ярлык, findvalue
for my $sec ($parser->findnodes('/Details/Info')) {
for my $branch ($sec->findnodes('./branch')) {
my $branch_name = $branch->getAttribute('name');
my $stock_value = $branch->findvalue('*[contains(name(),"stock")]');
$branch_stock{$branch_name} = $stock_value;
}
}
Спасибо @zdim, это было невероятно полезно.
Большое спасибо @gillesquenot Документация - это все, что я искал. Большое спасибо !
@Perl_Newbie Рад, что это помогло. Дайте мне знать, если есть вопросы.
XML::Xpath
застрял с XPath1.fn:ends-with()
это XPath2