PHP XPath. Convert complex XML to array

2020-02-15 04:40发布

问题:

After three days attempting to work it out by myself I have to give up. This is an excerpt of a huge XML exported from a dB of italian laws. I would like to convert this XML to a PHP array as the goal is to use the array to build a formatted Word document using a PHP class as PHP2RTF.

<CodiceRegionale>
    <sommario>
        <elementoCapitolo>CAP a1</elementoCapitolo>
        <elementoCapitoloDescr>
        </elementoCapitoloDescr>
        <elementoSommarioLegge>
             <elementoTesto>1.1 Legge</elementoTesto>
        </elementoSommarioLegge>
    </sommario>
    <LeggeRegionale id="urn:nir:2014-12-12;26" xmlns="http://www.normeinrete.it/nir/2.1/">
         <elementoSommario xmlns="">
             <elementoCapitolo>CAP a1</elementoCapitolo>
             <elementoCapitoloDescr>
             </elementoCapitoloDescr>
             <elementoSettore />
             <elementoSettoreDescr />
         </elementoSommario>
         <intestazione>Lex 12 2014, n. 26.</intestazione>
             <articolato>
                 <articolo id="art41" xmlns="http://www.normeinrete.it/nir/2.1/">
                     <num>Art. 41</num>
                     <rubrica>(Riforma della finanza locale)</rubrica>
                     <comma id="art41-com1">
                          <num>1. </num>
                          <alinea>Al fine di supportare...</alinea>
                          <el id="art41-com1-let_a">
                              <num>a) </num>
                              <corpo>definizione di...</corpo>
                          </el>
                          <el id="art41-com1-let_b">
                               <num>b) </num>
                               <corpo>coordinamento della...</corpo>
                           </el>
                           <el id="art41-com1-let_c">
                               <num>c) </num>
                               <corpo>definizione delle...</corpo>
                           </el>
                           <el id="art41-com1-let_d">
                               <num>d) </num>
                               <corpo>la disciplina...</corpo>
                           </el>
                      </comma>
                      <comma id="art41-com2">
                           <num>2. </num>
                           <alinea>La revisione di...</alinea>
                           <el id="art41-com2-let_a">
                               <num>a) </num>
                               <corpo>
                                    razionalizzazione e...
                                    <rif xlink:href="urn:nir:stato:legge:2010-12-13;220#art1-com154" xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink">articolo 1, comma 154, della legge 13 dicembre 2010, n. 220</rif>
                                    (Legge di stabilità 2011);
                               </corpo>
                           </el>
                           <el id="art41-com2-let_b">
                               <num>b) </num>
                               <corpo>
                                    applicazione dei...
                                    <rif xlink:href="urn:nir:stato:costituzione:1947-12-27#art119" xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink">articolo 119 della Costituzione</rif>
                                    , nonché del principio...
                               </corpo>
                           </el>
                           <el id="art41-com2-let_c">
                               <num>c) </num>
                               <corpo>valorizzazione...</corpo>
                           </el>
                           <el id="art41-com2-let_d">
                               <num>d) </num>
                               <corpo>previsione di...</corpo>
                           </el>
                           <el id="art41-com2-let_e">
                               <num>e) </num>
                               <corpo>valorizzazione del...</corpo>
                           </el>
                           <el id="art41-com2-let_f">
                               <num>f) </num>
                               <corpo>previsione di...</corpo>
                           </el>
                       </comma>
                       <comma id="art41-com3">
                           <num>3. </num>
                           <corpo>La revisione normativa...</corpo>
                       </comma>
                       <comma id="art41-com4">
                           <num>4. </num>
                           <corpo>I disegni di...</corpo>
                       </comma>
                  </articolo>
                  <articolo id="art42" xmlns="http://www.normeinrete.it/nir/2.1/">
                      <num>Art. 42</num>
                      <rubrica>(Supporto finanziario regionale agli enti locali)</rubrica>
                      <comma id="art42-com1">
                          <num>1. </num>
                          <corpo>Il supporto...</corpo>
                      </comma>
                      <comma id="art42-com2">
                          <num>2. </num>
                          <corpo>Per le finalità di...</corpo>
                      </comma>
                      <comma id="art42-com3">
                          <num>3. </num>
                          <corpo>Gli incentivi regionali...</corpo>
                      </comma>
                      <comma id="art42-com4">
                          <num>4. </num>
                          <corpo>
                                In attuazione...
                                <rif xlink:href="urn:nir:stato:decreto.legislativo:1997-01-02;9#art9" xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink">articolo 9 ...</rif>
                                (Norme di attuazione dello
                                <rif xlink:href="urn:nir:regione.friuli.venezia.giulia:statuto:" xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink">statuto</rif>
                                speciale...
                          </corpo>
                      </comma>
                      <comma id="art42-com5">
                          <num>5. </num>
                          <corpo>Le modalità...</corpo>
                      </comma>
                  </articolo>
             </articolato>
        </LeggeRegionale>
        <LeggeRegionale id="urn:nir:2015-05-22;12" xmlns="http://www.normeinrete.it/nir/2.1/">
            <elementoSommario xmlns="">
                <elementoCapitolo>CAP a7</elementoCapitolo>
                <elementoCapitoloDescr>
                </elementoCapitoloDescr>
                <elementoSettore />
                <elementoSettoreDescr />
            </elementoSommario>
            <intestazione>Lex 22 2015 n. 12...</intestazione>
            <articolato>
                <articolo id="art6">
                    <num>Art. 6</num>
                    <rubrica>(Regolamento interno del CAL)</rubrica>
                    <comma id="art6-com1">
                        <num>1. </num>
                        <corpo>Il CAL approva...</corpo>
                    </comma>
                    <comma id="art6-com2">
                        <num>2. </num>
                        <alinea>Il regolamento...</alinea>
                        <el id="art6-com2-let_a">
                            <num>a) </num>
                            <corpo>l'elezione...</corpo>
                        </el>
                        <el id="art6-com2-let_b">
                            <num>b) </num>
                            <corpo>le funzioni degli organi del CAL;</corpo>
                        </el>
                        <el id="art6-com2-let_c">
                            <num>c) </num>
                            <corpo>la costituzione...</corpo>
                        </el>
                        <el id="art6-com2-let_d">
                            <num>d) </num>
                            <corpo>la programmazione...</corpo>
                        </el>
                        <el id="art6-com2-let_e">
                            <num>e) </num>
                            <corpo>i casi nei...</corpo>
                        </el>
                        <el id="art6-com2-let_f">
                            <num>f) </num>
                            <corpo>le modalità di...</corpo>
                        </el>
                    </comma>
                    <comma id="art6-com3">
                        <num>3. </num>
                        <corpo>Il regolamento è pubblicato...</corpo>
                    </comma>
               </articolo>
          </articolato>
     </LeggeRegionale>
     <LeggeRegionale id="urn:nir:2014-12-12;26" xmlns="http://www.normeinrete.it/nir/2.1/">
         <elementoSommario xmlns="">
             <elementoCapitolo>CAP a8</elementoCapitolo>
             <elementoCapitoloDescr>
             </elementoCapitoloDescr>
             <elementoSettore />
             <elementoSettoreDescr />
         </elementoSommario>
         <intestazione>Lex 12 2014, n. 26....</intestazione>
         <articolato>
             <articolo id="art17">
                  <num>Art. 17</num>
                  <rubrica>(Piano dell'Unione)</rubrica>
                  <comma id="art17-com1">
                      <num>1. </num>
                      <corpo>Il Piano dell'Unione...</corpo>
                  </comma>
                  <comma id="art17-com2">
                      <num>2. </num>
                      <corpo>
                                            Il Piano ...
                                            <rif xlink:href="urn:nir:stato:decreto.legislativo:2000;267#art170" xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink">articolo 170 ...</rif>
                                            .
                      </corpo>
                 </comma>
                 <comma id="art17-com3">
                     <num>3. </num>
                     <corpo>Il Piano dell'Unione...</corpo>
                 </comma>
                 <comma id="art17-com4">
                     <num>4. </num>
                     <corpo>La relazione annuale...</corpo>
                 </comma>
            </articolo>
        </articolato>
   </LeggeRegionale>
   <LeggeRegionale id="urn:nir:regione.friuli.venezia.giulia:legge:2014-12-12;26" xmlns="http://www.normeinrete.it/nir/2.1/">
       <elementoSommario xmlns="">
           <elementoCapitolo>CAP a14</elementoCapitolo>
           <elementoCapitoloDescr>
           </elementoCapitoloDescr>
           <elementoSettore />
           <elementoSettoreDescr />
       </elementoSommario>
       <intestazione>Lex 12 dicembre 2014, n. 26 ....</intestazione>
       <articolato>
           <articolo id="art5">
               <num>Art. 5</num>
               <rubrica>(Unioni territoriali intercomunali)</rubrica>
               <comma id="art5-com1">
                   <num>1. </num>
                   <corpo>Le Unioni territoriali...</corpo>
               </comma>
               <comma id="art5-com2">
                   <num>2. </num>
                   <corpo>
                                L'Unione ha...
                                <rif xlink:href="urn:nir:stato:decreto.legislativo:2000-08-18;267#art32" xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink">articolo 32...</rif>
                                (Testo unico delle leggi sull'ordinamento degli enti locali).
                   </corpo>
              </comma>
         </articolo>
    </articolato>
</LeggeRegionale>

The array I'm trying to build should be similar to the following:

Array
(
    [LeggeRegionale] => Array
        (
            [intestazione] => Lex 12 2014, n. 26.
            [articolato] => Array
                (
                    [num] => Art. 41
                    [articolo] => Array
                        (
                            [rubrica] => (Riforma della finanza locale)
                            [commi] => Array
                                (
                                    [num_alinea] => 1. Al fine di supportare...
                                    [el] => Array
                                        (
                                            [num_corpo] => a) definizione di...
                                            [num_corpo] => b) coordinamento della...
                                        )
                                    [num_alinea] => 2. La revisione di...
                                 )
                          )
                     [num] => Art. 42
                     ...
                  )
           )
   )

With this code I can read ALL the XML but it's absolutely useless:

$results = array();
$query = '//a:LeggeRegionale';
$result = $xpath->query($query);

$results = array();

for($i=0; $i < $result->length; $i++) {
    $results[$i] = $result->item($i)->nodeValue;
    echo $results[$i]."<BR><BR>";
}
echo "<BR>";

This code does return something structured (although it is not the array I'm looking for) but I don't understand why it's returning only the last occurrence of LeggeRegionale when I used a foreach loop.

foreach ($result as $entry) {

    $result = [
        'intestazione' => $xpath->query('//a:LeggeRegionale')->item($i)->nodeValue,
        'articoli' => array(
                        'numero'    => $xpath->query('//a:articolato/a:articolo/a:num')->item($i)->nodeValue,
                        'rubrica'   => $xpath->query('//a:articolato/a:articolo/a:rubrica')->item($i)->nodeValue,
                        'commi' => array(
                                    'numero'    => $xpath->query('//a:articolato/a:articolo/a:comma/a:num')->item($i)->nodeValue,
                                    'corpo'     => $xpath->query('//a:articolato/a:articolo/a:comma/a:corpo|//a:articolato/a:articolo/a:comma/a:alinea')->item($i)->nodeValue,
                                    'lettere'   => array(
                                                    'lettera'   => $xpath->query('//a:articolato/a:articolo/a:comma/a:el')->item($i)->nodeValue,
                                    ),
                        ),
        ),
    ];
    array_push($a, $result);
    $i++;
}

I think there is a better way - thanks to XPath - to read all descendants rather than write a foreach loop for every XML level.

回答1:

Converting complex XML into an array will result ... in a complex array. You find the XML difficult to read and you think that arrays are much easier for you so the array must be the solution. But actually XML is well suited for tree parsing in PHP. An array isn't as accessible. For example, you can't run an xpath query on an array.

And for the array, you have the mistake in your pseudo-array that you have duplicate keys. The structure looks more like:

Array
(
    [LeggeRegionale] => Array
        (
            [0] => Array
                (
                    [intestazione] => Lex 12 2014, n. 26.
                    [articolato] => Array
                        (
                            [articolo] => Array
                                (
                                    [0] => Array
                                        (
                                            [num] => Art. 41
                                            [rubrica] => (Riforma della finanza locale)
                                            [commi] => Array
                                                (
                                                    [0] => Array
                                                        (
                                                            [num_alinea] => 1. Al fine di supportare...
                                                            [num_corpo] => Array
                                                                (
                                                                    [0] => a) definizione di...
                                                                    [1] => b) coordinamento della...
                                                                    [2] => c) definizione delle...
                                                                    [3] => d) la disciplina...
                                                                )

                                                        )

                                                    [1] => Array
                                                        (
                                                            [num_alinea] => 2. La revisione di...
                                                            [num_corpo] => Array
                                                                (
                                                                    [0] => a) razionalizzazione e... articolo 1, comma 154, della legge 13 dicembre 2010, n. 220 (Legge di stabilità 2011);
                                                                    [1] => b) applicazione dei... articolo 119 della Costituzione , nonché del principio...
                                                                    [2] => c) valorizzazione...
                                                                    [3] => d) previsione di...
                                                                    [4] => e) valorizzazione del...
                                                                    [5] => f) previsione di...
                                                                )

...

And that is already with some optimization (you can ignore the wrong characters with the encoding, this is a copy & paste error).

You perhaps want to make use of a library that makes it easier to query child-nodes via xpath directly. I've given an answer that show a small & quick example in use DOMXPath and function query php, you would need to add namespace support for that. You then would need to take care of the array building, which actually is quite complex:

$doc = new DOMDocument();
$doc->load('example.xml');

/* DOMBLAZE II XMLNS */ $doc->registerNodeClass("DOMElement", "DOMBLAZE"); # ...

/** @var $root DOMBLAZE */
$root = $doc->documentElement;
$root()->registerNamespace('a', 'http://www.normeinrete.it/nir/2.1/');

$array = [];
foreach ($root('a:LeggeRegionale') as $leggioRegionale) {
    $entry                 = [];
    $entry['intestazione'] = $leggioRegionale('string(./a:intestazione)');
    $articolato            = [];
    foreach ($leggioRegionale('a:articolato/a:articolo') as $articolo) {
    }
    $array[] = $entry;
}

print_r($array);

This example is (obviously) incomplete.

Alternatively I experimented writing down the xpath expressions in an XML itself which defines the "array". This then could be used with a tailored SimpleXMLElement to build the array recursively based on the definition:

$doc = new DOMDocument();
$doc->load('example.xml');

$buffer = <<<XML
<xmlarray>
    <xml>
        <namespace prefix="a" uri="http://www.normeinrete.it/nir/2.1/"/>
    </xml>
    <array>
        <LeggeRegionale expr="a:LeggeRegionale">
            <intestazione expr="string(a:intestazione)"/>
            <articolato expr="a:articolato">
                <articolo expr="a:articolo">
                    <num expr="string(a:num)"/>
                    <rubrica expr="string(a:rubrica)"/>
                    <commi expr="a:comma">
                        <num_alinea expr="concat(a:num, a:alinea)"/>
                        <el expr="a:el" alias="num_corpo">
                            <num_corpo expr="normalize-space(concat(a:num, a:corpo))" cast="string"/>
                        </el>
                    </commi>
                </articolo>
            </articolato>
        </LeggeRegionale>
    </array>
</xmlarray>
XML;

$xmlArray = new XmlArrayElement($buffer);
$xmlArray->assignDocument($doc);
print_r($xmlArray->toArray());

This does effectively produce the array presented in the beginning of the answer.

Sure this now looks like the super solution to you, but all it did was wrapping the XML tree into another tree, this time in an array. The XmlArrayElement is not part of the example in the answer but in a gist.

It's perhaps better to make use of the recursion to create another XML document on the fly.

Also worth a consideration in your case would by XSLT which technically has been made for that. You could directly convert in a HTML document. HTML document are much more portable than RTF documents and there are existing tools to convert them into RTF but also other documents.