Supprimer un enfant avec un attribut spécifique, en SimpleXML pour PHP

j'ai plusieurs éléments identiques avec des attributs différents auxquels j'accède avec SimpleXML:

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

j'ai besoin de supprimer une seg "151960920 d'élément avec l'id "A12", comment puis-je faire cela? J'ai essayé la boucle à travers les seg elements et unset ting le spécifique, mais cela ne fonctionne pas, les éléments restent.

foreach($doc->seg as $seg)
{
    if($seg['id'] == 'A12')
    {
        unset($seg);
    }
}
43
demandé sur Stefan Gehrig 2008-11-04 19:24:50

17 réponses

alors que SimpleXML fournit une façon de supprimer les noeuds XML, ses capacités de modification sont quelque peu limitées. Une autre solution consiste à utiliser L'extension DOM . dom_import_simplexml () vous aidera à convertir votre SimpleXMLElement en DOMElement .

juste un exemple de code (testé avec 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();

sorties

<?xml version="1.0"?>
<data><seg id="A1"/><seg id="A5"/><seg id="A29"/><seg id="A30"/></data>

soit dit en passant: sélectionner des noeuds spécifiques est beaucoup plus simple quand vous utilisez XPath ( SimpleXMLElement - >xpath ):

$segs=$doc->xpath('//seq[@id="A12"]');
if (count($segs)>=1) {
    $seg=$segs[0];
}
// same deletion procedure as above
51
répondu Stefan Gehrig 2017-05-23 11:47:13

contrairement à la croyance populaire dans les réponses existantes, chaque noeud D'élément Simplexml peut être retiré du document par lui-même et unset() . Le point dans le cas est juste que vous devez comprendre comment SimpleXML fonctionne réellement.

localisez D'abord l'élément que vous voulez supprimer:

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

puis supprimer l'élément représenté dans $element vous désactivez son référence personnelle :

unset($element[0]);

Cela fonctionne parce que le premier élément d'un élément est l'élément lui-même dans Simplexml (auto-référence). Cela a à voir avec sa nature magique, les indices numériques représentent les éléments dans n'importe quelle liste (par exemple parent->enfants), et même l'enfant unique est une telle liste.

les indices non numériques de chaîne de caractères représentent des attributs (dans array-access) ou un(des) élément (s) enfant (s) (dans property-access).

donc indecies numériques dans la propriété-accès comme:

unset($element->{0});

fonctionne aussi.

naturellement avec cet exemple de xpath, il est plutôt simple (en PHP 5.4):

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

le code d'exemple complet ( 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');

sortie:

<?xml version="1.0"?>
<data>
    <seg id="A1"/>
    <seg id="A5"/>

    <seg id="A29"/>
    <seg id="A30"/>
</data>
54
répondu hakre 2014-11-01 14:20:02

il suffit de débrancher le noeud:

$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

ce code a été pris dans comment supprimer / supprimer les noeuds dans SimpleXML .

22
répondu datasn.io 2013-04-10 07:45:18

je crois que la réponse de Stefan est juste. Si vous voulez supprimer un seul noeud (plutôt que tous les noeuds correspondants), voici un autre exemple:

//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');

noter que les sections chargent XML... (le premier) et le Format XML... (last) peut être remplacé par du code différent selon l'origine de vos données XML et ce que vous voulez faire avec la sortie; ce sont les sections entre celles qui trouvent un noeud et le suppriment.

en outre, la déclaration if n'est là que pour s'assurer que le nœud cible existe avant d'essayer de le déplacer. Vous pouvez choisir différentes façons de gérer ou d'ignorer cette affaire.

10
répondu Witman 2012-03-02 09:56:14

si vous prolongez la classe de base SimpleXMLElement, vous pouvez utiliser cette méthode:

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>
4
répondu Michał Tatarynowicz 2010-09-10 19:13:23

ce travail pour moi:

$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
répondu sunnyface45 2014-10-04 01:29:06

pour référence future, supprimer des noeuds avec SimpleXML peut parfois être pénible, surtout si vous ne connaissez pas la structure exacte du document. C'est pourquoi j'ai écrit SimpleDOM , une classe qui étend SimpleXMLElement pour ajouter quelques méthodes de commodité.

par exemple, deleteNodes() supprimera tous les noeuds correspondant à une expression XPath. Et si vous voulez supprimer tous les noeuds avec l'attribut " id "égal à "A5", tout ce que vous avez à faire est:

// 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
répondu Josh Davis 2009-11-15 15:44:31

pour supprimer/conserver les noeuds ayant une certaine valeur d'attribut ou tombant dans un tableau de valeurs d'attribut, vous pouvez étendre la classe SimpleXMLElement comme ceci (version la plus récente dans mon 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: /q/simplexml-remove-nodes-60742/"A12"/> node calling:

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

ou supprimer plusieurs <seg /> noeuds:

$doc->seg->filterAttribute('id', ['A1', 'A12', 'A29'], FALSE);

pour la conservation seulement <seg id="A5"/> et <seg id="A30"/> noeuds et en enlevant le reste:

$doc->seg->filterAttribute('id', ['A5', 'A30'], TRUE);
2
répondu Krzysztof Przygoda 2018-04-23 14:13:18

il y a un moyen de supprimer un élément enfant via SimpleXml. Le code se présente pour un élément, et ne fait rien. Sinon, il ajoute l'élément à une chaîne. Puis, il écrit la chaîne dans un fichier. Notez également que le code enregistre une sauvegarde avant d'écraser le fichier original.

$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
répondu 2008-12-06 03:58:27

Une nouvelle idée: simple_xml fonctionne comme un tableau.

nous pouvons rechercher les index du" tableau "que nous voulons supprimer, et ensuite, utiliser la fonction unset() pour supprimer ces index de tableau. Mon exemple:

$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]]);
}
1
répondu joan16v 2014-10-04 01:31:44

même si SimpleXML n'a pas de méthode détaillée pour supprimer des éléments, vous peut supprimer des éléments de SimpleXML en utilisant unset() de PHP . Pour ce faire, il faut réussir à cibler l'élément désiré. Au moins une façon de cibler est d'utiliser l'ordre des éléments. D'abord trouver le numéro de commande de l'élément que vous voulez supprimer (par exemple avec une boucle), puis retirez l'élément:

$target = false;
$i = 0;
foreach ($xml->seg as $s) {
  if ($s['id']=='A12') { $target = $i; break; }
  $i++;
}
if ($target !== false) {
  unset($xml->seg[$target]);
}

vous pouvez même enlever plusieurs éléments avec cette, en stockant le numéro de commande de la cible éléments dans un tableau. Rappelez-vous juste de faire l'enlèvement dans un ordre inverse ( array_reverse($targets) ), parce que l'enlèvement d'un élément réduit naturellement le nombre d'ordre des éléments qui viennent après elle.

certes, c'est un peu une farce, mais ça a l'air de marcher.

0
répondu Ilari Kajaste 2009-10-11 13:45:51

j'étais également strugling avec cette question et la réponse est beaucoup plus facile que ceux fournis ici. vous pouvez simplement le chercher en utilisant xpath et le désactiver la méthode suivante:

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

ce code recherchera un noeud nommé" NODESNAME "avec l'attribut id" test " et supprimera la première occurrence.

n'oubliez pas de sauvegarder le xml en utilisant $XML->saveXML(...);

0
répondu Ben Yitzhaki 2013-05-22 12:29:01

comme J'ai rencontré la même erreur fatale que Gerry et que je ne suis pas familier avec DOM, j'ai décidé de le faire comme ceci:

$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 )
0
répondu WoodrowShigeru 2014-10-04 01:21:51

Idée à propos des fonctions d'assistance est à partir de l'un des commentaires de DOM sur php.net et de l'idée sur l'utilisation de la fonction unset est de kavoir.com . Pour moi, Cette solution a finalement fonctionné:

function Myunset($node)
{
 unsetChildren($node);
 $parent = $node->parentNode;
 unset($node);
}

function unsetChildren($node)
{
 while (isset($node->firstChild))
 {
 unsetChildren($node->firstChild);
 unset($node->firstChild);
 }
}

en l'utilisant: $xml is SimpleXmlElement

Myunset($xml->channel->item[$i]);

le résultat est stocké dans $xml, donc ne vous inquiétez pas de l'affecter à n'importe quelle variable.

0
répondu Ula Karzelek 2014-10-04 01:37:21

avec FluidXML vous pouvez utiliser XPath pour sélectionner les éléments à supprimer.

$doc = fluidify($doc);

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

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


XPath //*[@id="A12"] signifie:

  • en tout point du document ( // )
  • chaque noeud ( * )
  • avec l'attribut id égal à A12 ( [@id="A12"] ).
0
répondu Daniele Orlando 2016-01-23 23:33:03

si vous voulez couper la liste d'éléments enfants similaires (non uniques), par exemple des éléments de flux RSS, vous pouvez utiliser ce code:

for ( $i = 9999; $i > 10; $i--) {
    unset($xml->xpath('/rss/channel/item['. $i .']')[0]->{0});
}

il va couper queue de RSS à 10 éléments. J'ai essayé d'enlever avec

for ( $i = 10; $i < 9999; $i ++ ) {
    unset($xml->xpath('/rss/channel/item[' . $i . ']')[0]->{0});
}

mais il fonctionne d'une façon ou d'une autre au hasard et ne Coupe que certains des éléments.

0
répondu Columbus 2016-03-09 09:07:22

votre approche initiale était juste, mais vous avez oublié une petite chose au sujet de foreach. Il ne fonctionne pas sur le tableau/objet original, mais crée une copie de chaque élément au fur et à mesure qu'il itère, donc vous avez désactivé la copie. Utilisez la référence comme ceci:

foreach($doc->seg as &$seg) 
{
    if($seg['id'] == 'A12')
    {
        unset($seg);
    }
}
-2
répondu posthy 2010-03-13 02:36:25