Я провел целые дни с функциями 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 я не могу написать более понятную. Я попробовал какое-то тривиальное решение, но ни один из них не помог мне.
Решение проблемы 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
. Это слишком похоже на желание, чтобы тебя кормили с ложечки.