I am new to the xml dom and I am trying to to retrieve the value in the '' tags.
The problem is I am not sure how to work down to that level in the dom.
I have tried to do something like this:
itemXML.getElementById("gender").getElementsByTagName("item").nodeValue;
But that returns 'undefined', how would I specify which tag I want to retrieve the value of?
Below is the contents of my xml document:
<dataFields>
<items id="gender">
<item>Male</item>
<item>Female</item>
</items>
<items id="age">
<item>0-3 years</item>
<item>3-6 years</item>
<item>7-16 years</item>
<item>17-25 years</item>
<item>26-40 years</item>
<item>41-65 years</item>
<item>65+ years</item>
</items>
$xpath = new DOMXPath($itemXML);
$genderItems = $xpath->query('/dataFields/items[#id="gender"]/item');
foreach ($genderItems as $genderItem) {
echo $genderItem->nodeValue . "\n"; // "Male", "Female"
}
$items = $doc->getElementsByTagName('item');
The id attribute in XML without a namespace prefix is just a normal attribute, only in html it is the identifier. So in xml documents, the attribute name would have to be "xml:id" to be found by getElementById(). The getElementsByTagName() method returns a list of element nodes, not just one. You will have to use foreach() or the item() method to access the elements in the list.
But here is an easier way. DOMXpath:evaluate() allows you to use xpath expression to fetch nodes and values from a DOM.
Here is some demo source:
$xml = <<<'XML'
<dataFields>
<items id="gender" xml:id="xml_gender">
<item>Male</item>
<item>Female</item>
</items>
<items id="age">
<item>0-3 years</item>
<item>3-6 years</item>
<item>7-16 years</item>
<item>17-25 years</item>
<item>26-40 years</item>
<item>41-65 years</item>
<item>65+ years</item>
</items>
</dataFields>
XML;
$dom = new DOMDocument();
$dom->loadXml($xml);
// difference between id and xml:id
var_dump(
$dom->getElementById('gender'),
get_class($dom->getElementById('xml_gender'))
);
/* Output:
NULL
string(10) "DOMElement"
*/
// getElementsByTagName returns a node list
var_dump(
get_class($dom->getElementById('xml_gender')->getElementsByTagName('item'))
);
/* Output:
NULL
string(11) "DOMNodeList"
*/
// you can iterate node lists
foreach ($dom->getElementById('xml_gender')->getElementsByTagName('item') as $item) {
var_dump($item->nodeValue);
}
/* Output:
string(4) "Male"
string(6) "Female"
*/
// it is easier with xpath
$xpath = new DOMXpath($dom);
foreach ($xpath->evaluate('//items[#id = "gender"]/item') as $item) {
var_dump($item->nodeValue);
}
/* Output:
string(4) "Male"
string(6) "Female"
*/
// DOMXpath::evaluate() can be used to fetch values directly, too.
var_dump(
$xpath->evaluate('string(//items[#id = "gender"]/item[2])')
);
/* Output:
string(6) "Female"
*/
Related
I happen to be unfortunate enough to be working with an api that has images on the same XML tag level as the other tags and have the subscripts i.e 1,2,3,4 as part of the tag name of the image. Total images of each vehicle will vary in count.
<Vehicle>
<TITLE>Some car name i dont need</TITLE>
<DESCRIPTION>Some description i also dont need</DESCRIPTION>
<IMAGE_URL1>{imagelinkhere i want}</IMAGE_URL1>
<IMAGE_URL2>{imagelinkhere i want}</IMAGE_URL2>
<IMAGE_URL3>{imagelinkhere i want}</IMAGE_URL3>
<IMAGE_URL4>{imagelinkhere i want}</IMAGE_URL4>
</Vehicle>
I am using PHP's method simplexml_load_file(xml_url) to parse the entire xml into an object array.
My question: Is there a way to get these images using the same method which is also efficient and clean?
EDIT:
I have just refined the xml to show that there are other tags i dont need there and already handling.
$xml = '<Vehicle>
<DESCRIPTION/>
<IMAGE_URL1>{imagelinkhere}</IMAGE_URL1>
<IMAGE_URL2>{imagelinkhere}</IMAGE_URL2>
<IMAGE_URL3>{imagelinkhere}</IMAGE_URL3>
<IMAGE_URL4>{imagelinkhere}</IMAGE_URL4>
</Vehicle>';
$parsed = simplexml_load_string($xml);
If you know, that the image url tags will always contain the name IMAGE_URL, you can check them:
foreach ($parsed as $key => $image) {
if (strpos($key, 'IMAGE_URL') !== false) {
echo $image, '</br>';
}
}
You can fetch the nodes with Xpath.
$xml = <<<'XML'
<Vehicle>
<TITLE>Some car name i dont need</TITLE>
<DESCRIPTION>Some description i also dont need</DESCRIPTION>
<IMAGE_URL1>image1</IMAGE_URL1>
<IMAGE_URL2>image2</IMAGE_URL2>
<IMAGE_URL3>image3</IMAGE_URL3>
<IMAGE_URL4>image4</IMAGE_URL4>
</Vehicle>
XML;
$vehicle = new SimpleXMLElement($xml);
foreach ($vehicle->xpath('*[starts-with(local-name(), "IMAGE_URL")]') as $imageUrl) {
var_dump((string)$imageUrl);
}
Output:
string(6) "image1"
string(6) "image2"
string(6) "image3"
string(6) "image4"
* selects all element child nodes. [] is a condition. In this case a validation that the local name (tag name without any namespace prefix) starts with a specific string.
This looks not that much different in DOM. But you start at the document context.
$document = new DOMDocument();
$document->loadXml($xml);
$xpath = new DOMXpath($document);
foreach ($xpath->evaluate('/Vehicle/*[starts-with(local-name(), "IMAGE_URL")]') as $imageUrl) {
var_dump($imageUrl->textContent);
}
i have two tags in my sample xml as below,
<EmailAddresses>2</EmailAddresses>
<EmailAddresses>
<string>Allen.Patterson01#fantasyisland.com</string>
<string>Allen.Patterson12#fantasyisland.com</string>
</EmailAddresses>
how to differentiate these two xml tags based on the childnodes that means how to check that first tag has no childnodes and other one has using DOM php
Hope it will meet your requirement. Just copy,paste and run it. And change/add logic whatever you want.
<?php
$xmlstr = <<<XML
<?xml version='1.0' standalone='yes'?>
<email>
<EmailAddresses>2</EmailAddresses>
<EmailAddresses>
<string>Allen.Patterson01#fantasyisland.com</string>
<string>Allen.Patterson12#fantasyisland.com</string>
</EmailAddresses>
</email>
XML;
$email = new SimpleXMLElement($xmlstr);
foreach ($email as $key => $value) {
if(count($value)>1) {
var_dump($value);
//write your logic to process email strings
} else {
var_dump($value);
// count of emails
}
}
?>
You can use ->getElementsByTagName( 'string' ):
foreach( $dom->getElementsByTagName( 'EmailAddresses' ) as $node )
{
if( $node->getElementsByTagName( 'string' )->length )
{
// Code for <EmailAddresses><string/></EmailAddresses>
}
else
{
// Code for <EmailAddresses>2</EmailAddresses>
}
}
2 is considered as <EmailAddresses> child node, so in your XML ->haschildNodes() returns always True.
You have this problem due your weird XML structure conception.
If you don't have particular reason to maintain this XML syntax, I suggest you to use only one tag:
<EmailAddresses count="2">
<string>Allen.Patterson01#fantasyisland.com</string>
<string>Allen.Patterson12#fantasyisland.com</string>
</EmailAddresses>
Xpath allows you to do that.
$xml = <<<'XML'
<xml>
<EmailAddresses>2</EmailAddresses>
<EmailAddresses>
<string>Allen.Patterson01#fantasyisland.com</string>
<string>Allen.Patterson12#fantasyisland.com</string>
</EmailAddresses>
</xml>
XML;
$document = new DOMDocument();
$document->loadXml($xml);
$xpath = new DOMXpath($document);
var_dump(
$xpath->evaluate('number(//EmailAddresses[not(*)])')
);
foreach ($xpath->evaluate('//EmailAddresses/string') as $address) {
var_dump($address->textContent);
}
Output:
float(2)
string(35) "Allen.Patterson01#fantasyisland.com"
string(35) "Allen.Patterson12#fantasyisland.com"
The Expressions
Fetch the first EmailAddresses node without any element node child as a number.
Select any EmailAddresses element node:
//EmailAddresses
That does not contain another element node as child node:
//EmailAddresses[not(*)]
Cast the first of the fetched EmailAddresses nodes into a number:
number(//EmailAddresses[not(*)])
Fetch the string child nodes of the EmailAddresses element nodes.
Select any EmailAddresses element node:
//EmailAddresses
Get their string child nodes:
//EmailAddresses/string
In you example the first EmailAddresses seems to be duplicate information and stored in a weird way. Xpath can count nodes, too. The expression count(//EmailAddresses/string) would return the number of nodes.
How can I convert XML to an array that I can iterate over.
Here is the example of my XML
<user>
<student>yes</student>
<id>1</id>
<name>John</name>
</user>
<user>
<student>yes</student>
<id>1</id>
<name>Billy</name>
</user>
My php looks like this
$tmpTemplates = new XDomDocument();
$tmpTemplates->load('.....myFile.xml');
$xPath = new DomXPath($tmpTemplates);
$query = "//user[student='yes']";
$tmpTemplate = $xPath->query($query);
What I want to be able to do is
foreach($tmpTemplate as $tt){
var_dump($tt->student);
var_dump($tt->id);
var_dump($tt->name);
}
Now I'm only able to print out nodeValue which gives me something like this:
yes
1
John
How can I make it an array or an object so I can apprach each value by its key?
You used the same id for both records in your example, so I changed the id of the first one and added a document element.
<users>
<user>
<student>yes</student>
<id>2</id>
<name>John</name>
</user>
<user>
<student>yes</student>
<id>1</id>
<name>Billy</name>
</user>
</users>
Use DOMXpath:evaluate() to fetch the details, the second argument is the context node for the expression.
$document = new DOMDocument();
$document->loadXml($xmlString);
$xPath = new DomXPath($document);
$students = [];
foreach ($xPath->evaluate("//user[student='yes']") as $student) {
$id = $xPath->evaluate('string(id)', $student);
$students[$id] = [
'id' => $id,
'name' => $xPath->evaluate('string(name)', $student)
];
}
var_dump($students);
Output:
array(2) {
[2]=>
array(2) {
["id"]=>
string(1) "2"
["name"]=>
string(4) "John"
}
[1]=>
array(2) {
["id"]=>
string(1) "1"
["name"]=>
string(5) "Billy"
}
}
The return value depends on the expression. A location path like //user[student='yes'] or name returns a DOMNodeList. But you can cast the node list directly in Xpath. string(name) will return the contents of the first name child node or an empty string.
How can I make it an array or an object so I can apprach each value by its key?
Just for clarification, you've got an object so far, it's the SimpleXMLElement and you're using then $tt variable to access it:
...
var_dump($tt->student);
var_dump($tt->id);
var_dump($tt->name);
...
Now that $tt variable comes from another variable, namely by iterating over it, and the other variable is named $tmpTemplate:
...
foreach ($tmpTemplate as $tt) {
var_dump($tt->student);
...
That variable by the way is an array. So you can use it by using the index (starting at zero) to access each <user> element containing a <student> child-element with the value "student" in document-order (as you formulated the xpath for it):
$tmpTemplate[0] contains the first user SimpleXMLElement.
$tmpTemplate[1] contains the second user SimpleXMLElement.
... and so on and so forth.
I hope this makes this a bit more visible.
Try with this php function
xml_parse_into_struct
sample from php manual
<?php
$simple = "<para><note>simple note</note></para>";
$p = xml_parser_create();
xml_parse_into_struct($p, $simple, $vals, $index);
xml_parser_free($p);
echo "Index array\n";
print_r($index);
echo "\nVals array\n";
print_r($vals);
?>
You can use simplexml_load_file, which convert xml to object.
For instance :
$users = simplexml_load_file("users.xml");
foreach($users as $user) {
...
}
I have a really weird XML response and i need to extract it's data. I need to get the data in the "value" attribute but i need to choose them according to their "key" attributes.
This is how it looks like
<phone>
2125556666
</phone>
<State>
ny
</State>
<Response>
<data key="Supported" value="Yes"/>
<data key="Host" value="Remote"/>
<data key="WholeProductList">
<data key="Product" value="a-z44"/>
<data key="Product" value="c-k99"/>
<data key="Product" value="e-b089"/>
<data key="Product" value="z-p00"/>
<data key="Product" value="r-333"/>
<data key="Product" value="t-RS232"/>
<data key="Product" value="4-lve"/>
<data key="Product" value="Shutdown"/>
</data>
</Response>
In PHP i currenty have
$xmltmp = new DomDocument;
$xmltmp->loadXml($response);
$phone = $xmlresponse->getElementsByTagName('phone')->item(0)->nodeValue;
$state = $xmlresponse->getElementsByTagName('state')->item(0)->nodeValue;
echo $phone;
echo $state;
This currently outputs both phone number and state. It works fine.
Now i need to know if the "Supported" key's value is Yes or No, and if it's Yes, i need to get all "Products". I'm kinda stuck because i am having a hard time making the foreach statement and then checking the "key" attribute value.
Thanks!
Your XML is invalid. An XML document always needs a single document element node.
Example:
<root>
<phone>2125556666</phone>
<State>ny</State>
<Response>
<data key="Supported" value="Yes"/>
...
</data>
</Response>
</root>
The easiest way to fetch data from a DOM is XPath. In PHP that is provided by the DOMXPath class and part of the ext/dom. DOMXPath::evaluate() allows you to fetch node lists or scalar values from the DOM document.
$dom = new DOMDocument;
$dom->loadXml($xml);
$xpath = new DOMXPath($dom);
$phone = $xpath->evaluate('string(/*/phone)');
$state = $xpath->evaluate('string(/*/State)');
var_dump($phone, $state);
Output:
string(10) "2125556666"
string(2) "ny"
An expression like /*/phone selects all phone element child nodes inside the document element. string(/*/phone) casts the first found node into a string and return that. If no node was found, it will return an empty string.
The XPath expression for the supported status is slightly more complex. Conditions for nodes are provided in []. It is possible to compare the result directly in XPath. The return value will be an boolean.
$supported = $xpath->evaluate('/*/Response/data[#key="Supported"]/#value = "Yes"');
var_dump($supported);
Output:
bool(true)
If the expression returns a node list you can iterate it with foreach().
$nodes = $xpath->evaluate(
'/*/Response/data[#key="WholeProductList"]/data[#key="Product"]/#value'
);
$products = [];
foreach ($nodes as $attributeNode) {
$products[] = $attributeNode->value;
}
var_dump($products);
Output:
array(8) {
[0]=>
string(5) "a-z44"
[1]=>
string(5) "c-k99"
[2]=>
string(6) "e-b089"
[3]=>
string(5) "z-p00"
[4]=>
string(5) "r-333"
[5]=>
string(7) "t-RS232"
[6]=>
string(5) "4-lve"
[7]=>
string(8) "Shutdown"
}
This won't quite work "as is" since I don't know what the actual structure of the XML document is, but in short you map the XML nodes to XPath like //root/node/child_node/#attribute and so on.
It should also have some sanity (not null) type checking in.
$xmltmp = new DomDocument;
$xmltmp->loadXml($response);
$xQuery = new DOMXPath($xmltmp);
//not sure what your root node is so the query path is probably wrong
$supported = $xQuery->query('/Response/data[#key="Supported"]/#value')->value;
You can also replace:
$phone = $xmlresponse->getElementsByTagName('phone')->item(0)->nodeValue;
$state = $xmlresponse->getElementsByTagName('state')->item(0)->nodeValue;
With something like (again - without the full structure of the XML document the path itself is probably not quite right):
$phone = $xQuery->query('/phone')->item(0)->nodeValue;
$state = $xQuery->query('/State')->item(0)->nodeValue;
I am trying to use Xpath to find a username value from a REST response. When I use
$data = new simpleXMLElement($response->getBody());
$matches = $data->xpath('//KEY[#name="username"]');
I get
array(1) {
[0]=>
object(SimpleXMLElement)#7 (2) {
["#attributes"]=>
array(1) {
["name"]=>
string(8) "username"
}
["VALUE"]=>
string(5) "guest"
}
}
My question is, what is the Xpath expression to get only the value to display? in this example it is guest, but will change depending on the user.
Any suggestions greatly appreciated.
XML structure below.
<?xml version="1.0" encoding="UTF-8" ?>
<RESPONSE>
<MULTIPLE>
<SINGLE>
<KEY name="id">
<VALUE>1</VALUE>
</KEY>
<KEY name="username">
<VALUE>guest</VALUE>
</KEY>
<KEY name="firstname">
<VALUE>Guest user</VALUE>
</KEY>
Not possible with SimpleXML. The xpath method will always return an Array.
But you can do:
$keys = simplexml_load_string($xml);
echo current($keys->xpath('//KEY[#name="username"]/VALUE'));
demo
Or use DOM. The following will return the string immediately:
$dom = new DOMDocument;
$dom->loadXml($xml);
$xpath = new DOMXPath($dom);
echo $xpath->evaluate('string(//KEY[#name="username"]/VALUE)');
demo
I am not sure if simpleXml can evaluate this expression, but any compliant XPath implementation will:
string(//KEY[#name="username"]/VALUE)
When this expression is evaluated, the result is the string value of the Value element that is a child of the (first in document order) KEY element that has a name attribute, whose string value is "username".
Also, if the structure of the XML document is known, then it may be more efficient (fast) not to use //:
string(/RESPONSE/MULTIPLE/SINGLE/KEY[#name="username"]/VALUE)