解决问题 1:
W3C defines:xml:id 属性的含义作为 XML 文档中的 ID 属性,并定义了在没有验证、不获取外部资源且不依赖于一个内部子集。
换句话说,当你使用
$element->setAttribute('xml:id', 'test');
您无需调用setIdAttribute,也无需指定DTD 或Schema。当与getElementById 一起使用时,DOM 将识别xml:id 属性,而无需验证文档或其他任何内容。这是最省力的方法。但请注意,根据您的操作系统和 libxml 版本,您根本无法使用 getElementById。
解决问题 2:
即使使用 getElementById 无法获取 ID,您仍然可以使用 XPath 获取它们:
$xpath->query('/pages/page[@id=1]');
肯定会奏效。您还可以直接获取特定页面的子产品:
$xpath->query('//pages/page[@id=1]/products');
除此之外,几乎没有什么可以让 DOM 代码看起来不那么冗长,因为它确实是一个冗长的接口。必须是,因为DOM is a language agnostic interface, again defined by the W3C。
在下方评论后编辑
它就像我上面解释的那样工作。这是一个完整的测试用例。第一部分是编写 使用 DOM 的新 XML 文件。这就是您需要设置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 的 ID 属性。它仍将在 XML 中,但 id 属性将只是一个常规属性。 You have to be aware that ID attributes are special in 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 或 Schema。这在上面链接的规范中进行了解释。
它输出空格的原因是因为格式化输出在开始标签、两个产品标签和结束标签之间有 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 或 Schema,并且必须针对它验证 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 是关于属性的字面意思,这意味着如果getElementById 不可用,您可以查询具有属性id 的页面元素的DOM。请注意,要查询 ID 为 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 查询文档中的任何其他内容。这个不会输出空格,因为它只会返回id为p2的页面下方的product元素。
你也可以从一个节点遍历整个 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'
顺便说一句,是的,我在上面的原始代码中有错字。这是product 不是products。但我发现,当您只需要更改 s 时,声称代码不起作用是不合理的。这感觉太像想要被喂了。