Удалить дочерний элемент с определенным атрибутом в SimpleXML для PHP - PullRequest
46 голосов
/ 04 ноября 2008

У меня есть несколько идентичных элементов с разными атрибутами, к которым я обращаюсь с помощью SimpleXML:

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

Мне нужно удалить определенный элемент seg с идентификатором «A12», как я могу это сделать? Я пробовал циклически проходить элементы seg и unset tting определенного элемента, но это не работает, элементы остаются.

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

Ответы [ 17 ]

55 голосов
/ 17 апреля 2013

Вопреки распространенному мнению о существующих ответах, каждый узел элемента Simplexml может быть удален из документа сам по себе и unset(). Дело в том, что вам нужно понять, как на самом деле работает SimpleXML.

Сначала найдите элемент, который вы хотите удалить:

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

Затем удалите элемент, представленный в $element Вы сбросили его Самостоятельная ссылка :

unset($element[0]);

Это работает, потому что первым элементом любого элемента является сам элемент в Simplexml (ссылка на себя). Это связано с его магической природой, числовые индексы представляют элементы в любом списке (например, parent-> children), и даже один дочерний элемент является таким списком.

Нечисловые строковые индексы представляют атрибуты (в доступе к массиву) или дочерний элемент (элементы) (в доступе к свойству).

Следовательно, числовые значения при доступе к собственности, такие как:

unset($element->{0});

тоже работает.

Естественно, с этим примером xpath он довольно прост (в PHP 5.4):

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

Полный пример кода ( Демо ):

<?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>
51 голосов
/ 04 ноября 2008

Хотя 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();

выходы * * 1016

<?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
23 голосов
/ 27 декабря 2008

Просто сбросьте узел:

$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 .

10 голосов
/ 25 ноября 2009

Я считаю, что ответ Стефана правильный. Если вы хотите удалить только один узел (а не все совпадающие узлы), вот еще один пример:

//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 существует только для того, чтобы убедиться, что целевой узел существует, прежде чем пытаться его переместить. Вы можете выбрать разные способы обработки или игнорировать этот случай.

5 голосов
/ 26 августа 2010

Эта работа для меня:

$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();
4 голосов
/ 10 сентября 2010

Если вы расширяете базовый класс 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>
2 голосов
/ 15 ноября 2009

Для дальнейшего использования удаление узлов с помощью 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"]');
2 голосов
/ 29 августа 2016

Чтобы удалить / сохранить узлы с определенным значением атрибута или попасть в массив значений атрибутов, вы можете расширить класс 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);
1 голос
/ 06 декабря 2008

Есть способ удалить дочерний элемент через 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);
1 голос
/ 17 марта 2010

Новая идея: 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]]);
}
...