все,
У меня есть файл inputXml.xml, как показано ниже:
<content> <item name="book" label="Book"> <![CDATA[ book name ]]> </item> <item name="price" label="Price"> <![CDATA[ 35 ]]> </item> </content>
И когда я использую код, как показано ниже, для анализа XML-файла:
$obj = simplexml_load_string(file_get_contents($inputXml),'SimpleXMLElement', LIBXML_NOCDATA); $json = json_encode($obj); $inputArray = json_decode($json,TRUE);
Я получаю массив, как показано ниже:
[content] => Array ( [item] => Array ( [0] => book name [1] => 35 ) )
Мне интересно, возможно ли получить ассоциативный массив, используя значение атрибутов «имя» или «метка» в качестве ключа, как показано ниже:
[content] => Array ( [item] => Array ( [name] => book name [price] => 35 ) )
Я быстро просмотрел документы SimpleXMLElement
, которые показали, что на самом деле довольно легко построить массив, как вы хотите:
$xml = simplexml_load_file($file, 'SimpleXMLElement', LIBXML_NOCDATA); $result = array();//store assoc array here foreach ($xml->item as $item) {//iterate over item nodes if (isset($item['name'])) {//attributes are accessible as array keys $result[(string) $item['name']] = (string) $item;//casts required! } } var_dump($result);
Это связано с тем, что SimpleXMLElement
является SimpleXMLElement
объектом, поэтому вы можете получить доступ к его свойствам, как если бы это был массив. Однако нам нужно SimpleXMLElement
свойства, потому что они все экземпляры класса SimpleXMLElement
.
Вышеприведенный код является упрощенной версией того, что я написал вначале:
$xml = simplexml_load_file($fileName, 'SimpleXMLElement', LIBXML_NOCDATA); foreach ($xml as $name => $node) { if ($name === 'item') { $key = false; foreach ($node->attributes() as $name => $attr) { if ($name == 'name') { $key = (string) $attr;//attr is object, still break; } } if ($key !== false) $result[$key] = (string) $node; } }
Это тоже работает. Однако код выглядит, я думаю, вы согласитесь, довольно грязный. Я придерживаюсь первой версии, которую я разместил здесь …
Первоначальный ответ (с использованием DOMDocument
)
Я рассмотрю, как это сделать, используя simpleXML, но пока я расскажу, как я начал заниматься данными CDATA с помощью DOMDocument
API:
$dom = new DOMDocument; $dom->load($file); //get items $items = $dom->getElementsByTagName('item'); $cData = array(); foreach ($items as $node) { if ($node->hasChildNodes()) { foreach ($node->childNodes as $cNode) { if ($cNode->nodeType === XML_CDATA_SECTION_NODE) $cData[] = $cNode->textContent;//get contents } } }
Используйте это в сочетании с другими методами, такими как $node->attributes->getNamedItem('name');
получить атрибут узла, $node->attributes->getNamedItem('name')->nodeValue;
чтобы получить значение этого атрибута.
Я признаю, что DOMDocument
api выглядит довольно многословным (потому что это так), и он чувствует себя немного неуклюжим (как это всегда делалось), но это действительно не так сложно понять, как только вы прочтете руководство
Прежде всего, вас обманул какой-то другой код, который вам понадобится json_encode
и json_decode
чтобы получить массив из SimpleXMLElement . Вместо этого вам нужно всего лишь передать массив:
$inputArray = (array) $obj;
Тогда у вас возникла проблема в том, что сериализация массива, которую вы ищете, не является сериализацией по умолчанию, которую SimpleXMLElement предоставляет с этим XML.
Кроме того, еще одна небольшая проблема, с которой вы сталкиваетесь, – это зависимость от использования LIBXML_NOCDATA
потому что в противном случае вы не получили бы формат, чтобы приблизиться. Но не в зависимости от этого флага (и, следовательно, в точке, если базовый XML будет использовать CDATA или не для XML-кодирования значения элемента), было бы полезно также получить определенную стабильность кода.
Поскольку SimpleXMLElement не обеспечивает ваше желаемое поведение, у вас обычно есть два варианта: Расширение от SimpleXMLElement или его украшение. Обычно я предлагаю украшение, поскольку расширение ограничено. Например, вы не можете вмешиваться через расширение с помощью (array)
casting, однако вы можете использовать сериализацию JSON. Но это не то, что вы ищете, вы ищете сериализацию массива.
Таким образом, для типичной сериализации массива SimpleXMLElement вы можете реализовать это с помощью сериализатора и объекта стратегии о том, как массировать сериализацию определенного элемента.
Для этого сначала нужен сериализатор :
interface ArraySerializer { public function arraySerialize(); } class SimpleXMLArraySerializer implements ArraySerializer { /** * @var SimpleXMLElement */ private $subject; /** * @var SimpleXMLArraySerializeStrategy */ private $strategy; public function __construct(SimpleXMLElement $element, SimpleXMLArraySerializeStrategy $strategy = NULL) { $this->subject = $element; $this->strategy = $strategy ?: new DefaultSimpleXMLArraySerializeStrategy(); } public function arraySerialize() { $strategy = $this->getStrategy(); return $strategy->serialize($this->subject); } /** * @return SimpleXMLArraySerializeStrategy */ public function getStrategy() { return $this->strategy; } }
Этот массив-сериализатор все еще не имеет возможности для сериализации. Это было направлено на стратегию, чтобы впоследствии ее можно было легко обменять. Для этого используется стратегия по умолчанию:
abstract class SimpleXMLArraySerializeStrategy { abstract public function serialize(SimpleXMLElement $element); } class DefaultSimpleXMLArraySerializeStrategy extends SimpleXMLArraySerializeStrategy { public function serialize(SimpleXMLElement $element) { $array = array(); // create array of child elements if any. group on duplicate names as an array. foreach ($element as $name => $child) { if (isset($array[$name])) { if (!is_array($array[$name])) { $array[$name] = [$array[$name]]; } $array[$name][] = $this->serialize($child); } else { $array[$name] = $this->serialize($child); } } // handle SimpleXMLElement text values. if (!$array) { $array = (string)$element; } // return empty elements as NULL (self-closing or empty tags) if (!$array) { $array = NULL; } return $array; } }
Этот объект содержит обычный способ преобразования элемента SimpleXMLElement в массив. Он ведет себя сопоставимо с тем, что уже делает ваш XML как SimpleXMLElement с LIBXML_NOCDATA
. Однако это не проблема с CDATA. Чтобы показать это, следующий пример уже дает результат, который у вас есть:
$obj = new SimpleXMLElement($xml); $serializer = new SimpleXMLArraySerializer($obj); print_r($serializer->arraySerialize());
Теперь, поскольку до сих пор сериализация массива была реализована в своих типах, ее легко изменить в соответствии с потребностями. Для элемента контента у вас есть другая стратегия, чтобы превратить его в массив. Это также намного проще:
class ContentXMLArraySerializeStrategy extends SimpleXMLArraySerializeStrategy { public function serialize(SimpleXMLElement $element) { $array = array(); foreach ($element->item as $item) { $array[(string) $item['name']] = (string) $item; } return array('item' => $array); } }
SimpleXMLArraySerializer
это с SimpleXMLArraySerializer
в правильном состоянии. Например, в зависимости от имени элемента:
... /** * @return SimpleXMLArraySerializeStrategy */ public function getStrategy() { if ($this->subject->getName() === 'content') { return new ContentXMLArraySerializeStrategy(); } return $this->strategy; } }
Теперь тот же пример сверху:
$obj = new SimpleXMLElement($xml); $serializer = new SimpleXMLArraySerializer($obj); print_r($serializer->arraySerialize());
даст вам желаемый результат (украшенный):
Array ( [item] => Array ( [book] => book name [price] => 35 ) )
Поскольку ваш XML, вероятно, имеет только этот один элемент, я бы сказал, что такой уровень абстракции может быть немного. Однако, если XML изменится, и у вас есть несколько требований к формату массива в одном документе, это правдоподобный путь.
Сериализация по умолчанию, которую я использовал в моем примере, основана на примере украшения в SimpleXML и JSON Encode в PHP – Part III и End .