views:

638

answers:

1

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/"&gt;

  <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

  1. element RDF, in namespace rdf declares
    1. attribute rdf in namespace xmlns with value http://www.w3.org/1999/02/22-rdf-syntax-ns#
    2. attribute rdfs in namespace xmlns with value http://www.w3.org/2000/01/rdf-schema#
    3. attribute foaf in namespace xmlns with value http://xmlns.com/foaf/0.1/
    4. 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 doc 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') this 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 this 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 it errored with "can't create a new namespace on an attribute!" Which seems to be exactly what the name implies, and the 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 declairation 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 nothing with *NS wants to work, even though I know these are 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#"/&gt;

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 w/ a DOM.

+2  A: 

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.

Andrew Walker
This is a great post but it doesn't compile at all for me? Any ideas?
Evan Carroll
@Evan: what part doesn't compile?
Ether
Presumably my memory is fallible. Any useful error message?
Andrew Walker
Fails to compile with XML::LibXML 1.70: Can't locate object method "createElementNS" via package "XML::LibXML::Element"
Evan Carroll
It's been said that the W3C DOM API is rather counter intuitive. Try this version.
Andrew Walker
`createElementNS` is a method of the document. I think you want `addNewChild` when creating child nodes. Also, isn't `name` supposed to be a child of `Person`, not the root node?
cjm
Still getting an error: appendChild: HIERARCHY_REQUEST_ERR
Evan Carroll
Ok - I think that the an error appending the attribute. Is this any better?
Andrew Walker
That seems to work pretty much (forgot a few semicolons) it has `<Person xmlns="http://xmlns.com/foaf/0.1/" rdf:ID="me"><name>Evan Carroll</name></Person>`, my first question would be how do you make a node that consists of `<foaf:Person rdf:ID="me">`. I know how to setAttributeNS but how do you create NS-aware a `<foaf:Person>` node?
Evan Carroll
I think this question is answered here: http://www.justskins.com/forums/namespace-declarations-in-libxml-101052-2.html It appears as if they're syntactically the same, and XML::LibXML doesn't support the pretty NS option. I wonder if social-crawling mechanism in google will pick up `<person xmlns="foafurl">` or not...
Evan Carroll
I'm not sure this is correct, see my follow up http://stackoverflow.com/questions/2359920/using-perls-xmllibxml-how-do-you-use-xml-prefixes-and-not-xmlns-attributes
Evan Carroll