Remove a child with a specific attribute, in SimpleXML for PHP

后端 未结 17 2399
星月不相逢
星月不相逢 2020-11-22 02:50

I have several identical elements with different attributes that I\'m accessing with SimpleXML:


    
    

        
相关标签:
17条回答
  • 2020-11-22 02:58

    Even though SimpleXML doesn't have a detailed way to remove elements, you can remove elements from SimpleXML by using PHP's unset(). The key to doing this is managing to target the desired element. At least one way to do the targeting is using the order of the elements. First find out the order number of the element you want to remove (for example with a loop), then remove the 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]);
    }
    

    You can even remove multiple elements with this, by storing the order number of target items in an array. Just remember to do the removal in a reverse order (array_reverse($targets)), because removing an item naturally reduces the order number of the items that come after it.

    Admittedly, it's a bit of a hackaround, but it seems to work fine.

    0 讨论(0)
  • 2020-11-22 02:58

    I was also strugling with this issue and the answer is way easier than those provided over here. you can just look for it using xpath and unset it it the following method:

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

    this code will look for a node named "NODESNAME" with the id attribute "test" and remove the first occurence.

    remember to save the xml using $XML->saveXML(...);

    0 讨论(0)
  • 2020-11-22 02:59

    Your initial approach was right, but you forgot one little thing about foreach. It doesn't work on the original array/object, but creates a copy of each element as it iterates, so you did unset the copy. Use reference like this:

    foreach($doc->seg as &$seg) 
    {
        if($seg['id'] == 'A12')
        {
            unset($seg);
        }
    }
    
    0 讨论(0)
  • 2020-11-22 03:01

    Contrary to popular belief in the existing answers, each Simplexml element node can be removed from the document just by itself and unset(). The point in case is just that you need to understand how SimpleXML actually works.

    First locate the element you want to remove:

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

    Then remove the element represented in $element you unset its self-reference:

    unset($element[0]);
    

    This works because the first element of any element is the element itself in Simplexml (self-reference). This has to do with its magic nature, numeric indices are representing the elements in any list (e.g. parent->children), and even the single child is such a list.

    Non-numeric string indices represent attributes (in array-access) or child-element(s) (in property-access).

    Therefore numeric indecies in property-access like:

    unset($element->{0});
    

    work as well.

    Naturally with that xpath example, it is rather straight forward (in PHP 5.4):

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

    The full example code (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');
    

    Output:

    <?xml version="1.0"?>
    <data>
        <seg id="A1"/>
        <seg id="A5"/>
    
        <seg id="A29"/>
        <seg id="A30"/>
    </data>
    
    0 讨论(0)
  • 2020-11-22 03:04

    A new idea: simple_xml works as a array.

    We can search for the indexes of the "array" we want to delete, and then, use the unset() function to delete this array indexes. My example:

    $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]]);
    }
    
    0 讨论(0)
  • 2020-11-22 03:08

    To remove/keep nodes with certain attribute value or falling into array of attribute values you can extend SimpleXMLElement class like this (most recent version in my 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;
        }
    }
    

    Then having your $doc XML you can remove your <seg id="A12"/> node calling:

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

    or remove multiple <seg /> nodes:

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

    For keeping only <seg id="A5"/> and <seg id="A30"/> nodes and removing the rest:

    $doc->seg->filterAttribute('id', ['A5', 'A30'], TRUE);
    
    0 讨论(0)
提交回复
热议问题