2

I am trying to achieve something that I'm sure should be simple. This is my objective:

<s:Envelope
xmlns:s="http://www.w3.org/2003/05/soap-envelope"
xmlns:a="http://www.w3.org/2005/08/addressing"
xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">

and my closest unsuccessful attempt is:

$envelope = $this->doc->createElementNS('http://www.w3.org/2003/05/soap-envelope', 's:Envelope');
$envelope->setAttributeNS('http://www.w3.org/2003/05/soap-envelope', 'xmlns:u', 'http://www.w3.org/2005/08/addressing');
$envelope->setAttributeNS('http://www.w3.org/2003/05/soap-envelope', 'xmlns:u', 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd');

Which delivers:

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" 
s:a="http://www.w3.org/2005/08/addressing" 
s:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">

I have tried dozens of combinations of the parameters, particularly in parameter 1 of the setAttributeNS, including 'http://www.w3.org/2000/xmlns/', all either give me a name space error or are just ignored.

Shouldn't be hard, eh ...

Dave Spencer
  • 495
  • 1
  • 4
  • 15

1 Answers1

0

Define "simple" :). You are concerned about XML Namespaces and in specific you want to create the reserved attributes (that are attributes starting with "xml" like "xmlns") and set their values.

Here some things clash. First of all DOMDocument manages the Namespaces for you. A Namespace in XML first of all has an URI (the namespace name). That is an identifier that normally looks like an URL, e.g. "http://www.w3.org/2003/05/soap-envelope" or "http://www.w3.org/2005/08/addressing". As a document would grow in size at large if each node would be prefixed with that Namespace URI always, there are ways to map each Namespace URI to a shorter representation, the namespace prefix (e.g. the "s" in "s:Envelope").

Let's just take the soap envelope element:

<{http://www.w3.org/2003/05/soap-envelope}Envelope />

Instead of writing that envelope element this way, it's done as:

<Envelope xmlns="http://www.w3.org/2003/05/soap-envelope" />

Which means that the envelope element is in the http://www.w3.org/2003/05/soap-envelope namespace.

Note that the attribute named "xmlns" is reserved as its name starts with "xml". It has a special meaning and it is also bound implicit or explicit to the namespace name "http://www.w3.org/XML/1998/namespace". In the example it sets the namespace of the element it belongs to, here the envelope element.

As you can have more than one namespace in the same document and you don't want to repeat the full URI with each node (e.g. element), it is also possible to define a prefix and then use the prefix instead. That is what you do already, you set the prefix "s" to the namespace name "http://www.w3.org/2003/05/soap-envelope":

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" />

Let's see that in plain PHP code:

$doc = new DOMDocument();
$envelope = $doc->createElementNS('http://www.w3.org/2003/05/soap-envelope', 'Envelope');
$doc->appendChild($envelope);

This creates the element named "Envelope" with the namespace name "http://www.w3.org/2003/05/soap-envelope":

<?xml version="1.0"?>
<Envelope xmlns="http://www.w3.org/2003/05/soap-envelope"/>

As you also want to control which namespace prefix is to be used for that namespace (here: "s"), you can add it as well (as you have found out on your own already):

$doc = new DOMDocument();
$envelope = $doc->createElementNS('http://www.w3.org/2003/05/soap-envelope', 's:Envelope');
$doc->appendChild($envelope);

Resulting in the following XML:

<?xml version="1.0"?>
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"/>

Now the next attribute you want to add (e.g. "xmlns:a") on that same element is again reserved (as it starts with "xml"). You tried adding it like:

$envelope->setAttributeNS('http://www.w3.org/2003/05/soap-envelope', 'xmlns:a', 'http://www.w3.org/2005/08/addressing');

but it doesn't create the result you want to achieve:

<?xml version="1.0"?>
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" s:a="http://www.w3.org/2005/08/addressing"/>

As the outcome XML shows and as on closer look on the code perhaps now is more obvious, that you don't want to add an attribute with the namespace name "http://www.w3.org/2003/05/soap-envelope" (first parameter of setAttributeNS). Instead you want to add an attribute which is in the (reserved) namespace name "http://www.w3.org/XML/1998/namespace" as that attribute name ("xmlns:a") starts with "xml" and therefore is reserved.

Luckily as I wrote earlier these attributes have their namespace implicit. You add them like "normal" attributes, that is w/o any namespace:

$envelope->setAttribute('xmlns:a', 'http://www.w3.org/2005/08/addressing');

Which then results in the XML you're looking for:

<?xml version="1.0"?>
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing"/>

This has been answered before, but I right now don't find the existing Q&A material of it.

That about the simple. You made the mistake to add the attribute with the wrong namespace name, most likely because you didn't know how to add the attribute directly. Take care here that all attributes starting with "xml" (case insensitive btw.) are reserved and they have implicit meanings.

For example adding a child element with that second namespace name will be added correctly to the document:

$test = $doc->createElementNS('http://www.w3.org/2005/08/addressing', 'a:test');
$doc->documentElement->appendChild($test);

But the outcome is verbose:

<?xml version="1.0"?>
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
  <a:test xmlns:a="http://www.w3.org/2005/08/addressing"/>
</s:Envelope>

The added "a:test" element has the correct namespace declaration with it ("xmlns:a="http://www.w3.org/2005/08/addressing""). Strictly speaking this is not necessary (but it doesn't hurt, it's correct) and that is because "Namespaces are not defined on or for a XML document, but on element nodes". When you have specified all namespace prefixes on the document element already you can reload the document and then the same code will not add that extra definition:

$doc = new DOMDocument();
$envelope = $doc->createElementNS('http://www.w3.org/2003/05/soap-envelope', 's:Envelope');
$doc->appendChild($envelope);

$envelope->setAttribute('xmlns:a', 'http://www.w3.org/2005/08/addressing');

$doc->loadXML($doc->saveXML()); # reload the document

$test = $doc->createElementNS('http://www.w3.org/2005/08/addressing', 'test');
$doc->documentElement->appendChild($test);

The output of the document shows this:

<?xml version="1.0"?>
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
  <a:test/>
</s:Envelope>

That is because on reload of the document, the DOMDocument object knows which namespaces are already prefixed and it can re-use the prefix for that namespace name. Technically this is not needed (the document is the same), if you're concerned to create the same textual XML serialization of the document, this might be interesting for you. It should not be necessary though. As well as the prefixes must not match at all.

So what you considered to be simple is actually pretty complicated (imagine the length of text to read for that answer already). Instead add namespaced elements and attributes where you need to and let the extension take care of it to register the Namespaces properly. That is what the API is for. Don't focus too much on the textual representation (even I showed you here how to create the same text results). Key is to understand the namespaces and how to use the API. Just looking for the same textual representation is more (often too much) work - but possible and but not simple.

hakre
  • 193,403
  • 52
  • 435
  • 836
  • Firstly, I can't tell you how much I appreciate you spending as much time on an answer that is SO detailed. Perhaps I should employ you to solve my larger "InvalidSecurity" problem ;-). Anyway, sadly, the setAttribute method was one of the ones I tried and just seems to ignore it and just returns as if I'd not tried at all. I'm not sure if it is safe to put my test URL in this question but I'd be more than happy for you to take a look. For the record, I am just trying to replicate exactly the example to rule it out as a cause. – Dave Spencer Jul 02 '17 at 04:33
  • @DaveSpencer: I posted a working example, see here:: https://3v4l.org/TviiW - perhaps you can isolate your example so it's easier to trouble-shoot? – hakre Jul 02 '17 at 07:32
  • Just left a message on your WordPress blog – Dave Spencer Jul 02 '17 at 09:26