Select some but not all child nodes - php

I have the following XML structure:
<?xml version="1.0" encoding="ISO-8859-1"?>
<articles>
<article id="1">
<title>Article title 001</title>
<short>Short text</short>
<long>Long text</long>
</article>
<article id="2">
<title>Article title 002</title>
<short>Short text</short>
<long>Long text</long>
</article>
</articles>
I want to select only <title> and <short>.
Currently using this to display everything:
$queryResult = $xpathvar->query('//articles/article'); // works fine grabs all articles
foreach($queryResult as $result){
echo $result->textContent;
}
The expected output would be:
Article title 001
Short text
Any assistance would be greatly appreciated.
Working solution!
if ($artId == "") {
$queryResult = $xpathvar->query('//articles/article/*'); // grab all children
foreach($queryResult as $result){
if($result->nodeName === 'title' || $result->nodeName === 'short') {
echo $result->textContent;
}
}
}else{
$queryResult = $xpathvar->query(sprintf('//articles/article[#id="%s"]/*', $artId)); // Show requested article
foreach($queryResult as $result){
if($result->nodeName === 'title' || $result->nodeName === 'long') {
echo $result->textContent;
}
}
}

You can use
/articles/article/*[name()="title" or name()="short"]
which would only return children of any "articles/article" with an element name of "title" or "short".
As an alternative, change the XPath to /articles/article/* to fetch all childNodes of article and when iterating $results check if DOMNode::nodeName is "title" or "short", e.g.
$queryResult = $xpathvar->query('/articles/article/*'); // grab all children
foreach($queryResult as $result){
if($result->nodeName === 'title' || $result->nodeName === 'short') {
echo $result->textContent;
}
}
If you dont want to change the XPath, you have to iterate the childNodes of the article, e.g.
$queryResult = $xpathvar->query('/articles/article');
foreach($queryResult as $result) {
foreach($result->childNodes as $child) {
if($child->nodeName === 'title' || $child->nodeName === 'short') {
echo $child->textContent;
}
}

Use:
/*/*/*[self::title or self::short]
or if the title and short children of a specific article with known #id (say '2') should be displayed:
/*/article[#id='2']/*[self::title or self::short]
Always try to avoid using the // abbreviation when this is possible (when the structure of the XML document is known).
Using // very often results in grossly-inefficient evaluation, because // causes the whole (sub) tree rooted in the current node to be searched.

Related

How to loop through two XML files and print result

I've been trying unsuccessfully with PHP to loop through two XML files and print the result to the screen. The aim is to take a country's name and output its regions/states/provinces as the case may be.
The first block of code successfully prints all the countries but the loop through both files gives me a blank screen.
The countries file is in the format:
<row>
<id>6</id>
<name>Andorra</name>
<iso2>AD</iso2>
<phone_code>376</phone_code>
</row>
And the states.xml:
<row>
<id>488</id>
<name>Andorra la Vella</name>
<country_id>6</country_id>
<country_code>AD</country_code>
<state_code>07</state_code>
</row>
so that country_id = id.
This gives a perfect list of countries:
$xml = simplexml_load_file("countries.xml");
$xml1 = simplexml_load_file("states.xml");
foreach($xml->children() as $key => $children) {
print((string)$children->name); echo "<br>";
}
This gives me a blank screen except for the HTML stuff on the page:
$xml = simplexml_load_file("countries.xml");
$xml1 = simplexml_load_file("states.xml");
$s = "Jamaica";
foreach($xml->children() as $child) {
foreach($xml1->children() as $child2){
if ($child->id == $child2->country_id && $child->name == $s) {
print((string)$child2->name);
echo "<br>";
}
}
}
Where have I gone wrong?
Thanks.
I suspect your problem is not casting the name to a string before doing your comparison. But why are you starting the second loop before checking if it's needed? You're looping through every single item in states.xml needlessly.
$countries = simplexml_load_file("countries.xml");
$states = simplexml_load_file("states.xml");
$search = "Jamaica";
foreach($countries->children() as $country) {
if ((string)$country->name !== $search) {
continue;
}
foreach($states->children() as $state) {
if ((string)$country->id === (string)$state->country_id) {
echo (string)$state->name . "<br/>";
}
}
}
Also, note that naming your variables in a descriptive manner makes it much easier to figure out what's going on with code.
You could probably get rid of the loops altogether using an XPath query to match the sibling value. I don't use SimpleXML, but here's what it would look like with DomDocument:
$search = "Jamaica";
$countries = new DomDocument();
$countries->load("countries.xml");
$xpath = new DomXPath($countries);
$country = $xpath->query("//row[name/text() = '$search']/id/text()");
$country_id = $country[0]->nodeValue;
$states = new DomDocument();
$states->load("states.xml");
$xpath = new DomXPath($states);
$states = $xpath->query("//row[country_id/text() = '$country_id']/name/text()");
foreach ($states as $state) {
echo $state->nodeValue . "<br/>";
}

How do I fix this PHP code?

I have to create a live search on a website. I have to use PHP, but I have never studied it and I am pretty much a beginner in programming.
I have an XML-file as a database and the aim is to get node values and display them as suggestions as a user types in something. The problem with this code is that it spits out all of the node values onto the web page.
Here is the PHP code:
<?php
$xmlDoc = new DOMDocument();
$xmlDoc->load("collection.xml");
$books = $xmlDoc->getElementsByTagName("book");
$q = $_GET["q"];
if (strlen($q) > 0) {
$hint = "";
foreach ($books as $book) {
$name = $book->getElementsByTagName("name")->item(0)->nodeValue;
echo "$name <br/>";
}
}
if ($hint == "")
{
$response="no suggestion";
}
else
{
$response=$hint;
}
//output the response
echo $response;
?>
Here is the XML-file:
<books>
<book>
<name>Harry Potter</name>
<quantity> 50 </quantity>
<price>19.90</price>
</book>
<book>
<name>Casino Royale</name>
<quantity> 50 </quantity>
<price>12.99</price>
</book>
<book>
<name>The Great Gatsby</name>
<quantity> 40 </quantity>
<price>14.90</price>
</book>
</books>
Can someone please help me fix this issue so that I can continue working on my project. Thank you in advance for your time and help! Aprreciate it a lot!
The issue is here:
$hint = "";
foreach ($books as $book) {
$name = $book->getElementsByTagName("name")->item(0)->nodeValue;
echo "$name <br/>";
}
Notice that you have a "foreach loop" here. The "$name=$book...." line simply reads the value of that particular XML node and assigns it to the $name variable. Then you are doing a call to echo $name. So in essence, all you're doing here is reading the value of the XML node and printing it. No part of your code compares the $name to your search query ($q). It seems that what you want to happen is only print out books that somehow match $q.
In order to do that we need to apply some logic to your foreach loop to only print out values that match $q.
Here is a suggestion:
$hint = "";
foreach ($books as $book) {
$name = $book->getElementsByTagName("name")->item(0)->nodeValue;
// Let's only show this book if $q appears somewhere in $name.
if (strpos($name, $q) !== false && strpos($name, $q) >= 0)
{
echo $name . "<br />";
}
}

Delete XML node with SimpleXML, PHP

I'm trying to delete XML node with PHP (SimpleXML).
This is my XML:
<?xml version="1.0"?>
<items>
<a>
<name>A1</name>
<b>
<title>Item1</title>
<url>item1</url>
</b>
<b>
<title>Item2</title>
<url>item2</url>
</b>
<b>
<title>Item3</title>
<url>item3</url>
</b>
</a>
<a>
<name>A2</name>
<b>
<title>Item1</title>
<url>item1</url>
</b>
</a>
</items>
and this is my PHP code:
<?php
$xml = simplexml_load_file($_GET["xml"]);
$sxe = new SimpleXMLElement($xml->asXML());
$ID = $_GET["ID"];
$i = -1;
$num = $_GET["num"];
foreach ($sxe->children() as $var) {
if ($var == $ID) {
foreach ($var->children() as $data) {
if ($data == "link") {
$i++;
if ($i == $num) {
if ( ! empty($sxe)) {
unset($sxe[0]);
}
}
}
}
}
}
$sxe->asXML($_GET["xml"]);
?>
This code looks for with data of $ID (for example, $ID="A1"). The node it looks to delete is a node (with its and ), which is the #$num node.
Example: if $ID="A1" and $num=1,
it needs to delete the node with the title "Item2" and url "item2".
What am I doing wrong?
Thanks!
Use xpath to find a node. With your example it will be
//a[name="A1"]/b[2]
and use DomDocument method removeChild to change xml
$sxe = simplexml_load_string($xml);
$node = $sxe->xpath('//a[name="'. $ID .'"]/b['. $num .']');
$dom=dom_import_simplexml($node[0]);
$dom->parentNode->removeChild($dom);
echo $sxe->asXML();

PHP XML - How to build a tree of xml nodes that are at at same level with parent child relationship

I am trying to read the xml from a file and display them just like a tree. I am using PHP and I want to get the output as shown below. The content of xml file is as follows...
<Categories>
<Category>
<Id>1</Id>
<Name>Parent 1</Name>
<ParentId>1</ParentId>
<ParentName>Parent 1</ParentName>
</Category>
<Category>
<Id>2</Id>
<Name>Child 1</Name>
<ParentId>1</ParentId>
<ParentName>Parent 1</ParentName>
</Category>
<Category>
<Id>3</Id>
<Name>Child 2</Name>
<ParentId>1</ParentId>
<ParentName>Parent 1</ParentName>
</Category>
<Category>
<Id>8</Id>
<Name>Grand Child 1 -1</Name>
<ParentId>2</ParentId>
<ParentName>Child 1</ParentName>
</Category>
<Category>
<Id>12</Id>
<Name>Parent 2</Name>
<ParentId>12</ParentId>
<ParentName>Parent 2</ParentName>
</Category>
<Category>
<Id>15</Id>
<Name>Child 2-1</Name>
<ParentId>12</ParentId>
<ParentName>Parent 2</ParentName>
</Category>
</Categories>
</CategoryList>
I want to read this xml file (I know how to read it) But I can not format it like follows... How would I get all the nodes that are the top most parents and get child of those parent nodes (using recursion or what so ever)
<ul>
<li>Parent 1
<ul>
<li> Child 1
<ul>
<li>Grand Child 1 -1</li>
</ul>
</li>
<li> Child 2</li>
</ul>
</li>
<li>Parent 2
<ul>
<li>Child 2-1 </li>
</ul>
</li>
</ul>
Please any help will be greatly appreciated....
Edit* What I have done so far...
$xml= simplexml_load_string('myxmlstring');
get_categories($xml, 0);
function get_categories($xml, $id) {
if ($id==0)
$Categories = $xml->xpath('Categories/Category[ParentId=Id]');
else
$Categories = $xml->xpath('Categories/Category[ParentId='.$id.' and Id!='.$id.']');
echo '<ul id="catlist'.$id.'">';
foreach($Categories as $Category) {
echo "<li>ID: " . $Category->Id . "--Name: " . $Category->Name;
get_categories($xml, $Category->Id);
echo "</li>";
}
echo "</ul>";
}
Now I just want to confirm that this is the optimal solution. or someone can come with better idea...
ParentName is excessive, it's enough to put just parent id.
Since you do a search for each node, execution time will be O(N2), where N is amout of nodes.
There's an option to do this in linear time, though: Firstly traverse data and build tree structure (or somewhat) and then traverse that structure and output nodes according to it.
Output buffering is also a good option here.
// init
$childrenReferences = array();
$rootNodes = array();
$xmlNodes = array();
// gathering structure
$cats = $xml->getElementsByTagName('Category');
for ($i = 0; $i < $cats->length; $i++) {
$cat = $cats[$i];
$id = $children->Id;
$parentId = $cat->ParentId;
$xmlNodes[$id] = $cat;
if ($parentId == $id) {
$rootNodes []= $id;
continue;
}
if (array_key_exists($parentId, $childrenReferences)) {
$childrenReferences[$parentId] []= $id;
} else {
$childrenReferences[$parentId] = array($id);
}
}
// output
function out_nodes($nodes) {
global $childrenReferences, $xmlNodes; // this is not required since php 5.3 or something about
echo "<ul>";
foreach ($nodes as $id) {
$cat = $xmlNodes[$id];
echo "<li>ID: " . $cat->Id . "--Name: " . $cat->Name;
if (array_key_exists($id, $childrenReferences)) { // intermediate node
out_nodes($childrenReferences[$id]);
}
echo "</li>";
}
echo "</ul>";
}
ob_start();
out_nodes($rootNodes);
ob_end_flush();
Code may not work or even compile, but you get the idea.
Thanks kirilloid.... that was great help... I am using the code with some modifications... the xml is from curl and I know ParentName is excessive, but I've no control over it. Here is the final code....
$childrenReferences = array();
$rootNodesIDs = array();
$xmlNodes = array();
$dom = new DOMDocument;
$dom->loadXML($xml);
$Categories = $dom->getElementsByTagName('Category');
$length = $Categories->length;
for ($i = 0; $i < $length; $i++) {
$cat = $Categories->item($i); //Get the DOMNode
$id = $cat->getElementsByTagName('Id')->item(0)->nodeValue;
$parentId = $cat->getElementsByTagName('ParentId')->item(0)->nodeValue;
$xmlNodes[$id] = $cat;
if ($parentId == $id) {
$rootNodesIDs []= $id;
continue;
}
if (array_key_exists($parentId, $childrenReferences)) {
$childrenReferences[$parentId] []= $id;
} else {
$childrenReferences[$parentId] = array($id);
}
}
function out_nodes($rootids) {
global $childrenReferences, $xmlNodes;
echo "<ul>";
foreach ($rootids as $id) {
$cat = $xmlNodes[$id];
echo "<li>ID: " . $cat->getElementsByTagName('Id')->item(0)->nodeValue . "--Name: " . $cat->getElementsByTagName('Name')->item(0)->nodeValue;
if (array_key_exists($id, $childrenReferences)) { // intermediate node
out_nodes($childrenReferences[$id]);
}
echo "</li>";
}
echo "</ul>";
}
ob_start();
out_nodes($rootNodesIDs);
ob_end_flush();

PHP Simple XML Parse Attributes

I have an XML file with data stored like this:
<myxml>
<item name="column18">88744544</item>
<item name="column11">47884994</item>
<item name="column3">44788894</item>
</myxml>
I need to first check (and be sure that) column11 is defined (there is no particular order), and then get its value.
Using simple XML is not seeming to work.
I have the following, but the value is missing.
<?php
if (count($xml->myxml->item) > 0)
{
foreach ($xml->myxml->item as $item)
{
var_dump($item->attributes());
}
}
?>
$item->attributes()->column11 doesn't work.
Dont include the opening tabs and attributes. For example:
<?php
if (count($xml->item) > 0)
{
foreach ($xml->item as $item)
{
var_dump($item); //For the info
echo $item['name']; //if you needed the name
}
}
?>
Try XPath.
if ($xml->xpath('//item[#name="column11"]'))
{
echo 'exists';
}

Categories