web-dev-qa-db-fra.com

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

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

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

Je dois supprimer un élément seg spécifique, avec un identifiant de "A12", comment puis-je faire cela? J'ai essayé de faire une boucle entre les éléments seg et unset en spécifiant celui-ci, mais cela ne fonctionne pas, les éléments restent.

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

Alors que SimpleXML fournit un moyen de supprimer 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.

Quelques exemples 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();

les sorties

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

Au fait: la sélection de nœuds spécifiques est beaucoup plus simple lorsque vous utilisez XPath ( SimpleXMLElement-> xpath ): 

$segs=$doc->xpath('//seq[@id="A12"]');
if (count($segs)>=1) {
    $seg=$segs[0];
}
// same deletion procedure as above
51
Stefan Gehrig

Contrairement à la croyance populaire dans les réponses existantes, chaque nœud d'élément Simplexml peut être supprimé du document simplement par lui-même et par unset(). Le cas d'espèce est simplement que vous devez comprendre le fonctionnement réel de SimpleXML.

Commencez par localiser l'élément que vous souhaitez supprimer:

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

Ensuite, supprimez l’élément représenté dans $element pour que vous désactiviez son self-reference:

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 index numériques représentent les éléments de n'importe quelle liste (par exemple, parent-> enfants), et même l'enfant unique est une telle liste.

Les indices de chaîne non numériques représentent des attributs (dans un accès réseau) ou des éléments enfants (dans un accès propriété).

Donc indecies numériques dans property-access comme:

unset($element->{0});

travailler aussi bien.

Naturellement, avec cet exemple xpath, il est plutôt simple (dans 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>
55
hakre

Désactivez simplement 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 provient de Comment supprimer/supprimer des nœuds dans SimpleXML .

23
datasn.io

Je crois que la réponse de Stefan est exacte. Si vous souhaitez supprimer un seul nœud (plutôt que tous les nœuds 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');

Notez que les sections Load XML ... (première) et Format XML ... (last) peuvent être remplacées par un code différent selon la provenance de vos données XML et ce que vous voulez faire de la sortie. ce sont les sections situées entre les deux qui trouvent un nœud et le suppriment. 

De plus, l'instruction if est uniquement là pour vous assurer que le nœud cible existe avant d'essayer de le déplacer. Vous pouvez choisir différentes manières de gérer ou d'ignorer ce cas.

10
Witman

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
sunnyface45

Si vous étendez la classe SimpleXMLElement de base, 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

Pour référence ultérieure, la suppression de nœuds avec SimpleXML peut parfois s'avérer difficile, notamment 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 pratiques.

Par exemple, deleteNodes () supprimera tous les nœuds correspondant à une expression XPath. Et si vous souhaitez supprimer tous les nœuds avec l'attribut "id" égal à "A5", il vous suffit de:

// 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
Josh Davis

Pour supprimer/conserver les nœuds 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: 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;
    }
}

Ensuite, avec votre XML $doc, vous pouvez supprimer votre appel de noeud <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);

ou supprimer plusieurs nœuds <seg />:

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

Pour ne conserver que les nœuds <seg id="A5"/> et <seg id="A30"/> et supprimer le reste:

$doc->seg->filterAttribute('id', ['A5', 'A30'], TRUE);
2
Krzysztof Przygoda

Il existe un moyen de supprimer un élément enfant via SimpleXml. Le code cherche un élément, et ne fait rien. Sinon, l'élément est ajouté à une chaîne. Il écrit ensuite la chaîne dans un fichier. Notez également que le code enregistre une sauvegarde avant de remplacer le fichier d'origine.

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

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

Nous pouvons rechercher les index du "tableau" à supprimer, puis utiliser la fonction unset() pour supprimer ce 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
joan16v

L’idée des fonctions d’aide provient d’un des commentaires de DOM sur php.net et l’idée d’utiliser unset provient 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);
 }
}

l'utiliser: $ xml est SimpleXmlElement

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

Le résultat est stocké dans $ xml, ne vous inquiétez donc pas de l’affecter à une variable.

0
Ula Karzelek

Si vous souhaitez 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 coupera 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 cela fonctionne d'une manière ou d'une autre au hasard et ne coupe que certains des éléments.

0
Columbus

J'étais également aux prises avec ce problème et la réponse est bien plus simple que celles fournies ici . Vous pouvez simplement le rechercher à l'aide de xpath et le désactiver avec la méthode suivante:

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

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

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

0
Ben Yitzhaki

Même si SimpleXML ne dispose pas d'un moyen détaillé pour supprimer des éléments, vous pouvez supprimer des éléments de SimpleXML en utilisant la fonction unset() de PHP. La clé pour y parvenir consiste à cibler l’élément souhaité. Au moins une façon de faire le ciblage consiste à utiliser l'ordre des éléments. Commencez par trouver le numéro d'ordre de l'élément que vous souhaitez supprimer (par exemple avec une boucle), puis supprimez 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 supprimer plusieurs éléments avec cela en stockant le numéro d'ordre des éléments cibles dans un tableau. N'oubliez pas de procéder à la suppression dans l'ordre inverse (array_reverse($targets)), car la suppression d'un article réduit naturellement le numéro d'ordre des articles qui le suivent.

Certes, c'est un peu un hackaround, mais cela semble bien fonctionner.

0
Ilari Kajaste

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


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

  • en tout point du document (//)
  • chaque nœud (*)
  • avec l'attribut id égal à A12 ([@id="A12"]).
0
Daniele Orlando

Depuis que j'ai rencontré la même erreur fatale que Gerry et que je ne connais pas bien 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
WoodrowShigeru