PHP5: ext/dom: Namespaces

php.internals

Vivian Steller

22 years ago
hello, once again a post about the tricky namespaces. I played with the following functions concerning namespaces: domdocument->createElementNS domdocument->createAttributeNS domelement->setAttributeNS domelement->getAttributeNS (domelement->hasAttributeNS) as i found out the behavior of some functions differs when handling namespaces. maybe we can start a thread thinking about this behavior - at the moment i don't have the "enlightened" view into this very complex theme. would be nice if you could help me/us understanding namespaces a bit more... ok here are the things i found out. copy the code to your editor and step through the examples - the important output is given in the comments. Thanks for taking time to that :) /*############################ START ############################*/ <pre> /* SIMPLE NAMESPACE EXAMPLE */ <?php $doc = new DomDocument(); $xpath = new DomXPath($doc); /* SIMPLE NAMESPACE EXAMPLE */ $node1 = $doc->createElementNS("namespaceURI", "nodeName"); $node1 = $doc->appendChild($node1); print(htmlspecialchars("\n" . $doc->saveXML() . "\n")); // Output // > <nodeName xmlns="namespaceURI"/> $namespaces = $xpath->query("//namespace::*"); foreach($namespaces as $item) { print($item->nodeName . " = " . $item->nodeValue . "\n"); } // Output // > xmlns:xml = http://www.w3.org/XML/1998/namespace // > xmlns = namespaceURI $doc->removeChild($node1); ?> Right behavior so far! /* NAMESPACE WITH PREFIX EXAMPLE */ <?php /* NAMESPACE WITH PREFIX EXAMPLE */ $node2 = $doc->createElementNS("namespaceURI", "prefix:localName"); $node2 = $doc->appendChild($node2); print(htmlspecialchars("\n" . $doc->saveXML() . "\n")); // Output // > <prefix:localName xmlns:prefix="namespaceURI"/> $namespaces = $xpath->query("//namespace::*"); foreach($namespaces as $item) { print($item->nodeName . " = " . $item->nodeValue . "\n"); } // Output // > xmlns:xml = http://www.w3.org/XML/1998/namespace // > xmlns:prefix = namespaceURI // $doc->removeChild($node2); ?> Still right behavior! /* NAMESPACE WITH EXISTING PREFIX EXAMPLE */ <?php /* NAMESPACE WITH EXISTING PREFIX EXAMPLE */ $node3 = $doc->createElementNS("namespaceURI", "localName"); $node3 = $node2->appendChild($node3); print(htmlspecialchars("\n" . $doc->saveXML() . "\n")); // Output // > <prefix:localName xmlns:prefix="namespaceURI"> ¬ // <prefix:localName/> ¬ // </prefix:localName> $namespaces = $xpath->query("//namespace::*"); foreach($namespaces as $item) { print($item->nodeName . " = " . $item->nodeValue . "\n"); } // Output // > xmlns:xml = http://www.w3.org/XML/1998/namespace (node2) // > xmlns:prefix = namespaceURI (node2) // > xmlns:xml = http://www.w3.org/XML/1998/namespace (node3) // > xmlns:prefix = namespaceURI (node3) // $doc->removeChild($node3); ?> Right behavior, but namespace node will be removed, xpath-expression shows prefix - thats still right. /* IMPORT NAMESPACE NODE WITHOUT CHILDREN EXAMPLE */ <?php /* IMPORT NAMESPACE NODE WITHOUT CHILDREN EXAMPLE */ $newDoc = new DomDocument(); $newXpath = new DomXPath($newDoc); $newNode3 = $newDoc->importNode($node3); // second argument $deep = FALSE!! $newNode3 = $newDoc->appendChild($newNode3); print(htmlspecialchars("\n" . $newDoc->saveXML() . "\n")); // Output // > <localName/> $newNamespaces = $newXpath->query("//namespace::*"); foreach($newNamespaces as $item) { print($item->nodeName . " = " . $item->nodeValue . "\n"); } // Output // > xmlns:xml = http://www.w3.org/XML/1998/namespace (newNode3) $newDoc->removeChild($newNode3); ?> Maybe right behavior, but i think loosing the namespace while importing the node without its children is a bit dangerous... /* IMPORT NAMESPACE NODE EXAMPLE */ <?php /* IMPORT NAMESPACE NODE EXAMPLE */ $newNode3 = $newDoc->importNode($node3, TRUE); // second argument $deep = TRUE now!! $newNode3 = $newDoc->appendChild($newNode3); print(htmlspecialchars("\n" . $newDoc->saveXML() . "\n")); // Output // > <prefix:localName xmlns:prefix="namespaceURI"/> $newNamespaces = $newXpath->query("//namespace::*"); foreach($newNamespaces as $item) { print($item->nodeName . " = " . $item->nodeValue . "\n"); } // Output // > xmlns:xml = http://www.w3.org/XML/1998/namespace (newNode3) // > xmlns:prefix = namespaceURI (newNode3) $newDoc->removeChild($newNode3); ?> Now the namespace node will be imported as well, but it is not possible to import the "namespaced" node without its children... /* SET A NAMESPACE ATTRIBUTE WITHOUT PREFIX AND UNDEFINED NAMESPACE */ <?php /* SET A NAMESPACE ATTRIBUTE WITHOUT PREFIX AND UNDEFINED NAMESPACE */ //$node3->setAttributeNS("attrNamespaceURI", "attrName", "attrValue"); // Output // > Fatal error: Uncaught exception 'domexception' with message 'Namespace Error' ?> Error message is acceptable because attribute nodes could not have their own "xmlns" attributes! They have to be defined with a prefix (if their is no prefix defined for this namespace!)... Therefor the folling example should not occur an error... <?php /* SET A NAMESPACE ATTRIBUTE WITH EXISTING NAMESPACE */ //$node3->setAttributeNS("namespaceURI", "attrName", "attrValue"); // Output would be // > <prefix:localName xmlns:prefix="namespaceURI"> // <prefix:localName prefix:attrName="attrValue"/> // </prefix:localName> ?> And it doesn't. Right! The import of this node will be done correctly! /* SET A NAMESPACE ATTRIBUTE WITH PREFIX AND UNDEFINED NAMESPACE EXAMPLE */ <?php $node3->setAttributeNS("attrNamespaceURI", "prefix:attrName", "attrValue"); print(htmlspecialchars("\n" . $doc->saveXML() . "\n")); // Output // > <prefix:localName xmlns:prefix="namespaceURI"> ¬ // <prefix:localName xmlns:prefix="attrNamespaceURI" prefix:attrName="attrValue"/> ¬ // </prefix:localName> $namespaces = $xpath->query("//namespace::*"); foreach($namespaces as $item) { print($item->nodeName . " = " . $item->nodeValue . "\n"); } // Output // > xmlns:xml = http://www.w3.org/XML/1998/namespace (node2) // > xmlns:prefix = namespaceURI (node2) // > xmlns:xml = http://www.w3.org/XML/1998/namespace (node3) // > xmlns:prefix = attrNamespaceURI (node3) // $doc->removeChild($node3); print("Node3 namespace: " . $node3->namespaceURI . "\n"); // Output // > Node3 namespace: namespaceURI ?> Seems confusing but namespace of node is still "namespaceURI" And it will result in a conflict in the following example... /* IMPORT NAMESPACE NODE WITH NAMESPACE-PREFIX DEFINITION EXAMPLE */ <?php /* IMPORT NAMESPACE NODE EXAMPLE */ $newNode3 = $newDoc->importNode($node3, TRUE); // second argument $deep = TRUE now!! $newNode3 = $newDoc->appendChild($newNode3); print(htmlspecialchars("\n" . $newDoc->saveXML() . "\n")); // Output // > <prefix:localName xmlns:prefix="attrNamespaceURI" prefix:attrName="attrValue"/> $newNamespaces = $newXpath->query("//namespace::*"); foreach($newNamespaces as $item) { print($item->nodeName . " = " . $item->nodeValue . "\n"); } // Output // > xmlns:xml = http://www.w3.org/XML/1998/namespace (newNode3) // > xmlns:prefix = namespaceURI (newNode3) print("NewNode3 namespace: " . $newNode3->namespaceURI . "\n"); // Output // > NewNode3 namespace: attrNamespaceURI $newDoc->removeChild($newNode3); $node2->removeChild($node3); ?> WOW!! NamespaceURI has been changed while importing node - i think that this behavior isn't right yet. NamespaceURI shouldn't depend on xmlns:prefix attribute - instead it simply should loose its prefix and set its "xmlns" attribute to its current namespaceURI! /* SET A NAMESPACE ATTRIBUTE WITH NEW ATTRIBUTE NODE EXAMPLE */ I'm now changing to node2 - getting to complicated <?php //$attr2 = $doc->createAttributeNS("attrNamespaceURI", "prefix:attrName"); // Output // > Fatal error: Uncaught exception 'domexception' with message 'Namespace Error' ?> It seems that the behavior handling namespaces isn't the same in DomDocument::createAttributeNS() and DomElement::setAttributeNS(), as you can see in the SET A NAMESPACE ATTRIBUTE WITH PREFIX AND UNDEFINED NAMESPACE EXAMPLE But using a new namespace-prefix it will work correctly. <?php $attr2 = $doc->createAttributeNS("attrNamespaceURI", "newPrefix:attrName"); // Output: // > <prefix:localName xmlns:prefix="namespaceURI" xmlns:newPrefix="attrNamespaceURI"/> ?> Note: the "xmlns:newPrefix" declaration will be appended to the root node of the document on creation of this NS attribute. This could result in trouble while cloning/importing/deleting a node holding an "newPrefix"ed attribute!? <?php $node3 = $doc->createElementNS("namespaceURI", "nodeName"); $node3 = $node2->appendChild($node3); $node3->appendChild($attr2); print(htmlspecialchars("\n" . $doc->saveXML() . "\n")); // Output // > <prefix:localName xmlns:prefix="namespaceURI" xmlns:newPrefix="attrNamespaceURI"> // <prefix:nodeName newPrefix:attrName=""/> // </prefix:localName> $namespaces = $xpath->query("//namespace::*"); foreach($namespaces as $item) { print($item->nodeName . " = " . $item->nodeValue . "\n"); } // Output // > xmlns:xml = http://www.w3.org/XML/1998/namespace (node2) // > xmlns:newprefix = attrNamespaceURI (node2) // > xmlns:prefix = namespaceURI (node2) // > xmlns:xml = http://www.w3.org/XML/1998/namespace (node3) // > xmlns:newprefix = attrNamespaceURI (node3) // > xmlns:prefix = namespaceURI (node3) $node2->removeChild($node3); // Output // > <prefix:localName xmlns:prefix="namespaceURI" xmlns:newPrefix="attrNamespaceURI"/> ?> As you can see the "xmlns:newPrefix" attribute is still appended to the root node of the document. Because of the lack of a (reasonable) possibility to remove an "xmlns" declaration the reset of an attribute using this prefix will result in an error: <?php // $attr2 = $doc->createAttributeNS("newAttrNamespaceURI", "newPrefix:attrName"); // Output // > Fatal error: Uncaught exception 'domexception' with message 'Namespace Error' ?> I think it would be easier to append the "xmlns" declarations on those nodes which have an attribute/child of this namespace. /* SET AN XMLNS NAMESPACE ATTRIBUTE EXAMPLE */ As I said before there is no (reasonable) possiblity to remove an "xmlns" declaration (you could do this by cloning nodes in the hope for a bug in the source leading to the loss of this declaration... very uncomforable:). But there is a possibility to check if a xmlns is defined using xpath (I use this xpath in this document all the time). I found out that you also can set an "xmlns" declaration using the right namespaces (which you can find in the sources of libxml and ext/dom). Have a look: <?php // xmlns Namespace: "http://www.w3.org/2000/xmlns/" // xml Namespace: "http://www.w3.org/XML/1998/namespace" $node2->setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:anotherNewPrefix", "anotherNewNamespace"); print(htmlspecialchars("\n" . $doc->saveXML() . "\n")); // Output // > <prefix:localName xmlns:prefix="namespaceURI" xmlns:newPrefix="attrNamespaceURI" xmlns:anotherNewPrefix="anotherNewNamespace"/> $namespaces = $xpath->query("//namespace::*"); foreach($namespaces as $item) { print($item->nodeName . " = " . $item->nodeValue . "\n"); } // Output // > xmlns:xml = http://www.w3.org/XML/1998/namespace (node2) // > xmlns:anotherNewPrefix = anotherNewNamespace (node2) // > xmlns:newprefix = attrNamespaceURI (node2) // > xmlns:prefix = namespaceURI (node2) ?> This is very fine but the behavior (in this case) isn't very logic: setting an elements'/attributes' namespace the intepreter checks whether a prefix is defined for this namespace or not. Though the following expression should not result in an error while a prefix for the used namespace is defined already: <?php $node2->setAttributeNS("http://www.w3.org/2000/xmlns/", "justAnotherNewPrefix", "justAnotherNewNamespace"); // Output: // > Fatal error: Uncaught exception 'domexception' with message 'Namespace Error' ?> But unforunately it does! You could solve this problem by simply setting the "http://www.w3.org/2000/xmlns/" namespace as a default namespace like "http://www.w3.org/XML/1998/namespace". Then the behavior would be more consequent... </pre> /*############################ END ############################*/ Ok I think this is enough about namespaces for today. I hope i could help someone understanding namespaces and the problems with it. Any comments would be appreciated. Thanks in advance. vivi

Derick Rethans

22 years ago
Don't crosspost to two irrelevant lists, please Derick On Sun, 14 Mar 2004, Vivian Steller wrote: