1

Using Perl XML::Twig, how can I loop on each sibling until reaching the last node ?

while (condition_sibling_TWIG, $v)
{
$v=$v->next_sibling;
$v->print;

# process $v 
}

Should the condition be ($v != undef) ?

thanks

laurentngu
  • 357
  • 2
  • 13

2 Answers2

3

You can use next_siblings to get the list of siblings:

foreach my $sibling ($elt->next_siblings)
  { # process sibling
  }

next_siblings accepts an optional condition as argument, which is an XPath step, or at least the subset of XPath supported by XML::Twig: $elt->next_siblings('p[@type="secret"]'))

mirod
  • 15,923
  • 3
  • 45
  • 65
  • thank you it works perfectly. But how to do, if I want TWIG to continue in the next Element ( SOURCE id="USA" ) to look for next_siblings: `
    `
    – laurentngu Jun 18 '12 at 11:45
  • -> next_siblings makes me jump from DEST to DEST. But I can not access the second Element ("USA") – laurentngu Jun 18 '12 at 12:33
  • it looks like you need to navigate a 2-level structure, SOURCE and nested DEST. You probably need to use nested loops, the outer one on SOURCE elements, the inner one on DEST children. Or have a handler on SOURCE and a loop on DEST children (using the `children` method) within. I may be wrong, but it seems to me that the mental model you are using is not the right one: you need to navigate the XML as a tree, not as a linear flow of data. Use `children` and (maybe) `parent` instead of `next-sibling` – mirod Jun 18 '12 at 15:22
  • I will revise the algorithm and give you update upon my implementation – laurentngu Jun 18 '12 at 15:58
  • I am using both next_elt (move from SOURCE) and first_child (to step down one level) `my $city="PARIS"; $v= $v->next_elt(SOURCE[@id=\$city]'); $v=$v->first_child; foreach my $v ($v ->next_siblings) { print $v->start_tag; # process $v }` But I face a problem to use the variable $city. `next_elt(SOURCE[@id=\$city]')` does not understand $city. Any help ? – laurentngu Jun 19 '12 at 11:30
  • `v= $v->next_elt("city[\@id\=\"$tot\"]");` was the solution – laurentngu Jun 19 '12 at 11:45
  • 1
    As it is you don't process the first child, is that OK? If you need to process all of them you can replace the `first_child`/`siblungs` combo by just `children`. I would also use 2 different variables, with 2 names: one for sources and one for dests. Finally, you can also use perl flexible quoting system to avoid some backslashes: `$v->next_elt( qq{city[\@id="$tot"]})` – mirod Jun 19 '12 at 13:58
  • thank you. Yes I need to use `children`, else I have to insert two times my "process code" (`first_child`, then `next_siblings`). Amy chance you could copy/paste some sample code using `children` (I need the tags values, not the hashes) ? – laurentngu Jun 20 '12 at 07:09
  • You should not access directly the hashes, an element is an object, use methods to access it: `$elt->tag` for the tag name, `$elt->att( 'toto')` for the attribute 'toto'. Read the docs and the tutorial. – mirod Jun 20 '12 at 08:13
2

Update:

The sibling method returns the next sibling or undef if no siblings are left. You can use it to fetch the next one until there are none left.

sibling ($offset, $optional_condition)

Return the next or previous $offset-th sibling of the element, or the $offset-th one matching $optional_condition. If $offset is

negative then a previous sibling is returned, if $offset is positive then a next sibling is returned. $offset=0 returns the element if there is no condition or if the element matches the condition>, undef otherwise.

Here's an example:

use strict; use warnings; 
use XML::Twig;
my $t= XML::Twig->new();
$t->parse(<<__XML__
<root>
    <stuff>
        <entry1></entry1>
        <entry2></entry2>
        <entry3></entry3>
        <entry4></entry4>
        <entry5></entry5>
    </stuff>
</root>
__XML__
);
my $root = $t->root;
my $entry = $root->first_child('stuff')->first_child('entry1');
while ($entry = $entry->sibling(1)) {
  say $entry->print . ' (' . $entry->path . ')';
}

This only gives you the ones that come after the element you already have. If you start at entry 3 you will only get entries 4 and 5.


Original (edited) answer:

You can also use the siblings method to iterate over a list of all siblings of an element.

siblings ($optional_condition)

Return the list of siblings (optionally matching $optional_condition) of the element (excluding the element itself).

The elements are ordered in document order.

Replace the code from above with this:

my $root = $t->root;
my $entry1 = $root->first_child('stuff')->first_child('entry1');
# This is going to give us entries 2 to 5
foreach my $sibling ($entry1->siblings) {
  say $sibling->print . ' (' . $sibling->path . ')';
}

This gives you all siblings of your starting element, but not that one itself. If you start at entry3 you will get entries 1, 2, 4 and 5.

simbabque
  • 53,749
  • 8
  • 73
  • 136
  • should I use: foreach my $sibling ($entry1->siblings) {say $sibling->print . ' (' . $sibling->path . ')'; **OR** while ($entry = $entry->sibling(1)) { say $entry->print . ' (' . $entry->path . ')'; – laurentngu Jun 18 '12 at 09:34
  • You can pick the one you like, or even use `next_siblings` instead of `siblings` like @mirod proposed. It's up to you. But if you only want the siblings that come **after** the one you have in your document, the `while`-loop with `sibling(1)` or a `foreach`-loop with `next_siblings` are the way to go. I changed the answer accordingly. – simbabque Jun 18 '12 at 10:57