Using XML::LibXML: How do I create namespaces and child elements and make them work together?
I'm trying to do some stuff with FOAF and Perl. I'm unhappy with the current solutions and I want to roll my own. Please do not reference any module other than XML::LibXML
.
For reference here is a snippet from a FOAF file
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
xmlns:foaf="http://xmlns.com/foaf/0.1/"
xmlns:admin="http://webns.net/mvcb/">
<foaf:Person rdf:ID="me">
<foaf:name>Evan Carroll</foaf:name>
....
Now, excluding whitespace, I'm trying to recreate this with XML::LibXML
. However, I'm unfortunately stuck on the very first line. This just focuses on the first line:
I read this to be
- element
RDF
, in namespacerdf
declares- attribute
rdf
in namespacexmlns
with valuehttp://www.w3.org/1999/02/22-rdf-syntax-ns开发者_Python百科#
- attribute
rdfs
in namespacexmlns
with valuehttp://www.w3.org/2000/01/rdf-schema#
- attribute
foaf
in namespacexmlns
with valuehttp://xmlns.com/foaf/0.1/
- attribute
admin
in namespacexmlns
with valuehttp://webns.net/mvcb/
- attribute
Firstly you need an element rdf:RDF
, this seems to be tricky. Reading the documentation for XML::LibXML::Document
I found createElementNS()
but this doesn't seem to do what I want:
use XML::LibXML;
my $doc = XML::LibXML::Document->new( '1.0', 'UTF-8' );
my $foaf = $doc->createElementNS( 'RDF', 'rdf' );
print $foaf->toString; # prints <rdf xmlns="RDF"/>
Now, I try createElement('rdf:RDF')
and it works! I got the root element rdf:RDF
. Is this how we're supposed to create root elements? Am I just reading XML wrong?
Now, I need to create the attributes (schema declarations). I tried the poorly documented XML::LibXML::Document
's createAttributeNS
but it didn't work either:
my $doc = XML::LibXML::Document->new( '1.0', 'UTF-8' );
my $foaf = $doc->createElement( 'rdf:RDF' );
$doc->createAttributeNS( 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'rdf' );
In fact I get this error: "can't create a new namespace on an attribute!" which seems contradictory to what the method name implies, and to its description on the docs: "Creates an Attribute bound to a namespace."
So, I figure, ok I can't create
an attributeNS
, maybe I can set
an attributeNS
then. And, I proceed with the next documented method this time on XML::LibXML::Element
that looks applicable: setAttributeNS
.
my $doc = XML::LibXML::Document->new( '1.0', 'UTF-8' );
my $foaf = $doc->createElement( 'rdf:RDF' );
$foaf->setAttributeNS( 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'rdf' );
This time I get a different error: "bad ns attribute!". So I review some of the tests, and find this one requires a attribute key-value other than the namespace declaration to do what I want.. Which isn't what I want.
Here are some possible combinations and outputs:
$foaf->setAttributeNS( http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'rdf:', undef );
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" rdf:=""/>
$foaf->setAttributeNS( 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'rdf:foo', 'bar' );
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" rdf:foo="bar"/>
It seems none of the *NS methods work, even though I know they are related to XML namespaces. Finally, I try the non-NS version:
my $doc = XML::LibXML::Document->new( '1.0', 'UTF-8' );
my $foaf = $doc->createElement( 'rdf:RDF' );
$foaf->setAttribute( 'xmlns:rdf', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' );
print $foaf->toString; # <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"/>
I get this horrible feeling that I'm not doing this right. Did I do this right? How do I add a child Element with the DOM (not using appendTextChild
)?
This whole XML::LibXML
is very poorly documented but seems to be the best Perl has to offer for fast XML creation with a DOM.
From memory (and edited for correctness), you do it like this:
use XML::LibXML;
my $doc = XML::LibXML::Document->new( '1.0', 'UTF-8' );
my $foaf = $doc->createElementNS( 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'RDF' );
$doc->setDocumentElement( $foaf );
$foaf->setNamespace( 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' , 'rdf', 1 );
$foaf->setNamespace( 'http://www.w3.org/2000/01/rdf-schema#' , 'rdfs', 0 );
$foaf->setNamespace( 'http://xmlns.com/foaf/0.1/' , 'foaf', 0 );
$foaf->setNamespace( 'http://webns.net/mvcb/' , 'admin', 0 );
my $node = $doc->createElementNS( 'http://xmlns.com/foaf/0.1/', 'Person');
$foaf->appendChild($node);
$node->setAttributeNS( 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'ID', 'me');
my $node2 = $doc->createElementNS( 'http://xmlns.com/foaf/0.1/', 'name');
$node2->appendTextNode('Evan Carroll');
$node->appendChild($node2);
print $doc->toString;
That is, you always need to use the namespace URIs and add the namespace declarations to your root node (this is the only place you specify the namespace prefix - I think libxml will invent its own prefixes if you don't provide them). It would obviously be sensible from the maintenance point of view to put these in some variables.
Your final (non-namespaced) version will work as long as you don't need the in-memory DOM to be namespace aware. This works as the Level 1 DOM does not know about namespaces: you can treat namespaces as ordinary attributes and the document will still be well formed (but possibly not namespace well formed).
This works to a point, but the namespace prefixes are not as given. A discussion of this is elsewhere.
精彩评论