translating sql-like syntax to xpath in PHP - php

I am writing a class to read content of an xml with mysql-like syntax (of course it not completly the same syntax and I ignore many things, just want to have some basics).
First of all, statements look like:
"SELECT name passwd FROM table WHERE id = 1 and description = 'desc'"
Some basic rules:
There has to be a whitespace between everything
not more than two comparisons after the "WHERE"
I haven't done anything but the method for the select statement.
I explode the statement, sort it into an array and then try to translate it to DOMXpath
It works if there's no WHERE. But I'm struggling with the where-clauses (here what I have done so far:)
statement: "SELECT name pw FROM user WHERE id = '1' and description = 'test or test2'"
the array looks like:
array(4) {
["type"]=>
string(6) "SELECT"
["searchFields"]=>
array(2) {
["searchField0"]=>
string(4) "name"
["searchField1"]=>
string(2) "pw"
}
["tableName"]=>
string(4) "user"
["comparer"]=>
array(2) {
["where0"]=>
array(3) {
["field"]=>
string(2) "id"
["operator"]=>
string(1) "="
["value"]=>
string(3) "'1'"
}
["where1"]=>
array(4) {
["splitTag"]=>
string(3) "and"
["field"]=>
string(11) "description"
["operator"]=>
string(1) "="
["value"]=>
string(15) "'test or test2'"
}
}
}
How I'm trying to convert the statement to Xpath with the following code:
for ($i = 0; $i < count($arrQuery['searchFields']); $i++) {
$conditions = "";
foreach ($arrQuery['comparer'] as $value) {
switch (count($arrQuery['comparer'])) {
case 1:
$conditions .= '//parent::content[#name="'.$value['field'].'" and text()='.$value['value'].']';
break;
case 2:
if (!isset($value['splitTag']) || $value['splitTag'] == "and") {
$conditions .= '//parent::content[#name="'.$value['field'].'" and text()='.$value['value'].']';
break;
} else {
//$conditions .= 'content[#'.$value['field'].' and text()='.$value['value'].']//parent::*';
}
break;
}
}
$xpathquery = '//datarange[#name="'.$arrQuery['tableName'].'"]'.$conditions.'//content[#name="'.$arrQuery['searchFields']['searchField'.$i].'"]';
$nodeList = $this->xpath->query($xpathquery);
foreach ($nodeList as $node) {
$arrContent['searchField'.$i][] = $node->nodeValue;
}
}
My first Point is: if the condition of the if-clause in case 2 is confirmed, the created xpath isn't working (might be problem with parent or my logic)
My second Point is: I still have no idea how to handle the case that the condition doesn't match and $value['splitTag'] is "or". If anyone has a hint how to solve that, I would be very thankfull.
/EDIT: Ok, thanks for the tip, here's an example of my xml:
<database>
<datarange id="user">
<dataset id="0">
<content name="id">
1
</content>
<content name="description">
test or test2
</content>
<content name="name">
Name
</content>
<content name="pw">
Passwort
</content>
</dataset>
<dataset id="1">
<content name="name">
Name2
</content>
<content name="pw">
Passwort2
</content>
</dataset>
</datarange>
<datarange id="config">
<dataset id="0">
<content name="type">
command
</content>
<content name="name">
lab
</content>
<content name="type">
request
</content>
<content name="target">
target_name
</content>
<content name="desc">
Address Book
</content>
</dataset>
</datarange>
</database>

Given your input document and the query: SELECT name pw FROM user WHERE id = '1' and description = 'test or test2 you will have to build yourself the following XPath to get to the dataset node that has the values you need:
//datarange[#id = 'user']/dataset
[normalize-space(content[#name = 'id']) = '1']
[normalize-space(content[#name = 'description']) = 'test or test2']
This one will give you the dataset node that you can then run the following on:
normalize-space(content[#name = 'name'])
to get the name and:
normalize-space(content[#name = 'pw'])
Here's a simple XSLT to test it:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:apply-templates select="//datarange[#id = 'user']/dataset
[normalize-space(content[#name = 'id']) = '1']
[normalize-space(content[#name = 'description']) = 'test or test2']"/>
</xsl:template>
<xsl:template match="dataset">
name: <xsl:value-of select="normalize-space(content[#name = 'name'])"/>
pwd: <xsl:value-of select="normalize-space(content[#name = 'pw'])"/>
</xsl:template>
</xsl:stylesheet>
When applied to your input document it will produce:
name: Name
pwd: Passwort
You can now factor it into your query SQL-like-to-XPath engine.
One more thing though. If you can upgrade to XPath 2.0 you may want to try the conditional and quantified expressions to make your XPath more query-like. And who knows, maybe you won't need a SQL-like syntax to begin with. Plus there's XQuery.

Related

get xml element using php

I have an XML page called (www.example.com/name.xml)
it contains the bellow elements :
<xml>
<names>
<name id='6' >Name 1 </name>
<name id='7'>Name 2</name>
<name id='8'>Name 3</name>
</names>
</xml>
and here is my PHP script :
<?php
$id='6';
$url = "www.example.come/name.xml";
$xml = simplexml_load_file($url);
$position ="$xml->name id='$id' ";
?>
So how can I get it ?
You can simply use xpath for this:
$id='6';
$xml = simplexml_load_file($url);
$position = (string)$xml->xpath("//name[#id='$id']")[0];
echo $position;
Output:
Name 1
This uses xpath to get the text from the <name> where id is 6. Then it casts the SimpleXMLElement to (string), thus providing the output.
Based on your provided data, you could do it like this:
$id='6';
foreach($xml->names->name as $name) {
if ((string)$name->attributes()->id === $id) {
// here $name will be the element for which the attribute id='6'
var_dump($name);
}
}
Will result in:
object(SimpleXMLElement)#3 (2) {
["#attributes"]=>
array(1) {
["id"]=>
string(1) "6"
}
[0]=>
string(7) "Name 1 "
}
$name is of type SimpleXMLElement and the value you are looking for is in the attributes.

Extract Data off multiple XML row

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;

Xpath for a REST response with Keys

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)

Using PHP to manage GMail Mail Filter XML Files

Since GMail made it possible to import and export the mail filters, I'd like to manage the XML files that are exported from GMail using a PHP script, as there are some known issues with the number of characters in the search filters.
I've found the simplexml_load_file function in PHP, and have performed var_dump() against the performed export, but I now don't seem to be able to access the apps namespace within the generated XML file.
Following various pages within the PHP manual, I created this very simple script to read the XML out so I can start to process out the filters I've already created. Unfortunately, it seems to be missing the key parts!
<pre><?php
if(file_exists("mailfilters.xml")) {
$xml = simplexml_load_file("mailfilters.xml");
$namespaces = $xml->getNamespaces(true);
foreach ($namespaces as $prefix => $ns) {
$xml->registerXPathNamespace($prefix, $ns);
}
var_dump($xml);
} else {
die("Failed to open filter file");
}
?></pre>
This returns this data (extract)
["entry"]=>
array(268) {
[0]=>
object(SimpleXMLElement)#3 (5) {
["category"]=>
object(SimpleXMLElement)#271 (1) {
["#attributes"]=>
array(1) {
["term"]=>
string(6) "filter"
}
}
["title"]=>
string(11) "Mail Filter"
["id"]=>
string(45) "tag:mail.google.com,2008:filter:1284991916868"
["updated"]=>
string(20) "2010-10-28T11:59:31Z"
["content"]=>
object(SimpleXMLElement)#272 (0) {
}
}
[1]=>
object(SimpleXMLElement)#4 (5) {
["category"]=>
object(SimpleXMLElement)#272 (1) {
["#attributes"]=>
array(1) {
["term"]=>
string(6) "filter"
}
}
["title"]=>
string(11) "Mail Filter"
["id"]=>
string(45) "tag:mail.google.com,2008:filter:1284991925003"
["updated"]=>
string(20) "2010-10-28T11:59:31Z"
["content"]=>
object(SimpleXMLElement)#271 (0) {
}
}
Here's an extract from the XML file I have downloaded today, used to create the above output:
<?xml version='1.0' encoding='UTF-8'?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:apps='http://schemas.google.com/apps/2006'>
<title>Mail Filters</title>
<id>tag:mail.google.com,2008:filters:1284991916868,...,1287734777820</id>
<updated>2010-10-28T11:59:31Z</updated>
<author>
<name>My Name</name>
<email>my#email.addr.es</email>
</author>
<entry>
<category term='filter'></category>
<title>Mail Filter</title>
<id>tag:mail.google.com,2008:filter:1284991916868</id>
<updated>2010-10-28T11:59:31Z</updated>
<content></content>
<apps:property name='from' value='an#email.addr.es'/>
<apps:property name='shouldArchive' value='true'/>
<apps:property name='shouldTrash' value='true'/>
</entry>
<entry>
<category term='filter'></category>
<title>Mail Filter</title>
<id>tag:mail.google.com,2008:filter:1284993579743</id>
<updated>2010-10-28T11:59:31Z</updated>
<content></content>
<apps:property name='subject' value='Some Relevant Subject'/>
<apps:property name='label' value='MyCoolLabel'/>
<apps:property name='shouldArchive' value='true'/>
</entry>
Other "apps:property" verbs include:
<apps:property name='hasTheWord' value=''/>
<apps:property name='shouldAlwaysMarkAsImportant' value=''/>
<apps:property name='doesNotHaveTheWord' value=''/>
I recently had a similar "problem". I was trying to get the email signature settings from Google Apps. This is what the XML Response from Google looks like:
<?xml version='1.0' encoding='UTF-8'?>
<entry xmlns='http://www.w3.org/2005/Atom' xmlns:apps='http://schemas.google.com/apps/2006'>
<id>https://apps-apis.google.com/a/feeds/emailsettings/2.0/domain.com/user/signature</id>
<updated>2012-08-31T15:22:03.067Z</updated>
<link rel='self' type='application/atom+xml' href='https://apps-apis.google.com/a/feeds/emailsettings/2.0/domain.com/user/signature?xoauth_requestor_id=user#domain.com'/>
<link rel='edit' type='application/atom+xml' href='https://apps-apis.google.com/a/feeds/emailsettings/2.0/domain.com/user/signature?xoauth_requestor_id=user#domain.com'/>
<apps:property name='signature' value='Here goes the email signature...'/>
</entry>
I was not able the get the attributes out of apps:property using the SimpleXMLElement. This is how i finally solved it:
// create SimpleXMLElement with XML input as string
$xml = new SimpleXMLElement($feed);
// get namespace from XML
$namespaces = $xml->getNamespaces(true);
// get children (using namespace) and attributes
$appsProperty = $xml->children($namespaces['apps'])->attributes();
// attributes are now stored in array $appsProperty and can be accessed
echo "Name: ".$appsProperty['name'];
echo "Signature: ".$appsProperty['value'];
Jon,
Full marks for linking this from Facebook, I'd never have seen it otherwise and I was doing EXACTLY this yesterday, for a google service!! I think using XPath is a bit of a red herring, let me show you how to access the elements and hopefully it will give you enough information for what you want to do.
You're on the right tracks with $xml->getNamespaces() - but don't iterate there, you need to use the namespace you want on the parent element you want. Firstly, you need to work only on the entry elements, since these are the majority, you might as well loop and check if you have the right one:
foreach($xml as $tag) {
if($tag->getName() == "entry") {
// awesome, do Cool Stuff (TM) here
}
}
Now, you have the element in the $tag variable, and you want the apps: namespaced child elements. So do this:
$tag->getChildren($namespaces['apps']);
When you iterate over that collection, you will see the info you wanted.
HTH,
Lorna

Need to extract values from a XML file

I have a XML file, and the layout is such
<author>
<name></name>
<iso></iso>
<price></price>
</author>
I know how it loops. I want to know how I can extract the value of
<name>
Thanks
Jean
[edit]
my apologies, if in the
<author>
<name>
<first_name></first_name>
<last_name></lastname>
</name>
</author>
I want to extract first_name
Use simplexml or similar:
<?php
$string = <<<XML
<author>
<name>
<first_name>John</first_name>
<last_name>Smith</last_name>
</name>
</author>
XML;
$xml = simplexml_load_string($string);
var_dump($xml);
?>
Will output something like this:
object(SimpleXMLElement)#1 (1) {
["name"]=>
object(SimpleXMLElement)#2 (2) {
["first_name"]=>
string(4) "John"
["last_name"]=>
string(5) "Smith"
}
}
And you can access the name like this:
echo $xml->name->first_name; // outputs 'John'
echo $xml->name->last_name; // outputs 'Smith'
Use SimpleXML.
Edit: I see. That wasn't showing up before. Try this:
$xml = simple_xml_load_string([your XML string])
echo $xml->name;
Does that work?

Categories