Я получил фид Google Shopping как этот (выдержка):
<?xml version="1.0" encoding="utf-8" ?>
<rss version="2.0" xmlns:g="http://base.google.com/ns/1.0">
...
<g:id><![CDATA[Blah]]></g:id>
<title><![CDATA[Blah]]></title>
<description><![CDATA[Blah]]></description>
<g:product_type><![CDATA[Blah]]></g:product_type>
Теперь SimpleXML может читать теги «title» и «description», но не может читать теги с префиксом «g:».
В этом конкретном случае есть решения для stackoverflow, использующие функцию children.
Но я не только хочу читать Google Shopping XML, мне нужно, чтобы он не зависел от структуры или пространства имен, я ничего не знаю о файле (я рекурсивно перебираю узлы как многомерный массив).
Есть ли способ сделать это с SimpleXML? Я мог бы заменить двоеточия, но я хочу иметь возможность хранить массив и повторно собирать XML (в данном случае специально для Google Shopping), поэтому я не хочу потерять информацию.
Вы хотите использовать SimpleXMLElement извлечь данные из XML и преобразовать их в массив.
Как правило, это возможно, но с некоторыми оговорками. До пространств имен XML ваш XML поставляется с CDATA. Для преобразования XML в массивы с помощью Simplexml необходимо преобразовать CDATA в текст при загрузке строки XML. Это сделано с LIBXML_NOCDATA
флаг. Пример:
$xml = simplexml_load_string($buffer, null, LIBXML_NOCDATA);
print_r($xml); // print_r shows how SimpleXMLElement does array conversion
Это дает вам следующий вывод:
SimpleXMLElement Object
(
[@attributes] => Array
(
[version] => 2.0
)
[title] => Blah
[description] => Blah
)
Как вы уже можете видеть, нет удобной формы для представления атрибутов в массиве, поэтому Simplexml по соглашению помещает их в @attributes
ключ.
Другая проблема, с которой вы столкнулись, заключается в обработке этих нескольких пространств имен XML. В предыдущем примере конкретное пространство имен не использовалось. Это дефолт Пространство имен. Когда вы конвертируете SimpleXMLElement в массив, пространство имен SimpleXMLElement используется. Поскольку ни одно не было явно указано, дефолт пространство имен было занято.
Но если вы укажете пространство имен при создании массива, тот пространство имен занято.
Пример:
$xml = simplexml_load_string($buffer, null, LIBXML_NOCDATA, "http://base.google.com/ns/1.0");
print_r($xml);
Это дает вам следующий вывод:
SimpleXMLElement Object
(
[id] => Blah
[product_type] => Blah
)
Как вы можете видеть, на этот раз пространство имен, которое было указано, когда SimpleXMLElement был создан используется в преобразовании массива: http://base.google.com/ns/1.0
,
Когда вы пишете, вы хотите принять во внимание все пространства имен из документа, вам нужно сначала получить их, включая стандартное:
$xml = simplexml_load_string($buffer, null, LIBXML_NOCDATA);
$namespaces = [null] + $xml->getDocNamespaces(true);
Затем вы можете перебрать все пространства имен и рекурсивно объединить их в один массив показано ниже:
$array = [];
foreach ($namespaces as $namespace) {
$xml = simplexml_load_string($buffer, null, LIBXML_NOCDATA, $namespace);
$array = array_merge_recursive($array, (array) $xml);
}
print_r($array);
Это, наконец, должно создать и вывести массив по вашему выбору:
Array
(
[@attributes] => Array
(
[version] => 2.0
)
[title] => Blah
[description] => Blah
[id] => Blah
[product_type] => Blah
)
Как видите, это вполне возможно при SimpleXMLElement. Однако важно, чтобы вы понимали, как SimpleXMLElement преобразуется в массив (или сериализуется в JSON, который следует тем же правилам). Для имитации SimpleXMLElement-преобразование в массив, вы можете использовать print_r
для быстрого вывода.
Обратите внимание, что не все конструкции XML могут быть одинаково хорошо преобразованы в массив. Это не является конкретным ограничением Simplexml, а заключается в природе структур, которые может представлять XML, и структур, которые может представлять массив.
Поэтому чаще всего лучше хранить XML внутри такого объекта, как SimpleXMLElement (или же DOMDocument) обращаться к данным и работать с ними, а не с массивом.
Тем не менее, это прекрасно для преобразования данных в массив, если вы знаете, что делаете, и вам не нужно писать много кода, чтобы получить доступ к членам глубже по дереву в структуре. Иначе SimpleXMLElement предпочтительнее массива, поскольку он предоставляет выделенный доступ не только ко многим функциям XML, но и к запросам, как к базе данных с SimpleXMLElement::xpath
метод. Вам нужно было бы написать много строк собственного кода для доступа к данным внутри дерева XML, которые удобны для массива.
Чтобы получить лучшее из обоих миров, вы можете расширить SimpleXMLElement для ваших конкретных потребностей конверсии:
$buffer = <<<BUFFER
<?xml version="1.0" encoding="utf-8" ?>
<rss version="2.0" xmlns:g="http://base.google.com/ns/1.0">
...
<g:id><![CDATA[Blah]]></g:id>
<title><![CDATA[Blah]]></title>
<description><![CDATA[Blah]]></description>
<g:product_type><![CDATA[Blah]]></g:product_type>
</rss>
BUFFER;
$feed = new Feed($buffer, LIBXML_NOCDATA);
print_r($feed->toArray());
Который выводит:
Array
(
[@attributes] => stdClass Object
(
[version] => 2.0
)
[title] => Blah
[description] => Blah
[id] => Blah
[product_type] => Blah
[@text] => ...
)
Для базовой реализации:
class Feed extends SimpleXMLElement implements JsonSerializable
{
public function jsonSerialize()
{
$array = array();
// json encode attributes if any.
if ($attributes = $this->attributes()) {
$array['@attributes'] = iterator_to_array($attributes);
}
$namespaces = [null] + $this->getDocNamespaces(true);
// json encode child elements if any. group on duplicate names as an array.
foreach ($namespaces as $namespace) {
foreach ($this->children($namespace) as $name => $element) {
if (isset($array[$name])) {
if (!is_array($array[$name])) {
$array[$name] = [$array[$name]];
}
$array[$name][] = $element;
} else {
$array[$name] = $element;
}
}
}
// json encode non-whitespace element simplexml text values.
$text = trim($this);
if (strlen($text)) {
if ($array) {
$array['@text'] = $text;
} else {
$array = $text;
}
}
// return empty elements as NULL (self-closing or empty tags)
if (!$array) {
$array = NULL;
}
return $array;
}
public function toArray() {
return (array) json_decode(json_encode($this));
}
}
Что является усыновлением с пространствами имен Изменение правил кодирования JSON пример, приведенный в SimpleXML и JSON кодируют в PHP — часть III и конец.
Других решений пока нет …