【问题标题】:SimpleXMLElement using xpath dropping CDATASimpleXMLElement 使用 xpath 删除 CDATA
【发布时间】:2013-07-05 07:05:21
【问题描述】:

我需要将 XML 的一个节点递归地转换为 json 字符串。我大部分时间都有

$sku = "AC2061414";
$dom = new SimpleXMLElement(file_get_contents( "/usr/share//all_products.xml" )); 
$query = '//sku[text() = "'.$sku.'"]';
$entries = $dom->xpath($query);

foreach ($entries as $entry) {

    $parent_div = $entry->xpath( 'parent::*' );
    $nodearray=array();

    foreach($parent_div as $node) {
        if ($node->nodeType == XML_CDATA_SECTION_NODE) {
            $nodearray[$node->getName()]=$node->textContent;
        }else{
            $nodearray[$node->getName()]=$node;
        }
    }
    $ajax = json_encode( $nodearray );
    print($ajax);
}

运行

<?xml version="1.0" encoding="UTF-8"?>
<products>
   <product active="1" on_sale="0" discountable="1">
    <sku>AC2061414</sku>
    <name><![CDATA[ALOE CADABRA ORGANIC LUBE PINA COLADA 2.5OZ]]></name>
    <description><![CDATA[ text text ]]></description>
    <keywords/>
    <price>7.45</price>
    <stock_quantity>30</stock_quantity>
    <reorder_quantity>0</reorder_quantity>
    <height>5.25</height>
    <length>2.25</length>
    <diameter>0</diameter>
    <weight>0.27</weight>
    <color></color>
    <material>aloe vera, vitamin E</material>
    <barcode>826804006358</barcode>
    <release_date>2012-07-26</release_date>
    <images>
      <image>/AC2061414/AC2061414A.jpg</image>
    </images>
    <categories>
      <category code="528" video="0" parent="0">Lubricants</category>
      <category code="531" video="0" parent="528">Flavored</category>
      <category code="28" video="0" parent="25">Oral Products</category>
      <category code="532" video="0" parent="528">Natural</category>
    </categories>
    <manufacturer code="AC" video="0">Aloe Cadabra Lubes</manufacturer>
    <type code="LU" video="0">Lubes</type>
  </product>
</products>

结尾
{"product":{"@attributes":{"active":"1","on_sale":"0","discountable":"1"},"sku":"AC2061414","name":{},"description":{},"keywords":{},"price":"7.45","stock_quantity":"30","reorder_quantity":"0","height":"5.25","length":"2.25","diameter":"0","weight":"0.27","color":{},"material":"aloe vera, vitamin E","barcode":"826804006358","release_date":"2012-07-26","images":{"image":"\/AC2061414\/AC2061414A.jpg"},"categories":{"category":["Lubricants","Flavored","Oral Products","Natural"]},"manufacturer":"Aloe Cadabra Lubes","type":"Lubes"}}

除了缺少的节点值是 CDATA 之外,这似乎没问题。我确实试图解释它,但它不起作用。这里有什么诀窍?

【问题讨论】:

  • 我知道这不是您真正要问的,但是您为什么还要将 XML 转换为 JSON?为什么不将节点序列化为 XML 并在下一个处理阶段对其进行解析?
  • @IMSoP 的简短之处在于它是一个临时视图系统,它是 ajaxed 的,并且由于 xml 是 40mb,因此创建迷你 json 文件以供以后使用会更快......基本上,尽管看起来很奇怪,但它在整个范围内简化了事情。这里有更多具体项目,所以我没有运行整个过程,只是我需要的部分。
  • @jeremyBass_DC 够公平的。不过,您仍然可以创建 min-XML 文件而不是 mini-JSON - 只是认为 '@attributes' 键无论如何都会有效地将您与 SimpleXML 联系起来。
  • @IMSoP 是的,您是对的,我本可以将它们转换为 mini-xml 包,但是 javascript 而不是我发送 json,所以为什么不在 xml 已经在内存中而不是额外的时候执行该步骤IO 和什么不要把它推下去,因为现在我可以只提供字符串了。这是工作的扩散,实际上,这是这里的偏好,但是对于提出这个问题的普通人来说,重要的部分是 LIBXML_NOCDATA 标志很重要。谢谢你虽然

标签: php xml json xpath simplexml


【解决方案1】:

您可以尝试在构造函数中添加LIBXML_NOCDATA 选项。

$dom = new SimpleXMLElement(file_get_contents( "/usr/share//all_products.xml" ), LIBXML_NOCDATA);
...

更多详情here.

【讨论】:

    【解决方案2】:

    您在这里遇到的问题是因为json_encode,它根据它们的魔术接口处理您拥有的simplexmlelements。例如,请参阅序列化 @attributes。并且还会跳过所有 child-cdata-nodes,因为在魔术模式下读取元素值时(比较 simplexmlelements 的 print_rvar_dump 输出),这些会被删除。

    因为CDATA nodes can be normalized into surrounding text or just into common text-nodes,SimpleXML 提供了LIBXML_NOCDATA option(在使用newsimplexml_load_* 函数进行实例化时)来做到这一点:将这些CDATA 节点转换为文本节点并将这些文本节点合并到周围的文本中-nodes(如果有)(“将 CDATA 合并为文本节点”)。

    这将使print_rjson_encode 然后将节点值作为字符串@attributes 返回,因为现在它是节点值。这已在"PHP, SimpleXML, decoding entities in CDATA" 中详细解释(很好)。

    除此之外,还有另一个误解,您可以从中受益匪浅。即使您的代码已经包含通过属性值选择元素的 xpath,您也更感兴趣的是它的父级。 SimpleXML 将提供 所有的孩子 已经迭代。对于json_encode,SimpleXML 的神奇属性也是如此。比较一下如何减少代码:

    $xml = simplexml_load_file("/usr/share/all_products.xml", NULL, LIBXML_NOCDATA); 
    
    // NOTE: Prevent XPath Injection by not allowing " (or ') for 
    //       SKU value (validate it against a whitelist of allowed
    //       characters for example)
    $sku   = "AC2061414";
    $query = sprintf('(//sku[text() = "%s"])[1]/..', $sku); 
    
    $products = $xml->xpath($query);
    
    if ($products) {
        echo json_encode(["product" => $products[0]]);
    }
    

    See the Demo.

    这应该会给你相同的输出,而无需实际编写那么多代码。在创建 SimpleXMLElement 时请参阅LIBXML_NOCDATA 选项以及修改后的 xpath 查询,该查询将直接查询相关(第一个)sku 元素的父 (&lt;product&gt;) 节点。 json_encode 然后照顾所有孩子,因为它提供了魔法属性的共同遍历。

    参见:

    【讨论】:

    • 这里的扩展解释很好,是的,我确实知道我已经扩展了节点的横向,但是我想不出还有什么方法可以根据子节点按顺序添加条件找出问题的根源。我离你那里的东西不远,但可以肯定这是一个更好的方法。我会将第一个答案保留为已接受,因为它是最重要的,但到目前为止,这对读者来说提供了更多信息,谢谢。
    • 感谢您的评论,我有点这么认为。使用 XML 需要将头部包裹在层次结构中,而使用 JSON 则类似。在这里摆弄你的很有趣,我还能够挖掘一些现有的信息,希望它们是有用的。我有点想知道一件事:对于进入 json_encode 的元素,创建了 @attributes 属性,但只针对这个,而不是子级。我不确定这是否也是您的问题的一部分。
    • 老实说,我不介意让 @attributes 坚持下去,但在这种情况下它不会做或死,但未来明智的是,它正在放弃它们是奇怪的。我相信那里有小车,因为它应该递归处理。至少恕我直言。如果你有想法,我肯定会欢迎的。谢谢
    • 是的,到目前为止,我还没有找到可以很好地解释为什么会发生这种情况的解释。尽管这看起来并不明显,但这可能是有充分理由的。但到目前为止,我的结局还没有很好的线索,但我可能会在以后挑选这些松散的结局。如果您正在寻找如何control json serialization more with simplexml, I have once covered this more generally in another answer。知道 PHP 可以做到这一点可能对您有好处。
    • 关于@attributes 以及对第一个元素与遍历元素的不同处理,我可以写一篇文章,我想我可以对此有所了解:SimpleXML and JSON Encode in PHP – Part I。看起来这是一个权衡/妥协。我计划写第二部分,展示我在另一个答案中链接的内容。
    猜你喜欢
    • 2023-03-05
    • 2011-09-09
    • 2011-02-27
    • 2013-11-05
    • 2014-10-27
    • 2018-09-01
    相关资源
    最近更新 更多