wake-up-neo.com

Entfernen Sie ein Kind mit einem bestimmten Attribut in SimpleXML für PHP

Ich habe mehrere identische Elemente mit unterschiedlichen Attributen, auf die ich mit SimpleXML zugreife:

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

Ich muss ein bestimmtes seg -Element mit der ID "A12" entfernen. Wie kann ich das tun? Ich habe versucht, die einzelnen seg - Elemente zu durchlaufen und unset das spezifische Element zu durchlaufen.

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

Während SimpleXMLeine Möglichkeit zum Entfernen XML-Knoten bietet, sind die Modifikationsmöglichkeiten etwas eingeschränkt. Eine andere Lösung besteht darin, auf die Erweiterung DOM zurückzugreifen. dom_import_simplexml () hilft Ihnen bei der Umwandlung Ihrer SimpleXMLElement in eine DOMElement.

Nur ein Beispielcode (getestet mit 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();

ausgänge

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

Übrigens: Die Auswahl bestimmter Knoten ist viel einfacher, wenn Sie XPath verwenden ( SimpleXMLElement-> xpath ): 

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

Entgegen der weit verbreiteten Annahme der vorhandenen Antworten kann jeder Simplexml-Elementknoten nur von sich selbst und unset() aus dem Dokument entfernt werden. In diesem Fall müssen Sie nur verstehen, wie SimpleXML tatsächlich funktioniert.

Suchen Sie zuerst das Element, das Sie entfernen möchten:

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

Entfernen Sie dann das Element, das in $element dargestellt ist, und setzen Sie seine self-reference auf:

unset($element[0]);

Dies funktioniert, da das erste Element eines Elements das Element selbst in Simplexml ist (Selbstreferenz). Dies hat mit seiner magischen Natur zu tun, numerische Indizes repräsentieren die Elemente in einer Liste (z. B. Parent-> Child) und sogar das einzelne Kind ist eine solche Liste.

Nicht-numerische String-Indizes repräsentieren Attribute (bei Array-Zugriff) oder untergeordnete Elemente (bei Eigenschafts-Zugriff).

Daher sind numerische Unabhängigkeit im Property-Access wie:

unset($element->{0});

arbeit auch.

Bei diesem Xpath-Beispiel ist es natürlich recht einfach (in PHP 5.4):

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

Der vollständige Beispielcode ( 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');

Ausgabe:

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

    <seg id="A29"/>
    <seg id="A30"/>
</data>
55
hakre

Einfach den Knoten aufheben:

$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

Dieser Code wurde aus Entfernen/Entfernen von Knoten in SimpleXML entnommen.

23
datasn.io

Ich glaube, dass Stefans Antwort richtig ist. Wenn Sie nur einen Knoten (und nicht alle übereinstimmenden Knoten) entfernen möchten, finden Sie hier ein anderes Beispiel:

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

Beachten Sie, dass die Abschnitte XML laden ... (zuerst) und XML formatieren ... (zuletzt) ​​durch unterschiedlichen Code ersetzt werden können, je nachdem, woher Ihre XML-Daten stammen und was Sie mit der Ausgabe machen möchten. Es sind die Abschnitte dazwischen, die einen Knoten finden und entfernen. 

Außerdem ist die if-Anweisung nur vorhanden, um sicherzustellen, dass der Zielknoten vorhanden ist, bevor er versucht wird, ihn zu verschieben. Sie können verschiedene Methoden wählen, um diesen Fall zu behandeln oder zu ignorieren.

10
Witman

Diese Arbeit für mich:

$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

Wenn Sie die Basisklasse SimpleXMLElement erweitern, können Sie diese Methode verwenden:

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>

Das Löschen von Knoten mit SimpleXML kann zuweilen sehr schwierig sein, insbesondere wenn Sie die genaue Struktur des Dokuments nicht kennen. Deshalb habe ich SimpleDOM geschrieben, eine Klasse, die SimpleXMLElement erweitert, um einige Convenience-Methoden hinzuzufügen.

Beispielsweise löscht deleteNodes () alle Knoten, die mit einem XPath-Ausdruck übereinstimmen. Und wenn Sie alle Knoten löschen möchten, deren Attribut "id" gleich "A5" ist, müssen Sie lediglich Folgendes tun:

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

Um Knoten mit einem bestimmten Attributwert zu entfernen oder in einem Array von Attributwerten zu bleiben, können Sie die SimpleXMLElement-Klasse folgendermaßen erweitern (neueste Version in meinem 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;
    }
}

Wenn Sie dann Ihre $doc-XML haben, können Sie Ihren <seg id="A12"/>-Knoten entfernen, indem Sie Folgendes aufrufen:

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

oder mehrere <seg />-Knoten entfernen:

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

Um nur <seg id="A5"/>- und <seg id="A30"/>-Knoten zu behalten und den Rest zu entfernen:

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

Es gibt eine Möglichkeit, ein untergeordnetes Element über SimpleXml zu entfernen. Der Code sucht nach einem Element und tut nichts. Andernfalls wird das Element einem String hinzugefügt. Die Zeichenfolge wird dann in eine Datei geschrieben. Beachten Sie auch, dass der Code eine Sicherung speichert, bevor Sie die Originaldatei überschreiben.

$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

Eine neue Idee: simple_xml arbeitet als Array.

Wir können nach den Indizes des "Arrays" suchen, das wir löschen möchten, und dann die Funktion unset() verwenden, um diese Array-Indizes zu löschen. Mein Beispiel:

$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

Die Idee zu Hilfsfunktionen stammt von einem der Kommentare für DOM auf php.net und die Idee zur Verwendung von unset stammt von kavoir.com . Für mich hat diese Lösung endlich funktioniert: 

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

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

verwenden Sie es: $ xml ist SimpleXmlElement

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

Das Ergebnis wird in $ xml gespeichert. Machen Sie sich also keine Sorgen, es einer Variablen zuzuordnen.

0
Ula Karzelek

Wenn Sie eine Liste ähnlicher (nicht eindeutiger) untergeordneter Elemente ausschneiden möchten, z. B. Elemente eines RSS-Feeds, können Sie diesen Code verwenden:

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

Der RSS-Ausschnitt wird auf 10 Elemente reduziert. Ich habe versucht mit zu entfernen

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

Aber es funktioniert irgendwie zufällig und schneidet nur einige Elemente.

0
Columbus

Ich hatte auch Probleme mit diesem Problem und die Antwort ist viel einfacher als die hier vorgestellten. Sie können es einfach mit xpath suchen und die folgende Methode deaktivieren:

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

dieser Code sucht nach einem Knoten mit dem Namen "NODESNAME" mit dem ID-Attribut "Test" und entfernt das erste Vorkommen.

denken Sie daran, die XML-Datei mit $ XML-> saveXML (...) zu speichern.

0
Ben Yitzhaki

Auch wenn SimpleXML keine detaillierte Methode zum Entfernen von Elementen hat, können Sie can Elemente mit Hilfe von PHP unset() aus SimpleXML entfernen. Der Schlüssel dazu ist, das gewünschte Element als Ziel festzulegen. Mindestens eine Möglichkeit für das Targeting besteht in der Reihenfolge der Elemente. Ermitteln Sie zunächst die Bestellnummer des Elements, das Sie entfernen möchten (zum Beispiel mit einer Schleife), und entfernen Sie dann das Element:

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

Sie können sogar mehrere Elemente entfernen, indem Sie die Bestellnummer der Zielelemente in einem Array speichern. Denken Sie daran, das Entfernen in umgekehrter Reihenfolge durchzuführen (array_reverse($targets)), da das Entfernen eines Elements die Bestellnummer der darauf folgenden Elemente reduziert.

Zugegeben, es ist ein bisschen hackarisch, aber es scheint gut zu funktionieren.

0
Ilari Kajaste

Mit FluidXML können Sie XPath verwenden, um die zu entfernenden Elemente auszuwählen.

$doc = fluidify($doc);

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

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


Der XPath //*[@id="A12"] bedeutet:

  • in einem beliebigen Punkt des Dokuments (//)
  • jeder Knoten (*)
  • mit dem Attribut id gleich A12 ([@id="A12"]).
0
Daniele Orlando

Da ich auf denselben schwerwiegenden Fehler wie Gerry gestoßen bin und mir DOM nicht vertraut ist, habe ich mich dazu entschlossen, dies so zu tun:

$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