Search code examples
phpxmlunset

How can I delete from xml in foreach?


I have xml file.

<shop>
 <categories>
    <category id="132">kids</category>
    <category id="62" parentId="133">women</category>
    <category id="172" parentId="1">men</category>
</categories>

<materials>
    <material id="1">one</material>
    <material id="2">two</material>
    <material id="3">soon</material>
</materials>

<offers>

        <offer id="41850" available="true">
            <price>3220</price>
            <currencyId>EUR</currencyId>
            <date>2015-02-05</date>
        </offer>
        <offer id="41850" available="true">
            <price>3220</price>
            <currencyId>EUR</currencyId>
            <date>2015-02-05</date>
        </offer>
        <offer id="41850" available="true">
            <price>3220</price>
            <currencyId>EUR</currencyId>
            <date>2015-02-05</date>
        </offer>
    <offer id="77777" available="true">
            <price>3250</price>
            <currencyId>EUR</currencyId>
            <date>2015-02-05</date>
        </offer>
    <offer id="41340" available="true">
            <price>3120</price>
            <currencyId>EUR</currencyId>
            <date>2015-02-05</date>
        </offer>

I need delete categories and materials. After that i need delete all offers which are not unique and write it to new xml file.

My problem. I can't delete not unique offers. Please help me! Thx. Here is my code.

 $url = 'test.xml';
$yml = simplexml_load_file($url);  
unset($yml->shop->categories);     
unset($yml->shop->materials);      

$itemid = '0'; 

foreach ($yml->shop->offers->offer as $item){
    $sravnit = $item['id'];  
    if("$sravnit" == "$itemid") {
        echo "$sravnit eq $itemid delete<br>";
        unset($yml->shop->offers->offer[$sravnit]); 
        continue; 
    else {echo "$sravnit not eq $itemid ";
    $itemid = $sravnit;
    echo "itemid to - $itemid <br>"; 
    }


 }
$yml->asXML('odd.xml'); 

Solution

  • This syntax doesn't make sense in multiple ways:

    unset($yml->shop->offers->offer[$sravnit]); 
    
    1. <shop> is your root node and that is represented by $yml.
      So the path is $yml->offers->offer

    2. you can't select the <offer> with the id of $sravnit like this. It is wrong.
      Instead SimpleXml will select the nth node in a list like this:

      <drinks>
          <drink id="2">beer</drink>
          <drink id="5">wine</drink>
          <drink id="0">water</drink>
      </drinks>
      

      echo $xml->drink[2]; is water, I'm sorry. Check it: https://eval.in/464564
      http://php.net/manual/en/simplexml.examples-basic.php is a recommended read.

    3. use of unset(): see this answer Remove a child with a specific attribute, in SimpleXML for PHP for a good in-depth demonstration on how to combine xpath() and unset() to delete specific nodes with SimpleXml.

    In your case, you might first check for non-unique id-attributes in the <offer> nodes, and delete those in a second step.

    step #1: list of non-unique id

    1. Create an array of all id values with xpath() like this:

      $ids = $yml->xpath("//offer/@id");
      
    2. $idsis an array of SimpleXml-elements, use array_map() to convert to integer values:

      $ids = array_map("intval", $ids);
      
    3. To get the non-unique values of the array $ids:

      $ids = array_count_values($ids);
      

      will return an array with index = id and value = count:

      array(3) {
        [41850]=>int(3)
        [77777]=>int(1)
        [41340]=>int(1)
      }
      
    4. a unique id has the value of 1, so let us delete all those:

      $ids = array_diff($ids, array(1));
      
    5. Now you have an array with all non-unique id, let's flip index and value:

      $ids = array_flip($ids);
      

      This is the result:

      array(1) {
        [3]=>int(41850)
      }
      
    6. Summary: let's write all previous steps in two lines:

      $ids = $yml->xpath("//offer/@id");
      $ids = array_flip(array_diff(array_count_values(array_map("intval", $ids)), array(1)));
      

    step #2: delete all <offer> with their id attribute in $ids:
    I will use xpath() to select the node, and unset() to delete it:

    foreach ($ids as $id) {
        foreach ($xml->xpath("//offer[@id='$id']") as $item)
            unset ($item[0]);
    }
    

    See it in action: https://eval.in/464608