phpxmlxpathforeachxmldom

Looping XML containing a few child and children to output as a table in PHP


<?xml version="1.0" encoding="utf-8"?>
    <Agenda>
        <responseMessage>Success.</responseMessage>
        <jobs>
            <trip>
                <header>
                    <reservation_number>10562</reservation_number>
                    <recipient_first>John</recipient_first>
                    <recipient_middle>H</recipient_middle>
                    <recipient_last>Doe</recipient_last>
                </header>
                <legs>
                    <leg>
                        <trip_id>42390</trip_id>
                        <leg_status>Active</leg_status>
                        <pickup_date>12/24/2020</pickup_date>
                        <pickup_time>0600</pickup_time>
                        <pickup_state>New York</pickup_state>
                        <pickup_country>USA</pickup_country>
                        <dropoff_state>Pennsylvania</dropoff_state>
                        <dropoff_country>USA</dropoff_country>
                    </leg>
                    <leg>
                        <trip_id>42391</trip_id>
                        <leg_status>Canceled</leg_status>
                        <pickup_date>01/02/2021</pickup_date>
                        <pickup_time>1800</pickup_time>
                        <pickup_state>Pennsylvania</pickup_state>
                        <pickup_country>USA</pickup_country>
                        <dropoff_state>New York</dropoff_state>
                        <dropoff_country>USA</dropoff_country>
                    </leg>
                </legs>
                <secondary_services>
                    <service>
                        <service_leg_id>42390</service_leg_id>
                        <service_name>Tolls</service_name>
                        <service_rate>3.00</service_rate>
                        <service_quantity>1</service_quantity>
                    </service>
                    <service>
                        <service_leg_id>42390</service_leg_id>
                        <service_name>addtl.miles</service_name>
                        <service_rate>3.40</service_rate>
                        <service_quantity>25</service_quantity>
                    </service>
                    <service>
                        <service_leg_id>42391</service_leg_id>
                        <service_name>Tolls</service_name>
                        <service_rate>18.00</service_rate>
                        <service_quantity>1</service_quantity>
                    </service>
                    <service>
                        <service_leg_id>42391</service_leg_id>
                        <service_name>addtl.miles</service_name>
                        <service_rate>3.40</service_rate>
                        <service_quantity>29</service_quantity>
                    </service>
                </secondary_services>
            </trip>
            <trip>
                <header>
                    <reservation_number>10575</reservation_number>
                    <recipient_first>Emily</recipient_first>
                    <recipient_middle></recipient_middle>
                    <recipient_last>Santana</recipient_last>
                </header>
                <legs>
                    <leg>
                        <trip_id>64593</trip_id>
                        <leg_status>Active</leg_status>
                        <pickup_date>12/27/2020</pickup_date>
                        <pickup_time>1700</pickup_time>
                        <pickup_state>New York</pickup_state>
                        <pickup_country>USA</pickup_country>
                        <dropoff_state>Connecticut</dropoff_state>
                        <dropoff_country>USA</dropoff_country>
                    </leg>
                    <leg>
                        <trip_id>64594</trip_id>
                        <leg_status>Active</leg_status>
                        <pickup_date>01/04/2021</pickup_date>
                        <pickup_time>1200</pickup_time>
                        <pickup_state>Connecticut</pickup_state>
                        <pickup_country>USA</pickup_country>
                        <dropoff_state>New York</dropoff_state>
                        <dropoff_country>USA</dropoff_country>
                    </leg>
                </legs>
                <secondary_services>
                    <service>
                        <service_leg_id>64593</service_leg_id>
                        <service_name>Tolls</service_name>
                        <service_rate>0.00</service_rate>
                        <service_quantity>0</service_quantity>
                    </service>
                    <service>
                        <service_leg_id>64593</service_leg_id>
                        <service_name>addtl.miles</service_name>
                        <service_rate>3.40</service_rate>
                        <service_quantity>10</service_quantity>
                    </service>
                    <service>
                        <service_leg_id>64594</service_leg_id>
                        <service_name>Tolls</service_name>
                        <service_rate>04.00</service_rate>
                        <service_quantity>1</service_quantity>
                    </service>
                    <service>
                        <service_leg_id>64594</service_leg_id>
                        <service_name>addtl.miles</service_name>
                        <service_rate>3.40</service_rate>
                        <service_quantity>11</service_quantity>
                    </service>
                </secondary_services>
            </trip>
        </jobs>
    </Agenda>

I am having trouble looping through an foreach loop to output a table such as this one.

Reservation ID Status Name Date Time PU Location DO Location Tolls Addlt. Miles
10562-42390 Active John H Doe 12/24/2020 0600 New York, USA Pennsylvania, USA 3.00 25
10562-42391 Canceled John H Doe 01/02/2021 1800 Pennsylvania, USA New York, USA 18.00 29
10575-64593 Active Emily Santana 12/27/2020 1700 New York, USA Connecticut, USA 0.00 10
10575-64594 Active Emily Santana 01/04/2021 1200 Connecticut, USA New York, USA 4.00 11

I've tried a foreach loop with keys and values inside another foreach loop but I was unable to match the service element with the leg element as they have their independent loops.


Solution

  • Consider XSLT, the special-purpose language designed to transform XML files such as handling all the concatenation of values and matching of leg ids. If needed, XSLT can even convert to HTML. PHP can run XSLT 1.0 scripts with its xsl class using DOMDocument library.

    XSLT (save as .xsl file, a special .xml file)

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
        <xsl:strip-space elements="*"/>
        
        <xsl:key name="leg_key" match="leg" use="trip_id" />
        
        <xsl:template match="/Agenda">
            <xsl:copy>
                <xsl:apply-templates select="descendant::leg[generate-id() =
                                                             generate-id(key('leg_key', trip_id)[1])]"/>
            </xsl:copy>
        </xsl:template>
        
        <xsl:template match="leg">
            <xsl:copy>
                <xsl:variable name="curr_leg_id" select="trip_id"/>
                <Reservation_id>
                    <xsl:value-of select="concat(ancestor::trip/header/reservation_number, '-', trip_id)"/>
                </Reservation_id>
                <Status><xsl:value-of select="leg_status"/></Status>
                <Name><xsl:value-of select="concat(ancestor::trip/header/recipient_first, ' ', 
                                                   ancestor::trip/header/recipient_middle, ' ', 
                                                   ancestor::trip/header/recipient_last)"/></Name>
                <Date><xsl:value-of select="pickup_date"/></Date>
                <Time><xsl:value-of select="pickup_time"/></Time>
                <PU_location><xsl:value-of select="concat(pickup_state, ' ', pickup_country)"/></PU_location>
                <DO_location><xsl:value-of select="concat(dropoff_state, ' ', dropoff_country)"/></DO_location>
                <Tolls>
                    <xsl:value-of select="ancestor::trip/secondary_services/service[service_leg_id = $curr_leg_id and
                                                                                    service_name='Tolls']/service_rate"/>
                </Tolls>
                <Addl>
                    <xsl:value-of select="ancestor::trip/secondary_services/service[service_leg_id = $curr_leg_id and
                                                                                    service_name='addtl.miles']/service_quantity"/>
                </Addl>
            </xsl:copy>
        </xsl:template>
    
    </xsl:stylesheet>
    

    Online Demo

    PHP (use $new_xml for final end-use needs)

    // LOAD XML
    $xml = new DOMDocument('1.0', 'UTF-8');
    $xml->load('/path/to/Input.xml');
    
    // LOAD XSLT 
    $xsl = new DOMDocument('1.0', 'UTF-8');   
    $xsl->load('/path/to/XSLT_Script.xsl');
    
    // INITIALIZE TRANSFORMER
    $proc = new XSLTProcessor;
    $proc->importStyleSheet($xsl);
    
    // TRANSFORM ORIGINAL DOCUMENT
    $new_xml = $proc->transformToDoc($xml);
    
    // ECHO TO SCREEN 
    echo $new_xml->saveXML();
    
    // SAVE TO FILE
    file_put_contents('/path/to/Output.xml', $new_xml);