2010-08-04 57 views
7

我花了整整一天的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防止存储在同一页面树不止一次该作品罚款直到阅读发生因为这些ID与某种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 . "<br />"; 
    } 
} 

Queston 2: 我认为上面的代码是例如约如何不解析一个XML。但由于我对PHP的DOM功能知之甚少,我不能自己写一个更清洁的东西。我尝试了一些简单的解决方案,但没有一个为我工作。

请帮助我,如果你可以。

谢谢, FABRIK

+0

我假设你刚才给出了你的XML文档的结构大纲?因为它不是XML你发布的(我只是想确定;))。 – 2010-08-04 11:45:22

+0

当然这只是一个大纲。 XML验证很好,看起来不错,不像我的代码:o – fabrik 2010-08-04 11:59:14

+0

simplexml函数对于您的需求太简单了吗? – stillstanding 2010-08-04 12:11:44

回答

12

解决问题1:

W3C defines:所述属性xml:id作为XML文档的ID属性的含义和定义该属性以识别所述ID的处理没有验证,没有获取外部资源,也没有依赖内部子集。

换句话说,当你使用

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

你不需要调用setIdAttribute,也没有指定DTD或模式。 DOM在与getElementById一起使用时将识别xml:id属性,而无需验证文档或任何内容。这是最省力的方法。请注意,根据你的操作系统和libxml的版本,你根本不会得到getElementById

解决Problem2:

即使没有被标识与getElementById可提取,你仍然可以非常使用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再次读取

<?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> 

,新的DOM实例不再知道您已声明非名称空间id属性作为ID属性与setIdAttribute。它仍然在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或模式。这在上面链接的规格中有解释。 输出空白的原因是由于格式化输出在开始标记,两个产品标记和结束标记之间存在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是文字有关的属性,这意味着你可以查询DOM的网页元素与属性id如果getElementById不可用。请注意,要查询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来查询文档中的其他内容。这个将不会输出空格,因为它只会返回页面下方的product元素,其ID为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' 

在旁注上,是的,我在上面的原始代码中有一个错字。它是product而不是products。但是我发现,当你必须改变的是s时,声称代码不起作用是没有道理的。这只是感觉太想要被舀。

+0

在写入XML文件之前设置页面的“id”工作正常。当我阅读XML时,我不能/不想设置属性,因为我想根据这些属性读取XML源。所以问题1还没有解决。 问题2绝对没有解决,您的第一个XPath查询失败。第二个失败,因为我没有节点'产品',而是我有许多'产品'节点内的一页。 (这是在我的问题中定义的。) – fabrik 2010-08-04 13:16:34

+0

@fabrik这两个问题都解决了。查看我的更新以获取证明。 – Gordon 2010-08-04 16:39:42

+1

太棒了!谢谢你的深入解释。它速度很快,完全符合我的要求。除了一件事情,但这是我的错:我在示例XML片段中犯了一个错误,因为我需要节点的名称和值,所以我还需要两个foreach:o当然,我会接受你的答案,因为它是做的伎俩。再次感谢! – fabrik 2010-08-05 06:46:10