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

我有几个与SimpleXML访问的具有不同属性的相同元素:

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

我需要删除一个特定的seg元素,ID为“A12”,我该怎么做? 我已经试过了seg元素循环,而不是特定的元素,但是这不起作用,元素依然存在。

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

尽pipeSimpleXML提供了一种删除 XML节点的方法,但其修改function却有所限制。 另一个解决scheme是诉诸使用DOM扩展。 dom_import_simplexml()将帮助您将SimpleXMLElement转换为DOMElement

只是一些示例代码(使用PHP 5.2.5进行testing):

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

输出

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

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

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

与普遍认为的现有答案相反,每个Simplexml元素节点可以通过自身和unset()从文档中删除。 在这种情况下,您只需要了解SimpleXML是如何工作的。

首先find你想要删除的元素:

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

然后移除$element element中表示的$element ,取消其自引用

 unset($element[0]); 

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

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

因此,属性访问中的数字不确定性如下所示:

 unset($element->{0}); 

工作也是如此。

当然,用这个xpath的例子,它是相当直接的(在PHP 5.4中):

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

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

输出:

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

只需取消设置节点:

 $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 

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

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

请注意,Load XML …(first)和Format XML …(last)部分可以用不同的代码replace,具体取决于您的XML数据来自何处以及您想要如何处理输出; 它是中间的部分,find一个节点并将其删除。

另外,if语句只是为了确保目标节点在尝试移动之前存在。 你可以select不同的方式来处理或忽略这种情况。

如果扩展基类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> 

这为我工作:

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

为了将来的参考,使用SimpleXML删除节点有时候会很痛苦,特别是如果你不知道文档的确切结构。 这就是为什么我写了SimpleDOM ,一个扩展SimpleXMLElement的类来添加一些方便的方法。

例如,deleteNodes()将删除所有匹配XPathexpression式的节点。 如果你想删除属性“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"]'); 

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

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

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

我们可以search要删除的“数组”的索引,然后使用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]]); } 

尽pipeSimpleXML没有详细的删除元素的方法,但可以使用PHP的unset()从SimpleXML中删除元素。 做到这一点的关键是设法瞄准所需的元素。 至less有一种方法是使用元素的顺序。 首先找出你想要删除的元素的顺序号(例如用一个循环),然后删除元素:

 $target = false; $i = 0; foreach ($xml->seg as $s) { if ($s['id']=='A12') { $target = $i; break; } $i++; } if ($target !== false) { unset($xml->seg[$target]); } 

您甚至可以通过将目标项目的订单号存储在数组中来删除多个元素。 只要记得以相反的顺序( array_reverse($targets) )进行删除,因为删除一个项目自然会减less它后面的项目的订单号。

诚然,这是一个hackaround,但它似乎工作正常。

我也在解决这个问题,答案比这里提供的更容易。 你可以使用xpath来查找它,并取消它下面的方法:

 unset($XML->xpath("NODESNAME[@id='test']")[0]->{0}); 

这段代码将查找一个名为“NODESNAME”的节点,id属性为“test”,并删除第一个事件。

记得使用$ XML-> saveXML(…)保存xml;

由于我遇到与Gerry相同的致命错误,而且我对DOM不熟悉,所以我决定这样做:

 $item = $xml->xpath("//seg[@id='A12']"); $page = $xml->xpath("/data"); $id = "A12"; if ( count($item) && count($page) ) { $item = $item[0]; $page = $page[0]; // find the numerical index within ->children(). $ch = $page->children(); $ch_as_array = (array) $ch; if ( count($ch_as_array) && isset($ch_as_array['seg']) ) { $ch_as_array = $ch_as_array['seg']; $index_in_array = array_search($item, $ch_as_array); if ( ($index_in_array !== false) && ($index_in_array !== null) && isset($ch[$index_in_array]) && ($ch[$index_in_array]['id'] == $id) ) { // delete it! unset($ch[$index_in_array]); echo "<pre>"; var_dump($xml); echo "</pre>"; } } // end of ( if xml object successfully converted to array ) } // end of ( valid item AND section ) 

有关帮助函数的想法是来自php.net上的 DOM的注释之一,而使用unset的想法来自kavoir.com 。 对我来说,这个解决scheme终于奏效

 function Myunset($node) { unsetChildren($node); $parent = $node->parentNode; unset($node); } function unsetChildren($node) { while (isset($node->firstChild)) { unsetChildren($node->firstChild); unset($node->firstChild); } } 

使用它:$ xml是SimpleXmlElement

 Myunset($xml->channel->item[$i]); 

结果存储在$ xml中,所以不用担心分配给任何variables。

使用FluidXML,您可以使用XPath来select要移除的元素。

 $doc = fluidify($doc); $doc->remove('//*[@id="A12"]'); 

https://github.com/servo-php/fluidxml


XPath //*[@id="A12"]表示:

  • 在文档的任何一点(/)
  • 每个节点( *
  • 属性id等于A12[@id="A12"] )。

如果要删除类似(不唯一)子元素的列表,例如RSS源的项目,则可以使用以下代码:

 for ( $i = 9999; $i > 10; $i--) { unset($xml->xpath('/rss/channel/item['. $i .']')[0]->{0}); } 

它将RSS的尾巴切成10个元素。 我试图删除

 for ( $i = 10; $i < 9999; $i ++ ) { unset($xml->xpath('/rss/channel/item[' . $i . ']')[0]->{0}); } 

但它随机地工作,只削减了一些元素。

要删除/保留具有特定属性值的节点或属于属性值数组,可以像下面这样扩展SimpleXMLElement类:

 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; } } 

然后让您的$doc XML可以删除您的<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); 

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

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

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

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

你最初的方法是正确的,但是你忘记了关于foreach的一点点。 它在原始数组/对象上不起作用,但在迭代时创build每个元素的副本,所以您没有复制副本。 使用这样的参考:

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