simplexmlelement - Eliminar un niño con un atributo específico, en SimpleXML para PHP
simplexml php tutorial (17)
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 seg específico, con una identificación de "A12", ¿cómo puedo hacer esto? Intenté recorrer los elementos seg y desconectar el específico, pero esto no funciona, los elementos permanecen.
foreach($doc->seg as $seg)
{
if($seg[''id''] == ''A12'')
{
unset($seg);
}
}
Aunque SimpleXML no tiene una forma detallada de eliminar elementos, puede eliminar elementos de SimpleXML mediante el uso de unset()
PHP. La clave para hacerlo es lograr el elemento deseado. Al menos una forma de hacer la orientación es usar el orden de los elementos. Primero, encuentre el número de orden del elemento que desea eliminar (por ejemplo, con un bucle) y 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 múltiples elementos con esto, almacenando el número de orden de los elementos de destino en una matriz. Solo recuerde hacer la eliminación en orden inverso ( array_reverse($targets)
), ya que la eliminación de un elemento reduce de forma natural el número de orden de los artículos que vienen después.
Es cierto que es un poco complicado, pero parece funcionar bien.
Como me encontré con 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 )
Contrariamente a la creencia popular en las respuestas existentes, cada nodo de elemento Simplexml se puede eliminar del documento por sí mismo y unset()
. El punto en el caso es que usted necesita entender cómo SimpleXML realmente funciona.
Primero ubique el elemento que desea eliminar:
list($element) = $doc->xpath(''/*/seg[@id="A12"]'');
A continuación, elimine el elemento representado en $element
, desactivó 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 de cualquier lista (por ejemplo, padres-> hijos), e incluso el hijo único es una lista.
Los índices de cadena no numéricos representan los atributos (en el acceso a la matriz) o elementos secundarios (en el acceso a la propiedad).
Por lo tanto, indecies numéricas en acceso de propiedad como:
unset($element->{0});
trabajo tambien
Naturalmente, con el 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://.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>
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 Formato XML ... (último) podrían reemplazarse con un código diferente dependiendo de dónde provienen sus datos XML y qué desea hacer con el resultado; son las secciones intermedias las que encuentran un nodo y lo eliminan.
Además, la instrucción if solo está allí para garantizar que el nodo de destino existe antes de intentar moverlo. Puede elegir diferentes formas de manejar o ignorar este caso.
Este trabajo 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();
Hay una manera de eliminar un elemento secundario 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 = $_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);
La idea sobre las funciones auxiliares proviene 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 te preocupes por asignarlo a cualquier variable.
Para eliminar / mantener nodos con cierto valor de atributo o caer en una matriz de valores de atributo, puede extender la clase SimpleXMLElement
esta manera (la 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://.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, con su XML $doc
, puede eliminar su <seg id="A12"/>
llamada al nodo:
$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 los nodos <seg id="A5"/>
y <seg id="A30"/>
y eliminar el resto:
$doc->seg->filterAttribute(''id'', [''A5'', ''A30''], TRUE);
Para referencia futura, eliminar nodos con SimpleXML puede ser problemático a veces, especialmente si no conoce la estructura exacta del documento. Es por eso que he escrito SimpleDOM , una clase que amplía SimpleXMLElement para agregar algunos métodos de conveniencia.
Por ejemplo, deleteNodes () eliminará todos los nodos que coincidan con una expresión XPath. Y si quiere 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"]'');
Si bien 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 elemento 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 usas XPath ( SimpleXMLElement->xpath ):
$segs=$doc->xpath(''//seq[@id="A12"]'');
if (count($segs)>=1) {
$seg=$segs[0];
}
// same deletion procedure as above
Si desea cortar la lista de elementos secundarios similares (no únicos), por ejemplo elementos de 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. Intenté 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.
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>
Simplemente desarma 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 .
Su enfoque inicial fue correcto, pero olvidó una pequeña cosa sobre foreach. No funciona en la matriz / objeto original, pero crea una copia de cada elemento a medida que se itera, por lo que desarma la copia. Use una referencia como esta:
foreach($doc->seg as &$seg)
{
if($seg[''id''] == ''A12'')
{
unset($seg);
}
}
También estaba lidiando con este problema y la respuesta es mucho más fácil que las que se brindan aquí. simplemente puede 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 eliminará la primera ocurrencia.
recuerde guardar el xml usando $ XML-> saveXML (...);
Una nueva idea: simple_xml
funciona como una matriz.
Podemos buscar los índices de la "matriz" que 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]]);
}