it-swarm.dev

Odebrat podřízený objekt se specifickým atributem v aplikaci SimpleXML for PHP

Mám několik identických prvků s různými atributy, které používám v SimpleXML:

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

Musím odstranit konkrétní seg element, s ID "A12", jak to mohu udělat? Snažil jsem se smyčkování přes seg elementy a unset ting konkrétní, ale to nefunguje, prvky zůstanou.

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

Zatímco SimpleXML poskytuje způsob, jak odstranit XML uzly, jeho možnosti úpravy jsou poněkud omezené. Dalším řešením je použít DOM extension. dom_import_simplexml () vám pomůže s převodem SimpleXMLElement do DOMElement.

Příklad kódu (testováno pomocí 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();

výstupy

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

Mimochodem: volba konkrétních uzlů je mnohem jednodušší, když používáte XPath ( SimpleXMLElement-> xpath ): 

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

Na rozdíl od všeobecného přesvědčení o existujících odpovědích může být každý uzel prvku Simplexml z dokumentu odstraněn samotným a unset(). Jde o to, že musíte pochopit, jak SimpleXML skutečně funguje.

Nejprve vyhledejte prvek, který chcete odebrat:

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

Poté odstraňte prvek reprezentovaný v $element, který zrušíte jeho self-reference:

unset($element[0]);

Toto funguje, protože první prvek jakéhokoliv prvku je samotný prvek v Simplexml (self-reference). To má co do činění s jeho magickou povahou, číselné indexy reprezentují prvky v jakémkoliv seznamu (např. Rodiče-> děti), a dokonce i jediné dítě je takový seznam.

Non-numerické řetězcové indexy reprezentují atributy (v poli-přístup) nebo dítě-element (s) (v vlastnictví-přístup).

Číselné indexy v přístupu k nemovitostem jako:

unset($element->{0});

práce.

Přirozeně s tímto příkladem xpath je to spíše přímočaré (v PHP 5.4):

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

Kód plného příkladu ( 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');

Výstup:

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

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

Zrušit uzel:

$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

Tento kód byl převzat z Jak odstranit/odstranit uzly v SimpleXML .

23
datasn.io

Věřím, že Stefanova odpověď je na místě. Pokud chcete odstranit pouze jeden uzel (spíše než všechny odpovídající uzly), zde je další příklad:

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

Všimněte si, že sekce Nahrát XML ... (první) a Formát XML ... (poslední) mohou být nahrazeny jiným kódem v závislosti na tom, odkud data XML pocházejí a co chcete s výstupem dělat; jsou to sekce mezi nimi, které najdou uzel a odstraní jej. 

Navíc příkaz if je tam pouze proto, aby zajistil, že cílový uzel existuje před pokusem o jeho přesunutí. Můžete si vybrat různé způsoby, jak tento případ zvládnout nebo ignorovat.

10
Witman

Tato práce pro 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();
4
sunnyface45

Pokud rozšíříte základní třídu SimpleXMLElement, můžete tuto metodu použít:

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

Pro budoucí použití může být vymazání uzlů pomocí SimpleXML někdy bolestivé, zejména pokud neznáte přesnou strukturu dokumentu. To je důvod, proč jsem napsal SimpleDOM , třídu, která rozšiřuje SimpleXMLElement přidat několik pohodlných metod.

Například deleteNodes () odstraní všechny uzly odpovídající výrazu XPath. A pokud chcete smazat všechny uzly s atributem "id" rovným "A5", vše, co musíte udělat, je:

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

Chcete-li odebrat/zachovat uzly s určitou hodnotou atributu nebo spadající do pole hodnot atributů, můžete tuto třídu rozšířit SimpleXMLElement (poslední verze v mém 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;
    }
}

Poté, co máte $doc XML, můžete zrušit volání <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);

nebo odebrat více uzlů <seg />:

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

Pro zachování pouze uzlů <seg id="A5"/> a <seg id="A30"/> a odstranění zbytku:

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

Existuje způsob, jak odebrat podřízený prvek přes SimpleXml. Kód hledá prvek A neudělá nic. Jinak přidává prvek do řetězce. Potom zapíše řetězec do souboru. Všimněte si také, že kód uloží zálohu před přepsáním původního souboru.

$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

Nová myšlenka: simple_xml funguje jako pole.

Můžeme hledat indexy "pole", které chceme smazat, a pak pomocí funkce unset() smazat tento index. Můj příklad:

$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

Myšlenka na pomocné funkce je z jedné z poznámek pro DOM na php.net a nápad o použití unset je z kavoir.com . Toto řešení pro mě nakonec fungovalo: 

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

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

pomocí: $ xml je SimpleXmlElement

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

Výsledek je uložen v $ xml, takže se nemusíte starat o přiřazení proměnné.

0
Ula Karzelek

Pokud chcete vyjmout seznam podobných (ne jedinečných) podřízených prvků, například položek RSS kanálu, můžete použít tento kód:

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

To bude řez ocas RSS na 10 prvků. Pokusil jsem se odstranit

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

Funguje to ale náhodně a odstřihuje pouze některé prvky.

0
Columbus

S tímto problémem jsem se také potýkal a odpověď je mnohem jednodušší, než ta, která je zde poskytnuta. Můžete jej jednoduše vyhledat pomocí xpath a zrušit ji následujícím způsobem:

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

tento kód vyhledá uzel s názvem "NODESNAME" s atributem id "test" a odstraní první výskyt.

nezapomeňte uložit XML pomocí $ XML-> saveXML (...);

0
Ben Yitzhaki

Přestože SimpleXML nemá podrobný způsob, jak odstranit prvky, můžete můžete odstranit elementy z SimpleXML pomocí unset() PHP. Klíčem k tomu je řídit cílený prvek. Alespoň jeden způsob, jak cílit, je použít pořadí prvků. Nejprve zjistěte číslo objednávky prvku, který chcete odebrat (například smyčkou), poté prvek odeberte:

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

S tímto můžete také odstranit více prvků, a to uložením čísla pořadí cílových položek do pole. Jen nezapomeňte provést odstranění v opačném pořadí (array_reverse($targets)), protože odebrání položky přirozeně snižuje pořadové číslo položek, které po něm následují.

Je pravda, že je to trochu hackaround, ale zdá se, že to funguje dobře.

0
Ilari Kajaste

Pomocí FluidXML můžete použít XPath k výběru prvků, které chcete odebrat.

$doc = fluidify($doc);

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

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


XPath //*[@id="A12"] znamená:

  • v libovolném bodě dokumentu (//)
  • každý uzel (*)
  • s atributem id rovným A12 ([@id="A12"]).
0
Daniele Orlando

Jelikož jsem se setkal se stejnou fatální chybou jako Gerry a nejsem obeznámen s DOM, rozhodl jsem se, že to udělám takto:

$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