Разрешить пространства имен с помощью SimpleXML независимо от структуры или пространства имен

Я получил фид 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), поэтому я не хочу потерять информацию.

0

Решение

Вы хотите использовать 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 и конец.

9

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]