views:

429

answers:

3

I need to get a XML File into a Database. Thats not the problem. Cant read it, parse it and create some Objects to map to the DB. Problem is, that sometimes the XML File can contain namespaces and sometimes not. Furtermore sometimes there is no namespace defined at all.

So what i first got was something like this:

<?xml version="1.0" encoding="UTF-8"?>
<struct xmlns:b="http://www.w3schools.com/test/"&gt;
<objects>
<object>
<node_1>value1</node_1>
<node_2>value2</node_2>
<node_3 iso_land="AFG"/>
<coords lat="12.00" long="13.00"/>
</object>
</objects>
</struct>

And the parsing:

$obj = new stdClass();
$nodes = array('node_1', 'node_2');

$t = $xml->xpath('/objects/object');    
    foreach($nodes AS $node) {  
        if($t[0]->$node) {
            $obj->$node = (string) $t[0]->$node;
        }
    }

Thats fine as long as there are no namespaces. Here comes the XML File with namespaces:

<?xml version="1.0" encoding="UTF-8"?>
<b:struct xmlns:b="http://www.w3schools.com/test/"&gt;
<b:objects>
<b:object>
<b:node_1>value1</b:node_1>
<b:node_2>value2</b:node_2>
<b:node_3 iso_land="AFG"/>
<b:coords lat="12.00" long="13.00"/>
</b:object>
</b:objects>
</b:struct>

I now came up with something like this:

$xml = simplexml_load_file("test.xml");
$namespaces = $xml->getNamespaces(TRUE); 
$ns = count($namespaces) ? 'a:' : ''; 
$xml->registerXPathNamespace("a", "http://www.w3schools.com/test/");

$nodes = array('node_1', 'node_2');

$obj = new stdClass();

foreach($nodes AS $node) {
    $t = $xml->xpath('/'.$ns.'objects/'.$ns.'object/'.$ns.$node);   
    if($t[0]) {
        $obj->$node = (string) $t[0];
    }
}

$t = $xml->xpath('/'.$ns.'objects/'.$ns.'object/'.$ns.'node_3');
if($t[0]) {
    $obj->iso_land = (string) $t[0]->attributes()->iso_land;
}    

$t = $xml->xpath('/'.$ns.'objects/'.$ns.'object/'.$ns.'coords');
if($t[0]) {
    $obj->lat = (string) $t[0]->attributes()->lat;
    $obj->long = (string) $t[0]->attributes()->long;
}

That works with namespaces and without. But i feel that there must be a better way. Before that i could do something like this:

$t = $xml->xpath('/'.$ns.'objects/'.$ns.'object');  
foreach($nodes AS $node) {  
    if($t[0]->$node) {
        $obj->$node = (string) $t[0]->$node;
    }
}

But that just wont work with namespaces.

A: 

You can make your XPATH statements more generic by matching on any element * and using a predicate filter to match on the local-name(), which will match on the element name with/without namespaces.

An XPATH like this:

/*[local-name()='struct']/*[local-name()='objects']/*[local-name()='object']/*[local-name()='coords']

Applied to the code sample you were using:

$obj = new stdClass();
$nodes = array('node_1', 'node_2');

$t = $xml->xpath('/*[local-name()="objects"]/*[local-name()="object"]');    
    foreach($nodes AS $node) {  
        if($t[0]->$node) {
            $obj->$node = (string) $t[0]->$node;
        }
    }
Mads Hansen
this wont work: $t = $xml->xpath('/*[local-name()="objects"]/*[local-name()="object"]'); this works: $t = $xml->xpath('/*[local-name()="objects"]/*[local-name()="object"]/*'); but then i cant access the keys...
Mike
A: 

You could make 'http://www.w3schools.com/test/' the default namespace. This way a:objectswould match regardless of whether the document says <a:objects> or <objects>.

If memory usage is not a issue you can even do it with a textual replacement, e.g.

$data = '<?xml version="1.0" encoding="UTF-8"?>
<struct xmlns:b="http://www.w3schools.com/test/"&gt;
  <objects>
    <object>
      <node_1>value1</node_1>
      <node_2>value2</node_2>
      <node_3 iso_land="AFG"/>
      <coords lat="12.00" long="13.00"/>
    </object>
  </objects>
</struct>';

$data = str_replace( // or preg_replace(,,,1) if you want to limit it to only one replacement
  'xmlns:b="http://www.w3schools.com/test/"',
  'xmlns="http://www.w3schools.com/test/" xmlns:b="http://www.w3schools.com/test/"',
  $data
);
$xml = new SimpleXMLElement($data);
$xml->registerXPathNamespace("a", "http://www.w3schools.com/test/");

foreach($xml->xpath('//a:objects/a:object') as $n) {
  echo $n->node_1;
}
VolkerK
i would love to do it this way, but it aint working.$sxe = simplexml_import_dom($doc->importNode($this->reader->expand(), true));$sxe->registerXPathNamespace('a', "http://www.w3schools.com/test");foreach($sxe->xpath('a:object') as $n) { print_r($n->node_1);}
Mike
A: 

I know its rather odd to answer my own question, but none of the above answers worked well for me. Allthoug both of them are good practice. Now here is what i did. Maybe someone finds it usefull.

First i got the SimpleXML

$sxe = simplexml_import_dom($doc->importNode($this->reader->expand(), true));

Then i'm looking if a namespace is there:

$ns = $sxe->getNamespaces(true);
if($ns) {
   $ns = "http://www.w3schools.com/test/";                
}
else {
   $ns = "";
}

Now i can get all nodes regardless of the Namespace with:

$dc = $sxe->children($ns);  

And then:

$nodes = array('node_1', 'node_2');

$obj = new stdClass();

foreach($nodes AS $node) {
    if($dc->object->$node) {
        $obj->$node = $dc->object->$node
    }
}
Mike