Добавление пространства имен при использовании SimpleXMLElement
Это то, что я после
<!-- 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>
Это мой текущий код
<!-- 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());
но выводит
<!-- language: lang-xml -->
<Test>
<ws:somename2>somevalue2</ws:somename2>
<ws:make>
<ws:model>foo</ws:model>
<ws:model>bar</ws:model>
</ws:make>
</Test>
Как вы видите, ws: отсутствует в тесте
Ответы
Ответ 1
SimpleXML имеет необычную причуду, где префиксы пространства имен фильтруются из корневого элемента. Я не уверен, почему он это делает.
Однако обходной путь, который я использовал, был в основном префикс префикса, так что синтаксический анализатор удаляет только первые и оставляет второй
$xmlTest = new SimpleXMLElement('<xmlns:ws:Test></xmlns:ws:Test>', LIBXML_NOERROR, false, 'ws', true);
$xmlTest->addAttribute('xmlns:xmlns:ws', 'http://url.to.namespace');
$xmlTest->addAttribute('xmlns:xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
Кажется, это работает для меня, хотя мне интересно понять, почему SimpleXML делает это именно так.
Ответ 2
Проблема
Проблема с этим кодом находится в самой первой строке:
$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
и model
s? Давайте сравним:
$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
, и это может быть как переменная, константа класса, так и константа пространства имен. Код работает точно так же, он немного легче на глазу.
Ответ 3
$xmlTest = new \SimpleXMLElement('<ws:Test></ws:Test>', LIBXML_NOERROR, false, 'ws', true);
$xmlTest->addAttribute('xmlns:xmlns:ws', 'http://url.to.namespace');
$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');