2

I am trying to parse a XML like this:

<?xml version="1.0" encoding="UTF-8"?>
<gml:FeatureCollection 
    xmlns:ogc="http://www.opengis.net/ogc" 
    xmlns:gml="http://www.opengis.net/gml"
    xmlns:xlink="http://www.w3.org/1999/xlink" 
    xmlns:wfs="http://www.opengis.net/wfs"
    xmlns:p="http://example.org">
    <gml:featureMember>
        <p:Point>
            <gml:pointProperty>
                <gml:Point srsName="epsg:4258">
                    <gml:pos>-3.84307585 43.46031547</gml:pos>
                </gml:Point>
                <gml:Point srsName="epsg:4258">
                    <gml:pos>-3.84299411 43.46018513</gml:pos>
                </gml:Point>
                <gml:Point srsName="epsg:4258">
                    <gml:pos>-3.84299935 43.45998723</gml:pos>
                </gml:Point>
                <!-- 
                    ... many more <gml:Point> nodes ...
                --> 
                <gml:Point srsName="epsg:4258">
                    <gml:pos>-3.84309913 43.46054546</gml:pos>
                </gml:Point>
                <gml:Point srsName="epsg:4258">
                    <gml:pos>-3.84307585 43.46031547</gml:pos>
                </gml:Point>
            </gml:pointProperty>
        </p:Point>
    </gml:featureMember>
</gml:FeatureCollection>

I want to get each of gml:pos rows to save to a DB but for the moment I am happy printing them in webpace (echo...)

$output = simplexml_load_string($output);
$xml = $output->getNamespaces(true); 
//print_r( $xml);
$xml_document = $output->children($xml["p"]);
foreach($xml_document->Point->children($xml["gml"]);
    echo $xml_point->Point[0];
echo $xml->FeatureCollection; 
}

In $output I have the complete xml, tons of coordinates in gml:point

But I am trying to get to the points using namespaces but I have to be doing something wrong because I can't print anything but Array word (even by using print_r...)

3
  • Is that your atual XML source? It doesn't seem to be well formed. It won't parse. Commented Jun 7, 2014 at 18:21
  • I changed the xml. Now I set the complete one. The previous one was modified just to include a couple of points. This xml is what I received from a webservice Commented Jun 7, 2014 at 18:44
  • 1
    Just as a sidenote, if you're relying on the prefixes in the document anyway (given by getNamespaces(true)), you can just use them directly with ->children("p", true). It's better though to define your own array or set of constants with the actual namespace URIs, in case the code generating the XML changes in future and picks different prefixes with the same meaning. Commented Jun 7, 2014 at 19:40

2 Answers 2

3

You should not extract the namespace URIs from the document. The namespace URI is a unique string defining the XML semantic the tag is part of. In other words the URI identifies the namespace - not the alias. Your XML is a good example for that, because it has Point elements in two different namespaces.

  • p:Point resolves to {http://example.org}Point
  • gml:Point resolves to {http://www.opengis.net/gml}Point

The namespace prefixes like p and gml are aliases to make a document smaller and more readable. They are only valid for the element and its children. They can be redefined on any element and they are optional for elements. More important they are only valid for the document. The following three examples all resolve to {http://example.org}Point.

  • Prefix "p": <p:Point xmlns:p="http://example.org"/>
  • Prefix "example": <example:Point xmlns:example="http://example.org"/>
  • No prefix: <Point xmlns="http://example.org"/>

To read XML you define own prefixes for the namespaces and use them with Xpath or you use the namespace aware variants of the DOM methods like getAttributeNS(). Xpath is by a long way the more elegant solution. You can use the prefixes from the document or different ones.

SimpleXML requires you to register the namespaces on every SimpleXMLElement on that you're calling xpath(). To avoid code duplication I suggest to define an array with the namespaces and a function that registers them.

// define an array with the namespaces you're using
// the keys are your aliases - independent of the XML document.
$xmlns = [
  'gml' => 'http://www.opengis.net/gml',
  'p' => 'http://example.org'
];
// wrap the array in a function
$registerNamespaces = static function(SimpleXMLElement $target) use ($xmlns) {
  foreach ($xmlns as $alias => $uri) {
    $target->registerXPathNamespace($alias, $uri);
  }
};

$element = simplexml_load_string($content);
$registerNamespaces($element);

$result = [];
// use your prefixes to references the namespaces in Xpath
$positions = $element->xpath('//p:Point[1]//gml:pos');
foreach ($positions as $pos) {
  $result[] = (string)$pos;
}

var_dump($result);

Output: https://eval.in/159739

array(5) {
  [0]=>
  string(23) "-3.84307585 43.46031547"
  [1]=>
  string(23) "-3.84299411 43.46018513"
  [2]=>
  string(23) "-3.84299935 43.45998723"
  [3]=>
  string(23) "-3.84309913 43.46054546"
  [4]=>
  string(23) "-3.84307585 43.46031547"
}
Sign up to request clarification or add additional context in comments.

1 Comment

"You should not read the namespaces from the document." very confusing statement.
0

This would be easier using XPath, since you have nodes nested deeply in alternating namespaces, but since you are using SimpleXML I'll show you a solution using that framework.

This

$output->children($xml["p"]);

won't work because the root node has no children in the p namespace. You have to navigate the tree until you are in the right context. With XPath you could fetch them all with a descendant axis expression, which would be simpler. The code below works with SimpleXML:

$pointProperty = $output
                 ->children($xml["gml"])->featureMember
                 ->children($xml["p"])->Point
                 ->children($xml["gml"]);

Now you can loop on the children of pointProperty and you will have your Point nodes:

foreach($pointProperty->children($xml["gml"]) as $point)
    print_r($point);

From there on, the namespace doesn't change, so you can navigate normally and get the data in the pos elements. Here is an example:

echo '<table border="1">'."\n";
echo '  <tr><th>srsName</th><th>Longitude</th><th>Latitude</th></tr>'."\n";
foreach($pointProperty->children($xml["gml"]) as $point) {
    $coords = explode (' ', $point->pos);
    echo '  <tr><td>'.$point->attributes()['srsName'].'</td>';
    echo '<td>'.$coords[0].'</td>';
    echo '<td>'.$coords[1].'</td></tr>'."\n";
}
echo '</table>'."\n";

This will print a table containing your data. You can adapt this to fit your needs:

<table border="1">
  <tr><th>srsName</th><th>Longitude</th><th>Latitude</th></tr>
  <tr><td>epsg:4258</td><td>-3.84307585</td><td>43.46031547</td></tr>
  <tr><td>epsg:4258</td><td>-3.84299411</td><td>43.46018513</td></tr>
  ...
  <tr><td>epsg:4258</td><td>-3.84307585</td><td>43.46031547</td></tr>
</table>

Here's a working PHP Fiddle you can try out online.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.