【问题标题】:Remove all nodes from XML but specific ones in PHP从 XML 中删除所有节点,但在 PHP 中删除特定节点
【发布时间】:2022-08-19 00:46:48
【问题描述】:

我有一个来自 Google 的 XML,内容如下:

<?xml version=\"1.0\" encoding=\"UTF-8\" ?>
<rss version=\"2.0\" xmlns:g=\"http://base.google.com/ns/1.0\">
 <channel>
  <title>E-commerce\'s products.</title>
  <description><![CDATA[Clothing and accessories.]]></description>
  <link>https://www.ourwebsite.com/</link>
  <item>
   <title><![CDATA[Product #1 title]]></title>
   <g:brand><![CDATA[Product #1 brand]]></g:brand>
   <g:mpn><![CDATA[5643785645]]></g:mpn>
   <g:gender>Male</g:gender>
   <g:age_group>Adult</g:age_group>
   <g:size>Unica</g:size>
   <g:condition>new</g:condition>
   <g:id>fr_30763_06352</g:id>
   <g:item_group_id>fr_30763</g:item_group_id>
   <link><![CDATA[https://www.ourwebsite.com/product_1_url.htm?mid=62367]]></link>
   <description><![CDATA[Product #1 description]]></description>
   <g:image_link><![CDATA[https://data.ourwebsite.com/imgprodotto/product-1_big.jpg]]></g:image_link>
   <g:sale_price>29.25 EUR</g:sale_price>
   <g:price>65.00 EUR</g:price>
   <g:shipping_weight>0.5 kg</g:shipping_weight>
   <g:featured_product>y</g:featured_product>
   <g:product_type><![CDATA[Product #1 category]]></g:product_type>
   <g:availability>in stock</g:availability>
   <g:availability_date>2022-08-10T00:00-0000</g:availability_date>
   <qty>3</qty>
   <g:payment_accepted>Visa</g:payment_accepted>
   <g:payment_accepted>MasterCard</g:payment_accepted>
   <g:payment_accepted>CartaSi</g:payment_accepted>
   <g:payment_accepted>Aura</g:payment_accepted>
   <g:payment_accepted>PayPal</g:payment_accepted>
  </item>
  <item>
   <title><![CDATA[Product #2 title]]></title>
   <g:brand><![CDATA[Product #2 brand]]></g:brand>
   <g:mpn><![CDATA[573489547859]]></g:mpn>
   <g:gender>Unisex</g:gender>
   <g:age_group>Adult</g:age_group>
   <g:size>Unica</g:size>
   <g:condition>new</g:condition>
   <g:id>fr_47362_382936</g:id>
   <g:item_group_id>fr_47362</g:item_group_id>
   <link><![CDATA[https://www.ourwebsite.com/product_2_url.htm?mid=168192]]></link>
   <description><![CDATA[Product #2 description]]></description>
   <g:image_link><![CDATA[https://data.ourwebsite.com/imgprodotto/product-2_big.jpg]]></g:image_link>
   <g:sale_price>143.91 EUR</g:sale_price>
   <g:price>159.90 EUR</g:price>
   <g:shipping_weight>8.0 kg</g:shipping_weight>
   <g:product_type><![CDATA[Product #2 category]]></g:product_type>
   <g:availability>in stock</g:availability>
   <g:availability_date>2022-08-10T00:00-0000</g:availability_date>
   <qty>1</qty>
   <g:payment_accepted>Visa</g:payment_accepted>
   <g:payment_accepted>MasterCard</g:payment_accepted>
   <g:payment_accepted>CartaSi</g:payment_accepted>
   <g:payment_accepted>Aura</g:payment_accepted>
   <g:payment_accepted>PayPal</g:payment_accepted>
  </item>
  ...
 </channel>
</rss>

我需要生成一个 XML 文件,从 &lt;item&gt; 中的所有标签中清除,&lt;g:mpn&gt;&lt;link&gt;&lt;g:sale_price&gt;&lt;qty&gt; 除外。

在上面的例子中,结果应该是

<?xml version=\"1.0\" encoding=\"UTF-8\" ?>
<rss version=\"2.0\" xmlns:g=\"http://base.google.com/ns/1.0\">
 <channel>
  <title>E-commerce\'s products.</title>
  <description><![CDATA[Clothing and accessories.]]></description>
  <link>https://www.ourwebsite.com/</link>
  <item>
   <g:mpn><![CDATA[5643785645]]></g:mpn>
   <link><![CDATA[https://www.ourwebsite.com/product_1_url.htm?mid=62367]]></link>
   <g:sale_price>29.25 EUR</g:sale_price>
   <qty>3</qty>
  </item>
  <item>
   <g:mpn><![CDATA[573489547859]]></g:mpn>
   <link><![CDATA[https://www.ourwebsite.com/product_2_url.htm?mid=168192]]></link>
   <g:sale_price>143.91 EUR</g:sale_price>
   <qty>1</qty>
  </item>
  ...
 </channel>
</rss>

我查看过 SimpleXML、DOMDocument、XPath 文档,但找不到排除特定元素的方法。我不想按名称选择我必须删除的节点,因为将来 Google 可以添加一些节点并且它们不会被我的脚本删除。

我还尝试使用 SimpleXML 遍历命名空间元素,如果与我必须保留的节点不匹配,则取消设置它们:

$g = $element->children($namespaces[\'g\']); //$element is the SimpleXMLElement of <item> tag
foreach ($g as $gchild) {
    if ($gchild->getName() != \"mpn\") {  //for example
        unset($gchild);
    }
}

但是上面的代码并没有删除除&lt;g:mpn&gt; 之外的所有节点,例如。

PS:考虑 XML 包含命名空间和非命名空间元素的事实

先感谢您。

编辑:我已经设法使用以下代码做到这一点:

$elementsToKeep = array(\"mpn\", \"link\", \"sale_price\", \"qty\");

$domdoc = new DOMDocument();
$domdoc->preserveWhiteSpace = FALSE;
$domdoc->formatOutput = TRUE;
$domdoc->loadXML($myXMLDocument->asXML());  //$myXMLDocument is the SimpleXML document related to the original XML
$xpath = new DOMXPath($domdoc);

foreach ($element->children() as $child) {
    $cname = $child->getName();
    if (!in_array($cname, $elementsToKeep)) {
        foreach($xpath->query(\'/rss/channel/item/\'.$cname) as $node) {
            $node->parentNode->removeChild($node);
        }
    }
}

$g = $element->children($namespaces[\'g\']);
foreach ($g as $gchild) {
    $gname = $gchild->getName();
    if (!in_array($gname, $elementsToKeep)) {
        foreach($xpath->query(\'/rss/channel/item/g:\'.$gname) as $node) {
            $node->parentNode->removeChild($node);
        }
    }
}

为了使用 DOMDocument 的 removeChild 函数,我使用了 DOMDocument 和 DOMXPath 以及在无命名空间标签和命名空间标签上的两个循环。

真的没有更清洁的解决方案吗?再次感谢

  • 对于 XSLT 来说,这是一项微不足道的任务。

标签: php xml xpath simplexml domdocument


【解决方案1】:

稍微简单一些:

$items = $xpath->query('//item');
foreach($items as $item) {
        $targets = $xpath->query('.//*',$item);
        foreach($targets as $target) {
            if (!in_array($target->localName, $elementsToKeep)) {
                $target->parentNode->removeChild($target);
            }
        };
    };

【讨论】:

  • 谢谢你,杰克。我需要了解更多关于 xpath 查询的信息。 :)
  • @Ma3x 不客气,祝你好运!
【解决方案2】:

使用 XPath 来表达您想要删除的所有子元素。

然后使用您选择的库来删除元素。

SimpleXMLElement 示例:

$sxe = simplexml_load_string($xml);
foreach ($sxe->xpath('//item/*[
    not(
           name() = "g:mpn" 
        or name() = "link" 
        or name() = "g:sale_price" 
        or name() = "qty"
    )
]') as $child) unset($child[0]);


echo $sxe->asXML(), "\n";

DOMDocument 示例:

这个与前一个示例基本相同,只是在 xpath 表达式上有一些变化,以显式使用元素的命名空间 URI。这可以防止它在命名空间前缀更改时中断(它也适用于 SimpleXMLElement 示例):

$doc = new DOMDocument();
$doc->loadXML($xml);
$xpath = new DOMXPath($doc);

foreach ($xpath->query('//item/*[
    not(
           (local-name() = "mpn"        and namespace-uri() = "http://base.google.com/ns/1.0") 
        or (local-name() = "link"       and namespace-uri() = "") 
        or (local-name() = "sale_price" and namespace-uri() = "http://base.google.com/ns/1.0") 
        or (local-name() = "qty"        and namespace-uri() = "")
    )
]') as $child) {
    $child->parentNode->removeChild($child);
}

echo $doc->saveXML(), "\n";

【讨论】: