laurentngu
laurentngu

Reputation: 367

How to select siblings (xpath syntax) with Perl's XML::Twig?

I need to select the next node via next_sibling or first_elt. But I want to filter by node name (containing the string "TON")

first_elt ('HILTON[@method]' or 'SHERATON[@method]');

or

next_sibling ('HILTON[@method]' or 'SHERATON[@method]');

or

next_sibling ('TON[@method]');

Example I tried (not working):

#!/usr/bin/perl -w
use warnings;
use XML::Twig;
$t-> parsefile ('file.xml');

my $Y0=$t->first_elt('HILTON[@method]' or 'SHERATON[@method]');

it will just process for 'HILTON[@method]'

my $Y0=$t->first_elt('/*TON[@method]');

wrong navigation condition '/*TON[@method]' () at C:/strawberry/perl/site/lib/XML/Twig.pm line 3523

Upvotes: 2

Views: 995

Answers (2)

mirod
mirod

Reputation: 16136

As this is outside of the XPath subset supported by XML::Twig, you have to use a custom filter, by passing code to first_elt:

$t->first_elt( sub {  $_[0]->tag=~ m{TON$} && $_[0]->att( 'method') })

This returns the first element for which the sub returns a true value.

The need for such an expression is a bit troubling though. In your example you define a class of elements by the fact that their name ends in TON. What happens when you have a CARLTON element? Or when MARRIOTT elements need to be processed with SHERATON and HILTON? Do you need to rewrite your queries?

If you are the one designing the format of the data, I would suggest revising the format. HILTON and SHERATON should probably be attributes of a HOTEL, BRAND or OWNER tag. Maybe an additional attribute would be useful, to mark that both types should be processed similarly. This attribute would only make sense if it is a property intrinsic to the data.

If the data is what it is and you have no input on its format, then I would have a list of the tags to process and check on these:

my %TAGS_TO_PROCESS= map { $_ => 1 } qw( HILTON SHERATON);
my $elt= $t->first_elt( sub {  $TAGS_TO_PROCESS{$_[0]->tag} && $_[0]->att( 'method') })

This way adding/substracting other tags is easy.

Upvotes: 3

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243459

Use:

*[substring(name(), string-length(name()) - 2) = 'TON'][@method][1]

Explanation:

This expression uses an XPath 1.0 equevalent for the XPath 2.0 standard function ends-with():

The XPath 1.0 equivalent of the XPath 2.0 expression:

ends-with($s, $s2)

is:

substring($s, string-lenth() - string-length($s2) + 1) = $s2

In this last expression we substitute $s with name() and $s2 with 'TON'

XSLT - based verification:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:template match="/*">
     <xsl:copy-of select=
     "*[substring(name(), string-length(name()) - 2) = 'TON'][@method] "/>
==========  
     <xsl:copy-of select=
     "*[substring(name(), string-length(name()) - 2) = 'TON'][@method][1] "/>
 </xsl:template>
</xsl:stylesheet>

when applied on this XML document:

<t>
 <HILTON method="buy"/>
 <TON method="burn"/>
 <TONIC method="drink"/>
 <HILTON nomethod="yes"/>
 <SHERATON/>
 <SHERATON method="visit"/>
</t>

the transformation evaluates the two XPath expressions and the selected nodes are copied to the output:

<HILTON method="buy"/>
<TON method="burn"/>
<SHERATON method="visit"/>
==========  
     <HILTON method="buy"/>

The first expression selects all elements - children of the context node, whose name ends with "TON" and that also have a method attribute.

The second expression selects the first node from those, selected by the first expression.

Upvotes: 2

Related Questions