Using XML::LibXML: How do I create namespaces and child elements and make them work together?
libxml2, perl, xml, xml-namespaces
Solution
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.
Problem
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 namespace `rdf` declares - attribute `rdf` in namespace `xmlns` with value `http://www.w3.org/1999/02/22-rdf-syntax-ns#` - attribute `rdfs` in namespace `xmlns` with value `http://www.w3.org/2000/01/rdf-schema#` - attribute `foaf` in namespace `xmlns` with value `http://xmlns.com/foaf/0.1/` - attribute `admin` in namespace `xmlns` with value `http://webns.net/mvcb/` 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.