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 />";
}
}
Related
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/>";
}
I am having a hard time trying to understand why I can't compare the values of two arrays in PHP. If I echo both of these during the loop using "echo $description->ItemDesriptionName;" and "echo $item->ItemName;" the values seem to show as the same, but when I try to compare them using if, nothing works. What am I missing?
<?php
$xml=simplexml_load_file("test.xml") or die("Error: Cannot create object");
$categories = $xml->Menu->Categories;
$items = $xml->Menu->Categories->Items->ItemObject;
$itemdescription = $xml->Menu->Options->Description->DescriptionObject;
foreach($items as $item) {
echo $item->ItemName . ' - ' . $item->Price . '</br>';
foreach ($itemdescription as $description) {
if ($description->ItemDescriptionName == $item->ItemName) {
echo 'We have a match!';
//where I would echo $description->ItemDescription;
}
}
}
?>
Here is the XML file
<?xml version="1.0" encoding="utf-8"?>
<Root>
<Menu>
<Categories>
<Name>Category 1</Name>
<Items>
<ItemObject>
<ItemName>Item 1</ItemName>
<Price>1</Price>
</ItemObject>
<ItemObject>
<ItemName>Item 2</ItemName>
<Price>3</Price>
</ItemObject>
</Items>
</Categories>
<Options>
<Description>
<DescriptionObject>
<ItemDescriptionName>Item 1</ItemDescriptionName>
<ItemDescription>A Great item</ItemDescription>
</DescriptionObject>
<DescriptionObject>
<ItemDescriptionName>Item 2</ItemDescriptionName>
<ItemDescription>A Great item as well</ItemDescription>
</DescriptionObject>
</Description>
</Options>
</Menu>
</Root>
compare as string
and you have typo in ItemDescriptioName (ItemDescriptionName)
if ( (string)$description->ItemDescriptionName == (string)$item->ItemName) {
Convert to string and then compare
<?php
$xml=simplexml_load_file("test.xml") or die("Error: Cannot create object");
$menu = $xml->Menu;
$categories = $xml->Menu->Categories;
$items = $xml->Menu->Categories->Items->ItemObject;
$itemdescription = $xml->Menu->Options->Description->DescriptionObject;
foreach($items as $item) {
$itemname = $item->ItemName;
foreach ($itemdescription as $description) {
$descriptionname = $description->ItemDescriptionName ;
echo $itemname." ---- ".$descriptionname."<br/>";
if((string)$itemname === (string)$descriptionname){
echo "Yes its matched";
}
}
}
?>
Working fine for me
The properties like $description->ItemDescriptionName are SimpleXMLElement objects. So you do not compare strings but two objects.
SimpleXMLElement objects implement the magic method __toString(). They can be cast to string automatically, but a compare between to objects will not trigger that. You can force it:
if ((string)$description->ItemDescriptionName === (string)$item->ItemName) {
...
Can you access them directly instead using an accordant index?
...
$items = $xml->Menu->Categories->Items->ItemObject;
$itemdescription = $xml->Menu->Options->Description;
$i = 0;
foreach ($items as $item) {
echo $i.' '.$item->ItemName . ' - ' . $item->Price;
echo $itemdescription->DescriptionObject[$i]->ItemDescriptionName[0];
echo ' ';
echo $itemdescription->DescriptionObject[$i]->ItemDescription[0];
echo '</br>';
$i++;
}
The code below is being used to retrieve the value of the "store" element from the XML file below and insert the values into an array (the storeArray). I do NOT want duplicate values put into the array (IE I don't want Best Buy inserted twice), so I am using the in_array method to prevent duplication.
This code works fine:
$xmlDoc = simplexml_load_file("products.xml");
$storeArray = array();
foreach($xmlDoc->product as $Product) {
echo "Name: " . $Product->name . ", ";
echo "Price: " . $Product->price . ", ";
if( !in_array( (string)$Product->store, $storeArray )) {
$storeArray[] = (string)$Product->store;
}}
foreach ($storeArray as $store) {
echo $store . "<br>";
}
But when I try to put those array values (from the XML store element) into a link (like below), the values are duplicated (IE Best Buy is displayed twice. Any advice?
if( !in_array( (string)$Product->store, $storeArray )) {
$storeArray[] = "<a href='myLink.htm'>" . (string)$Product->store . "</a>";
foreach ($storeArray as $store) {
echo $store . "<br>";
}
Here is the XML file:
<product type="Electronics">
<name> Desktop</name>
<price>499.99</price>
<store>Best Buy</store>
</product>
<product type="Electronics">
<name>Lap top</name>
<price>599.99</price>
<store>Best Buy</store>
</product>
<product type="Hardware">
<name>Hand Saw</name>
<price>99.99</price>
<store>Lowes</store>
</product>
</products>
There is an issue with your in_array check. You are checking if the store is in the array, but actually add the link to the array, therefore in_array will always be false.
Bad check:
// you are checking the existance of $Product->store
if (!in_array((string)$Product->store, $storeArray)) {
// but add something else
$storeArray[] = "<a href='myLink.htm'>" . (string)$Product->store . "</a>";
}
Instead try using the store as an array key:
$store = (string)$Product->store;
if (!array_key_exists($store, $storeArray)) {
$storeArray[$store] = "<a href='myLink.htm'>" . $store . "</a>";
}
Your approach is fine. It won't add the value to $storeArray twice.
I think you have a bug with closing brackets in the second code block you shown.
See this phpfiddle - it works:
http://phpfiddle.org/main/code/1ph-6rs
You can also use array_unique() function to print unique values.
I'm just learning PHP. I have a bunch of scripts I have started and one has me stuck. I'm fetching an xml and printing the results to the page. However I only want the rows where the refTypeID = 10 and also I need to trim the text "DESC:" out of the reason area.
My current code
<?php
// Populate the following with your API Data
$vCode = "XXXXXXX";
$keyID = "XXXXXXX";
// Create the URL to the EVE API
$eveAPI = "http://api.eve-online.com/corp/WalletJournal.xml.aspx?keyID=".$keyID."&vCode=".$vCode."";
// Get the xml data
$xml = simplexml_load_file($eveAPI);
// Loop Through Skills
foreach ($xml->result->rowset->row as $value) {
echo "Skill Number:".$value['refTypeID']." -- Skill Points: ".$value['ownerName1']." -- Level: ".$value['reason']."<br />";
};
?>
What I'm parsing
<eveapi version="2">
<currentTime>2012-11-12 10:36:35</currentTime>
<result>
<rowset name="entries" key="refID" columns="date,refID,refTypeID,ownerName1,ownerID1,ownerName2,ownerID2,argName1,argID1,amount,balance,reason">
<row date="2012-11-12 10:46:49" refID="6570815512" refTypeID="10" ownerName1="Captain Vampire" ownerID1="159434479" ownerName2="The Condemned and Convicted" ownerID2="98032142" argName1="" argID1="0" amount="5000000.00" balance="13072537.98" reason="DESC: something "/>
<row date="2012-11-10 02:27:48" refID="6561124130" refTypeID="85" ownerName1="CONCORD" ownerID1="1000125" ownerName2="Justin Schereau" ownerID2="90541382" argName1="Unertek" argID1="30002413" amount="42300.00" balance="7972463.03" reason="10015:1,10019:1,11899:1,22822:1,"/>
<row date="2012-11-09 23:27:24" refID="6560673105" refTypeID="85" ownerName1="CONCORD" ownerID1="1000125" ownerName2="Blackcamper" ownerID2="754457655" argName1="Illamur" argID1="30002396" amount="25000.00" balance="7930163.03" reason="11898:1,"/>
</rowset>
</result>
<cachedUntil>2012-11-12 11:03:35</cachedUntil>
</eveapi>
Any help would be much appreciated
Thanks
You can use xpath directly as below
$xml = simplexml_load_file($eveAPI);
/* Search for <a><b><c> */
$result = $xml->xpath('//result/rowset/row[#refTypeID=10]');
foreach($result as $value) {
echo $value['reason'] = trim(str_replace('DESC:','',$value['reason']));
echo "Skill Number:".$value['refTypeID']." -- Skill Points: ".$value['ownerName1']." -- Level: ".$value['reason']."<br />";
}
Try
// Loop Through Skills
foreach ($xml->result->rowset->row as $value) {
if($value['refTypeID'] == 10){
echo "Skill Number:".$value['refTypeID']." -- Skill Points: ".$value['ownerName1']." -- Level: ".str_replace('DESC:', '', $value['reason'])."<br />";
}
};
You can use continue to skip rows you don't need :
foreach ($xml->result->rowset->row as $value) {
if ($value['refTypeID'] != "10") {
// skip
continue;
}
//etc ...
}
and use str_replace for remove the DESC: form your string:
$reason = str_replace('DESC: ','',$value['reason']);
NOTE: this also removes the space after DESC:
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.