Wednesday, May 02, 2007

Angry at the DOM

This is not a bug, it's a feature. This really frustrates me.

In short, the below code example demonstrates a huge inconsistency between uses of a particularly really poorly documented (in the areas you want to use, anyway) implementation of DOM level 3.

$d = new DOMDocument();
$example = $d->createElementNS('http://foo.com','example');
$example->setAttributeNS('http://bar.com', 'bar:bar',"value");
$example->setAttributeNS('http://bar.com', 'monkey',"value");

$d = new DOMDocument();
$example = $d->createElementNS('http://foo.com','example');
$example->setAttributeNS('http://fish.com', 'bar:bar',"value");
$example->setAttributeNS('http://bar.com', 'monkey',"value");


What the heck am I angry about? You can...
  • $a->setAttribute('href', 'http://google.com');
  • $a->getAttribute('href');
and
  • $a->getAttributeNS('http://xml.com/xhtml74', 'href');
but only 50% of the time do you get to do
  • $a->setAttributeNS('http://xml.com/xhtml74', 'href', 'http://google.com');
Now the reason for it makes sense. If http://xml.com/xhtml74 is not mapped to an xml namespace (xmlns); then how does it add that? Accordingly, you have to add in a qualifiedName

  • $a->setAttributeNS('http://xml.com/xhtml74', 'xhtml:href', 'http://google.com');
Up until this point, I am perfectly fine with how things are implemented, except forgetting to put in a qualified name gives you a "Namespace Error", when it should be telling you that "No qualified xmlns was found". I can deal with that. What I can't deal with is this:
  1. $a->setAttributeNS('http://xml.com/xhtml74', 'xhtml:href', 'http://google.com');
  2. $a->setAttributeNS('http://xml.com/xhtml74', 'href', 'http://google.com');
What threw an exception moments ago no longer does, because doing the first line changed a state internally, and invisibly.

So to get it right all the time, you use the same qualified name prefix, right? 'xhtml:foo'.
  1. $a->setAttributeNS('http://xml.com/xhtml74', 'xhtml:href', 'http://google.com');
  2. $a->setAttributeNS('http://xml.com/xhtml74', 'xhtml:href', 'http://google.com');
But what if someone gives you a document and uses different qualifiers for the same urls?
  1. $a->setAttributeNS('http://xml.com/xhtml74', $a->lookupPrefix('http://xml.com/xhtml74') . ':href', 'http://google.com');
  2. $a->setAttributeNS('http://xml.com/xhtml74', $a->lookupPrefix('http://xml.com/xhtml74') . ':href', 'http://google.com');
But now, you are stuck, because if the namespace isn't declared, lookupPrefix gives you a null. You are suddenly right back at the start, and you get a heck of a lot of exceptions. This should be easy to do. Instead, you have to be careful about doing it, otherwise you end up with a pile of unusable code very quickly.

So the best strategy to deal with this is:

determinePrefix($element, $namespace, $suggestedPrefix = null)
  • Should check for the existence $namespace in $element
  • If no $namespace prefix exists, register the namespace on the element
  • If a $suggestedPrefix is defined, use that
  • If a $namespace prefix is found, return that

... which is clunky and verbose.

Why doesn't the internals of DOMDocument either:
  • Refuse at any time to let you add something without a qualified prefix, and give you methods to add such a prefix - registerNamespace(); adds (xmlns:fish="http://good.com"/)
  • Or, let you add in all of the unqualified prefixes you like, and give them anonymous names; and if you later append the node to another document or whatever, resolve them then.
Post a Comment