Добавление пространства имен при использовании 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');