Search code examples
phpxmlsimplexml

Modification and replacement of nodes in xml file


On daily basis, i receive an .xml file, but some additional editing of element values is required. I've done what i need in Excel via pivot tables, but that solution is not acceptable, since that creates new structure and i cant save it as xml, and i need same xml structure as output. Since i have PHP Composer installed, i think simpleXML library is the easyest solution. Raw .xml file is uploaded and modifications are done via simplexml_load_string function. Thing is, im not very profficient in that syntax, and i need some assistance. This is my .xml file:

<?xml version="1.0" encoding="ISO-8859-2"?>
<ZCOLL>
  <IDOC BEGIN="1">
    <EDI_DC40 SEGMENT="1">
        <DOCNUM>0000000008857855</DOCNUM>
        <SERIAL>20191025143123</SERIAL>
    </EDI_DC40>
    <Z1COLL_AGENCY SEGMENT="1">
        <GPART>0000000101</GPART>
        <EMAIL>domain@domain.com</EMAIL>                        
        <Z1COLL_HEADER SEGMENT="1">
            <VKONT>200000541301</VKONT>
            <GPART>1000447089</GPART>
            <VKONA>22611402001</VKONA>
            <INKNO>00000000000101953558</INKNO>
            <INKBP>0000000101</INKBP>
            <INKDAT>20191025</INKDAT>
            <INKENDAT>20200123</INKENDAT>
            <BANKRUPTDAT>00000000</BANKRUPTDAT>
            <CLOSED/>
            <UPDATED_DATE>00000000</UPDATED_DATE>
            <COLLREFNR>200005413019-024-6</COLLREFNR>
            <NAME_ORG1>SOME OTHER NAME</NAME_ORG1>
            <LEG_CITY1>SOME OTHER CITY</LEG_CITY1>
            <LEG_POST_CODE1>105677</LEG_POST_CODE1>
            <LEG_STREET>ADDRESS 2</LEG_STREET>
            <LEG_HOUSE_NUM1/>
            <BU_SORT2>02226696981</BU_SORT2>
            <MAIL_CITY1>CITY 1</MAIL_CITY1>
            <MAIL_POST_CODE1>35220</MAIL_POST_CODE1>
            <MAIL_STREET>MAIL STREET 1</MAIL_STREET>
            <MAIL_HOUSE_NUM1/>
               <Z1COLL_ITEM SEGMENT="1">
                   <OPBEL>000210625857</OPBEL>
                   <XBLNR>0000198653579124</XBLNR>
                   <FAEDN>20190916</FAEDN>
                   <AGDAT>20191025</AGDAT>
                   <INITAMNT>         80.00</INITAMNT>
                   <PAYAMNT>          0.00</PAYAMNT>
                   <WRTOFFAMNT>          0.00</WRTOFFAMNT>
                   <OPENAMNT>         80.00</OPENAMNT>
                   <INVAMNT>         80.00</INVAMNT>
                   <WAERS>HRK</WAERS>
                   <RECINKDAT>00000000</RECINKDAT>
               </Z1COLL_ITEM>

               <Z1COLL_ITEM SEGMENT="1">
                   <OPBEL>000210625857</OPBEL>
                   <XBLNR>0000198653579124</XBLNR>
                   <FAEDN>20191016</FAEDN>
                   <AGDAT>20191025</AGDAT>
                   <INITAMNT>         80.00</INITAMNT>
                   <PAYAMNT>          0.00</PAYAMNT>
                   <WRTOFFAMNT>          0.00</WRTOFFAMNT>
                   <OPENAMNT>         80.00</OPENAMNT>
                   <INVAMNT>         80.00</INVAMNT>
                   <WAERS>HRK</WAERS>
                   <RECINKDAT>00000000</RECINKDAT>
               </Z1COLL_ITEM>

               <Z1COLL_ITEM SEGMENT="1">
                   <OPBEL>019183828875</OPBEL>
                   <XBLNR>2261140200119081</XBLNR>
                   <FAEDN>20190816</FAEDN>
                   <AGDAT>20191025</AGDAT>
                   <INITAMNT>        159.00</INITAMNT>
                   <PAYAMNT>          0.00</PAYAMNT>
                   <WRTOFFAMNT>          0.00</WRTOFFAMNT>
                   <OPENAMNT>        159.00</OPENAMNT>
                   <INVAMNT>        159.00</INVAMNT>
                   <WAERS>CURRENCY</WAERS>
                   <RECINKDAT>00000000</RECINKDAT>
               </Z1COLL_ITEM>

               <MSISDNS>
                   <MSISDN>381653490012</MSISDN>
               </MSISDNS>
               <MCD_DATA>
                   <MCD_MONTHS/>
                   <MCD_AMOUNT/>
               </MCD_DATA>
            <PHONE_NUM/>
        </Z1COLL_HEADER>            
     </Z1COLL_AGENCY>
  </IDOC>
</ZCOLL>

What i need is, if OPBEL and XBLNR element values are identical, delete that node and replace it with single duplicated value of OPBEL and XBLNR and with summarized values of INITAMNT, PAYAMNT, WRTOFFAMNT, OPENAMNT and INVAMNT, and return .xml file with same structure.

This is my code, im stuck half way:

if (isset($_POST['submit'])) {
    //echo "<pre>";
    //print_r($_FILES['file']['name']);
    //echo "</pre>";
    if(isset($_FILES['file']['name'])) {
        $get = file_get_contents($_FILES['file']['tmp_name']);
        $arr = simplexml_load_string($get);

    foreach ($arr->IDOC->Z1COLL_AGENCY->Z1COLL_HEADER as $element) {
        $delete_node = array();
        foreach($element->Z1COLL_ITEM as $item) {   
            $doubles_XBLNR = array_count_values($item->XBLNR);
            $doubles_OPBEL = array_count_values($item->OPBEL);
            $doubles_no_XBLNR = count($doubles_XBLNR);
            $doubles_no_OPBEL = count($doubles_OPBEL);
                if (($doubles_no_XBLNR > 2) && ($doubles_no_OPBEL > 2)){
                    $item->INITAMNT += $item->INITAMNT;
                    $item->PAYAMNT += $item->PAYAMNT;
                    $item->WRTOFFAMNT += $item->WRTOFFAMNT;
                    $$item->OPENAMNT += $item->OPENAMNT;
                    $item->INVAMNT += $item->INVAMNT;
                }                   
        }
        echo "<br />";  
    }

And this is desired xml output:

<?xml version="1.0" encoding="ISO-8859-2"?>
<ZCOLL>
  <IDOC BEGIN="1">
    <EDI_DC40 SEGMENT="1">
        <DOCNUM>0000000008857855</DOCNUM>
        <SERIAL>20191025143123</SERIAL>
    </EDI_DC40>
    <Z1COLL_AGENCY SEGMENT="1">
        <GPART>0000000101</GPART>
        <EMAIL>domain@domain.com</EMAIL>                    
        <Z1COLL_HEADER SEGMENT="1">
            <VKONT>200000541301</VKONT>
            <GPART>1000447089</GPART>
            <VKONA>22611402001</VKONA>
            <INKNO>00000000000101953558</INKNO>
            <INKBP>0000000101</INKBP>
            <INKDAT>20191025</INKDAT>
            <INKENDAT>20200123</INKENDAT>
            <BANKRUPTDAT>00000000</BANKRUPTDAT>
            <CLOSED/>
            <UPDATED_DATE>00000000</UPDATED_DATE>
            <COLLREFNR>200005413019-024-6</COLLREFNR>
            <NAME_ORG1>SOME OTHER NAME</NAME_ORG1>
            <LEG_CITY1>SOME OTHER CITY</LEG_CITY1>
            <LEG_POST_CODE1>105677</LEG_POST_CODE1>
            <LEG_STREET>ADDRESS 2</LEG_STREET>
            <LEG_HOUSE_NUM1/>
            <BU_SORT2>02226696981</BU_SORT2>
            <MAIL_CITY1>CITY 1</MAIL_CITY1>
            <MAIL_POST_CODE1>35220</MAIL_POST_CODE1>
            <MAIL_STREET>MAIL STREET 1</MAIL_STREET>
            <MAIL_HOUSE_NUM1/>
               <Z1COLL_ITEM SEGMENT="1">
                   <OPBEL>000210625857</OPBEL>
                   <XBLNR>0000198653579124</XBLNR>
                   <FAEDN>20190916</FAEDN>
                   <AGDAT>20191025</AGDAT>
                   <INITAMNT>         160.00</INITAMNT>
                   <PAYAMNT>          0.00</PAYAMNT>
                   <WRTOFFAMNT>          0.00</WRTOFFAMNT>
                   <OPENAMNT>         160.00</OPENAMNT>
                   <INVAMNT>         160.00</INVAMNT>
                   <WAERS>CURRENCY</WAERS>
                   <RECINKDAT>00000000</RECINKDAT>
               </Z1COLL_ITEM>

               <Z1COLL_ITEM SEGMENT="1">
                   <OPBEL>019183828875</OPBEL>
                   <XBLNR>2261140200119081</XBLNR>
                   <FAEDN>20190816</FAEDN>
                   <AGDAT>20191025</AGDAT>
                   <INITAMNT>        159.00</INITAMNT>
                   <PAYAMNT>          0.00</PAYAMNT>
                   <WRTOFFAMNT>          0.00</WRTOFFAMNT>
                   <OPENAMNT>        159.00</OPENAMNT>
                   <INVAMNT>        159.00</INVAMNT>
                   <WAERS>CURRENCY</WAERS>
                   <RECINKDAT>00000000</RECINKDAT>
               </Z1COLL_ITEM>
               <MSISDNS>
                   <MSISDN>381653490012</MSISDN>
               </MSISDNS>
               <MCD_DATA>
                   <MCD_MONTHS/>
                   <MCD_AMOUNT/>
               </MCD_DATA>
               <PHONE_NUM/>
         </Z1COLL_HEADER>           
    </Z1COLL_AGENCY>
  </IDOC>
 </ZCOLL>

Any help is appreciated. Thank you.


Solution

  • Document manipulations are much easier with DOM because each part is represented by a node object that knows about its context. For your case you can build up an index of nodes grouped by a key generated from the OPBEL and XBLNR values. Then merge the grouped nodes.

    $document = new DOMDocument();
    $document->loadXML(getXML());
    $xpath = new DOMXPath($document);
    
    // iterate all header elements (do not merge items from different headers)
    foreach ($xpath->evaluate('//Z1COLL_HEADER ') as $header) {
        $groups = [];
        // iterate the items
        foreach ($xpath->evaluate('Z1COLL_ITEM', $header) as $item) {
            // combine keys into a single string
            $groupKey = $xpath->evaluate('concat(OPBEL, "|", XBLNR)', $item);
            if (!isset($groups[$groupKey])) {
                $groups[$groupKey] = [];
            }
            // add the current item to a group defined by the generated key
            $groups[$groupKey][] = $item;
        } 
        // now filter for groups with multiple items
        $groups = array_filter($groups, function($group) { return count($group) > 1; });
    
        // iterate the groups with multiple items
        foreach ($groups as $group) {
            // extract the first item node from the array
            $firstItem = array_shift($group);
            // iterate the other items of the group
            foreach ($group as $item) {
                $merges = ['INITAMNT', 'PAYAMNT', 'WRTOFFAMNT', 'OPENAMNT', 'INVAMNT'];
                // iterate the child node names to merge
                foreach ($merges as $merge) {
                    // get the node of the first item
                    $target = $xpath->evaluate($merge, $firstItem)->item(0);
                    // get the node of the current item
                    $source = $xpath->evaluate($merge, $item)->item(0);
                    // if here are both
                    if ($target && $source) {
                       // sum the values and format them 
                       $target->textContent = number_format(
                           $target->textContent + $source->textContent, 2
                       );
                    } elseif ($source) {
                       // if the child does not exists in the first node move it over 
                       $firstItem->appendChild($source);    
                    }
                }
                $item->parentNode->removeChild($item);
            }
        }
    }
    
    echo $document->saveXML();