Удалить ребенка с определенным атрибутом, в 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); } } с foreach($doc->seg as $seg) { if($seg['id'] == 'A12') { unset($seg); } } 

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

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

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

Полный примерный код ( 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'); с <?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» .

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

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

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

Если вы расширите базовый класс 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(); с $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 () удалит все узлы, соответствующие выражению 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"]'); 

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

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

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

 $target = false; $i = 0; foreach ($xml->seg as $s) { if ($s['id']=='A12') { $target = $i; break; } $i++; } if ($target !== false) { unset($xml->seg[$target]); } с $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) ), потому что удаление элемента естественно уменьшает номер заказа предметов, которые появляются после него.

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

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

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

этот код будет искать узел с именем «NODESNAME» с атрибутом id «test» и удалить первое вхождение.

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

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

Идея о вспомогательных функциях – это один из комментариев для 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); } } с function Myunset($node) { unsetChildren($node); $parent = $node->parentNode; unset($node); } function unsetChildren($node) { while (isset($node->firstChild)) { unsetChildren($node->firstChild); unset($node->firstChild); } } с 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, поэтому не беспокойтесь о назначении его какой-либо переменной.

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

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

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

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