This is what I am after
<!-- language: lang-xml -->
<ws:Test>
<ws:somename2>somevalue2</ws:somename2>
<ws:make>
<ws:model>foo</ws:model>
<ws:model>bar</ws:model>
</ws:make>
</ws:Test>
This is my current code
<!-- language: lang-php -->
$xmlTest = new SimpleXMLElement('<Test/>', 0, false, 'ws', true);
$xmlTest->addChild("ws:somename2", "somevalue2", 'http://microsoft.com/wsdl/types/');
$make = $xmlTest->addChild('ws:make', null, 'ws');
#$make->addAttribute('name','Ford');
$make->addChild('ws:model', 'foo', 'ws');
$make->addChild('ws:model', 'bar', 'ws');
header ("Content-Type:text/xml");
print_r($xmlTest->asXML());
but it outputs
<!-- language: lang-xml -->
<Test>
<ws:somename2>somevalue2</ws:somename2>
<ws:make>
<ws:model>foo</ws:model>
<ws:model>bar</ws:model>
</ws:make>
</Test>
As you can see the ws: is missing from Test
The Problem
The problem with this code is in the very first line:
Before doing anything else, let's output this as XML:
That makes sense, we got out what we put in.
The manual is rather vague on what the
$ns
argument does, but in this case it is not doing anything useful. What it does is set the context for reading the XML, so that->foo
refers to<ws:foo>
and['bar']
refers tows:bar="..."
. It doesn't do anything to change the structure of the XML itself.Setting a Root Namespace
To set a namespace on the root element, we just have to include it in our string defining the root element:
So far so good...
Setting Namespaces on Children
Next, let's sanity check what the code in the question actually outputs (I've added some whitespace to make it more readable):
OK, so this document contains two namespace declarations:
xmlns:ws="http://microsoft.com/wsdl/types/"
on thesomename2
elementxmlns:ws="ws"
on themake
element, which is then inherited by themodel
elementsWhat happens if we add our corrected root element?
Cool, so the
somename2
element now inherits its namespace definition from the root element, and doesn't re-declare it. But what's wrong with themake
andmodel
s? Let's compare:That third argument is supposed to be the namespace URI, not just the prefix. So when we gave it as
'ws'
, SimpleXML assumed we wanted to declare the actual namespace URI asws
, so added anxmlns
attribute to do so.What we actually wanted was all the elements to be in the same namespace:
Great, we've got our desired output!
Tidying Up
But that code looks rather ugly, why do we have to repeat the URI everywhere? Well, as far as SimpleXML is concerned, there's not much choice: the same prefix can mean different things in different parts of the document, so we have to tell it what we want.
What we can do is tidy up our code using a variable or constant for the namespace URI, rather than writing it out in full each time:
There's nothing special about the name
XMLNS_WS
here, and it could equally be a variable, a class constant, or a namespace constant. The code runs exactly the same, it's just a little easier on the eye.SimpleXML has an unusual quirk where the namespace prefixes are filtered from the root element. I'm not sure why it does this.
However, a workaround I've used has been to basically prefix the prefix, so that the parser only removes the first ones, and leaves the second
This seems to work for me, though I'm interested to understand why SimpleXML does this exactly.