2

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

  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 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.

doubleDown
  • 8,048
  • 1
  • 32
  • 48
Evan Carroll
  • 78,363
  • 46
  • 261
  • 468
  • Since Evan has explicitly prohibited it I won't mention any other modules, but I will note that when working with RDF, an RDF-specific module would be better, especially since XML is a poor serialization of RDF. – MkV Mar 01 '10 at 22:41
  • I'm doing this for FOAF, if you know of a more widespread adoption of it than XML then go for it! If not, what I'm trying to do is create a model that will give you FOAF and HCARD templates and renderings from within TT and Catalyst. More specifically to create social-web type stuff and cater to crawlers that can utilize it (google maps) – Evan Carroll Mar 01 '10 at 23:20

1 Answers1

6

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.

Community
  • 1
  • 1
Andrew Walker
  • 2,451
  • 2
  • 18
  • 15
  • This is a great post but it doesn't compile at all for me? Any ideas? – Evan Carroll Mar 01 '10 at 21:15
  • @Evan: what part doesn't compile? – Ether Mar 01 '10 at 21:31
  • Fails to compile with XML::LibXML 1.70: Can't locate object method "createElementNS" via package "XML::LibXML::Element" – Evan Carroll Mar 01 '10 at 21:32
  • 1
    It's been said that the W3C DOM API is rather counter intuitive. Try this version. – Andrew Walker Mar 01 '10 at 21:55
  • `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 Mar 01 '10 at 21:56
  • Ok - I think that the an error appending the attribute. Is this any better? – Andrew Walker Mar 01 '10 at 22:16
  • That seems to work pretty much (forgot a few semicolons) it has `Evan Carroll`, my first question would be how do you make a node that consists of ``. I know how to setAttributeNS but how do you create NS-aware a `` node? – Evan Carroll Mar 01 '10 at 22:20
  • 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 `` or not... – Evan Carroll Mar 01 '10 at 22:32
  • 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 Mar 01 '10 at 23:49