Search code examples
phparraysxmlsimplexml

How can I parse XML data to array using php?


I'm not to experienced with XML and I have been trying to figure this out without to much progress.

I need to get the results from the XML file to a array using PHP.

Here is the XML

<ns2:messageContainer xmlns="datex2.eu/schema/3/common" xmlns:ns2="datex2.eu/schema/3/messageContainer" xmlns:ns3="datex2.eu/schema/3/exchangeInformation" xmlns:ns4="datex2.eu/schema/3/informationManagement" xmlns:ns5="datex2.eu/schema/3/dataDictionaryExtension" xmlns:ns6="datex2.eu/schema/3/cctvExtension" xmlns:ns7="datex2.eu/schema/3/locationReferencing" xmlns:ns8="datex2.eu/schema/3/alertCLocationCodeTableExtension" xmlns:ns9="datex2.eu/schema/3/extension" xmlns:ns10="datex2.eu/schema/3/roadTrafficData" xmlns:ns11="datex2.eu/schema/3/vms" xmlns:ns12="datex2.eu/schema/3/situation" modelBaseVersion="3">
    <ns2:payload xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="ns7:PredefinedLocationsPublication" lang="no" modelBaseVersion="3">
        <publicationTime>2023-01-25T13:56:15.615+01:00</publicationTime>
        <publicationCreator>
            <country>no</country>
            <nationalIdentifier>NPRA</nationalIdentifier>
        </publicationCreator>
        <ns7:headerInformation>
            <confidentiality>noRestriction</confidentiality>
            <informationStatus>real</informationStatus>
        </ns7:headerInformation>

        <ns7:predefinedLocationReference xsi:type="ns7:PredefinedLocation" id="100356" version="1">
            <ns7:predefinedLocationName>
                <values>
                    <value lang="no">Eikås - Åsanevegen</value>
                </values>
            </ns7:predefinedLocationName>
            <ns7:location xsi:type="ns7:LinearLocation">
                <ns7:gmlLineString srsName="http://www.opengis.net/gml/srs/epsg.xml#32633">
                    <ns7:posList>-27946 6743813</ns7:posList>
                </ns7:gmlLineString>
            </ns7:location>
        </ns7:predefinedLocationReference>
        <ns7:predefinedLocationReference xsi:type="ns7:PredefinedLocation" id="100361" version="1">
            <ns7:predefinedLocationName>
                <values>
                    <value lang="no">Ammerud - Bjerke</value>
                </values>
            </ns7:predefinedLocationName>
            <ns7:location xsi:type="ns7:LinearLocation">
                <ns7:gmlLineString srsName="http://www.opengis.net/gml/srs/epsg.xml#32633">
                    <ns7:posList>269553 6653843</ns7:posList>
                </ns7:gmlLineString>
            </ns7:location>
        </ns7:predefinedLocationReference>
    </ns2:payload>
    <ns2:exchangeInformation modelBaseVersion="3">
        <ns3:exchangeContext>
            <ns3:codedExchangeProtocol>snapshotPull</ns3:codedExchangeProtocol>
            <ns3:exchangeSpecificationVersion>3</ns3:exchangeSpecificationVersion>
            <ns3:supplierOrCisRequester>
                <ns3:internationalIdentifier>
                    <country>no</country>
                    <nationalIdentifier>NPRA</nationalIdentifier>
                </ns3:internationalIdentifier>
            </ns3:supplierOrCisRequester>
        </ns3:exchangeContext>
        <ns3:dynamicInformation>
            <ns3:exchangeStatus>undefined</ns3:exchangeStatus>
            <ns3:messageGenerationTimestamp>2023-01-25T13:56:15.615+01:00</ns3:messageGenerationTimestamp>
        </ns3:dynamicInformation>
    </ns2:exchangeInformation>
</ns2:messageContainer>

Here is my PHP code

        $xml = simplexml_load_string($response->raw_body, "SimpleXMLElement", LIBXML_NOCDATA, 'ns2', true);
        
        $xml->registerXPathNamespace('ns7','http://datex2.eu/schema/3/locationReferencing');

        $count = 0;
        foreach($xml->xpath('//ns7:predefinedLocationReference') as $event) {

            $return[$count]['id'] = intval($event->attributes()->id);
            $predefinedLocationName = $event->xpath('ns7:predefinedLocationName');
            foreach ($predefinedLocationName[0]->values as $locVal) {
                $return[$count]['name'] = strval($locVal->value);
            }

            $count++;
        }

I'm sure there is a better way but here is what I got:

            {
                "id": 100356,
                "name": "Eikås - Åsanevegen"
            },
            {
                "id": 100361,
                "name": "Ammerud - Bjerke"
            }

What I'm missing is to get out the posList value from the XML and add it to my array in PHP


Solution

  • Rather than messing around with XPath, I would use the main SimpleXML access methods, noting this reference of how SimpleXML handles namespaces.

    Specifically, I would note down the path I wanted to take through the document, expanding namespaces to their full identifiers rather than their local aliases:

    • Start at messageContainer, in the datex2.eu/schema/3/messageContainer namespace
    • Go into payload, in the same namespace
    • Loop over each predefinedLocationReference, in the datex2.eu/schema/3/locationReferencing namespace
    • Get the "id" from the (non-namespaced) id attribute
    • Go into predefinedLocationName, still in the datex2.eu/schema/3/locationReferencing namespace
    • Go into values, in the datex2.eu/schema/3/common namespace (defined as the default xmlns at the top of the document)
    • Get the "name" from the value element in that namespace
    • From the predefinedLocationReference we had earlier, go into the location (in the same namespace as predefinedLocationReference)
    • Go into gmlLineString, in the same namespace
    • Get the "postList" from the posList, in the same namespace

    That then translates directly to this PHP code:

    // Some constants to make namespaces easier to read
    const NS_MSG_CONT = 'datex2.eu/schema/3/messageContainer';
    const NS_LOC_REF = 'datex2.eu/schema/3/locationReferencing';
    const NS_COMMON = 'datex2.eu/schema/3/common';
    
    // Initialise our return array
    $return = [];
    
    // Start at `messageContainer`, in the `datex2.eu/schema/3/messageContainer` namespace
    $xml = simplexml_load_string($response->raw_body, "SimpleXMLElement", 0, NS_MSG_CONT);
    
    // Go into `payload`, in the same namespace
    $payload = $xml->payload;
    
    // Loop over each `predefinedLocationReference`, in the `datex2.eu/schema/3/locationReferencing` namespace
    foreach ($payload->children(NS_LOC_REF)->predefinedLocationReference as $predefinedLocationReference ) {
        // Initialise the return item
        $item = [];
    
        // Get the "id" from the (non-namespaced) `id` attribute
        $item['id'] = (string)$predefinedLocationReference->attributes(null)->id;
    
        // Go into `predefinedLocationName`, still in the `datex2.eu/schema/3/locationReferencing` namespace
        $predefinedLocationName = $predefinedLocationReference->predefinedLocationName;
    
        // Go into `values`, in the `datex2.eu/schema/3/common` namespace (defined as the default `xmlns` at the top of the document)
        $values = $predefinedLocationName->children(NS_COMMON)->values;
    
        // Get the "name" from the `value` element in that namespace
        $item['name'] = (string)$values->value;
    
        // From the `predefinedLocationReference` we had earlier, go into the `location` (in the same namespace as `predefinedLocationReference`)
        $location = $predefinedLocationReference->location;
    
        // Go into `gmlLineString`, in the same namespace
        $gmlLineString = $location->gmlLineString;
    
        // Get the "posList" from the `posList`, in the same namespace
        $item['posList'] = (string)$gmlLineString->posList;
    
        // Add item to our final results
        $return[] = $item;
    }
    
    // Test
    var_dump($return);
    

    This can obviously be made much shorter by removing comments and intermediate variables to taste; a very shortened version of exactly the same code looks like this:

    const NS_MSG_CONT = 'datex2.eu/schema/3/messageContainer';
    const NS_LOC_REF = 'datex2.eu/schema/3/locationReferencing';
    const NS_COMMON = 'datex2.eu/schema/3/common';
    
    $xml = simplexml_load_string($raw_body, "SimpleXMLElement", 0, NS_MSG_CONT);
    // Note: in PHP >8.0, you can skip the parameters you're not interested in:
    // $xml = simplexml_load_string($raw_body, namespace_or_prefix: NS_MSG_CONT);
    $return = [];
    foreach ($xml->payload->children(NS_LOC_REF)->predefinedLocationReference as $predefinedLocationReference ) {
        $return[] = [
            'id' => (string)$predefinedLocationReference->attributes(null)->id,
            'name' => (string)$predefinedLocationReference->predefinedLocationName->children(NS_COMMON)->values->value,
            'posList' => (string)$predefinedLocationReference->location->gmlLineString->posList,
        ];
    }