在SimpleXML for PHP中删除具有特定属性的子项

Tim*_*wdi 48 php xml dom simplexml

我有几个具有不同属性的相同元素,我正在使用SimpleXML访问:

<data>
    <seg id="A1"/>
    <seg id="A5"/>
    <seg id="A12"/>
    <seg id="A29"/>
    <seg id="A30"/>
</data>
Run Code Online (Sandbox Code Playgroud)

我需要删除id为"A12" 的特定seg元素,我该怎么做?我已经尝试循环遍历seg元素并取消设置特定元素,但这不起作用,元素仍然存在.

foreach($doc->seg as $seg)
{
    if($seg['id'] == 'A12')
    {
        unset($seg);
    }
}
Run Code Online (Sandbox Code Playgroud)

hak*_*kre 56

与现有答案中的流行信念相反,每个Simplexml元素节点都可以单独从文档中删除unset().问题的关键在于您需要了解SimpleXML的实际工作原理.

首先找到要删除的元素:

list($element) = $doc->xpath('/*/seg[@id="A12"]');
Run Code Online (Sandbox Code Playgroud)

然后删除$element你在其中表示的元素取消设置其自引用:

unset($element[0]);
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为任何元素的第一个元素都是Simplexml中的元素本身(自引用).这与它的神奇本质有关,数字索引表示任何列表中的元素(例如,父 - >子),甚至单个子列表也是如此.

非数字字符串索引表示属性(在数组访问中)或子元素(在属性访问中).

因此,属性访问中的数字缺省如下:

unset($element->{0});
Run Code Online (Sandbox Code Playgroud)

也工作.

当然,使用该xpath示例,它非常简单(在PHP 5.4中):

unset($doc->xpath('/*/seg[@id="A12"]')[0][0]);
Run Code Online (Sandbox Code Playgroud)

完整的示例代码(Demo):

<?php
/**
 * Remove a child with a specific attribute, in SimpleXML for PHP
 * @link http://stackoverflow.com/a/16062633/367456
 */

$data=<<<DATA
<data>
    <seg id="A1"/>
    <seg id="A5"/>
    <seg id="A12"/>
    <seg id="A29"/>
    <seg id="A30"/>
</data>
DATA;


$doc = new SimpleXMLElement($data);

unset($doc->xpath('seg[@id="A12"]')[0]->{0});

$doc->asXml('php://output');
Run Code Online (Sandbox Code Playgroud)

输出:

<?xml version="1.0"?>
<data>
    <seg id="A1"/>
    <seg id="A5"/>

    <seg id="A29"/>
    <seg id="A30"/>
</data>
Run Code Online (Sandbox Code Playgroud)

  • 很好地解释了答案.我没有立即理解的一个细节是你不能轻易地将XPath带出循环,因为删除普通`foreach($ doc-> seg as $ seg)`循环中的元素会混淆迭代器(经验法则) :不要改变迭代器中间循环的长度).SimpleXML的XPath实现没有这个问题,因为它的结果是一个普通的无关元素数组. (5认同)
  • @IMSoP:对于任何`Traversable`和那个问题(*live lists*),我强烈推荐[`iterator_to_array`](http://php.net/iterator_to_array),在SimpleXML迭代器中将key参数设置为FALSE因为SimpleXMLElement使用tag-name作为键,在这样的列表中通常是重复的,然后如果第二个参数不是"FALSE",那么该函数将只返回这些同名节点中的最后一个. (2认同)
  • 很好的提示,特别是关于额外参数。:) (2认同)

Ste*_*rig 52

虽然SimpleXML提供了一种删除 XML节点的方法,但其修改功能有限.另一种解决方案是使用DOM扩展.dom_import_simplexml()将帮助您将您SimpleXMLElement转换为DOMElement.

只是一些示例代码(使用PHP 5.2.5测试):

$data='<data>
    <seg id="A1"/>
    <seg id="A5"/>
    <seg id="A12"/>
    <seg id="A29"/>
    <seg id="A30"/>
</data>';
$doc=new SimpleXMLElement($data);
foreach($doc->seg as $seg)
{
    if($seg['id'] == 'A12') {
        $dom=dom_import_simplexml($seg);
        $dom->parentNode->removeChild($dom);
    }
}
echo $doc->asXml();
Run Code Online (Sandbox Code Playgroud)

输出

<?xml version="1.0"?>
<data><seg id="A1"/><seg id="A5"/><seg id="A29"/><seg id="A30"/></data>
Run Code Online (Sandbox Code Playgroud)

顺便说一下:当您使用XPath(SimpleXMLElement-> xpath)时,选择特定节点要简单得多:

$segs=$doc->xpath('//seq[@id="A12"]');
if (count($segs)>=1) {
    $seg=$segs[0];
}
// same deletion procedure as above
Run Code Online (Sandbox Code Playgroud)

  • 请注意,此代码仅删除遇到的第一个元素.我怀疑这是因为在迭代时修改数据会使迭代器位置无效,从而导致foreach循环终止.我通过将dom导入的节点保存到一个数组来解决这个问题,然后我重复执行该数组以执行删除.不是一个很好的解决方案,但它确实有效 (6认同)
  • 谢谢你 - 最初我倾向于避免这个答案,因为我想避免使用DOM.我尝试了几个其他不起作用的答案,然后再尝试你的 - 这完美无缺.对于任何考虑避免这个答案的人,先试试看,看看它是不是完全没有你想要的.我认为让我失望的是我没有意识到dom_import_simplexml()仍然使用与simplexml相同的底层结构,因此一个中的任何更改都会立即影响另一个,无需写入/读取或重新加载. (4认同)
  • 您实际上可以使用unset删除SimpleXML元素,请参阅posthy的解决方案答案. (4认同)
  • 实际上你可以使用unset删除SimpleXML元素,但它在我的答案中;)http://stackoverflow.com/a/16062633/367456 (2认同)

dat*_*.io 23

只是取消设置节点:

$str = <<<STR
<a>
  <b>
    <c>
    </c>
  </b>
</a>
STR;

$xml = simplexml_load_string($str);
unset($xml –> a –> b –> c); // this would remove node c
echo $xml –> asXML(); // xml document string without node c
Run Code Online (Sandbox Code Playgroud)

此代码取自如何删除/删除SimpleXML中的节点.

  • 仅当节点名称在集合中是唯一的时才有效.如果不是,则最终删除所有同名节点. (6认同)
  • @Dallas:你评论的是对的,但它也包含解决方案.如何只访问第一个元素?请参见此处:http://stackoverflow.com/a/16062633/367456 (2认同)

Wit*_*man 10

我相信Stefan的答案是正确的.如果您只想删除一个节点(而不是所有匹配的节点),这是另一个例子:

//Load XML from file (or it could come from a POST, etc.)
$xml = simplexml_load_file('fileName.xml');

//Use XPath to find target node for removal
$target = $xml->xpath("//seg[@id=$uniqueIdToDelete]");

//If target does not exist (already deleted by someone/thing else), halt
if(!$target)
return; //Returns null

//Import simpleXml reference into Dom & do removal (removal occurs in simpleXML object)
$domRef = dom_import_simplexml($target[0]); //Select position 0 in XPath array
$domRef->parentNode->removeChild($domRef);

//Format XML to save indented tree rather than one line and save
$dom = new DOMDocument('1.0');
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->loadXML($xml->asXML());
$dom->save('fileName.xml');
Run Code Online (Sandbox Code Playgroud)

请注意,根据XML数据的来源以及您想要对输出执行的操作,可以使用不同的代码替换"加载XML ...(第一个)"和"格式XML ...(最后一个)"部分; 中间的部分找到一个节点并将其删除.

此外,if语句仅用于确保目标节点在尝试移动之前存在.您可以选择不同的方式来处理或忽略这种情况.


小智 5

这项工作对我来说:

$data = '<data>
<seg id="A1"/>
<seg id="A5"/>
<seg id="A12"/>
<seg id="A29"/>
<seg id="A30"/></data>';

$doc = new SimpleXMLElement($data);

$segarr = $doc->seg;

$count = count($segarr);

$j = 0;

for ($i = 0; $i < $count; $i++) {

    if ($segarr[$j]['id'] == 'A12') {
        unset($segarr[$j]);
        $j = $j - 1;
    }
    $j = $j + 1;
}

echo $doc->asXml();
Run Code Online (Sandbox Code Playgroud)