我有几个相同的元素,我使用SimpleXML访问不同的属性:
<data>
<seg id="A1"/>
<seg id="A5"/>
<seg id="A12"/>
<seg id="A29"/>
<seg id="A30"/>
</data>
我需要删除ID为“A12”的特定 seg 元素,我该怎么做?我已经尝试循环遍历 seg 元素和取消设置特定的元素,但这不起作用,元素仍然存在。
foreach($doc->seg as $seg)
{
if($seg['id'] == 'A12')
{
unset($seg);
}
}
答案 0 :(得分:55)
与现有答案中的普遍看法相反,每个Simplexml元素节点都可以单独从文档中删除unset()
。问题的关键在于您需要了解SimpleXML的实际工作原理。
首先找到要删除的元素:
list($element) = $doc->xpath('/*/seg[@id="A12"]');
然后删除$element
中设置的元素,取消设置自引用:
unset($element[0]);
这是有效的,因为任何元素的第一个元素都是Simplexml中的元素本身(自引用)。这与其神奇的性质有关,数字索引代表任何列表中的元素(例如,父 - >儿童),甚至单个孩子也是这样的列表。
非数字字符串索引表示属性(在数组访问中)或子元素(在属性访问中)。
因此,属性访问中的数字缺省如:
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>
答案 1 :(得分:51)
虽然SimpleXML提供了a way to remove个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();
输出
<?xml version="1.0"?>
<data><seg id="A1"/><seg id="A5"/><seg id="A29"/><seg id="A30"/></data>
顺便说一句:当您使用XPath(SimpleXMLElement->xpath)时,选择特定节点要简单得多:
$segs=$doc->xpath('//seq[@id="A12"]');
if (count($segs)>=1) {
$seg=$segs[0];
}
// same deletion procedure as above
答案 2 :(得分: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
答案 3 :(得分: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');
请注意,根据XML数据的来源以及您想要对输出执行的操作,可以使用不同的代码替换部分加载XML ...(第一个)和格式XML ...(最后一个);中间的部分找到一个节点并将其删除。
此外,if语句仅用于确保目标节点在尝试移动之前存在。您可以选择不同的方式来处理或忽略这种情况。
答案 4 :(得分: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();
答案 5 :(得分:4)
如果扩展基本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>
答案 6 :(得分:2)
要移除/保留具有特定属性值的节点或属于属性值数组,您可以像这样扩展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;
}
}
然后使用您的$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);
答案 7 :(得分:2)
为了将来参考,使用SimpleXML删除节点有时会很痛苦,特别是如果您不知道文档的确切结构。这就是我编写SimpleDOM的原因,这是一个扩展SimpleXMLElement以添加一些便利方法的类。
例如,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"]');
答案 8 :(得分:1)
一个新想法:simple_xml
作为数组工作。
我们可以搜索要删除的“数组”的索引,然后使用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]]);
}
答案 9 :(得分:1)
有一种方法可以通过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);
答案 10 :(得分:0)
尽管SimpleXML没有详细的方法来删除元素,但可以使用PHP的unset()
从SimpleXML中删除元素。这样做的关键是管理目标所需的元素。至少一种进行定位的方法是使用元素的顺序。首先找出要删除的元素的订单号(例如使用循环),然后删除元素:
$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)
)进行删除,因为删除项目会自然地减少后面项目的订单号。
不可否认,这有点令人讨厌,但似乎工作正常。
答案 11 :(得分:0)
关于辅助函数的想法来自php.net上关于DOM的注释之一,关于使用unset的想法来自kavoir.com。对我来说,这个解决方案终于奏效了:
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中,因此不必担心将其分配给任何变量。
答案 12 :(得分:0)
我也对此问题感到不满,答案比这里提供的答案更容易。 您可以使用xpath查找它并使用以下方法取消它:
unset($XML->xpath("NODESNAME[@id='test']")[0]->{0});
此代码将查找名为“NODESNAME”的节点,其id属性为“test”,并删除第一次出现。
请记住使用$ XML-&gt; saveXML(...);
保存xml答案 13 :(得分:0)
由于我遇到了和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 )
答案 14 :(得分:0)
使用FluidXML,您可以使用XPath选择要删除的元素。
$doc = fluidify($doc);
$doc->remove('//*[@id="A12"]');
https://github.com/servo-php/fluidxml
XPath //*[@id="A12"]
表示:
//
)*
)id
等于A12
([@id="A12"]
)。答案 15 :(得分:0)
如果要剪切类似(非唯一)子元素的列表,例如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});
}
但它以某种方式随机工作,只削减了一些元素。
答案 16 :(得分:-2)
你最初的做法是正确的,但是你忘了关于foreach的一件小事。它不适用于原始数组/对象,但会在迭代时创建每个元素的副本,因此您确实取消了该副本的设置。使用这样的参考:
foreach($doc->seg as &$seg)
{
if($seg['id'] == 'A12')
{
unset($seg);
}
}