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 -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: http://www.php.net/unsub.php