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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/262351/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 22:06:58  来源:igfitidea点击:

Remove a child with a specific attribute, in SimpleXML for PHP

phpxmldomsimplexml

提问by TimTowdi

I have several identical elements with different attributes that I'm accessing with SimpleXML:

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

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

I need to remove a specific segelement, with an id of "A12", how can I do this? I've tried looping through the segelements and unsetting the specific one, but this doesn't work, the elements remain.

我需要删除一个特定的seg元素,id 为“A12”,我该怎么做?我尝试遍历seg元素并取消设置特定的元素,但这不起作用,元素仍然存在。

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

回答by hakre

Contrary to popular belief in the existing answers, each Simplexml element node can be removed from the document just by itself and unset(). The point in case is just that you need to understand how SimpleXML actually works.

与对现有答案的普遍看法相反,每个 Simplexml 元素节点都可以仅通过其自身和unset(). 关键在于您需要了解 SimpleXML 的实际工作方式。

First locate the element you want to remove:

首先找到要删除的元素:

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

Then remove the element represented in $elementyou unset its self-reference:

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

unset($element[0]);

This works because the first element of any element is the element itself in Simplexml (self-reference). This has to do with its magic nature, numeric indices are representing the elements in any list (e.g. parent->children), and even the single child is such a list.

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

Non-numeric string indices represent attributes (in array-access) or child-element(s) (in property-access).

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

Therefore numeric indecies in property-access like:

因此,属性访问中的数字索引如下:

unset($element->{0});

work as well.

工作也是如此。

Naturally with that xpath example, it is rather straight forward (in PHP 5.4):

很自然地,对于那个 xpath 示例,它相当简单(在 PHP 5.4 中):

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

The full example code (Demo):

完整的示例代码(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');

Output:

输出:

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

    <seg id="A29"/>
    <seg id="A30"/>
</data>

回答by Stefan Gehrig

While SimpleXMLprovides a way to removeXML nodes, its modification capabilities are somewhat limited. One other solution is to resort to using the DOMextension. dom_import_simplexml()will help you with converting your SimpleXMLElementinto a DOMElement.

虽然SimpleXML提供了一种删除XML 节点的方法,但它的修改能力有些有限。另一种解决方案是求助于使用DOM扩展。dom_import_simplexml()将帮助您将您SimpleXMLElementDOMElement.

Just some example code (tested with PHP 5.2.5):

只是一些示例代码(使用 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();

outputs

产出

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

By the way: selecting specific nodes is much more simple when you use XPath (SimpleXMLElement->xpath):

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

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

回答by datasn.io

Just unset the node:

只需取消设置节点:

$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

This code was taken from How to delete / remove nodes in SimpleXML.

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

回答by Witman

I believe Stefan's answer is right on. If you want to remove only one node (rather than all matching nodes), here is another example:

我相信 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');

Note that sections Load XML... (first) and Format XML... (last) could be replaced with different code depending on where your XML data comes from and what you want to do with the output; it is the sections in between that find a node and remove it.

请注意,Load XML... (first) 和 Format XML... (last) 部分可以替换为不同的代码,具体取决于您的 XML 数据来自何处以及您想对输出做什么;它是中间的部分,可以找到一个节点并将其删除。

In addition, the if statement is only there to ensure that the target node exists before trying to move it. You could choose different ways to handle or ignore this case.

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

回答by sunnyface45

This work for me:

这对我有用:

$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();

回答by Micha? Tatarynowicz

If you extend the base SimpleXMLElement class, you can use this method:

如果扩展基本 SimpleXMLElement 类,则可以使用此方法:

class MyXML extends SimpleXMLElement {

    public function find($xpath) {
        $tmp = $this->xpath($xpath);
        return isset($tmp[0])? $tmp[0]: null;
    }

    public function remove() {
        $dom = dom_import_simplexml($this);
        return $dom->parentNode->removeChild($dom);
    }

}

// Example: removing the <bar> element with id = 1
$foo = new MyXML('<foo><bar id="1"/><bar id="2"/></foo>');
$foo->find('//bar[@id="1"]')->remove();
print $foo->asXML(); // <foo><bar id="2"/></foo>

回答by Krzysztof Przygoda

To remove/keep nodes with certain attribute value or falling into array of attribute values you can extend SimpleXMLElementclass like this (most recent version in my GitHub Gist):

要删除/保留具有特定属性值或属于属性值数组的节点,您可以SimpleXMLElement像这样扩展类(我的GitHub Gist 中的最新版本):

class SimpleXMLElementExtended extends SimpleXMLElement
{    
    /**
    * Removes or keeps nodes with given attributes
    *
    * @param string $attributeName
    * @param array $attributeValues
    * @param bool $keep TRUE keeps nodes and removes the rest, FALSE removes nodes and keeps the rest 
    * @return integer Number o affected nodes
    *
    * @example: $xml->o->filterAttribute('id', $products_ids); // Keeps only nodes with id attr in $products_ids
    * @see: http://stackoverflow.com/questions/17185959/simplexml-remove-nodes
    */
    public function filterAttribute($attributeName = '', $attributeValues = array(), $keepNodes = TRUE)
    {       
        $nodesToRemove = array();

        foreach($this as $node)
        {
            $attributeValue = (string)$node[$attributeName];

            if ($keepNodes)
            {
                if (!in_array($attributeValue, $attributeValues)) $nodesToRemove[] = $node;
            }
            else
            { 
                if (in_array($attributeValue, $attributeValues)) $nodesToRemove[] = $node;
            }
        }

        $result = count($nodesToRemove);

        foreach ($nodesToRemove as $node) {
            unset($node[0]);
        }

        return $result;
    }
}

Then having your $docXML you can remove your <seg id="A12"/>node calling:

然后拥有您的$docXML,您可以删除您的<seg id="A12"/>节点调用:

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

$doc=new SimpleXMLElementExtended($data);
$doc->seg->filterAttribute('id', ['A12'], FALSE);

or remove multiple <seg />nodes:

或删除多个<seg />节点:

$doc->seg->filterAttribute('id', ['A1', 'A12', 'A29'], FALSE);

For keeping only <seg id="A5"/>and <seg id="A30"/>nodes and removing the rest:

仅保留<seg id="A5"/><seg id="A30"/>节点并删除其余节点:

$doc->seg->filterAttribute('id', ['A5', 'A30'], TRUE);

回答by Josh Davis

For future reference, deleting nodes with SimpleXML can be a pain sometimes, especially if you don't know the exact structure of the document. That's why I have written SimpleDOM, a class that extends SimpleXMLElement to add a few convenience methods.

为了将来参考,使用 SimpleXML 删除节点有时会很痛苦,尤其是当您不知道文档的确切结构时。这就是我编写SimpleDOM的原因,该类扩展 SimpleXMLElement 以添加一些方便的方法。

For instance, deleteNodes() will delete all nodes matching a XPath expression. And if you want to delete all nodes with the attribute "id" equal to "A5", all you have to do is:

例如,deleteNodes() 将删除与 XPath 表达式匹配的所有节点。如果你想删除属性“id”等于“A5”的所有节点,你所要做的就是:

// don't forget to include SimpleDOM.php
include 'SimpleDOM.php';

// use simpledom_load_string() instead of simplexml_load_string()
$data = simpledom_load_string(
    '<data>
        <seg id="A1"/>
        <seg id="A5"/>
        <seg id="A12"/>
        <seg id="A29"/>
        <seg id="A30"/>
    </data>'
);

// and there the magic happens
$data->deleteNodes('//seg[@id="A5"]');

回答by Josh Davis

There is a way to remove a child element via SimpleXml. The code looks for a element, and does nothing. Otherwise it adds the element to a string. It then writes out the string to a file. Also note that the code saves a backup before overwriting the original file.

有一种方法可以通过 SimpleXml 删除子元素。该代码查找一个元素,但什么都不做。否则,它将元素添加到字符串中。然后它将字符串写出到文件中。另请注意,该代码在覆盖原始文件之前会保存备份。

$username = $_GET['delete_account'];
echo "DELETING: ".$username;
$xml = simplexml_load_file("users.xml");

$str = "<?xml version=\"1.0\"?>
<users>";
foreach($xml->children() as $child){
  if($child->getName() == "user") {
      if($username == $child['name']) {
        continue;
    } else {
        $str = $str.$child->asXML();
    }
  }
}
$str = $str."
</users>";
echo $str;

$xml->asXML("users_backup.xml");
$myFile = "users.xml";
$fh = fopen($myFile, 'w') or die("can't open file");
fwrite($fh, $str);
fclose($fh);

回答by joan16v

A new idea: simple_xmlworks as a array.

一个新想法:simple_xml作为数组工作。

We can search for the indexes of the "array" we want to delete, and then, use the unset()function to delete this array indexes. My example:

我们可以搜索我们要删除的“数组”的索引,然后使用unset()函数删除这个数组索引。我的例子:

$pos=$this->xml->getXMLUser();
$i=0; $array_pos=array();
foreach($this->xml->doc->users->usr[$pos]->u_cfg_root->profiles->profile as $profile) {
    if($profile->p_timestamp=='0') { $array_pos[]=$i; }
    $i++;
}
//print_r($array_pos);
for($i=0;$i<count($array_pos);$i++) {
    unset($this->xml->doc->users->usr[$pos]->u_cfg_root->profiles->profile[$array_pos[$i]]);
}