views:

136

answers:

2

I'm familiar with the DOMDocument::importNode method for importing a tree of nodes from some other document element.

However, what I was wondering is if I can automatically change the namespace prefix on a tree of nodes as I import them, that is, specify a new prefix for all nodes of that namespace.

Say the nodes, in their existing document, all have names like "name", "identity", and so on. When importing them into my new document they will be alongside other namespaces, so I'd like them to appear as "nicnames:name", "nicnames:identity" and so on. I'd like to be able to change this prefix programmatically so that in another context I may be able to import them as, for instance, "myprefix:name", "myprefix:identity" depending on the document they're imported into.

Can anyone help me understand how to do this? Thanks.

Edit: as per the explanation in my answer, I figured out I don't actually need to do this. I was misunderstanding namespaces in XML.

A: 

You probably have to write your own import code then. E.g.

function importNS(DOMNode $target, DOMNode $source, $fnImportElement, $fnImportAttribute) {
  switch($source->nodeType) {
    case XML_ELEMENT_NODE:
      // invoke the callback that creates the new DOMElement node
      $newNode = $fnImportElement($target->ownerDocument, $source);
      if ( !is_null($newNode) && !is_null($source->attributes) ) {
        foreach( $source->attributes as $attr) {
          importNS($newNode, $attr, $fnImportElement, $fnImportAttribute);
        }
      }
      break;
    case XML_ATTRIBUTE_NODE:
      // invoke the callback that creates the new DOMAttribute node
      $newNode = $fnImportAttribute($target->ownerDocument, $source);
      break;
    default:
      // flat copy
      $newNode = $target->ownerDocument->importNode($source);
  }

  if ( !is_null($newNode) ) {
    // import all child nodes
    if ( !is_null($source->childNodes) ) {
      foreach( $source->childNodes as $c) {
        importNS($newNode, $c, $fnImportElement, $fnImportAttribute);
      }
    }
    $target->appendChild($newNode);
  }
}

$target = new DOMDocument;
$target->loadxml('<foo xmlns:myprefix="myprefixUri"></foo>');

$source = new DOMDocument;
$source->loadxml('<a>
  <b x="123">...</b>
</a>');

$fnImportElement = function(DOMDocument $newOwnerDoc, DOMElement $e) {
  return $newOwnerDoc->createElement('myprefix:'.$e->localName);
};

$fnImportAttribute = function(DOMDocument $newOwnerDoc, DOMAttr $a) {
  // could use namespace here, too....
  return $newOwnerDoc->createAttribute($a->name);
};

importNS($target->documentElement, $source->documentElement, $fnImportElement, $fnImportAttribute);
echo $target->savexml();

prints

<?xml version="1.0"?>
<foo xmlns:myprefix="myprefixUri"><myprefix:a>
  <myprefix:b x="123">...</myprefix:b>
</myprefix:a></foo>
VolkerK
A: 

I discovered I'd misunderstood XML namespaces. They are actually much better than I thought they were.

I'd thought that each XML namespace used in a single document had to have a different namespace prefix. This is not true.

You can use different namespaces throughout your document even without namespace prefixes, just by including the xmlns attribute where appropriate, and that xmlns attribute only has effect for that element and its descendants, overriding the namespace for that prefix which may have been set higher up the tree.

For example, to have one namespace within another, you don't have to do:

<record xmlns="namespace1">
  <person:surname xmlns:person="namespace2">Smith</person:surname>
</record>

You can just do

<record xmlns="namespace1">
  <surname xmlns="namespace2">Smith</person>
</record>

Namespace prefixes are a good shortcut in certain situations, but not necessary when just including one document inside another of a different namespace.

thomasrutter