2016-05-18 6 views
3

Имея этот код:Выберите 1-ый элемент только - с условием использования XML :: Twig

#!/usr/bin/env perl 
use 5.014; 
use warnings; 
use XML::Twig; 

my $twig = XML::Twig->parse(\*DATA); 
$twig->set_pretty_print('indented_a'); 

# 1st search 
# this prints OK the all <files> nodes where the <type> == 'release' 
$_->print for ($twig->findnodes('//type[string()="release"]/..')); 

# 2nd search  
# try to get first matched only 
my $latest = $twig->findnodes('(//type[string()="release"])[1]/..'); 
$latest->print; 

__DATA__ 
<root> 
    <files> 
     <type>beta</type> 
     <ver>3.0</ver> 
    </files> 
    <files> 
     <type>alpha</type> 
     <ver>3.0</ver> 
    </files> 
    <files> 
     <type>release</type> 
     <ver>2.0</ver> 
    </files> 
    <files> 
     <type>release</type> 
     <ver>1.0</ver> 
    </files> 
</root> 

Вышеприведенные принты

<files> 
    <type>release</type> 
    <ver>2.0</ver> 
    </files> 
    <files> 
    <type>release</type> 
    <ver>1.0</ver> 
    </files> 
error in xpath expression (//type[string()="release"])[1]/.. around (//type[string()="release"])[1]/.. at /opt/anyenv/envs/plenv/versions/5.24.0/lib/perl5/site_perl/5.24.0/XML/Twig.pm line 3648. 

разыскиваемого выход из 2-й категории

<files> 
     <type>release</type> 
     <ver>2.0</ver> 
    </files> 

например первый узел <files>, где <type> eq 'release'.

В соответствии с this answer используемое выражение XPath (//type[string()="release"])[1]/..' должно работать, но, похоже, я снова пропустил что-то важное.

Не могли бы помочь, пожалуйста?

ответ

4

XML::Twig не поддерживает полный синтаксис XPath. Документация для get_xpath метода (так же, как findnodes) говорит, что это

Подмножество сокращенного синтаксиса XPATH покрыта:

tag 
tag[1] (or any other positive number) 
tag[last()] 
tag[@att] (the attribute exists for the element) 
tag[@att="val"] 
tag[@att=~ /regexp/] 
tag[att1="val1" and att2="val2"] 
tag[att1="val1" or att2="val2"] 
tag[string()="toto"] (returns tag elements which text (as per the text method) 
        is toto) 
tag[string()=~/regexp/] (returns tag elements which text (as per the text 
         method) matches regexp) 
expressions can start with/(search starts at the document root) 
expressions can start with . (search starts at the current element) 
// can be used to get all descendants instead of just direct children 
* matches any tag 

Так подвыражение в скобках, не поддерживается, и вы может указывать только один предикат

Также важно, чтобы в скалярном контексте findnodes возвращал только количество найденных узлов. Вы должны использовать его в контексте списка, чтобы получить Узлы самих, что означает, что более простой способ найти только первый соответствующий элемент должен написать

my ($latest) = $twig->findnodes('//type[string()="release"]/..'); 

, который прекрасно работает

Если вам действительно нужна полная мощность XPath, тогда вы можете использовать XML::Twig::XPath. Этот модуль использует либо XML::XPath, либо отличный XML::XPathEngine, чтобы обеспечить полный синтаксис XPath путем перегрузки findnodes. (Другие методы get_xpath и find_nodes продолжать использовать пониженную XML::Twig вариации.)

findnodes в скалярном контексте теперь возвращает XML::XPathEngine::NodeSet объект, который имеет индексацию массива перегружен.Таким образом, вы можете написать

my $latest = $twig->findnodes('//type[string()="release"]/..'); 
$latest->[0]->print; 

или просто

my ($latest) = $twig->findnodes('//type[string()="release"]/..'); 

, как указано выше.

Наконец, я предпочел бы видеть /root/files[type[string()="release"]] в предпочтении к заднему parent::node(), но это сугубо личное

+0

ДА! Использование 'XML :: Twig :: XPath' и' my ($ latest) = $ twig-> findnodes ('/ root/files [type [string() = "release"]]'); 'решает мои потребности , Спасибо! ;) – cajwine

+0

@cajwine: Надеюсь, я дал понять, что если вы используете только один предикат, например 'my ($ latest) = $ twig-> findnodes ('/ root/files/type [string() =" release " ]/.. ') ', тогда стандартный' XML :: Twig' будет работать нормально – Borodin

+0

Да, просто попробовал оба. Для использования ''/root/files [type [string() = "release"]] ''(из вашего последнего утверждения) мне нужен XPath. А для '/ root/files/type [string() =" release "]/..' достаточно простого 'XML :: Twig'. Замечательный ответ! ;) – cajwine

2

XML :: Twig не поддерживает весь XPath. Выражение работает правильно в XML::LibXML.

Вы можете ходить структуру самостоятельно в Perl:

my $latest = ($twig->findnodes('//type[string()="release"]'))[0]->parent; 
+0

жемчужно-ходьба - да - но он говорит, 'Не удается вызвать метод«родителя»на неопределенный value' если здесь не тип 'release' (например, просто' beta') - так что нужно проверить возвращаемое значение. Поэтому я попытался использовать (расширенный) Xpath. Спасибо. :) – cajwine

3

XML :: Twig не поддерживает все XPath, но XML :: Twig :: XPath делает.

Так use XML::Twig::XPath;, то my $twig = XML::Twig::XPath->parse(... и вуаля ... теперь вы можете получить до фиксации $latest=... линии, которая должна быть:

my $latest = ($twig->findnodes('(//type[string()="release"])[1]/..'))[0]; 

(как вы его $ последняя является XML::XPathEngine::NodeSet, что вам нужно возьмем первый элемент этого множества).

+0

Это довольно не по теме, но было бы неплохо, если бы у 'XML :: Twig :: XPath' был способ указать, какой вспомогательный модуль использовать, если они оба установлены, так же, как' Text :: CSV' делает. Или, по крайней мере, способ узнать, какой из них был выбран. Возможно, изначально просто нужно изменить 'my $ XPATH' на' наш $ XPATH'? – Borodin

+1

Без вопросов, просто «спасибо» за хороший пакет «XML :: Twig»! :) – cajwine

+0

@borodin XML :: XPathEngine используется, если присутствует. XML :: XPath - это только вариант, потому что исторически это был первый, который использовался, прежде чем я разветвил часть XPath для создания XML :: XPathEngine. – mirod