Pregunta

Tengo varios elementos idénticos con diferentes atributos a los que estoy accediendo con SimpleXML:

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

Necesito eliminar un elemento específico seg , con una identificación de " A12 " ;, ¿cómo puedo hacer esto? He intentado recorrer los elementos seg y unset ajustar el específico, pero esto no funciona, los elementos permanecen.

foreach($doc->seg as $seg)
{
    if($seg['id'] == 'A12')
    {
        unset($seg);
    }
}
¿Fue útil?

Solución

Mientras que SimpleXML proporciona una forma de eliminar nodos XML, sus capacidades de modificación son algo limitadas. Otra solución es recurrir al uso de la extensión DOM . dom_import_simplexml () lo ayudará a convertir su SimpleXMLElement en un DOMElement .

Solo un código de ejemplo (probado con 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();

salidas

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

Por cierto: seleccionar nodos específicos es mucho más simple cuando usa XPath ( SimpleXMLElement- > xpath ):

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

Otros consejos

Contrariamente a la creencia popular en las respuestas existentes, cada nodo de elemento Simplexml se puede eliminar del documento solo y unset () . El caso es que necesitas entender cómo funciona SimpleXML.

Primero ubique el elemento que desea eliminar:

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

Luego elimine el elemento representado en $ element y desarme su autorreferencia :

unset($element[0]);

Esto funciona porque el primer elemento de cualquier elemento es el elemento en sí mismo en Simplexml (autorreferencia). Esto tiene que ver con su naturaleza mágica, los índices numéricos representan los elementos en cualquier lista (por ejemplo, parent- > children), e incluso el hijo único es una lista.

Los índices de cadena no numéricos representan atributos (en acceso de matriz) o elementos secundarios (en acceso de propiedad).

Por lo tanto, las indencias numéricas en el acceso a la propiedad como:

unset($element->{0});

también funciona.

Naturalmente con ese ejemplo de xpath, es bastante sencillo (en PHP 5.4):

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

El código de ejemplo completo ( 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');

Salida:

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

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

Simplemente desarme el nodo:

$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

Este código fue tomado de Cómo eliminar / eliminar nodos en SimpleXML .

Creo que la respuesta de Stefan es correcta. Si desea eliminar solo un nodo (en lugar de todos los nodos coincidentes), aquí hay otro ejemplo:

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

Tenga en cuenta que las secciones Cargar XML ... (primero) y Formatear XML ... (último) podrían reemplazarse con un código diferente dependiendo de dónde provienen sus datos XML y qué desea hacer con la salida; son las secciones intermedias las que encuentran un nodo y lo eliminan.

Además, la instrucción if solo está ahí para garantizar que el nodo objetivo exista antes de intentar moverlo. Puede elegir diferentes formas de manejar o ignorar este caso.

Esto funciona para mí:

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

Si extiende la clase base SimpleXMLElement, puede usar este método:

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>

Para referencia futura, eliminar nodos con SimpleXML puede ser un problema a veces, especialmente si no conoce la estructura exacta del documento. Es por eso que he escrito SimpleDOM , una clase que extiende SimpleXMLElement para agregar algunos métodos convenientes.

Por ejemplo, deleteNodes () eliminará todos los nodos que coincidan con una expresión XPath. Y si desea eliminar todos los nodos con el atributo '' id '' igual a " A5 " ;, todo lo que tiene que hacer es:

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

Para eliminar / mantener nodos con cierto valor de atributo o que caen en una matriz de valores de atributo, puede extender la clase SimpleXMLElement como esta (versión más reciente en mi 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;
    }
}

Luego, teniendo su XML $ doc puede eliminar su nodo < 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);

o elimine múltiples nodos < seg / > :

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

Para mantener solo < seg id = " A5 " / > y < seg id = " A30 " / > nodos y eliminar el resto :

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

Hay una manera de eliminar un elemento hijo a través de SimpleXml. El código busca un  elemento, y no hace nada. De lo contrario, agrega el elemento a una cadena. Luego escribe la cadena en un archivo. También tenga en cuenta que el código guarda una copia de seguridad antes de sobrescribir el archivo original.

$username = 

Hay una manera de eliminar un elemento hijo a través de SimpleXml. El código busca un  elemento, y no hace nada. De lo contrario, agrega el elemento a una cadena. Luego escribe la cadena en un archivo. También tenga en cuenta que el código guarda una copia de seguridad antes de sobrescribir el archivo original.

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

Una nueva idea: simple_xml funciona como una matriz.

Podemos buscar los índices de la " matriz " queremos eliminar, y luego, usar la función unset () para eliminar estos índices de matriz. Mi ejemplo:

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

Aunque SimpleXML no tiene una forma detallada de eliminar elementos, usted puede eliminar elementos de SimpleXML utilizando unset () de PHP. La clave para hacer esto es lograr apuntar al elemento deseado. Al menos una forma de hacer la orientación es usar el orden de los elementos. Primero descubra el número de orden del elemento que desea eliminar (por ejemplo, con un bucle), luego elimine el elemento:

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

Incluso puede eliminar varios elementos con esto, almacenando el número de pedido de los elementos de destino en una matriz. Solo recuerde realizar la eliminación en orden inverso ( array_reverse ($ blancos) ), porque eliminar un elemento reduce naturalmente el número de orden de los elementos que vienen después de él.

Es cierto que es un poco complicado, pero parece funcionar bien.

También estaba luchando con este problema y la respuesta es mucho más fácil que las proporcionadas aquí. puedes buscarlo usando xpath y desarmarlo con el siguiente método:

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

este código buscará un nodo llamado " NODESNAME " con el atributo id " test " y elimine la primera ocurrencia.

recuerde guardar el xml usando $ XML- > saveXML (...);

Como encontré el mismo error fatal que Gerry y no estoy familiarizado con DOM, decidí hacerlo así:

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

La idea sobre las funciones de ayuda es de uno de los comentarios para DOM en php. net y la idea sobre el uso de unset es de kavoir.com . Para mí, esta solución finalmente funcionó:

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

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

usándolo: $ xml es SimpleXmlElement

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

El resultado se almacena en $ xml, así que no se preocupe por asignarlo a ninguna variable.

Con FluidXML puede usar XPath para seleccionar los elementos para eliminar.

$doc = fluidify($doc);

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

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


El XPcode // * [@ id = " A12 "] significa:

  • en cualquier punto del documento ( // )
  • cada nodo ( * )
  • con el atributo id igual a A12 ( [@ id = " A12 "] ).

Si desea cortar una lista de elementos secundarios similares (no únicos), por ejemplo, elementos del feed RSS, puede usar este código:

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

Cortará la cola de RSS a 10 elementos. Traté de eliminar con

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

Pero funciona de alguna manera al azar y corta solo algunos de los elementos.

Su enfoque inicial fue correcto, pero olvidó una pequeña cosa acerca de foreach. No funciona en la matriz / objeto original, pero crea una copia de cada elemento a medida que itera, por lo que desarmó la copia. Utilice referencias como esta:

foreach($doc->seg as &$seg) 
{
    if($seg['id'] == 'A12')
    {
        unset($seg);
    }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top