worik has asked for the wisdom of the Perl Monks concerning the following question:

Given that I wish to build an XML node that may contain plain text like:

<owner> John Smith </owner>

Or it may contain a child node like:

<owner> <href>http://johnsmith.com</href> </owner>

And the text/node that is to be added is obtained from a function that can return either, is there an XML::LibXML function that I can call that will call appendTextNode in the first case and appendChildNode in the second? If there is I cannot find it.

What I have:

#!/usr/bin/perl -w use strict; use XML::LibXML; # The XML Document my $DOC = XML::LibXML->createDocument( "1.0", "UTF-8" ); # Set the root my $root = $DOC->createElement('owner'); $DOC->setDocumentElement($root); sub createOwner { if(rand() < 0.5){ return "John Smith"; }else{ my $ret = XML::LibXML::Element->new('href'); $ret->appendTextNode('http://johnsmith.com'); return $ret; } } my $owner = &createOwner(); if(ref($owner) eq 'XML::LibXML::Element'){ $root->appendChild($owner); }else{ $root->appendTextNode($owner); } print $DOC."\n";

This returns either:

<?xml version="1.0" encoding="UTF-8"?> <owner>John Smith</owner>

OR

<?xml version="1.0" encoding="UTF-8"?> <owner><href>http://johnsmith.com</href></owner>

What I want

#!/usr/bin/perl -w use strict; use XML::LibXML; # The XML Document my $DOC = XML::LibXML->createDocument( "1.0", "UTF-8" ); # Set the root my $root = $DOC->createElement('owner'); $DOC->setDocumentElement($root); sub createOwner { if(rand() < 0.5){ return "John Smith"; }else{ my $ret = XML::LibXML::Element->new('href'); $ret->appendTextNode('http://johnsmith.com'); return $ret; } } my $owner = &createOwner(); $root->appendChild($owner); print $DOC."\n";

This will have an error:

XML::LibXML::Node::appendChild() -- nNode is not a blessed SV reference

in the case that the plain string is returned. I wish to avoid the testing in the calling function.

Worik

Replies are listed 'Best First'.
Re: XML::LibXML creating nodes with a string OR a node
by choroba (Cardinal) on Jun 07, 2015 at 22:41 UTC
    I wish to avoid the testing in the calling function.

    Then add the testing to the called function:

    sub createOwner { if (0.5 > rand) { return ('XML::LibXML::Text'->new('John Smith')) } else { my $ret = 'XML::LibXML::Element'->new('href'); $ret->appendTextNode('http://johnsmith.com'); return ($ret) } }

    Update: Fixed a warning.

    لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ

      That misses the point. The calling function does not know, and the called function does not care.

      The answer I have come up with is a wrapper function that does what I want XML::LibXML to do:

      sub _addNode { my ($parent, $child) = @_; if(ref($child) eq 'XML::LibXML::Element'){ $parent->appendChild($child); }elsif(ref($child) eq ''){ $parent->appendTextNode($child); }else{ die "'_addNode child is type: ".ref($child). " Do not know what to do with that"; } return $parent; }

      Is the answer to my original question "yes" or "no"?

      To be rule compliant here is the runnable code....

      #!/usr/bin/perl -w use strict; use XML::LibXML; # The XML Document my $DOC = XML::LibXML->createDocument( "1.0", "UTF-8" ); # Set the root my $root = $DOC->createElement('owner'); $DOC->setDocumentElement($root); sub createOwner { if(rand() < 0.5){ return "John Smith"; }else{ my $ret = XML::LibXML::Element->new('href'); $ret->appendTextNode('http://johnsmith.com'); return $ret; } } sub _addNode { my ($parent, $child) = @_; if(ref($child) eq 'XML::LibXML::Element'){ $parent->appendChild($child); }elsif(ref($child) eq ''){ $parent->appendTextNode($child); }else{ die "'_addNode child is type: ".ref($child). " Do not know what to do with that"; } return $parent; } my $owner = &createOwner(); $root = &_addNode($root, $owner); print $DOC."\n";
        Is the answer to my original question "yes" or "no"?

        AFAIK it is "no". A thorough reading of the documentation will give you a definite answer though.

        The calling function does not know, and the called function does not care.

        But why? If the sub createOwner is under your control, then the fix suggested by choroba solves the problem in only a few characters. I may be wrong, but I get the feeling from this and the previous questions you have posted that you are trying to bend the module to your wishes instead of accepting the API the way it is and just using it...

        But anyway, here's an interesting hack ("just enough rope" etc. etc.): In your code, replace "sub _addNode {" with "sub XML::LibXML::Element::appendTextOrElement {" and then replace "$root = &_addNode($root, $owner);" with "$root->appendTextOrElement($owner);". Does that fit your expectations better? ;-)

        ...what I want XML::LibXML to do

        There is a meditation about that, see XML::LibXML - WHAR HASH TREES WHAR?! and wish upon a star :)(standards aren't always sugar, sometimes they're standards)

      Minor issue: Warning: Use of "rand" without parentheses is ambiguous (Perl v5.20)

Re: XML::LibXML creating nodes with a string OR a node
by Anonymous Monk on Jun 07, 2015 at 22:59 UTC

    choroba already gave you the answer; just two minor nitpicks:

    Putting perl -w on the shebang line enables warnings everywhere, even in modules not under your control; putting a use warnings; at the top of the script instead is usually better as only affects the current lexical scope (i.e. your code).

    Calling functions with the &foo(...) style is not necessary, foo(...) is sufficient. The former ignores prototypes, which is the kind of thing one should only do on purpose.