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

时间:2013-09-12 09:22:37

标签: php xml simplexml flatten

我必须解析并压缩由大量单个产品组成的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”和第二个产品的“Goodbye Cruel 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 with varying amount of child nodes for each parent node似乎是相似的,但它并没有真正解决具有不同数量的子节点的部分以及选择特殊子节点的任务。

2 个答案:

答案 0 :(得分:1)

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

  • 抛弃转换为数组(json_decode(json_encode()) hack)。您所做的只是丢弃SimpleXML提供的额外功能,并可能丢弃部分XML数据。
  • SimpleXML的一个不错的功能是你可以写$xml->Product->Name,它意味着第一个0NameProduct在第一个$xml->Product[0]->Name[0]上} {,Product也是如此 - 无论是否实际存在多个Nameforeach ( $xml->Product as $product )
  • 您也可以按照预期的方式使用Product - 再次说明,无论该特定文档中是否有多个Product节点,它都能正常工作。
  • 如果您不介意学习新语法,XPath可以用于根据节点的值来搜索节点。在SimpleXML中,您可以从任何节点(例如,特定的elseif)开始,并使用the ->xpath() method从该节点开始获取一个简单的“搜索结果”数组。
  • 您的代码也有一些不必要的重复,因为if执行与||相同的代码,因此您可以使用或($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; } } } )。 (我不确定这是否只是匿名化的结果。)

为进行比较,here's a live demo of your code, with the XML snippets combined into one XML document.

使用SimpleXML本身,而不是仅仅解析为数组,您可以将其简化为此(Live Demo):

if

使用简单的XPath表达式代替内部$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; } } 提供此版本(Live Demo):

[1]

或者你可以直接将所有逻辑放入XPath表达式中 - 注意结尾的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"; } d(Live Demo):

{{1}}

答案 1 :(得分:0)

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

不是将其更改为数组,而是使用getElementsByTagName()获取所需的子项。

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