How to perform XPath on individual elements in PHP using SimpleXML? - php

I have the following example XML document:
<?xml version="1.0" encoding="UTF-8" ?>
<database>
<record>
<id>1</id>
<a>5</a>
</record>
<record>
<id>5</id>
<a>8</a>
</record>
<record>
<id>7</id>
<!--No a record!-->
</record>
<record>
<id>6</id>
<a>10</a>
</record>
</database>
I want to iterate through each "record" element and fetch it's corresponding "a" element if it exists.
I attempted this using the following code:
$xml = new SimpleXMLElement($xmlStringDataFromFile);
foreach ($xml->record as $record) {
$id = $record->xpath("//id")[0];
$a = $record->xpath("//a")[0];
echo "{$id}: {$a}\n";
}
However the xpath performed is on the entire document. not on the individual "record" element. Thus I got the following output:
1: 5
1: 5
1: 5
1: 5
I want the following output:
1: 5
5: 8
7:
6: 10
How do I do this?

When you use // at the beginning of the XPath expression it will search from the document root and you always end up with the same result. Since the id and a elements are directly under record simply use
$id = $record->xpath("id")[0];
$a = $record->xpath("a")[0];
If they would be at same level below record start the XPath expression with a . to search relative to the context node:
$id = $record->xpath(".//id")[0];
$a = $record->xpath(".//a")[0];

Related

xpath finds nothing - PHP

Introduction
I have some codes in SQL. I go code by code in while loop in php and search these codes in XML feed.
Programm code
I have the following programm code
$x_search = $xml->xpath("//Item[#Sort='$sort']");
if(!$x_search){
$x_Id = $x_search[0]->attributes()->Id;
echo $sort." - ".$x_Id."<BR />";
}
Problem
It is possible, that some code is not in SQL. So I get this error message:
Undefined offset: 0 in
How to do something like if you find it in XML, $x_Id = $x_search[0]->attributes()->Id;?
I have tried already:
$x_search = $xml->xpath("//*[#Sort='$sort']");
if(!empty($x_search)){
if(isset($x_search)){
Example XML:
<?xml version="1.0" encoding="utf-8"?>
<Root>
<Item Id="12860" IdP="-2147483648" Sort="0001KC" Name="Computers">
<StoItem />
</Item>
</Root>
Examples for $sort:
00004M
12860
12859
12859
12861
12861
12862
12862
12863
12863
12864
Thank you
SimpleXMLElement::xpath() always returns an array of SimpleXMLElement objects. The array is empty, if nothing is matched. The result can equal false, if the expression is invalid (programming error). So if (!empty($x_search)) ... or if ($x_search) ... can be used as condition to check the result. false is an empty value and an empty array equals false. Both conditions will only be true if the result from the expression is an array with at least a single element.
$xmlString = <<<'XML'
<?xml version="1.0" encoding="utf-8"?>
<Root>
<Item Id="12860" IdP="-2147483648" Sort="0001KC" Name="Computers">
<StoItem />
</Item>
</Root>
XML;
$xml = new SimpleXmlElement($xmlString);
$sort = '0001KC';
$x_search = $xml->xpath("//Item[#Sort='$sort']");
if (!empty($x_search)) {
$x_Id = $x_search[0]->attributes()->Id;
echo $sort." - ".$x_Id."<BR />";
}
Output: https://eval.in/406640
0001KC - 12860<BR />
Most of your example values for $sort look like Id attribute values. The second one, is the Id attribute value in the example XML.
If you want to match the Id attribute, the Xpath expression would be:
//Item[#Id='$sort']
It is even possible to match both attributes:
//Item[#Id='$sort' or #Sort='$sort']
This is an example of the XML. A I wrote it is possible, that some code is not in SQL. So that's why I need to solve this problem. Something like write me an echo only if you find this sort in XML.
<?xml version="1.0" encoding="utf-8"?>
<Root>
<Item Id="12860" IdP="-2147483648" Sort="0001KC" Name="Computers">
<StoItem />
</Item>
</Root>
The begin of the list of codes that enter invariable $sort
00004M
12860
12859
12859
12861
12861
12862
12862
12863
12863
12864

Get XML node position

I have the following XML
<cds>
<record>
<id>1</id>
<artist>Rammstein</artist>
<album>random</album>
<trackNumbers>11</trackNumbers>
</record>
<record>
<id>2</id>
<artist>Rammstein</artist>
<album>random</album>
<trackNumbers>18</trackNumbers>
</record>
</cds>
I want to delete the record by the identifiier "ID" that I pass from another php file. So if I am not wrong I need the position of the record node to remove that node.
$xml=simplexml_load_file("books.xml") or die("Error: Cannot create object");
unset($xml->record[x]); // x should be the id passed
Is this achievable? I've been trying to obtain this I am not able to find the solution.
first select the <record> with xpath(), then delete it:
$xml = simplexml_load_string($x); // assume XML in $x
$record = $xml->xpath("/cds/record[id='2']")[0];
This will store the first result (index = 0) of that xpath "query" in $record. It will be a record-node with id = 2. Note that PHP >= 5.4 is needed to do array dereferencing.
Now use unset:
unset($record[0]);
See the changes:
echo $xml->asXML();

How Can I combine a list from XML in PHP

I Have a Parts List Coming from an XML File, It goes something like what is below. I Want to be able to generate using PHP a Combined List. For Example the "Part 1" Is recorded twice. I want the Qty to Show 13 Under Part 1 when Generated either in a JSON output or another XML Output. What would be the best way of doing that? I looked at the php function array_combine but wasn't able to figure out if the values could be combined mathematically instead of showing one of the results. I am loading the XML from a url in simplexml_load_file() function. Thank You for your help
My XML from URL:
<db>
<record>
<part>Part 1</part>
<qty>4</qty
</record>
<record>
<part>Part 2</part>
<qty>5</qty
</record>
<record>
<part>Part 1</part>
<qty>9</qty
</record>
</db>
Want to Display:
<db>
<record>
<part>Part 1</part>
<qty>13</qty>
</record>
<record>
<part>Part 2</part>
<qty>5</qty
</record>
</db>
Iterate over the XML document, accumulating part quantities as you go:
$simpleXmlElement = new SimpleXMLElement(<<<XML
<db>
<record>
<part>Part 1</part>
<qty>4</qty>
</record>
<record>
<part>Part 2</part>
<qty>5</qty>
</record>
<record>
<part>Part 1</part>
<qty>9</qty>
</record>
</db>
XML
);
$quantities = array();
foreach ($simpleXmlElement as $child) {
if ($child->part && $child->qty) {
$part = (string)$child->part;
if (!isset($quantities[$part])) {
$quantities[$part] = 0;
}
$quantities[$part] += (int)$child->qty;
}
}
echo json_encode($quantities);
simplexml_load_string() will parse the xml and build an array of all the records as 'record' => array(...)
you can then iterate the record array and group them by type
$parts = array();
foreach ($document['record'] as $item) {
if (isset($parts[$item['part']]) $parts[$item['part']] += $item['qty'];
else $parts[$item['part']] = $item['qty'];
}
after the above loop, $parts will contain a hash mapping the part name to qty

Getting information from more than one large XML file

I am trying to get information from external large xml files; from file 1 (vehicleList.xml) and file 2 (CheckVehicles.xml) into a PHP file. All values in XML file 2 are in XML file 1. I would like to display only values in file 1 that are in XML file 2.
My foreach loop code can bring results for up to 130 items (that is if I reduce the items in XML file 2 to 130 items/nodes). However if I remove the if statement, I am able to get all the 3340 items/vehicles from XML file 1.
Where am I going wrong? I tried arrays but failed.
Here is my code:
//XML FILE 1 with 1300 items
$myXML = new SimpleXMLElement('CheckVehicles.xml', NULL, TRUE);//
foreach($myXML->root->item as $item){
$listArrayNew[(int)$item->value] = (int)$item->value;
}
//XML FILE 2 with 3340 vehicles
$parser = new SimpleXMLElement('vehicleList.xml', NULL, TRUE);
foreach ($parser->GetVehiclesListResponse->GetVehiclesListResult->Vehicle as $Vehicle) {
if($listArrayNew[(int)$Vehicle->ID] == (int)$Vehicle->ID){
$vehicle = $Vehicle->Description;
$regNumber = $Vehicle->RegistrationNumber;
$siteID = $Vehicle->SiteID;
$row .= "<tr>
<td>".$vehicle."</td>
<td>".$regNumber."</td>
<td>".$siteId."</td>
</tr>";
}
}
Here are the XML files:
XML file 1: vehicleList.xml
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope>
<GetVehiclesListResponse>
<GetVehiclesListResult>
<Vehicle>
<ID>153</ID>
<SiteID>11</SiteID>
<GroupID>3</GroupID>
<Description>A.O Basid KAR 459 E</Description>
<RegistrationNumber>KAR 459 E</RegistrationNumber>
</Vehicle>
..............................
<Vehicle>
<ID>3340</ID>
<SiteID>25</SiteID>
<GroupID>4</GroupID>
<Description>UAR 712B White Nissan Tiida (Deus Mubangizi)</Description>
<RegistrationNumber>UAR 712B</RegistrationNumber>
</Vehicle>
</GetVehiclesListResult>
</GetVehiclesListResponse>
</soap:Envelope>
XML file 2: CheckVehicles.xml
<?xml version="1.0" encoding="utf-8"?>
<Result>
<root>
<item>
<index>0</index>
<value>153</value>
</item>
...................
<item>
<index>1300</index>
<value>128</value>
</item>
</root>
</Result>
I don't know where you go wrong in your case. However if you want to select elements from the second file based on a criteria (e.g. an ID / unique Number) from the first file I suggest you make use of xpath in your case:
Obtain the numbers from the first file that are the criteria (e.g. /*/root/item/value)
Select all elements from the second file that match the criteria (e.g. ID in /*/GetVehiclesListResponse/GetVehiclesListResult/Vehicle).
The later point can best be achieved by using the technique outlined in Is there anything for XPATH like SQL “IN”? which is creating a comma separated list of the numbers to select and then compare this against each elements number.
Example:
Consider there 2 500 out of 10 000 elements in a first file and in a second file there are 10 000 elements. Each element can be uniquely identified by it's ID.
The first file has this layout:
<?xml version="1.0"?>
<root>
<item>
<index>0</index>
<id>604</id>
</item>
<item>
<index>1</index>
<id>2753</id>
</item>
...
</root>
And the second file has this layout.
<?xml version="1.0"?>
<list>
<item>
<id>1</id>
<some>Number: 33</some>
</item>
<item>
<id>2</id>
<some>Number: 35</some>
</item>
...
</list>
The xpath query to get all IDs from the first file therefore is:
//item/id
And the query for the second file can be expressed with SimpleXML in PHP as:
$ids = implode(',', $file1->xpath('//item/id'));
$query = '//item[contains(",' . $ids . ',", concat(",", id, ","))]';
You can find example code of that example here: http://eval.in/6370

Hide XML declaration in files generated using PHP

I was tesing with a simple example of how to display XML in browser using PHP and found this example which works good
<?php
$xml = new DOMDocument("1.0");
$root = $xml->createElement("data");
$xml->appendChild($root);
$id = $xml->createElement("id");
$idText = $xml->createTextNode('1');
$id->appendChild($idText);
$title = $xml->createElement("title");
$titleText = $xml->createTextNode('Valid');
$title->appendChild($titleText);
$book = $xml->createElement("book");
$book->appendChild($id);
$book->appendChild($title);
$root->appendChild($book);
$xml->formatOutput = true;
echo "<xmp>". $xml->saveXML() ."</xmp>";
$xml->save("mybooks.xml") or die("Error");
?>
It produces the following output:
<?xml version="1.0"?>
<data>
<book>
<id>1</id>
<title>Valid</title>
</book>
</data>
Now I have got two questions regarding how the output should look like.
The first line in the xml file '', should not be displayed, that is it should be hidden
How can I display the TextNode in the next line. In total I am exepecting an output in this fashion
<data>
<book>
<id>1</id>
<title>
Valid
</title>
</book>
</data>
Is that possible to get the desired output, if so how can I accomplish that.
Thanks
To skip the XML declaration you can use the result of saveXML on the root node:
$xml_content = $xml->saveXML($root);
file_put_contents("mybooks.xml", $xml_content) or die("cannot save XML");
Please note that saveXML(node) has a different output from saveXML().
First question:
here is my post where all usable threads with answers are listed: How do you exclude the XML prolog from output?
Second question:
I don't know of any PHP function that outputs text nodes like that.
You could:
read xml using DomDocument and save each node as string
iterate trough nodes
detect text nodes and add new lines to xml string manually
At the end you would have the same XML with text node values in new line:
<node>
some text data
</node>

Categories