使用 PHP 展平具有不同子节点结构的 XML


Flatten XML with varying child node structure using PHP

我必须解析和扁平化一个由许多单个产品组成的XML文件。XML有详尽的文档记录,使用SimpleXML很容易在PHP中解析XML。请参阅下面的代码,了解如何从单个产品创建数组。然后,我访问所有必需的密钥并将数据存储在 SQL 数据库中。

我现在的问题是,如何处理不同的子节点。正如您在提供的 XML 代码段中看到的,可能只有一个"名称"节点,但有时有两个甚至更多。当有多个这样的节点时,我必须根据"NameType"决定使用哪个"NameText"。"价格"节点也会发生同样的情况。

<Product>
  <Id>123</Id>
  <Name>
    <NameType>3</NameType>
    <NameText>Hello World</NameText>
  </Name>
  <Price>
    <Country>US</Country>
    <Amount>9.90</Amount>
  </Price>
</Product>
<Product>
  <Id>124</Id>
  <Name>
    <NameType>1</NameType>
    <NameText>Goodbye Cruel World</NameText>
  </Name>
  <Name>
    <NameType>3</NameType>
    <NameText>Goodbye Cruel World, I'm Leaving You Today</NameText>
  </Name>
  <Name>
    <NameType>9</NameType>
    <NameText>Goodbye</NameText>
  </Name>
  <Price>
    <Country>CAN</Country>
    <Amount>27.90</Amount>
  </Price>
  <Price>
    <Country>US</Country>
    <Amount>19.90</Amount>
  </Price>
</Product>

这是我处理此问题的代码:我将XML转换为关联数组,然后使用大量if-magic来获取我需要的数据。提供的代码为第一个产品示例打印出"Hello World",为第二个产品示例打印出"再见残酷世界"。

$xml = simplexml_load_string($product);
$json = json_encode($xml);
$arr = json_decode($json, True);
// $arr['Name']['NameText'] contains the single NameText for this product in example one
// $arr['Name'][0]['NameText'] contains the first of three NameTexts in example two
if( array_key_exists(0, $arr['Name']) ) {
  foreach( $arr['Name'] as $n) {
    if( $n['NameType'] == 1 ) {
      echo $n['NameText']."'n";
      break;
    } elseif ( $n['NameType'] == 3 ) {
      echo $n['NameText']."'n";
      break;
    }
  }
} else {
  echo $arr['Name']['NameText']."'n";
}

虽然这段代码可以工作,但我对所有可能多次出现的节点的逐案分析不是很满意。我甚至必须依赖于子节点的"正确"顺序,假设 NameType "1" 总是恰好在 NameType "3" 之前。所以我倾向于希望有一个更聪明的解决方案。

每个父节点具有不同数量的子节点的 XML 问题似乎相似,但它并没有真正解决具有不同数量的子节点的部分以及选择特殊子节点的任务。

我并不完全清楚你想做什么(你没有明确解释所需的输出),但我会给你一些指示:

  • 放弃转换为数组(json_decode(json_encode())黑客)。您所做的只是丢弃 SimpleXML 提供的额外功能,并可能丢弃部分 XML 数据。
  • SimpleXML的一个很好的功能是你可以写$xml->Product->Name,这意味着第一个(如果你愿意的话,0 th)Name在第一个Product上,$xml->Product[0]->Name[0]也是如此 - 不管是否真的有多个ProductName s。
  • 您还可以以您期望的方式使用foreach ( $xml->Product as $product ) - 同样,无论该特定文档中是否存在多个Product节点,它都有效。
  • 如果您不介意学习新语法,XPath 可用于根据节点的值查找节点。在 SimpleXML 中,您可以从任何节点(例如,特定Product)开始,并使用 ->xpath() 方法获取从该节点开始的"搜索结果"的普通数组。
  • 您的代码也有一些不必要的重复,因为elseif执行与if相同的代码,因此您可以只使用 or (|| )。(我不确定这是否只是匿名化的结果。

为了进行比较,下面是代码的实时演示,其中 XML 代码段合并到一个 XML 文档中。

使用 SimpleXML 本身,而不仅仅是解析到数组,您可以将其简化为以下内容(现场演示):

$xml = simplexml_load_string($xml_data);
foreach ( $xml->Product as $product )
{
    foreach ( $product->Name as $name )
    {
        if ( $name->NameType == 1 || $name->NameType == 3 )
        {
            echo $name->NameText."'n";
            break;
        }
    }
}

使用简单的 XPath 表达式代替内部if给出此版本(实时演示):

$xml = simplexml_load_string($xml_data);
foreach ( $xml->Product as $product )
{
    foreach ( $product->xpath('Name[NameType=1 or NameType=3]') as $name )
    {
        echo $name->NameText."'n";
        break;
    }
}

或者你可以一路走下去,把所有的逻辑都放到一个 XPath 表达式中 - 注意末尾的[1],相当于内部循环中的break;,以阻止一个产品echo多个名称(现场演示):

$xml = simplexml_load_string($xml_data);
foreach ( $xml->xpath('Product/Name[NameType=1 or NameType=3][1]') as $name )
{
    echo $name->NameText."'n";
}
我找不到

合适的方法使用 SimpleXML .我更熟悉DomDocument及其loadXML()load方法。

与其将其更改为数组,不如使用getElementsByTagName()获取您想要的孩子。

在需要的地方嵌套foreach循环,它应该根据需要迭代多次。因此,这解决了逐案分析并依靠文档以特定顺序提供元素的问题。