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