Home > OS >  Struggling to get attributes from XML (PHP)
Struggling to get attributes from XML (PHP)

Time:03-22

I have the following XML returned by an API:

<ns3:calculateResponse xmlns="http://geomodel.eu/schema/common/geo" xmlns:ns2="http://geomodel.eu/schema/common/pv" xmlns:ns3="http://geomodel.eu/schema/ws/pvplanner">
<ns3:site lat="48.61259" lng="20.827079">
<terrain elevation="246" tilt="10.0" azimuth="176"/>
<horizon/>
<ns2:geometry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="ns2:GeometryFixedOneAngle" tilt="10.0" azimuth="175"/>
<ns2:system installedPower="1.0" installationType="ROOF_MOUNTED" availability="99.0">
<ns2:module type="CSI"/>
<ns2:inverter>
<ns2:efficiency xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="ns2:EfficiencyConstant" percent="97.5"/>
</ns2:inverter>
<ns2:losses dc="5.5" ac="1.5"/>
</ns2:system>
</ns3:site>
<ns3:irradiation>
<ns3:reference>
<ns3:Ghm monthly="31.0 49.4 97.2 126.6 159.0 166.3 165.4 152.6 101.8 65.6 33.7 23.8" yearly="1172.4"/>
<ns3:Ghd monthly="1.00 1.76 3.14 4.22 5.13 5.54 5.34 4.92 3.39 2.12 1.12 0.77" yearly="3.21"/>
<ns3:Dhd monthly="0.57 0.91 1.47 2.06 2.50 2.75 2.62 2.27 1.67 1.11 0.67 0.46" yearly="1.59"/>
<ns3:Td monthly="-2.5 -1.1 3.0 8.5 13.5 17.1 19.8 19.5 13.7 8.6 3.1 -1.8" yearly="8.5"/>
<ns3:Tmin monthly="-3.9 -3.0 0.2 4.1 7.9 11.2 13.8 14.0 9.2 5.6 1.6 -2.8"/>
<ns3:Tmax monthly="0.2 2.1 6.9 13.7 19.1 22.6 25.5 25.7 19.4 13.2 6.0 0.6"/>
<ns3:invar monthly="-1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0" yearly="-1.0"/>
<ns3:Rh monthly="90.0 90.0 80.0 73.0 72.0 67.0 63.0 61.0 69.0 78.0 86.0 87.0" yearly="76.0"/>
<ns3:Pwat monthly="9.0 9.0 10.0 13.0 18.0 22.0 25.0 24.0 19.0 15.0 12.0 9.0" yearly="15.0"/>
</ns3:reference>
<ns3:inplane>
<ns3:Gim monthly="39.9 59.7 110.5 136.4 165.8 171.2 171.5 162.6 112.6 76.8 41.6 30.9" yearly="1279.5"/>
<ns3:Gid monthly="1.28 2.13 3.56 4.54 5.34 5.71 5.53 5.24 3.75 2.48 1.39 0.99" yearly="3.50"/>
<ns3:Did monthly="0.62 0.98 1.57 2.15 2.57 2.82 2.69 2.37 1.76 1.19 0.72 0.50" yearly="1.67"/>
<ns3:Rid monthly="0.00 0.00 0.00 0.00 0.00 0.01 0.01 0.00 0.00 0.00 0.00 0.00" yearly="0.00"/>
<ns3:ShLoss monthly="0.5 0.4 0.3 0.4 0.4 0.5 0.5 0.3 0.4 0.4 0.5 0.5" yearly="0.4"/>
</ns3:inplane>
<ns3:comparison>
<ns3:horizontal yearlySum="1172.0" percentOpt="84.2"/>
<ns3:optimum yearlySum="1393.0" percentOpt="100.0"/>
<ns3:tracker2x yearlySum="1751.0" percentOpt="125.7"/>
<ns3:selected yearlySum="1279.0" percentOpt="91.9"/>
</ns3:comparison>
<ns3:optimum fixed="37.0"/>
</ns3:irradiation>
<ns3:calculation>
<ns3:output>
<ns3:Esm monthly="33.0 50.6 93.1 111.2 131.7 133.6 132.0 125.6 89.6 62.4 33.8 24.9" yearly="1021.5"/>
<ns3:Esd monthly="1.06 1.81 3.00 3.71 4.25 4.45 4.26 4.05 2.99 2.01 1.13 0.80" yearly="2.80"/>
<ns3:Etm monthly="33.0 50.6 93.1 111.2 131.7 133.6 132.0 125.6 89.6 62.4 33.8 24.9" yearly="1021.5"/>
<ns3:Eshare monthly="3.2 5.0 9.1 10.9 12.9 13.1 12.9 12.3 8.8 6.1 3.3 2.4" yearly="100.0"/>
<ns3:PR monthly="82.4 84.4 84.0 81.2 79.1 77.6 76.6 77.0 79.3 80.9 80.9 80.3" yearly="79.5"/>
</ns3:output>
<ns3:losses>
<ns3:global output="1285" PRp="100.0" PRc="100.0"/>
<ns3:terrain output="1279" lossAbs="-5" lossRel="-0.42" PRp="99.6" PRc="99.6"/>
<ns3:angular output="1231" lossAbs="-49" lossRel="-3.81" PRp="96.2" PRc="95.8"/>
<ns3:conversion output="1137" lossAbs="-94" lossRel="-7.63" PRp="92.4" PRc="88.5"/>
<ns3:dcLoss output="1074" lossAbs="-63" lossRel="-5.5" PRp="94.5" PRc="83.6"/>
<ns3:inverter output="1047" lossAbs="-27" lossRel="-2.5" PRp="97.5" PRc="81.5"/>
<ns3:acLoss output="1032" lossAbs="-16" lossRel="-1.5" PRp="98.5" PRc="80.3"/>
<ns3:availability output="1021" lossAbs="-10" lossRel="-1.0" PRp="99.0" PRc="79.5"/>
<ns3:total output="1021" lossAbs="-264" lossRel="-20.51" PRc="79.5"/>
</ns3:losses>
</ns3:calculation>
<ns3:summary>PV system: 1.0 kWp, crystalline silicon, fixed roof, azim. 175&deg; (south), inclination 10&deg;</ns3:summary>
</ns3:calculateResponse>

and, in PHP, I need to obtain the:

ns3:calculateResponse -> ns3:irradiation -> ns3:inplane -> ns3:Gim

attributes: monthly and yearly

But I can't!

$xml = simplexml_load_string($response);
foreach($xml->calculateResponse[0]->attributes() as $a => $b) {
    echo $a,'="',$b,"\"\n";
}

returns

Uncaught Error: Call to a member function attributes()

and if I use ns3: anywhere in names, seemingly the colon breaks everything.

I have tried a number of methods from documented standard examples, but there is seemingly something with this particular XML format causing problems.

Either that, or it's my brain causing the problems!

Any advice, most appreciated.

Thanks

CodePudding user response:

There is one problem with this XML sample: it contains a HTML entity &deg; that is not a valid XML entity. The contents of <ns3:summary> should probably be wrapped in a CDATA object.

If you fix that, you can register the ns3 namespace and use XPath to get to the <ns3:Gim> entities:

$xml = simplexml_load_string($response);
$xml->registerXPathNamespace('ns3', 'http://geomodel.eu/schema/ws/pvplanner');
foreach ($xml->xpath('//ns3:irradiation/ns3:inplane/ns3:Gim') as $gim) {
    foreach($gim->attributes() as $a => $b) {
        echo $a,'="',$b,"\"\n";
    }    
}

/*
monthly="39.9 59.7 110.5 136.4 165.8 171.2 171.5 162.6 112.6 76.8 41.6 30.9"
yearly="1279.5"
*/

https://3v4l.org/HLO40

CodePudding user response:

Without control of the XML at source, I used the following to 'fix' the invalid &deg; entity:

function load_invalid_xml($xml)
{
    $use_internal_errors = libxml_use_internal_errors(true);
    libxml_clear_errors(true);

    $sxe = simplexml_load_string($xml);

    if ($sxe)
    {
        return $sxe;
    }

    $fixed_xml = '';
    $last_pos  = 0;

    $xml = str_replace("&deg;", "degrees", $xml);

    // get file encoding
    $encoding = mb_detect_encoding($xml);

    foreach (libxml_get_errors() as $error)
    {
        $pos = $error->column;
        $invalid_char = mb_substr($xml, $pos, 1, $encoding);
        $fixed_xml .= substr($xml, $last_pos, $pos - $last_pos) . htmlspecialchars($invalid_char);
        $last_pos = $pos   1;
    }
    $fixed_xml .= substr($xml, $last_pos);

    libxml_use_internal_errors($use_internal_errors);

    return $fixed_xml;
}

So, then the full solution is:

$xml = load_invalid_xml($string);

$xml = simplexml_load_string($xml);
$xml->registerXPathNamespace('ns3', 'http://geomodel.eu/schema/ws/pvplanner');
foreach ($xml->xpath('//ns3:irradiation/ns3:inplane/ns3:Gim') as $gim) {
    foreach($gim->attributes() as $a => $b) {
        echo $a,'="',$b,"\"<br>";
    }    
}

Thanks for your help @rickdenhaan

  • Related