SimpleXML access nodes with namespace and subnodes without namespace - php

I am trying to access a list of nodes without namespace declaration within nodes with namespace declaration. My XML file has a main node with namespace ehd with two subnodes header, body within the same namespace. However, all subnodes within the body node have no further namespace declaration. I am struggling with accessing these nodes with SimpleXML.
Excerpt from the xml file:
<?xml version="1.0" encoding="ISO-8859-15"?>
<ehd:ehd ehd_version="1.40" xmlns:ehd="urn:ehd/001" xmlns="urn:ehd/go/001">
<ehd:header>
</ehd:header>
<ehd:body>
<gnr_liste>
<gnr V="01100"></gnr>
<gnr V="01101"></gnr>
<gnr V="01102"></gnr>
</gnr_liste>
</ehd:body>
</ehd:ehd>
My code is as follows:
$xml = simplexml_load_file($file) or die("Failed to load");
$ehd = $xml->children('ehd', true)->body;
simplexml_dump($ehd);
$gnr_liste = $ehd->children('gnr_liste')->children('gnr');
simplexml_dump($gnr_liste);
The output is:
SimpleXML object (1 item)
[
Element {
Namespace: 'urn:ehd/001'
Namespace Alias: 'ehd'
Name: 'ehd'
String Content: ''
Content in Namespace ehd
Namespace URI: 'urn:ehd/001'
Children: 2 - 1 'body', 1 'header'
Attributes: 0
Content in Default Namespace
Children: 0
Attributes: 1 - 'ehd_version'
}
]
SimpleXML object (1 item)
[
Element {
Namespace: 'urn:ehd/001'
Namespace Alias: 'ehd'
Name: 'body'
String Content: ''
Content in Default Namespace
Namespace URI: 'urn:ehd/go/001'
Children: 1 - 1 'gnr_liste'
Attributes: 0
}
]
How do I access all gnr items from the gnr_liste node?
Note: I am using simplexml_dump for debugging

The argument to ->children() is always a namespace identifier or local prefix, never the tag name. If these elements were in "no namespace", you would access them with ->children('').
However, the elements with no prefix in this document do not have no namespace - they are in the default namespace, in this case urn:ehd/go/001 (as defined by xmlns="urn:ehd/go/001").
If you use the full namespace identifiers rather than the prefixes (which is also less likely to break if the feed changes), you should be able to access these easily:
$xml = simplexml_load_file($file) or die("Failed to load");
$ehd = $xml->children('urn:ehd/001')->body;
$gnr_liste = $ehd->children('urn:ehd/go/001')->gnr_liste;
foreach ( $gnr_liste->gnr as $gnr ) {
simplexml_dump($gnr);
}
You might want to give your own names to the namespaces so you don't have to use the full URIs, but aren't dependent on the prefixes the XML is generated with; a common approach is to define constants:
const XMLNS_EHD_MAIN = 'urn:ehd/001';
const XMLNS_EHD_GNR = 'urn:ehd/go/001';
$xml = simplexml_load_file($file) or die("Failed to load");
$ehd = $xml->children(XMLNS_EHD_MAIN)->body;
$gnr_liste = $ehd->children(XMLNS_EHD_GNR)->gnr_liste;
foreach ( $gnr_liste->gnr as $gnr ) {
simplexml_dump($gnr);
}

Personally, I find DomDocument much more intuitive to work with – once you get over the barrier of XPath syntax. No matter what tool you use, namespaces are going to make everything more difficult though!
$xml = <<< XML
<?xml version="1.0" encoding="ISO-8859-15"?>
<ehd:ehd ehd_version="1.40" xmlns:ehd="urn:ehd/001" xmlns="urn:ehd/go/001">
<ehd:header>
</ehd:header>
<ehd:body>
<gnr_liste>
<gnr V="01100"></gnr>
<gnr V="01101"></gnr>
<gnr V="01102"></gnr>
</gnr_liste>
</ehd:body>
</ehd:ehd>
XML;
$dom = new DomDocument;
$dom->loadXML($xml);
$xp = new DomXPath($dom);
// need to get tricky due to namespaces https://stackoverflow.com/a/16719351/1255289
$nodes = $xp->query("//*[local-name()='gnr']/#V");
foreach ($nodes as $node) {
printf("%s\n", $node->value);
}
Output:
01100
01101
01102

Related

Unable to Parse XML having multiple Namespace Using PHP

I am parsing an XML file from php through simplexml_load_file function. They have multiple namespace in it.
<?xml version="1.0" encoding="utf-8"?>
<RETURN:RT xmlns:FORMM="http://example.com/5"
xmlns:Form="http://example.com/master"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:RETURN="http://example.com">
<FORMM:5F>
<Form:Info>
<Form:SWCreatedBy>SW10002087</Form:SWCreatedBy>
</Form:Info>
</FORMM:5F>
</RETURN:RT>
Here is my PHP Code :
$xml = simplexml_load_file('filename');
$namespaces = $xml->getNamespaces(true);
foreach ($namespaces as $key => $extension) {
$xml->registerXPathNamespace($key, $extension);
}
$main = $xml->xpath('//RETURN:RT/*');
foreach ($main as $itr) {
echo '<pre>';
print_r($itr);
echo '</pre>';
}
Output:
SimpleXMLElement Object
(
)
But when i remove the Form from namespace then it gives me the ouput
SimpleXMLElement Object
(
[Form:Info] => SimpleXMLElement Object
(
[Form:SWCreatedBy] => SW10002087
)
)
Can any give me solution regarding this i don't have to remove the form every time from xml file.
5F is an invalid tag name - they are not allowed to start with digits. The parser will throw warnings if you load this (invalid) XML.
Warning: simplexml_load_string(): namespace error : Failed to parse QName 'FORMM:' in /in/HHTnS on line 33
Warning: simplexml_load_string(): <FORMM:5F> in /in/HHTnS on line 33
Warning: simplexml_load_string(): ^ in /in/HHTnS on line 33
So you will need some error handling for this and/or have the XML fixed.
However LibXML (the library behind SimpleXML and DOM) tolerates it. So you can still read it.
Don't fetch namespaces from the document. This is method for debugging or generic transformations. The namespace URI is the identifying value of a namespace. Namespaces prefixes can change at any element in an XML. Define/register you own prefixes for the Xpath expressions and use the Namespace URI in the method calls.
print_r(), var_dump(), ... are using SimpleXMLs mapping of XML elements. This is a compromise and has limits (especially with namespaces). Try SimpleXMLElement::asXML() to debug a SimpleXMLElement instance.
You can still access the elements using property syntax, but you might need to use an SimpleXMLElement::children() to specify the namespace.
Here is an example based on your question:
$xml = <<<'XML'
<?xml version="1.0" encoding="utf-8"?>
<RETURN:RT
xmlns:FORMM="http://example.com/5"
xmlns:Form="http://example.com/master"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:RETURN="http://example.com">
<FORMM:5F>
<Form:Info>
<Form:SWCreatedBy>SW10002087</Form:SWCreatedBy>
</Form:Info>
</FORMM:5F>
</RETURN:RT>
XML;
// define namespaces you're going to use
$xmlns = [
'five' => 'http://example.com/5',
'master' => 'http://example.com/master',
'return' => 'http://example.com',
];
// a function to apply all of them to a SimpleXMLElement
function registerXpathNamespaces(SimpleXMLElement $target, array $xmlns) {
foreach ($xmlns as $prefix => $namespaceURI) {
$target->registerXpathNamespace($prefix, $namespaceURI);
}
}
$rt = simplexml_load_string($xml);
registerXpathNamespaces($rt, $xmlns);
$list = $rt->xpath('//return:RT/*');
foreach ($list as $fiveF) {
// serialize the node into a string for debugging
var_dump($fiveF->asXML());
// use the namespace list to access child elments in a specific namespace
var_dump(
(string)$fiveF->children($xmlns['master'])->Info->SWCreatedBy
);
}
Output:
string(130) "<FORMM:5F>
<Form:Info>
<Form:SWCreatedBy>SW10002087</Form:SWCreatedBy>
</Form:Info>
</FORMM:5F>"
string(10) "SW10002087"
You need to register the namespaces on each SimpleXMLElement instance you are calling xpath() on.
In DOM, here is a separate Xpath object, so you only need to register the namespaces once. Additionally DOMXpath::evaluate() can return scalar values (not just node lists). This it how the example would look with DOM:
// define namespaces you're going to use
$xmlns = [
'five' => 'http://example.com/5',
'master' => 'http://example.com/master',
'return' => 'http://example.com',
];
$document = new DOMDocument();
$document->loadXML($xml);
$xpath = new DOMXpath($document);
foreach ($xmlns as $prefix => $namespaceURI) {
$xpath->registerNamespace($prefix, $namespaceURI);
}
$list = $xpath->evaluate('//return:RT/*');
foreach ($list as $fiveF) {
// serialize the node into a string for debugging
var_dump($document->saveXML($fiveF));
// directly fetch the value using a string cast in Xpath
var_dump(
$xpath->evaluate(
'string(master:Info/master:SWCreatedBy)',
$fiveF
)
);
}
Using DOM you can take a look how the parser reads the broken tag.
// the RETURN:RT is valid - the parser can match it to the namespace
var_dump($document->documentElement->localName, $document->documentElement->namespaceURI);
foreach ($list as $fiveF) {
// the "5F" or "FORMM:5F" is invalid - the parser will not match it
var_dump($fiveF->localName, $fiveF->namespaceURI);
}
string(2) "RT"
string(18) "http://example.com"
string(8) "FORMM:5F"
NULL
So it keeps the alias/prefix as a part of the name and ignores the namespace.

Get XML namespace of root node

I receive xml like:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="http://adress.pl/FeResourceServlet/localTemplate/template1/styl.xsl"?>
<wnio:Dokument
xmlns:adr="http://adress.pl/xml/schema/adress/2009/11/09/"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
xmlns:ev="http://www.w3.org/2001/xml-events"
xmlns:inst="http://adress.pl/xml/schematy/instytucja/2009/11/16/"
xmlns:meta="http://adress.pl/xml/schematy/meta/2009/11/16/"
xmlns:oso="http://adress.pl/xml/schematy/osoba/2009/11/16/"
xmlns:str="http://adress.pl/xml/schematy/struktura/2009/11/16/" xmlns:wnio="http://epuap.gov.pl/FeResourceServlet/localTemplate/ZgloszenieBudowy/"
xmlns:xf="http://www.w3.org/2002/xforms"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xxforms="http://orbeon.org/oxf/xml/xforms"
xsi:schemaLocation="http://adress.pl/FeResourceServlet/localTemplate/template1/ http://epuap.gov.pl/FeResourceServlet/localTemplate/template1/schema.xsd">
...
My question is - How can I get a namespace of root?
Above my root node is wnio:Dokument and I know that wnio is "namespace of root" and name of root is Dokument.
But name and namespace can be changed. Then I will have root node but I wouldn't know namespace and name of root.
I used so far: SimpleXMLElement::getNamespaces and SimpleXMLElement::getDocNamespaces. But I received every namespace but I don't know which is root.
It's possible in PHP to get these information?
DOM nodes have a property $namespaceURI that returns the namespace of the node:
$document = new DOMDocument();
$document->loadXml(
'<wnio:Dokument xmlns:wnio="http://epuap.gov.pl/FeResourceServlet/localTemplate/ZgloszenieBudowy/"/>'
);
var_dump($document->documentElement->namespaceURI);
But namespaces are the most stable part of an XML document. A namespace specifies the format of the information. If the namespace changes the format changes and you will have to change the logic of your application.
You application need to know the format it reads and expect to get it. That is the namespace.
The namespace prefix on the other hand can change on any element node. You should expect a specific prefix, but you can and have to expect a specific namespace.
Here is an example:
$document = new DOMDocument();
$document->loadXml(
'<wnio:Dokument xmlns:wnio="http://epuap.gov.pl/FeResourceServlet/localTemplate/ZgloszenieBudowy/"/>'
);
$xpath = new DOMXpath($document);
$xpath->registerNamespace('w', 'http://epuap.gov.pl/FeResourceServlet/localTemplate/ZgloszenieBudowy/');
foreach ($xpath->evaluate('/w:Dokument') as $node) {
var_dump($node->nodeName);
}
Output:
string(13) "wnio:Dokument"
You can use DomDocument
$dom = new DOMDocument();
$response = $dom->loadXML($xml);//$xml is your xml string or file
$root = $dom->documentElement;//will return the document root element
$rootPrefix = $root->prefix;//getting the prefix of your element
$namespace = $root->lookupNamespaceURI($rootPrefix);//getting the namespace of the root element
The [documentElement][2] attribute it's a simple way to get the root element as a DOMElement.

PHP: get values from node by name (problems with namespaces)

I received these xml from external services:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href=(...)?>
<pos:Document xmlns:pos=(...) xmlns:str=(...) xmlns:xsi=(...) xsi:schemaLocation=(...)>
<pos:DescribeDocument>
(...)
</pos:DescribeDocument>
<pos:UPP>
(...)
</pos:UPP>
<ds:Signature Id="ID-9326" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo Id="ID-9325" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:pos="adress" xmlns:str="adress" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
(...)
</ds:SignedInfo>
<ds:Object>
<xades:QualifyingProperties Id="ID-9337a6d1" Target="#ID-932668c0-d4f9-11e3-bb2d-001a645ad128" xmlns:xades="http://uri.etsi.org/01903/v1.3.2#">
<xades:SignedProperties Id="ID-9337a6d0" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:pos="adress" xmlns:str="adress" xmlns:xades="adress" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xades:SignedSignatureProperties>
<xades:SigningTime>sometime</xades:SigningTime>
<xades:SigningCertificate>
<xades:Cert>
<xades:CertDigest>
<ds:DigestMethod Algorithm="adress"/>
<ds:DigestValue>someValue</ds:DigestValue>
</xades:CertDigest>
<xades:IssuerSerial>
<ds:X509IssuerName>CNsomeValue</ds:X509IssuerName>
<ds:X509SerialNumber>SerialsomeValue</ds:X509SerialNumber>
</xades:IssuerSerial>
</xades:Cert>
</xades:SigningCertificate>
<xades:SignaturePolicyIdentifier>
<xades:SignaturePolicyImplied/>
</xades:SignaturePolicyIdentifier>
</xades:SignedSignatureProperties>
<xades:SignedDataObjectProperties>
<xades:DataObjectFormat ObjectReference="#ID-93257e60">
<xades:Description>NEEDVALUE</xades:Description>
</xades:DataObjectFormat>
</xades:SignedDataObjectProperties>
</xades:SignedProperties>
</xades:QualifyingProperties>
</ds:Object>
</ds:Signature>
</pos:Document>
It's have a few namespace. And I have to get value in value.
I wrote a some code but nothing works:
$xmlFileContent = file_get_contents($pathToXML);
$dom = new SimpleXMLElement($xmlFileContent, LIBXML_COMPACT);
$namespaces = $dom->getNamespaces(true);
foreach ($namespaces as $key => $namespace) {
$dom->registerXPathNamespace($key, $namespace);
}
$matches = $dom->xpath('//xades:Description'); //no success
and
$doms = new DOMDocument;
$doms->loadXML($path);
foreach($doms->getElementsByTagNameNS($namespaces['xades'],'*') as $element){
echo 'local name: ', $element->localName, ', prefix: ', $element->prefix, "\n"; //no success
}
Can you help me to get to these node (xades:Description)?
PS:
i used it too (but no success):
$result1 = $dom->xpath('/Dokument/ds:Signature/ds:Object/xades:QualifyingProperties/xades:SignedProperties/xades:SignedDataObjectProperties/xades:DataObjectFormat/xades:Description');
You removed the namespace definitions from your XML. If you see an attribute like 'xmlns:xades' the actual namespace is the value of that attribute. It defines an alias/prefix for that namespace in the current context. Most of the time URLs are used as namespace identifiers (because it avoids potential conflicts). But a value like urn:somestring is valid.
You need to register a prefix for the namespace on the DOMXpath object, too. This prefix does not need to be identical to the one in the document. Look at it this way:
DOMDocument resolves a node name from prefix:Description to {namespace-uri}:Description.
DOMXpath resolves the node names in the Xpath expressions the same way using its own definition. For example //prefix:Description to //{namespace-uri}:Description. Then it compares the resolved names.
$dom = new DOMDocument();
$dom->loadXml($xml);
$xpath = new DOMXpath($dom);
$xpath->registerNamespace('x', 'urn:xades');
var_dump($xpath->evaluate('string(//x:Description)'));
Output: https://eval.in/181304
string(9) "NEEDVALUE"
There is possible workaround by modifying your XPath to ignore namespaces, just in case you can't find proper solution :
$result = $dom->xpath('//*[local-name() = "Description"]');

XPath in DOMDocument on WSDL file

I have some problems with make query to the XPath. I try to load WSDL file and them get some nodes using XPath.
$DOMDocument = new DOMDocument();
$DOMDocument->loadXML($wsdl);
$DOMXpath = new DOMXPath($DOMDocument);
$elements = $DOMXpath->query('//definitions//binding');
var_dump($elements);
Result is:
class DOMNodeList#15 (1) {
public $length =>
int(0)
}
Here is WSDL file: http://pastebin.com/YDRzbq3x
How to make correct XPath query to traversing over nodes.
Your XML has default namespace (xmlns="http://schemas.xmlsoap.org/wsdl/"). In this case, you need to register a prefix that point to that default namespace URI, then use that prefix in your XPath query :
.......
$DOMXpath->registerNamespace('d', "http://schemas.xmlsoap.org/wsdl/");
$elements = $DOMXpath->query('//d:definitions//d:binding');
.......

How to get XML namespace attributes with PHP simplexml

I'm pretty new to this, and I've followed several tutorials (including other OS questions), but I can't seem to get this to work.
I'm working with a library's EAD file (Library of Congress XML standard for describing library collections, http://www.loc.gov/ead/index.html), and I'm having trouble with the namespaces.
A simplified example of the XML:
<?xml version="1.0"?>
<ead xsi:schemaLocation="urn:isbn:1-931666-22-9 http://www.loc.gov/ead/ead.xsd" xmlns:ns2="http://www.w3.org/1999/xlink" xmlns="urn:isbn:1-931666-22-9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<c02 id="ref24" level="item">
<did>
<unittitle>"Lepidoptera and seas(on) of appearance"</unittitle>
<unitid>1</unitid>
<container id="cid71717" type="Box" label="Mixed materials">1</container>
<physdesc>
<extent>Pencil</extent>
</physdesc>
<unitdate>[1817]</unitdate>
</did>
<dao id="ref001" ns2:actuate="onRequest" ns2:show="embed" ns2:role="" ns2:href="http://diglib.amphilsoc.org/fedora/repository/graphics:92"/>
</c02>
<c02 id="ref25" level="item">
<did>
<unittitle>Argus carryntas (Butterfly)</unittitle>
<unitid>2</unitid>
<container id="cid71715" type="Box" label="Mixed materials">1</container>
<physdesc>
<extent>Watercolor</extent>
</physdesc>
<unitdate>[1817]</unitdate>
</did>
<dao ns2:actuate="onRequest" ns2:show="embed" ns2:role="" ns2:href="http://diglib.amphilsoc.org/fedora/repository/graphics:87"/>
</c02>
Following advise I found elsewhere, I was trying this (and variations on this theme):
<?php
$entries = simplexml_load_file('test.xml');
foreach ($entries->c02->children('http://www.w3.org/1999/xlink') as $entry) {
echo 'link: ', $entry->children('dao', true)->href, "\n";
}
?>
Which, of course, isn't working.
You have to understand the difference between a namespace and a namespace prefix. The namespace is the value inside the xmlns attributes. The xmlns attributes define the prefix, which is an alias for the actual namespace for that node and its descendants.
In you example are three namespaces:
http://www.w3.org/1999/xlink with the alias "ns2"
urn:isbn:1-931666-22-9 without an alias
http://www.w3.org/2001/XMLSchema-instance with the alias "xsi"
So elements and attributes starting with "ns2:" are inside the xlink namespace, elements and attributes starting with "xsi:" in the XML schema instance namespace. All elements without an namespace prefix are in the isbn specific namespace. Attributes without a namespace prefix are always in NO namespace.
If you query the xml dom, you need to define your own namespaces prefixes. The namespace prefixes in the xml documents can change, especially if they are external resources.
I don't use "SimpleXML", so here is an DOM example:
<?php
$xml = <<<'XML'
<?xml version="1.0"?>
<ead
xsi:schemaLocation="urn:isbn:1-931666-22-9 http://www.loc.gov/ead/ead.xsd"
xmlns:ns2="http://www.w3.org/1999/xlink"
xmlns="urn:isbn:1-931666-22-9"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<c02 id="ref24" level="item">
<did>
<unittitle>"Lepidoptera and seas(on) of appearance"</unittitle>
</did>
</c02>
</ead>
XML;
// create dom and load the xml
$dom = new DOMDocument();
$dom->loadXml($xml);
// create an xpath object
$xpath = new DOMXpath($dom);
// register you own namespace prefix
$xpath->registerNamespace('isbn', 'urn:isbn:1-931666-22-9');
foreach ($xpath->evaluate('//isbn:unittitle', NULL, FALSE) as $node) {
var_dump($node->textContent);
}
Output:
string(40) ""Lepidoptera and seas(on) of appearance""
Xpath is quite powerful and the most comfortable way to extract data from XML.
The default namespace in you case is weird. It looks like it is dynamic, so you might need a way to read it. Here is the Xpath for that:
$defaultNamespace = $xpath->evaluate('string(/*/namespace::*[name() = ""])');
It reads the namespace without a prefix from the document element.

Categories