Подтвердить что ты не робот

Удалить ребенка с определенным атрибутом, в SimpleXML для PHP

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

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

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

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

Ответ 1

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

выходы

<?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

Вопреки распространенному мнению в существующих ответах, каждый элемент Simplexml node может быть удален из документа сам по себе и 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]);

Полный пример кода (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>

Ответ 3

Просто отмените 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

Этот код был взят из Как удалить/удалить узлы в SimpleXML.

Ответ 4

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

//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... (последний) могут быть заменены другим кодом в зависимости от того, откуда взялись ваши XML-данные и что вы хотите делать с выходом; это промежутки между ними, которые находят node и удаляют его.

Кроме того, оператор if существует только для того, чтобы убедиться, что объект node существует, прежде чем пытаться его переместить. Вы можете выбрать различные способы обработки или игнорирования этого случая.

Ответ 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();

Ответ 6

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

Ответ 7

В будущем, удаляя узлы с помощью 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

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

Затем, используя ваш XML- $doc вы можете удалить свой узел <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);

Ответ 9

Существует способ удалить дочерний элемент через 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

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

Ответ 11

Идея о вспомогательных функциях относится к одному из комментариев для DOM на php.net, а идея об использовании 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

Несмотря на то, что SimpleXML не имеет подробного способа удаления элементов, вы можете удалить элементы из SimpleXML с помощью PHP unset(). Ключом к этому является управление целевым элементом. По крайней мере, один из способов сделать таргетинг - это порядок элементов. Сначала найдите номер заказа элемента, который вы хотите удалить (например, с помощью цикла), затем удалите элемент:

$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)), потому что удаление элемента естественно уменьшает номер заказа элементов, которые появляются после него.

По общему признанию, это немного хаккаунд, но, похоже, он работает нормально.

Ответ 13

Я также боролся с этой проблемой, и ответ был проще, чем те, которые были предоставлены здесь. вы можете просто искать его с помощью xpath и отключить его следующим образом:

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

этот код будет искать node с именем "NODESNAME" с атрибутом id "test" и удалить первое событие.

не забудьте сохранить xml, используя $XML- > saveXML (...);

Ответ 14

Поскольку я столкнулся с той же фатальной ошибкой, что и Джерри, и я не знаком с 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 )

Ответ 15

С помощью FluidXML вы можете использовать XPath для выбора элементов для удаления.

$doc = fluidify($doc);

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

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


XPath //*[@id="A12"] означает:

  • в любой точке документа (//)
  • каждый node (*)
  • с атрибутом id равным A12 ([@id="A12"]).

Ответ 16

Если вы хотите вырезать список похожих (не уникальных) дочерних элементов, например элементов 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});
}

Но он работает как-то случайным образом и разрезает только некоторые из элементов.

Ответ 17

МОЙ ВОПРОС: Как я могу удалить все элементы с определенными атрибутами в моем файле XML? Я хочу удалить все с помощью lang = "en, fr, es, nl, pl, ru, uk": -/Для меня важен только lang = "de". СПАСИБО ЗА ПОМОЩЬ. Ура, Сколл, Берни

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<products>
    <product>
        <code>1052103</code>
        <name>
            <value>
                <attr lang="de">Wok-Herd, 2 Brenner</attr>
                <attr lang="en">Wok cooker, 2 burners</attr>
                <attr lang="fr">Cuisinière à wok, 2 brûleurs</attr>
                <attr lang="es">Cocina wok 2 quemadores</attr>
                <attr lang="nl">Wokkookplaat, 2 branders</attr>
                <attr lang="pl">Kuchenka gazowa wok, 2 palniki</attr>
                <attr lang="ru">Вок-плита газовая GWH2</attr>
                <attr lang="uk">Плита вок, 2 конфорки</attr>
            </value>
        </name>
        <productType>HAWA</productType>
        <gtin>4015613610764</gtin>
        <netWidth>900.0</netWidth>
        <netDepth>600.0</netDepth>
        <netHeight>960.0</netHeight>
        <netWeight>58.0</netWeight>
        <grossWeight>95.0</grossWeight>
        <weightUnit>KGM</weightUnit>
        <volume>0.418</volume>
        <volumeUnit>MTQ</volumeUnit>
        <listPrice>1998.0</listPrice>
        <currency>Euro</currency>
        <listPriceValidFrom>2018-02-01T09:11:05.697+01:00</listPriceValidFrom>
        <listPriceValidTo>9999-12-31T00:00:00+01:00</listPriceValidTo>
        <discountGroup>01</discountGroup>
        <guarantee>TG</guarantee>
        <servicing>SVO</servicing>
        <customsTarifNumber>84198180</customsTarifNumber>
        <packaging>Stück</packaging>
        <datasheet>
            <value>
                <attr lang="de">http://www.bartscher.com/de/permalink/datasheet/1052103/de</attr>
                <attr lang="en">http://www.bartscher.com/de/permalink/datasheet/1052103/en</attr>
                <attr lang="fr">http://www.bartscher.com/de/permalink/datasheet/1052103/fr</attr>
                <attr lang="es">http://www.bartscher.com/de/permalink/datasheet/1052103/es</attr>
                <attr lang="nl">http://www.bartscher.com/de/permalink/datasheet/1052103/nl</attr>
                <attr lang="pl">http://www.bartscher.com/de/permalink/datasheet/1052103/pl</attr>
                <attr lang="ru">http://www.bartscher.com/de/permalink/datasheet/1052103/ru</attr>
                <attr lang="uk">http://www.bartscher.com/de/permalink/datasheet/1052103/uk</attr>
            </value>
        </datasheet>
  </product>
</products>

Ответ 18

Ваш первоначальный подход был прав, но вы забыли одну мелочь о foreach. Он не работает на исходном массиве/объекте, но создает копию каждого элемента при его итерации, поэтому вы удалили копию. Используйте ссылку следующим образом:

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