• Jetzt anmelden. Es dauert nur 2 Minuten und ist kostenlos!

SimpleXML node löschen / unset() / XPath

  • Ersteller Ersteller toney
  • Erstellt am Erstellt am
T

toney

Guest
Heyho,

ich versuche verzweifelt einen bestimmten Knoten aus meiner XML zu löschen. Das löschen an sich ist kein problem, wenns allerdings darum geht, einen bestimmten Knoten via XPath zu erreichen und zu löschen, dann kommt folgende Fehlermeldung:

Fatal error: Can't use method return value in write context in {path} on line 5

Hier mal der entsprechende Code:

PHP:
$db = simplexml_load_file("db/db.xml"); // XML file laden
unset($db->xpath("./entry[./id=$id]"); // zu löschenden Knoten wählen
$handle = fopen("db/db.xml", "wb"); // ins XML speichern
fwrite($handle, $db->asXML());
fclose($handle);

Wenn ich einen festen Knoten löschen will (also zB: unset($db->entry[0])), dann funktioniert das bestens. Nur mit dem XPath komme ich nicht weiter... Ich wäre deshalb für jede Hilfe dankbar :)

Gruß,
toney


//EDIT: und noch ne andere Frage: Kann ich via SimpleXML die Funktion position() irgendwie verwenden, um die Position des gewählten Knotens zu ermitteln?
 
Zuletzt bearbeitet von einem Moderator:
Du kannst unset nur mit „Symboltabellennamen“ verwenden ($foo, $foo['bar'], …). Was du dort übergibst, ist aber ein Array.

Möglich wäre:

PHP:
$items = $db->xpath("./entry[./id=$id]");
unset($items);

Das entfernt allerdings lediglich den Bezeichner $items aus der Symboltabelle. Über den Bezeichner $db sind die entsprechenden Objekte aber noch abrufbar, weshalb sie im Speicher verbleiben.

Der Trick mit unset kann mit Rückgaben der xpath-Methode meiner Ansicht nach rein prinzipiell nicht funktionieren, da für einen neuen Bezeichner (hier: $items) eine Kopie (shallow copy) der Objektstruktur angelegt wird. unset entfernt dann lediglich ein Alias auf die eigentlichen Daten.

Verwende dazu besser DOMDocument.

- xml - Remove a child with a specific attribute, in SimpleXML for PHP - Stack Overflow
- PHP: DOMDocument - Manual

Zur zweiten Frage weiß ich nichts, da ich die Funktion position nicht kenne.

Edit: Ach so, die XPath-Funktion meinst du? Ich denke schon, dass du die in einer XPath-Query verwenden kannst.
 
Ja, ich mein die XPath-Funktion. Man kann die auf jeden Fall verwenden, um einen bestimmten Knoten mit Position so und so herauszufinden, aber ich hab noch keine Möglichkeit gefunden, die Position selbst zu ermitteln...

DOM wollte ich eigentlich vermeiden, aber mir wird wohl keine andere Möglichkeit bleiben, wie ich das so sehe...
Danke für die Antwort ;)
 
Oh wie genial ich doch bin..
Ich hab nach gefühlten 2 Wochen Power-Googlen keine einzige Lösung gefunden, die mir hilft (auch nicht mit DOM XML). Angeblich ist es ja mit SimpleXML nicht möglich, Knoten zu löschen oder zu bearbeiten. Dabei ist es mit einer Mischung aus SimpleXML und fopen/fwrite/fclose so einfach.

Falls es noch irgendwen interessieren sollte, hier mein Code:

XML:
Code:
<?xml version="1.0"?>
<data>
  <images>
    <image>
      <url>img/Bild.jpg</url>
      <title>Titel</title>
      <id>1</id>
    </image>
  </images>
</data>
PHP:
$db = simplexml_load_file("admin/db.xml"); // XML Laden
$id = $_GET["id"]; // id wird per URL übergeben

foreach ($db->xpath("./images/image[./id=$id]") as $img){ // ins ParentNode des zu ändernden Knoten wechseln
$title = $img->title = $_POST["title"]; // Inhalt per Eingabe ändern
// oder: unset($img->title); zum löschen

$handle = fopen("admin/db.xml", "wb"); // XML öffnen/schreiben/schließen
fwrite($handle, $db->asXML()); 
fclose($handle);
}

Ist so simple und trotzdem ist das so nirgendwo zu finden.
 
Zuletzt bearbeitet von einem Moderator:
Siehst du auf die Weise auch eine Chance, ein gesamtes image-Element zu entfernen? Ich habe ein wenig rumprobiert, es ist mir aber nicht gelungen. (Problem ist, dass davon mehrere auf einer Ebene existieren und ein unset($images->image); aus irgendeinem Grund alle entfernt. SimpleXML…)

Mit DOM ist's einfach:

PHP:
<?php

$xmlString = <<<XML
<?xml version="1.0"?>
<data>
  <images>
    <image>
      <url>img/Bild.jpg</url>
      <title>Titel</title>
      <id>1</id>
    </image>
    <image>
      <url>img/Bild.jpg</url>
            <title>Titel</title>
      <id>2</id>
    </image>
  </images>
</data>
XML;

header('Content-Type: text/plain; charset=UTF-8');

$doc = new DOMDocument();
$doc->preserveWhiteSpace = false;
$doc->formatOutput = true;

$doc->loadXML($xmlString);

$xpath = new DOMXPath($doc);

foreach ($xpath->query('./images/image[id=1]') as $img) {
    /* @var $img DOMElement */
    $img->parentNode->removeChild($img);
}

echo $doc->saveXML();
 
„Implementiert SimpleXML evtl ArrayAccess” und unset($images->image[0]) somit möglich?

Jedenfalls hätte man statt verzweifelt zu versuchen etwas durchzudrücken, wovon man nicht einmal weiß ob es funktioniert, auf eine Alternative ausweichen können. Diese ist eben DOM+XPath. Mit SimpleXML stößt man schnell an Grenzen.

Ich sehe auch im oberen Code nicht wo etwas entfernt wird, sondern nur ersetzt.
 
toney schrieb:
PHP:
// oder: unset($img->title); zum löschen

Das haut hin, entfernt allerdings alle Kindelemente mit dem entsprechenden Elementnamen.

crash schrieb:
unset($images->image[0])

Ja, das würde gehen. Das Problem ist, diesen Index zu ermitteln.

Eventuell geht es so:

PHP:
<?php

$xmlString = <<<XML
<?xml version="1.0"?>
<data>
  <images>
    <image>
      <url>img/Bild.jpg</url>
      <title>Titel</title>
      <id>1</id>
    </image>
    <image>
      <url>img/Bild.jpg</url>
      <title>Titel</title>
      <id>2</id>
    </image>
    <image>
      <url>img/Bild.jpg</url>
      <title>Titel</title>
      <id>3</id>
    </image>
    <image>
      <url>img/Bild.jpg</url>
      <title>Titel</title>
      <id>4</id>
    </image>
  </images>
</data>
XML;

function x_simplexml_remove_element(SimpleXMLElement $element)
{
    $tmp = $element->xpath("parent::*");
    $parent = $tmp[0];

    $elementName = $element->getName();
    $found = false;
    $i = 0;

    foreach ($parent->$elementName as $img2) {
        if ($element == $img2) {
            $found = true;
            break;
        }
        $i++;
    }

    if ($found) {
        unset($parent->{$elementName}[$i]);
    }
}

$db = simplexml_load_string($xmlString);
$id = 3;

foreach ($db->xpath("./images/image[id=$id]") as $element) {
    x_simplexml_remove_element($element);
}

header('Content-Type: text/plain; charset=UTF-8');

echo $db->asXML();

Interessant:

- http://stackoverflow.com/questions/2174263/access-an-elements-parent-with-phps-simplexml
 
Zuletzt bearbeitet:
@ crash: Der Löschvorgang ist in dem Code auskommentiert. Ich brauchte sowieso für beides was, zum Löschen und zum bearbeiten. Und wie schon gesagt habe ich massenweise Beispiele ausprobiert, auch mit DOM, und nichts hat funktioniert. Dauernd kamen Fehlermeldungen von wegen "undefined function removeChild()" bla blubb, was ja in PHP5 normalerweise problemlos funktionieren sollte...

@ mermshaus: Das hab ich noch garnicht probiert, aber ich denke mal mit einem einfachen unset($img); sollte das doch eigentlich kein Problem sein?!... Werd ich morgen mal probieren.
 
Huch, hab ja direkt den letzten Post von mermshaus übersehen. Sorry.

War meine Freude wohl etwas zu früh, ne ;). Bearbeiten kann ich die XML mit meinem Code, löschen aber nur die Childs (wie ihr ja schon bemerkt habt). Ich habe deine beiden Codes ausprobiert und der zweite funktionert auch, nach einer kleinen Modifikation. Mein Problem war immer, dass ich ein externes XML-File vorliegen habe und keinen String, und da gab es immer Probleme. Hab das aber jetzt alles hinbekommen.

Danke ;)


\\EDIT: In meinem XML-File bleibt jetzt ne unschöne Leerzeile zurück, an der Stelle an der der Knoten gelöscht wurde. Bekomme ich die irgendwie noch weg?
 
Zuletzt bearbeitet von einem Moderator:
Weiß nicht, ob SimpleXML Möglichkeiten zur Neuformatierung bereitstellt. Wahrscheinlich könntest du dir aber im Zweifel eine eigene Exportfunktion schreiben, die ein SimpleXMLElement und dessen Kindelemente rekursiv abläuft und daraus das XML-Dokument erstellt.
 
Okay, werd ich mal schauen. Irgendwie muss das gehen.

Vielen Dank für deine Hilfe ;)
 
Zurück
Oben