Как получить доступ к тегу в xml, который заканчивается определенным текстом, используя конец с

У меня есть пример 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.

Может ли кто-нибудь помочь понять проблему и помочь преодолеть, пожалуйста? Заранее большое спасибо.

XML::Xpath застрял с XPath1. fn:ends-with() это XPath2
Gilles Quénot 13.02.2023 19:37

Я не понял, XPath1 и XPath2? Не могли бы вы уточнить, пожалуйста? :) Пожалуйста, предложите способ исправить это.

Perl_Newbie 13.02.2023 19:39

@GillesQuenot На самом деле у меня есть открытый запрос на извлечение для XML::XPath, чтобы добавить ends-with() и другие, но сопровождающий еще не объединил его. (Но код OP не использует этот модуль)

Shawn 13.02.2023 21:21

Хотелось бы иметь XPath2 с HTML::Tree builder::XPath! Может быть, однажды. Грустно, что мы застряли с XPath1. Вот почему я паршу с нодами и pptr.dev

Gilles Quénot 13.02.2023 21:36
Laravel с Turbo JS
Laravel с Turbo JS
Turbo - это библиотека JavaScript для упрощения создания быстрых и высокоинтерактивных веб-приложений. Она работает с помощью техники под названием...
Типы ввода HTML: Лучшие практики и советы
Типы ввода HTML: Лучшие практики и советы
HTML, или HyperText Markup Language , является стандартным языком разметки, используемым для создания веб-страниц. Типы ввода HTML - это различные...
Аутсорсинг разработки PHP для индивидуальных веб-решений
Аутсорсинг разработки PHP для индивидуальных веб-решений
Услуги PHP-разработки могут быть экономически эффективным решением для компаний, которые ищут высококачественные услуги веб-разработки по доступным...
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
Слишком много useState? Давайте useReducer!
Слишком много useState? Давайте useReducer!
Современный фронтенд похож на старую добрую веб-разработку, но с одной загвоздкой: страница в браузере так же сложна, как и бэкенд.
Узнайте, как использовать теги &lt;ul&gt; и &lt;li&gt; для создания неупорядоченных списков в HTML
Узнайте, как использовать теги <ul> и <li> для создания неупорядоченных списков в HTML
HTML предоставляет множество тегов для структурирования и организации содержимого веб-страницы. Одним из наиболее часто используемых тегов для...
2
4
117
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Используйте это выражение 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

¹ https://www.w3.org/TR/xpath-functions-31/#func-ends-with

Ответ принят как подходящий

Этот приятный 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, это было невероятно полезно.

Perl_Newbie 15.02.2023 06:03

Большое спасибо @gillesquenot Документация - это все, что я искал. Большое спасибо !

Perl_Newbie 15.02.2023 06:05

@Perl_Newbie Рад, что это помогло. Дайте мне знать, если есть вопросы.

zdim 15.02.2023 21:11

Другие вопросы по теме