How to get XML namespace attributes with PHP simplexml - php

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.

Related

SimpleXML access nodes with namespace and subnodes without namespace

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

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.

simplexml stuck parsing xml

I am trying to parse an xml file, I can get the d3pp1:key and d3p1:values alright.
foreach ($xml_contact->Attributes->KeyValuePairOfstringanyType as $node) {
$key = (string)$node->children('d3p1', TRUE)->key;
$value = (string)$node->children('d3p1', TRUE)->value;
do_stuff($key, $value);
}
But i also need to get this 9ccaa69b-fced-e411-80da-00155d0a0806
and I am struggling to figure out how to reference it.
I have tried various incarnations along these lines
$node->children('d3p1', TRUE)->value->Id
What am I doing wrong?
<KeyValuePairOfstringanyType>
<d3p1:key>birthdate</d3p1:key>
<d3p1:value xmlns:d5p1="http://www.w3.org/2001/XMLSchema" i:type="d5p1:dateTime">1940-12-10T11:00:00Z</d3p1:value>
</KeyValuePairOfstringanyType>
<KeyValuePairOfstringanyType>
<d3p1:key>parentcustomerid</d3p1:key>
<d3p1:value i:type="EntityReference">
<Id>9ccaa69b-fced-e411-80da-00155d0a0806</Id>
<KeyAttributes xmlns:d6p1="http://schemas.microsoft.com/xrm/7.1/Contracts"/>
<LogicalName>account</LogicalName>
<Name>Test ABC</Name>
<RowVersion i:nil="true"/>
</d3p1:value>
</KeyValuePairOfstringanyType>
The Id element has no namespace prefix, so is in the default namespace of the document, or in no namespace if the document has no default namespace. You need to call ->children() again to switch to the right namespace, as SimpleXML is currently looking for further nodes in the namespace with prefix d3p1.
If there is no default namespace, you just need to pass NULL:
$node->children('d3p1', TRUE)->value->children(NULL)->Id

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"]');

PHP: retrieve all declared namespaces of a DOMElement

I am using the DOM extension to parse an xml file containing xml namespaces. I would have thought that namespace declarations are treated just like any other attribute, but my tests seem to disagree. I have a document that starts like this:
<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://purl.org/rss/1.0/"
xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:syn="http://purl.org/rss/1.0/modules/syndication/"
xmlns:prism="http://purl.org/rss/1.0/modules/prism/"
xmlns:admin="http://webns.net/mvcb/"
>
And a test code like this:
$doc = new DOMDocument();
$doc->loadXml(file_get_contents('/home/soulmerge/tmp/rss1.0/recent.xml'));
$root = $doc->documentElement;
var_dump($root->tagName);
# prints 'string(7) "rdf:RDF"'
var_dump($root->attributes->item(0));
# prints 'NULL'
var_dump($root->getAttributeNode('xmlns'));
# prints 'object(DOMNameSpaceNode)#3 (0) {}'
So the questions are:
Does anyone know where could I find the documentation of DOMNameSpaceNode? A search on php.net does not yield any useful result.
How do I extract all those namespace declarations from that DOMElement?
Unless there is a more direct way you can use XPath and its namespace axis.
e.g.
<?php
$doc = new DOMDocument;
$doc->loadxml('<rdf:RDF
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns="http://purl.org/rss/1.0/"
xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:syn="http://purl.org/rss/1.0/modules/syndication/"
xmlns:prism="http://purl.org/rss/1.0/modules/prism/"
xmlns:admin="http://webns.net/mvcb/"
>
...
</rdf:RDF>');
$context = $doc->documentElement;
$xpath = new DOMXPath($doc);
foreach( $xpath->query('namespace::*', $context) as $node ) {
echo $node->nodeValue, "\n";
}
prints
http://www.w3.org/XML/1998/namespace
http://webns.net/mvcb/
http://purl.org/rss/1.0/modules/prism/
http://purl.org/rss/1.0/modules/syndication/
http://purl.org/dc/elements/1.1/
http://purl.org/rss/1.0/modules/taxonomy/
http://purl.org/rss/1.0/
http://www.w3.org/1999/02/22-rdf-syntax-ns#
edit and btw: I haven't found documentation for DOMNameSpaceNode either. But you can "deduct" (parts of) its functionality from the source code in ext/dom/php_dom.c
It doesn't seem to expose any methods and exposes the properties
"nodeName", "nodeValue", "nodeType",
"prefix", "localName", "namespaceURI",
"ownerDocument", "parentNode"
all handled by the same functions as the corresponding DOMNode properties.
Note, that
echo $root->getAttributeNode('xmlns')->nodeValue . "\n";
echo $root->getAttribute('xmlns') . "\n";
echo $root->getAttribute('xmlns:syn') . "\n";
all work as expected, and print out
http://purl.org/rss/1.0/
http://purl.org/rss/1.0/
http://purl.org/rss/1.0/modules/syndication/
because DOMNameSpaceNode is a Node, not a NodeCollection.
Just clarifying, that, unless something in PHP DOM extension changes, XPath (as explained by VolkerK) is the only native way to get all the namespaces, regardless of documentation.

Categories