问题
这段代码的问题在于第一行:
$xmlTest = new SimpleXMLElement('<Test/>', 0, false, 'ws', true);
在做任何其他事情之前,让我们将其输出为 XML:
echo $xmlTest->asXML();
<?xml version="1.0"?>
<Test/>
这是有道理的,我们拿出了我们投入的东西。
手册对$ns 参数的作用相当模糊,但在这种情况下,它没有做任何有用的事情。它所做的是设置读取 XML 的上下文,以便->foo 引用<ws:foo> 和['bar'] 引用ws:bar="..."。它不会改变 XML 本身的结构。
设置根命名空间
要在根元素上设置命名空间,我们只需将其包含在定义根元素的字符串中:
$xmlTest = new SimpleXMLElement('<ws:Test xmlns:ws="http://microsoft.com/wsdl/types/" />');
echo $xmlTest->asXML();
<?xml version="1.0"?>
<ws:Test xmlns:ws="http://microsoft.com/wsdl/types/"/>
到目前为止一切都很好......
为子级设置命名空间
接下来,让我们仔细检查问题中的代码实际输出的内容(我添加了一些空格以使其更具可读性):
<?xml version="1.0"?>
<Test>
<ws:somename2 xmlns:ws="http://microsoft.com/wsdl/types/">somevalue2</ws:somename2>
<ws:make xmlns:ws="ws">
<ws:model>foo</ws:model>
<ws:model>bar</ws:model>
</ws:make>
</Test>
好的,所以这个文档包含两个命名空间声明:
-
xmlns:ws="http://microsoft.com/wsdl/types/" 在 somename2 元素上
-
xmlns:ws="ws" 在make 元素上,然后由model 元素继承
如果我们添加正确的根元素会发生什么?
<?xml version="1.0"?>
<ws:Test xmlns:ws="http://microsoft.com/wsdl/types/">
<ws:somename2>somevalue2</ws:somename2>
<ws:make xmlns:ws="ws">
<ws:model>foo</ws:model>
<ws:model>bar</ws:model>
</ws:make>
</ws:Test>
酷,所以somename2 元素现在从根元素继承其命名空间定义,并且不重新声明它。但是make 和models 有什么问题?对比一下:
$xmlTest->addChild("ws:somename2", "somevalue2", 'http://microsoft.com/wsdl/types/');
$make = $xmlTest->addChild('ws:make', null, 'ws');
第三个参数应该是 命名空间 URI,而不仅仅是前缀。因此,当我们将其指定为 'ws' 时,SimpleXML 假设我们想要将实际的命名空间 URI 声明为 ws,因此添加了一个 xmlns 属性来执行此操作。
我们真正想要的是所有元素都在同一个命名空间中:
$xmlTest = new SimpleXMLElement('<ws:Test xmlns:ws="http://microsoft.com/wsdl/types/" />');
$xmlTest->addChild("ws:somename2", "somevalue2", 'http://microsoft.com/wsdl/types/');
$make = $xmlTest->addChild('ws:make', null, 'http://microsoft.com/wsdl/types/');
#$make->addAttribute('name','Ford', 'http://microsoft.com/wsdl/types/');
$make->addChild('ws:model', 'foo', 'http://microsoft.com/wsdl/types/');
$make->addChild('ws:model', 'bar', 'http://microsoft.com/wsdl/types/');
echo $xmlTest->asXML();
<?xml version="1.0"?>
<ws:Test xmlns:ws="http://microsoft.com/wsdl/types/">
<ws:somename2>somevalue2</ws:somename2>
<ws:make>
<ws:model>foo</ws:model>
<ws:model>bar</ws:model>
</ws:make>
</ws:Test>
太好了,我们得到了我们想要的输出!
整理
但是那段代码看起来相当难看,为什么我们必须到处重复 URI?好吧,就 SimpleXML 而言,没有太多选择:相同的前缀在文档的不同部分可能意味着不同的东西,所以我们必须告诉它我们想要什么。
我们可以做的是使用命名空间URI的变量或常量来整理我们的代码,而不是每次都完整地写出来:
define('XMLNS_WS', 'http://microsoft.com/wsdl/types/');
$xmlTest = new SimpleXMLElement('<ws:Test xmlns:ws="' . XMLNS_WS . '" />');
$xmlTest->addChild("ws:somename2", "somevalue2", XMLNS_WS);
$make = $xmlTest->addChild('ws:make', null, XMLNS_WS);
#$make->addAttribute('name','Ford', XMLNS_WS);
$make->addChild('ws:model', 'foo', XMLNS_WS);
$make->addChild('ws:model', 'bar', XMLNS_WS);
这里的名称XMLNS_WS 没有什么特别之处,它同样可以是变量、类常量或命名空间常量。代码运行完全一样,只是看起来更容易一些。