1
votes

Modification et remplacement des nœuds dans le fichier xml

Je reçois quotidiennement un fichier .xml, mais une modification supplémentaire des valeurs des éléments est nécessaire. J'ai fait ce dont j'avais besoin dans Excel via des tableaux croisés dynamiques, mais cette solution n'est pas acceptable, car cela crée une nouvelle structure et je ne peux pas l'enregistrer au format xml, et j'ai besoin de la même structure xml en sortie. Depuis que j'ai installé PHP Composer, je pense que la bibliothèque simpleXML est la solution la plus simple. Le fichier .xml brut est téléchargé et les modifications sont effectuées via la fonction simplexml_load_string. La chose est, je ne suis pas très compétent dans cette syntaxe, et j'ai besoin d'aide. Voici mon fichier .xml:

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

Ce dont j'ai besoin, c'est que si les valeurs des éléments OPBEL et XBLNR sont identiques, supprimez ce nœud et remplacez-le par une seule valeur dupliquée de OPBEL et XBLNR et par des valeurs résumées de INITAMNT, PAYAMNT, WRTOFFAMNT, OPENAMNT et INVAMNT, et retourne un fichier .xml avec la même structure.

Ceci est mon code, je suis coincé à mi-chemin:

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 />";  
    }

Et c'est la sortie xml souhaitée:

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

Toute aide est appréciée. Merci.


0 commentaires

4 Réponses :


0
votes

Que le code suivant réponde à votre question.

Vous pouvez également utiliser dom et xpath pour analyser le xml . Je créerais un tableau de recherche contenant les valeurs OPBEL et XBLNR par leur index d'occurrence.

Ensuite, vous pouvez trouver les index de nœuds dupliqués en utilisant array_unique , array_diff_assoc , array_keys et array_intersect , voir également cette réponse .

Vous pouvez maintenant supprimer les nœuds dupliqués et en insérer un nouveau avec leurs valeurs agrégées. Voici le code, où data / input.xml est votre fichier xml fourni.

<?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</INITAMNT>
                   <PAYAMNT>0</PAYAMNT>
                   <WRTOFFAMNT>0</WRTOFFAMNT>
                   <OPENAMNT>160</OPENAMNT>
                   <INVAMNT>160</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>

La sortie générée est la suivante:

$xml = file_get_contents(__DIR__ . '/data/input.xml');
$dom = new \DOMDocument('1.0', 'UTF-8');
@$dom->loadXML($xml);
$xpath = new \DOMXPath($dom);
$nodes = $xpath->query('//Z1COLL_ITEM');

$search = [];
$index = 0;

foreach ($nodes as $node) {
    $OPBEL = $xpath->query('OPBEL', $node)->item(0)->nodeValue;
    $XBLNR = $xpath->query('XBLNR', $node)->item(0)->nodeValue;

    $search[$index] = $OPBEL . $XBLNR;
    $index++;
}

// Unique values
$unique = array_unique($search);

// Duplicates
$duplicates = array_diff_assoc($search, $unique);

// Get duplicate keys
$duplicateIndeces = array_keys(array_intersect($search, $duplicates));

$aggregate = [];

$firstNode = $xpath->query('//Z1COLL_ITEM')->item($duplicateIndeces[0]);

// Iterate through the duplicated nodes
foreach (array_slice($duplicateIndeces, 1) as $duplicateIndex) {
    $node = $xpath->query('//Z1COLL_ITEM')->item($duplicateIndex);

    // Update the desired values for the first occurrence
    $xpath->query('INITAMNT', $firstNode)->item(0)->nodeValue += $xpath->query('INITAMNT', $node)->item(0)->nodeValue;
    $xpath->query('PAYAMNT', $firstNode)->item(0)->nodeValue += $xpath->query('PAYAMNT', $node)->item(0)->nodeValue;
    $xpath->query('WRTOFFAMNT', $firstNode)->item(0)->nodeValue += $xpath->query('WRTOFFAMNT', $node)->item(0)->nodeValue;
    $xpath->query('OPENAMNT', $firstNode)->item(0)->nodeValue += $xpath->query('OPENAMNT', $node)->item(0)->nodeValue;
    $xpath->query('INVAMNT', $firstNode)->item(0)->nodeValue += $xpath->query('INVAMNT', $node)->item(0)->nodeValue;

    // Remove the duplicated node
    $node->parentNode->removeChild($node);
}

echo $dom->saveXML();


4 commentaires

Hmm, pas d'erreurs, mais pas de valeurs en écho, var_dump donne la chaîne (39) ""


Ok, résolu, c'était mon erreur en éditant certaines parties, maintenant je ne peux pas me débarrasser de "Erreur non interceptée: appel à une fonction membre removeChild ()"


@Greedy Pour moi, le résultat est très bien en utilisant votre entrée xml. Quelle est la sortie des vairables $ search , $ nodes , $ duplicateIndeces , $ firstNode ?


Ok, je comprends, le code fonctionne comme prévu. Merci.



2
votes

Les manipulations de documents sont beaucoup plus faciles avec DOM car chaque partie est représentée par un objet nœud qui connaît son contexte. Pour votre cas, vous pouvez créer un index de nœuds regroupés par une clé générée à partir des valeurs OPBEL et XBLNR. Puis fusionnez les nœuds groupés.

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


3 commentaires

Intéressant à quel point l'utilisation de DOMDocument et SimpleXML est différente.


Différents points de vue: SimpleXML est une abstraction qui tente de mapper XML à des structures de classe / objet. Il traite les données comme du code. DOM lui-même est un arbre d'objets représentant une structure de données.


Je parlais plus des différences entre les API, les deux utilisent DOM avec leur propre interprétation de la façon dont vous pouvez accéder au contenu. Je sais que DOMDocument est plus puissant et plus précis sur la façon dont il le permet, mais SimpleXML (IMHO) a sa place dans le traitement de données pur comme cet exemple.



0
votes

Je recommanderais XSLT pour cela. Il me semble (dans XSLT 3.0):

<xsl:transform version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  expand-text="yes">

<xsl:mode on-no-match="shallow-copy"/>

<xsl:template match="Z1COLL_HEADER">
  <xsl:copy>
    <xsl:copy-of select="@*"/>
    <xsl:apply-templates select="* except Z1COLL_ITEM"/>
    <xsl:for-each-group select="Z1COLL_ITEM" group-by="OPBEL, XBLNR" composite="yes">
       <xsl:copy-of select="current-group()[1]/(OPBEL,XBLNR, FAEDN, AGDAT)"/>
       <INITAMNT>{sum(current-group()/INITAMT)}</INITAMNT>
       <PAYAMNT>{sum(current-group()/PAYAMNT)}</PAYAMNT>
       <WRTOFFAMNT>{sum(current-group()/WRTOFFAMNT)}</WRTOFFAMNT>
       <OPENAMNT>{sum(current-group()/OPENAMNT)}</OPENAMNT>
       <INVAMNT>{sum(current-group()/INVAMNT)}</INVAMNT>
       <xsl:copy-of select="current-group()[1]/(WAERS, RECINKDAT)"/>
    </xsl:for-each-group>
  </xsl:copy>
</xsl:template>

</xsl:transform>

Des solutions avec des versions antérieures de XSLT sont également possibles mais un peu plus verbeuses.


0 commentaires

0
votes

Cette solution utilise SimpleXML et garde essentiellement une trace des éléments qu'elle a trouvés jusqu'à présent, puis si elle trouve un autre enregistrement similaire, ajoute simplement les valeurs au premier trouvé. Il utilise ensuite (dans ce cas) unset ($ entry [0]); pour supprimer l'entrée dupliquée du document d'origine ...

$xml = simplexml_load_file($_FILES['file']['tmp_name']);
$existing = [];

foreach ( $xml->xpath("//Z1COLL_ITEM") as $entry )  {
    $index = $entry->OPBEL."#".$entry->XBLNR;
    if ( isset ($existing[$index]) )    {
        $existing[$index]->INITAMNT += $entry->INITAMNT;
        $existing[$index]->PAYAMNT += $entry->PAYAMNT;
        $existing[$index]->WRTOFFAMNT += $entry->WRTOFFAMNT;
        $existing[$index]->OPENAMNT += $entry->OPENAMNT;
        $existing[$index]->INVAMNT += $entry->INVAMNT;
        unset($entry[0]);
    }
    else {
        $existing[$index] = $entry;
    }
}


0 commentaires