Упростить PHP DOM XML парсинг - как?

Я провел целые дни с функциями PHP DOM, но пока не могу понять, как это работает. :( У меня есть простой XML-файл, который выглядит хорошо, но я не могу использовать его так, как я думаю, когда я создал его структуру.

Пример фрагмента XML:

-pages //root element
    -page id="1" //we can have any number of pages
        -product id="364826" //we can have any number of products
            -SOME_KIND_OF_VALUE
            -ANOTHER_VALUE
            ...

Моя первоначальная идея состояла в том, чтобы ускорить рабочий процесс моего клиента, поэтому я выбрасываю старый CSV и начали использовать XML.

Проблема 1: Когда я группирую продукты на странице, я использую setIdAttribute , чтобы предотвратить сохранение одной и той же страницы в дереве более одного раза. Это работает нормально, пока чтение не произойдет, потому что эти идентификаторы привязаны к некоторому DTD (основанному на getElementById ).

Вопрос 1: Как я могу написать простое DTD, которое предоставляет эту необходимую информацию, чтобы я мог использовать getElementById также на этапе чтения?

Проблема 2: Поскольку у меня есть страницы, я бы хотел загрузить как можно меньше информации. Вот почему я создал атрибут id на страницах. Теперь я не могу получить доступ к моей странице id = "2" напрямую, потому что проблема 1 выше ( getElementById не имеет смысла в настоящее время). Каким-то образом мне удалось получить необходимую информацию о каждом продукте на данной странице, но мой код выглядит страшно:

$doc      = DOMDocument::load('data.xml');
$xpath    = new DOMXPath($doc);
$query    = '/pages/page[' . $page . ']'; //$page is fine: was set earlier
$products = $xpath->query($query);
$_prods   = $doc->getElementsByTagName('product');
foreach($_prods as $product){
    foreach($product->childNodes as $node){
        echo $node->nodeName . ": " . $node->nodeValue . "
"; } }

Квестон 2: Я думаю, что приведенный выше код является примером о , как не для анализа XML. Но из-за моего ограниченного знания функций PHP в DOM я не могу написать более понятную. Я попробовал какое-то тривиальное решение, но ни один из них не помог мне.

7
задан Machavity 16 October 2018 в 15:05
поделиться

1 ответ

Решение проблемы 1:

W3C определяет : значение атрибута xml: id в качестве атрибута идентификатора в документах XML и определяет обработку этого атрибута для идентификации идентификаторов в отсутствие проверки, без извлечения внешних ресурсов и без использования внутреннего подмножества.

Другими словами, когда вы используете

$element->setAttribute('xml:id', 'test');

, вам не нужно вызывать setIdAttribute или указывать DTD или схему. DOM распознает атрибут xml: id при использовании с getElementById без необходимости проверять документ или что-то еще. Это подход с наименьшими усилиями. Однако обратите внимание, что в зависимости от вашей ОС и версии libxml вы вообще не получите getElementById для работы.

Решение проблемы 2:

Даже если идентификаторы не могут быть получены с помощью getElementById , вы все равно можете получить их с помощью XPath:

$xpath->query('/pages/page[@id=1]');

определенно сработает. И вы также можете напрямую получить дочерние продукты продукта для конкретной страницы:

$xpath->query('//pages/page[@id=1]/products');

Кроме того, вы очень мало можете сделать, чтобы код DOM выглядел менее подробным, потому что это действительно подробный интерфейс. Так и должно быть, потому что DOM - это не зависящий от языка интерфейс, снова определенный W3C .


ИЗМЕНИТЬ после комментария ниже

Это работает, как я объяснил выше. Вот вам полный тестовый пример. Первая часть предназначена для записи новых файлов XML с помощью DOM. Здесь нужно установить атрибут xml: id . Вы используете его вместо обычного атрибута id без пространства имен.

// Setup
$dom = new DOMDocument;
$dom->formatOutput = TRUE;
$dom->preserveWhiteSpace = FALSE;
$dom->loadXML('<pages/>');

// How to set a valid id attribute when not using a DTD or Schema
$page1 = $dom->createElement('page');
$page1->setAttribute('xml:id', 'p1');
$page1->appendChild($dom->createElement('product', 'foo1'));
$page1->appendChild($dom->createElement('product', 'foo2'));

// How to set an ID attribute that requires a DTD or Schema when reloaded
$page2 = $dom->createElement('page');
$page2->setAttribute('id', 'p2');
$page2->setIdAttribute('id', TRUE);
$page2->appendChild($dom->createElement('product', 'bar1'));
$page2->appendChild($dom->createElement('product', 'bar2'));

// Appending pages and saving XML
$dom->documentElement->appendChild($page1);
$dom->documentElement->appendChild($page2);
$xml = $dom->saveXML();
unset($dom, $page1, $page2);
echo $xml;

Будет создан XML-файл, подобный этому:

<?xml version="1.0"?>
<pages>
  <page xml:id="p1">
    <product>foo1</product>
    <product>foo2</product>
  </page>
  <page id="p2">
    <product>bar1</product>
    <product>bar2</product>
  </page>
</pages>

Когда вы снова читаете в XML, новый экземпляр DOM больше не знает, что вы объявили идентификатор без пространства имен атрибут как атрибут ID с setIdAttribute . Он по-прежнему будет в XML, но атрибут id будет просто обычным атрибутом. Вы должны знать, что атрибуты ID являются специальными в XML.

// Load the XML we created above
$dom = new DOMDocument;
$dom->loadXML($xml);

Теперь для некоторых тестов:

echo "\n\n GETELEMENTBYID RETURNS ELEMENT WITH XML:ID \n\n";
foreach( $dom->getElementById('p1')->childNodes as $product) {
    echo $product->nodeValue; // Will output foo1 and foo2 with whitespace
}

Вышеупомянутое работает, потому что совместимый с DOM синтаксический анализатор должен распознавать xml: id как атрибут ID, независимо от DTD или схемы. Это объясняется в спецификациях, указанных выше. Причина, по которой он выводит пробелы, заключается в том, что из-за форматированного вывода между открывающим тегом, двумя тегами продукта и закрывающими тегами есть узлы DOMText, поэтому мы повторяем пять узлов. Концепция узла имеет решающее значение для понимания при работе с XML.

echo "\n\n GETELEMENTBYID CANNOT FETCH NORMAL ID \n\n";
foreach( $dom->getElementById('p2')->childNodes as $product) {
    echo $product->nodeValue; // Will output a NOTICE and a WARNING
}

Вышеуказанное не будет работать, потому что id не является атрибутом ID. Чтобы синтаксический анализатор DOM распознал его как таковой, вам потребуется DTD или схема, и XML должен быть проверен по ним.

echo "\n\n XPATH CAN FETCH NORMAL ID \n\n";
$xPath = new DOMXPath($dom);
$page2 = $xPath->query('/pages/page[@id="p2"]')->item(0);
foreach( $page2->childNodes as $product) {
    echo $product->nodeValue; // Will output bar1 and bar2
}

XPath, с другой стороны, буквально описывает атрибуты, что означает, что вы можете запросить DOM для элемента страницы с атрибутом id , если getElementById недоступен. Обратите внимание, что для запроса страницы с идентификатором p1 вам необходимо включить пространство имен, например @xml: id = "p1" .

echo "\n\n XPATH CAN FETCH PRODUCTS FOR PAGE WITH ID \n\n";
$xPath = new DOMXPath($dom);
foreach( $xPath->query('/pages/page[@id="p2"]/product') as $product ) {
    echo $product->nodeValue; // Will output bar1 and bar2 w\out whitespace
}

Как уже говорилось, вы также можете использовать XPath для запроса чего-либо еще в документе.Он не будет выводить пробелы, потому что он вернет только элементы product под страницей с идентификатором p2.

Вы также можете обойти всю DOM из узла. Это древовидная структура. Поскольку DOMNode является наиболее важным классом в DOM, вы хотите ознакомиться с ним.

echo "\n\n TRAVERSING UP AND DOWN \n\n";
$product = $dom->getElementsByTagName('product')->item(2);
echo $product->tagName; // 'product'
echo $dom->saveXML($product); // '<product>bar1</product>'

// Going from bar1 to foo1
$product = $product->parentNode // Page Node
                   ->parentNode // Pages Node
                   ->childNodes->item(1)  // Page p1
                   ->childNodes->item(1); // 1st Product

echo $product->nodeValue; // 'foo1'

// from foo1 to foo2 it is two(!) nodes because the XML is formatted
echo $product->nextSibling->nodeName; // '#text' with whitespace and linebreak
echo $product->nextSibling->nextSibling->nodeName; // 'product'
echo $product->nextSibling->nextSibling->nodeValue; // 'foo2'

Кстати, да, у меня есть опечатка в исходном коде выше. Это товар , а не товар . Но я считаю едва ли оправданным утверждение, что код не работает, когда все, что вам нужно изменить, - это s . Это слишком похоже на желание, чтобы тебя кормили с ложечки.

12
ответ дан 6 December 2019 в 21:08
поделиться
Другие вопросы по тегам:

Похожие вопросы: