Beefy Boxes and Bandwidth Generously Provided by pair Networks
Welcome to the Monastery
 
PerlMonks  

perl & XML: getting last child?

by ambrill (Novice)
on Apr 17, 2013 at 20:14 UTC ( #1029217=perlquestion: print w/ replies, xml ) Need Help??
ambrill has asked for the wisdom of the Perl Monks concerning the following question:

Thanks for the help on the last child... Related question for situations where there is no value in the node but i need to return a "0" to maintain the appropriate output file structure. Modifying the original files slightly below:
xml <library> <cd> <artist></artist> </cd> <book> <title>Perl Best Practices</title> <author>Damian Conway</author> <isbn>0596001738</isbn> <pages>542</pages> <image src="http://www.oreilly.com/catalog/covers/perlbp.s.gif" width="145" height="190" /> </book> <book> <title>Perl Cookbook, Second Edition</title> <author>Tom Christiansen</author> <author>Nathan Torkington</author> <isbn>0596003137</isbn> <pages>964</pages> <image src="http://www.oreilly.com/catalog/covers/perlckbk2.s.gi +f" width="145" height="190" /> </book> <book> <title>Guitar</title> <author>Mark Phillips</author> <author>John Chappell</author> <isbn>076455106X</isbn> <pages>392</pages> <image src="http://media.wiley.com/product_data/coverImage/6X/07 +645510/076455106X.jpg" width="100" height="125" /> </book> </library>
Incorporating some of the previous suggestions, new code looks like this. Problem arises when <artist> is null (or there is no entry).
#!/usr/bin/perl -w use strict; use warnings; my $filename = 'library1.xml'; use XML::LibXML; my $parser = XML::LibXML->new(); my $doc = $parser->parse_file($filename); print $_->data . " | " foreach ($doc->findnodes('/library/cd/artis +t/text()')); my @nodes = $doc->findnodes('/library/book[last()]'); foreach my $node (@nodes) { print $node->findvalue( 'title')." | \n"; }

Comment on perl & XML: getting last child?
Select or Download Code
Re: perl & XML: getting last child?
by Your Mother (Canon) on Apr 17, 2013 at 20:25 UTC

    *A* way.

    use strictures; use XML::LibXML; my $dom = XML::LibXML ->load_xml({ IO => \*DATA }); my $last = [ $dom->findnodes("/library/book[last()]") ]->[0]; print $last->findvalue("title"), $/; __DATA__ <library> <book> <title>Perl Best Practices</title> <src>Use python. But I keed!</src> </book> <book> <title>Perl Cookbook, Second Edition</title> <src>You would make more cooking meth. But I keed!</src> </book> <book> <title>Guitar for Dummies</title> <src>Stratocaster. Oh, I'm sorry, did I stutter?</src> </book> </library>

      Another way is:

      my ($last) = $dom->findnodes("/library/book[last()]");
Re: perl & XML: getting last child?
by space_monk (Chaplain) on Apr 17, 2013 at 20:48 UTC

    Another way ... :-)

    #!/usr/bin/perl -w use strict; use warnings; my $filename = 'library.xml'; use XML::LibXML; my $parser = XML::LibXML->new(); my $doc = $parser->parse_file($filename); print "\nBooks\n"; my @nodes = $doc->findnodes("/library/book[last()]"); foreach my $node (@nodes) { print $node->findvalue( 'title')."\n"; my @authors = $node->findnodes('author'); print "Authors:".scalar(@authors)."\n"; foreach my $auth (@authors) { print $auth->textContent()."\n"; } }
    If any of my proposed solutions have minor errors, it's because I don't waste my genius on trivial matters. :-P
      This works well. In some cases, the last node is null (or does not exist). How can you print a "0" in those cases? thanks
        If you put some example XML data in a comment, I (or someone else) will almost certaintly work out a way to do it. :-)
        If any of my proposed solutions have minor errors, it's because I don't waste my genius on trivial matters. :-P
Re: perl & XML: getting last child?
by sundialsvc4 (Abbot) on Apr 18, 2013 at 00:52 UTC

    Indeed.   When you are doing any sort of real-world work with an XML file, “get to know XPath expressions .. very well.”   Don’t write procedural (Perl...) code, whose procedural structure will necessarily be tied more-or-less to that of the structure you’re parsing.   Instead, write an XPath expression to push, as much as possible, the entire responsibility into the library’s hands.   Just as you do every day with an SQL query ...

      Well , no duh, that's what the OP is asking about, that your post doesn't answer -- typical sundialsvc4

Re: perl & XML: getting last child?
by ambrill (Novice) on Apr 19, 2013 at 00:19 UTC
    Thanks for the help on the last child... Related question for situations where there is no value in the node but i need to return a "0" to maintain the appropriate output file structure. Modifying the original files slightly below:
    xml <library> <cd> <artist></artist> </cd> <book> <title>Perl Best Practices</title> <author>Damian Conway</author> <isbn>0596001738</isbn> <pages>542</pages> <image src="http://www.oreilly.com/catalog/covers/perlbp.s.gif" width="145" height="190" /> </book> <book> <title>Perl Cookbook, Second Edition</title> <author>Tom Christiansen</author> <author>Nathan Torkington</author> <isbn>0596003137</isbn> <pages>964</pages> <image src="http://www.oreilly.com/catalog/covers/perlckbk2.s.gi +f" width="145" height="190" /> </book> <book> <title>Guitar</title> <author>Mark Phillips</author> <author>John Chappell</author> <isbn>076455106X</isbn> <pages>392</pages> <image src="http://media.wiley.com/product_data/coverImage/6X/07 +645510/076455106X.jpg" width="100" height="125" /> </book> </library>
    Incorporating some of the previous suggestions, new code looks like this. Problem arises when <artist> is null (or there is no entry).
    #!/usr/bin/perl -w use strict; use warnings; my $filename = 'library1.xml'; use XML::LibXML; my $parser = XML::LibXML->new(); my $doc = $parser->parse_file($filename); print $_->data . " | " foreach ($doc->findnodes('/library/cd/artis +t/text()')); my @nodes = $doc->findnodes('/library/book[last()]'); foreach my $node (@nodes) { print $node->findvalue( 'title')." | \n"; }

      You can test what is returned from the text node. There is an xpath expression to do this also but I usually just test it like this. (untested code)

      foreach ($doc->findnodes('/library/cd/artist/text()'){ my $artist = $_->data; if ($artist){ print "$artist | "; } else { print "0 | "; } }

      If the artist was formerly called Prince I'm not sure whether Perl will evaluate that symbol to true or false, (or male or female)... it's a little confusing.

      Update: The above won't work since findnodes() will only return the nodes that have text. I often have the situation where I need to add text to empty text nodes. Here is something closer.

      foreach ($doc->findnodes('/library/cd/artist')){ #update: added ')' my $artist = $_->to_literal; if ($artist){ print "$artist | "; #to change the text use my $textnode=$_->findnodes('./text()') # then use $textnode->setData('new text'); } else { print "0 | "; # you can use $_->appendTextNode('text to add') here } }

      Another option is to loop through seperately the nodes with no text using the not() funtion of xpath.

      foreach ($doc->findnodes('/library/cd/artist[not(text())]'){
        I cannot get your code to work. Any ideas? thanks

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1029217]
Approved by Lotus1
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others having an uproarious good time at the Monastery: (5)
As of 2014-12-21 19:32 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    Is guessing a good strategy for surviving in the IT business?





    Results (107 votes), past polls