Как получить доступ к тегу в 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
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
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

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