【问题标题】:What is the difference in PHP between DOM nodes and XMLreader->expand() Nodes?DOM 节点和 XMLreader->expand() 节点之间的 PHP 有什么区别?
【发布时间】:2023-03-04 19:05:01
【问题描述】:

我重写了一个脚本,该脚本使用 PHP DOM 函数来遍历具有如下结构的 XML 文件:

<file>
    <record>
        <Source>
            <SourcePlace>
                <Country>Germany</Country>
            </SourcePlace>          
        </Source>
        <Person>
            <Name>
                <firstname>John</firstname>
                <lastname>Doe<lastname>
            </Name>
        </Person>
    </record>
    <record>
    ..
    </record>
</file>

我已将它替换为一个脚本,该脚本使用 XMLreader 查找每个单独的记录并将其转换为 DOM 文档,然后对其进行迭代。通过检查节点是否有子节点来完成迭代:

function findLeaves($node) {
   echo "nodeType: ".$node->nodeType.", nodeName:". $node->nodeName."\n";
   if($node->hasChildNodes() )  {
       foreach($node->childNodes as $element) {
           findLeaves($element)
       }
   }
   ELSE { <do something with leave> }
}

问题在于 findLeaves() 函数的行为在两者之间发生了变化。在 DOM 下,没有值的节点(如 Source)没有 #text 子节点。上面的输出是:

nodeType:1, nodeName:Source
nodeType:1, nodeName:SourcePlace
nodeType:1, nodeName:Country
nodeType:3, nodeName:#text ``` 

在 XMLreader 下变成:

nodeType: 1, nodeName:Source
nodeType: 3, nodeName:#text
nodeType: 1, nodeName:SourcePlace
nodeType: 3, nodeName:#text
nodeType: 1, nodeName:Country 

在输入这个函数之前,我已经检查了数据的 saveXML() 结果,但是除了一些额外的空格之外,它看起来是一样的。造成这种差异的原因可能是什么?

DOM下findleaves()函数之前加载文件的代码:

$xmlDoc = new DOMDocument();
$xmlDoc->preserveWhiteSpace = false; 
$xmlDoc->load($file);

$xpath = new DOMXPath($xmlDoc);
$records = $xpath->query('//record');

foreach($records as $record) {
        foreach ($xpath->query('.//Source', $record) as $source_record) {
            findleaves($source_record);

        }
}

XMLreader下findleaves()函数前加载文件的代码:

$xmlDoc = new XMLReader()
$xmlDoc->open($file)
while ($xmlDoc->read() ) { 
    if ($xmlDoc->nodeType == XMLReader::ELEMENT && $xmlDoc->name == 'record') {         
        $record_node = $xmlDoc->expand();

        $recordDOM = new DomDocument();
        $n = $recordDOM->importNode($record_node,true); 
        $recordDOM->appendChild($n);document        
        $recordDOM->preserveWhiteSpace = false; 

        $xpath = new DOMXPath($recordDOM);      
        $records = $xpath->query('//record'); 

        foreach($records as $record) {
        foreach ($xpath->query('.//Source', $record) as $source_record) {
            findleaves($source_record);
        }
}

【问题讨论】:

    标签: php xml dom xmlreader


    【解决方案1】:

    DOMDocument::$preserveWhiteSpace 属性会影响加载/解析功能。因此,如果您使用XMLReader::expand(),则文档的属性无效 - 您不会将 XML 字符串 加载 到其中。

    您已经在使用 Xpath。 .//*[not(*) and normalize-space(.) !== ""] 将选择没有元素子节点且没有任何文本内容(除了空格)的元素节点。

    这是一个例子(包括其他优化):

    $xml = <<<'XML'
    <file>
        <record>
            <Source>
                <SourcePlace>
                    <Country>Germany</Country>
                </SourcePlace>          
            </Source>
            <Person>
                <Name>
                    <firstname>John</firstname>
                    <lastname>Doe</lastname>
                </Name>
            </Person>
        </record>
    </file>
    XML;    
    
    $reader = new XMLReader();
    $reader->open('data://text/plain;base64,'.base64_encode($xml));
    
    $document = new DOMDocument();
    $xpath = new DOMXpath($document);
    
    // find first record
    while ($reader->read() && $reader->localName !== 'record') {
      continue;
    }
    
    while ($reader->localName === 'record') {
      // expand node into prepared document    
      $record = $reader->expand($document);
    
      // match elements without child elements and empty text content
      // ignore text nodes with only white space
      $expression = './Source//*[not(*) and normalize-space() != ""]';
      foreach ($xpath->evaluate($expression, $record) as $leaf) {
          var_dump($leaf->localName, $leaf->textContent);
      }
    
      // move to the next record sibling
      $reader->next('record');
    }
    $reader->close();
    

    输出:

    string(7) "Country"
    string(7) "Germany"
    

    【讨论】:

    • 谢谢,这似乎工作得很好。简单地要求所有节点都是叶子是非常优雅的。第一个问题:while ($reader-&gt;read() &amp;&amp; $reader-&gt;localName !== 'record') { 部分的重要性是什么?下一个 while 循环不是从第一次出现开始吗?
    • 不,next() 只查看以下兄弟姐妹。所以你需要使用read() 找到第一个节点(它查看后代)。
    猜你喜欢
    • 1970-01-01
    • 2018-09-13
    • 1970-01-01
    • 2018-04-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-06
    • 1970-01-01
    相关资源
    最近更新 更多