I am working in XPATH using PHP. I have done it by hard coded. but I made a lot of googling for making xpath queries dynamic. Is there any solution for making xpath queries dynamic?
I have the following xml code:
<information>
<field name="secondtitle">ABCt</field>
<field name="author">XYZ</field>
<field name="ISBN">9780712617611</field>
<field name="publisher">abc</field>
</information>
the other file's data is in the following:
<information>
<field name="deliveryCosts">1.95</field>
<field name="deliveryTime">3 - 5 Werkdagen</field>
<field name="EAN">9789021142523</field>
<field name="ISBN">9789021142539</field>
<field name="subcategories">abc</field>
<field name="auteur">IJK</field>
<field name="type">xyz</field>
</information>
only the attributes are differ now m trying to access them in a single php file. but all of my queries are hard coded but i want to access them dynamically.
following is my php code that i made hard coded:
$auth = $xml->xpath("/products/product[$i]/additional/field[#name='auteur']");
$type = $xml->xpath("/products/product[$i]/additional/field[#name='type']");
foreach($auth as $au)
{
foreach($type as $ty)
{
echo $au = mysql_real_escape_string($au);
echo $ty = mysql_real_escape_string($ty);
}}
This code is in working with the second code of xml that I have paste above.
You can have a "skelleton" expression and make an "executable instance". I don't know PHP, but in C# this can be done like this:
string XpathExpression
= string.Format("/products/product[$i]/additional/field[#name='{0}']",
myName)
XPATH provides some operator hopefully you are using them. As | operator is user for taking union of two queries and also there is an operator for taking intersection of two or more queries.
Here in the following I'm suggesting you use:
$auth = $xml->xpath("/products/product[$i]/additional/field[#name='auteur' or #name='author']");
foreach($auth as $au)
{
foreach($type as $ty)
{
echo $au = mysql_real_escape_string($au);
}}
It's working here in case of the author, and for the problem of the type of the product follow my following code.
$type = $xml->xpath("/products/product[$i]/additional/field[#name='type']");
if(!empty($type ))
{
echo 'the node is available';
}
else
{
echo 'the node is not available';
}
Related
Ok so I have some XML data.
$mydata = <<<XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE fmresultset PUBLIC "-//FMI//DTD fmresultset//EN" "https://HIDDEN:443/fmi/xml/fmresultset.dtd">
<fmresultset xmlns="http://www.filemaker.com/xml/fmresultset" version="1.0">
<resultset count="1" fetch-size="1">
<record mod-id="27" record-id="754">
<field name="a_Constant_c">
<data>1</data>
</field>
<field name="a_Sch_ID_pk">
<data>100060</data>
</field>
<field name="a_SchoolHead_pk">
<data>100060_1</data>
</field>
<field name="b___Data_____________">
<data/>
</field>
<field name="b_1Name_School_Code_t">
<data>PJA</data>
</field>
<field name="b_1Name_School_t">
<data>Palmetto</data>
</field>
<field name="b_1Name_SchoolHead_t">
<data>John Doe</data>
</field>
<field name="b_Ad_Address1_t">
<data/>
</field>
<field name="b_Ad_Address2_t">
<data>123 Main St.</data>
</record>
</resultset>
</fmresultset>
XML;
Now what I want to do is basically be able to read the value of the data from a specific field and assign it to a variable.
So far I have something like this...
$xml = simplexml_load_string($mydata);
Now I want to be able to assign let's say the data in the field name b_1Name_School_Code_t (which is PJA)
So I think it should be something like this
$school = $xml->resultset->record->field->data;
echo "School Name: ".$school;
Then I would like to see on the screen
School Name: PJA
So what I am missing to be able to make this happen?
You are only getting to the first field element in your example, which is why you get 1. Instead, loop through all the field elements, and stop when you get to the one you need:
$xml = simplexml_load_string($mydata);
$fields = $xml->resultset->record->field;
foreach ($fields as $field) {
if ((string) $field->attributes()->name === "b_1Name_School_Code_t") {
echo "School name: ".$field->data; // School name: PJA
break;
}
}
Demo
I use SimpleXMLElement::attributes() to get the name attribute of the element (note the cast to string, otherwise you get an SimpleXMLElement)
However, it would make more sense to use XPath to go directly to the element you're after:
$xml = simplexml_load_string($mydata);
$xml->registerXPathNamespace("fmresultset", "http://www.filemaker.com/xml/fmresultset");
$node = $xml->xpath("//fmresultset:resultset/fmresultset:record/fmresultset:field[#name='b_1Name_School_Code_t']");
var_dump($node[0]->data); // PJA
Demo
Notice the namespace registration and the accessing of the first element, since xpath() returns an array of SimpleXMLElements
I have a xml document with multidimensional structure:
<?xml version="1.0" encoding="UTF-8"?>
<main>
<products>
<product id="87" state="active">
<options>
<option id="99" state="active">
<item id="33" value="somevalue" />
<item id="35" value="somevalue2" />
</option>
<option id="12" state="deleted">
<item id="56" value="somevalue" />
<item id="34" value="somevalue2" />
</option>
</options>
<reports>
<report type="json">
<field id="123" state="active" />
<field id="234" state="deleted" />
<field id="238" state="active" />
<field id="568" state="deleted" />
</report>
</reports>
</product>
</products>
</main>
In the PHP backend I've written methods to detect items with "deleted" status and remove them.
Here is PHP part:
public function loadAndModify() {
$xml = simplexml_load_file($this->request->file('import_xml'));
$this->processXml($xml);
}
/**
* #param $element
*
* #return bool
*/
private function shouldRemove($element): bool
{
return ($element['state'] == SomeClass::STATE_DELETED);
}
/**
* #param $xml
*
* #return void
*/
private function processXml(&$xml): void
{
if ($xml->children()->count() > 0) {
foreach ($xml->children() as $child) {
if ($this->shouldRemove($child)) {
// this code works as expected with or without xdebug
//$node = dom_import_simplexml($child);
//$node->parentNode->removeChild($node);
// this code will work only with xdebug when breakpoint is set
unset($child[0]);
continue;
// end
} else {
$this->processXml($child);
}
}
}
}
I solve my problem by converting simpleXMLElement to DOMElement.
However it seems that PHP has some bug when I use unset with xdebug. When I add breakpoint to line with unset and go to next step in the debugger and then resume application - there is no problem. But when breakpoint is active and I just clicked resume application it cause error:
Uncaught ErrorException: Trying to get property of non-object in
\project\vendor\symfony\var-dumper\Cloner\AbstractCloner.php
If someone else had this error please explain why this is happened in this case.
Thanks.
As discussed in the comments under this previous answer, the problem you are encountering is that you are manipulating an object (in this case, the return value of $xml->children()) which you are iterating over (with a foreach loop).
Internally, the SimpleXMLElement object has a list of child items it is going to present, in turn, to the iterator code in foreach. When you delete the current child item, you necessarily change the shape of that internal list, so "next item" is not well defined. Deleting other items in the list can also have odd behaviour - for instance, deleting item 1 while inspecting item 2 may cause the iterator to "skip ahead" since item 4 has now moved into the place where item 3 was.
As hakre suggests in the comments linked above, the most robust solution is to copy the original list of items into an array, which can be achieved using iterator_to_array. Passing false as the second argument throws away the keys, which is important with SimpleXML because because it uses the tag name as the key, and there can be only one value for each key in the array.
foreach ( iterator_to_array($xml->children(), false) as $child) {
// Carry on as you were
}
The only thing to be aware of with this is that iterator_to_array will go through the whole list before returning, so if you have a large list and want to break out of the loop early, or stream output, this may be problematic.
I feel like this should be easy, but for the love of it I cannot get it right. I've read and tested for hours, trying different approaches I have found on the interwebs, both with simpleXML and PHP XML DOM, but none of them works just the way I need.
I got somewhat close, after tweaking and combining a few answers (that I cannot even find the way back to cause I've been through so many of them here), but not entirely correct.
My code:
$xml = simplexml_load_file(all_objects.xml");
$image = $xml->xpath('/objects/object/images/image');
foreach($image as $node) {
$id = (string) $node->field[0];
$url = (string) $node->field[4];
echo $id . " : " . $url . "<br>";
}
But instead of using the key/number they appear [4] in, I would like to target using the field name attribute, e.g. "id" and "image_url", as they are not always in this order.
Something like this:
$id = (string) $node->field["id"];
But it does not work, I tried with field->attribtues()->id too, but no luck.
The xml:
<objects>
<object>
<field name="id">1055</field>
<field name="title">example object</field>
<images>
<image number="1">
<field name="id">55</field>
<field name="version">1</field>
<field name="image_url_small">http://example.com/image-small.jpg</field>
<field name="image_url_medium">http://example.com/image-medium.jpg</field>
<field name="image_url_big">http://example.com/image-big.jpg</field>
<field name="image_url_original">http://example.com/image.jpg</field>
</image>
<image number="2">
<field name="id">56</field>
<field name="version">2</field>
<field name="image_url">http://example.com/image2.jpg</field>
</image>
<image number="3">...</image>
...
<image number="25">...</image>
</images>
</object>
<object>...</object>
<object>...</object>
</objects>
I would really appreciate any help/guidance! I am losing my mind over this.
You can use XPath to get child element with certain attribute value :
foreach($image as $node) {
$id = (string) $node->xpath('field[#name="id"]')[0];
$url = (string) $node->xpath('field[#name="image_url_big"]')[0];
echo $id . " : " . $url . "<br>";
}
eval.in demo
output :
55 : http://example.com/image-big.jpg
56 :
:
:
I've been searching for a while on this and haven't had much luck. I've found plenty of resources showing how to echo data from dynamic XML, but I'm a PHP novice, and nothing I've written seems to grab and print exactly what I want, though from everything I've heard, it should be relatively easy. The source XML (located at 192.168.0.15:8080/requests/status.xml) is as follows:
<root>
<fullscreen>0</fullscreen>
<volume>97</volume>
<repeat>false</repeat>
<version>2.0.5 Twoflower</version>
<random>true</random>
<audiodelay>0</audiodelay>
<apiversion>3</apiversion>
<videoeffects>
<hue>0</hue>
<saturation>1</saturation>
<contrast>1</contrast>
<brightness>1</brightness>
<gamma>1</gamma>
</videoeffects>
<state>playing</state>
<loop>true</loop>
<time>37</time>
<position>0.22050105035305</position>
<rate>1</rate>
<length>168</length>
<subtitledelay>0</subtitledelay>
<equalizer/>
<information>
<category name="meta">
<info name="description">
000003EC 00000253 00000D98 000007C0 00009C57 00004E37 000068EB 00003DC5 00015F90 00011187
</info>
<info name="date">2003</info>
<info name="artwork_url"> file://brentonshp04/music%24/Music/Hackett%2C%20Steve/Guitar%20Noir%20%26%20There%20Are%20Many%20Sides%20to%20the%20Night%20Disc%202/Folder.jpg
</info>
<info name="artist">Steve Hackett</info>
<info name="publisher">Recall</info>
<info name="album">Guitar Noir & There Are Many Sides to the Night Disc 2
</info>
<info name="track_number">5</info>
<info name="title">Beja Flor [Live]</info>
<info name="genre">Rock</info>
<info name="filename">Beja Flor [Live]</info>
</category>
<category name="Stream 0">
<info name="Bitrate">128 kb/s</info>
<info name="Type">Audio</info>
<info name="Channels">Stereo</info>
<info name="Sample rate">44100 Hz</info>
<info name="Codec">MPEG Audio layer 1/2/3 (mpga)</info>
</category>
</information>
<stats>
<lostabuffers>0</lostabuffers>
<readpackets>568</readpackets>
<lostpictures>0</lostpictures>
<demuxreadbytes>580544</demuxreadbytes>
<demuxbitrate>0.015997290611267</demuxbitrate>
<playedabuffers>0</playedabuffers>
<demuxcorrupted>0</demuxcorrupted>
<sendbitrate>0</sendbitrate>
<sentbytes>0</sentbytes>
<displayedpictures>0</displayedpictures>
<demuxreadpackets>0</demuxreadpackets>
<sentpackets>0</sentpackets>
<inputbitrate>0.016695899888873</inputbitrate>
<demuxdiscontinuity>0</demuxdiscontinuity>
<averagedemuxbitrate>0</averagedemuxbitrate>
<decodedvideo>0</decodedvideo>
<averageinputbitrate>0</averageinputbitrate>
<readbytes>581844</readbytes>
<decodedaudio>0</decodedaudio>
</stats>
</root>
What I'm trying to write is a simple PHP script that echoes the artist's name (In this example Steve Hackett). Actually I'd like it to echo the artist, song and album, but I'm confident that if I'm shown how to retrieve one, I can figure out the rest on my own.
What little of my script which actually seems to work goes as follows. I've tried more than what's below, but I left out the bits that I know for a fact aren't working.
<?PHP
$file = file_get_contents('http://192.168.0.15:8080/requests/status.xml');
$sxe = new SimpleXMLElement($file);
foreach($sxe->...
echo "Artist: "...
?>
I think I need to use foreach and echo, but I can't figure out how to do it in a way that will print what's between those info brackets.
I'm sorry if I've left anything out. I'm not only new to PHP, but I'm new to StackOverflow too. I've referenced this site in other projects, and it's always been incredibly helpful, so thanks in advance for your patience and help!
////////Finished Working Script - Thanks to Stefano and all who helped!
<?PHP
$file = file_get_contents('http://192.168.0.15:8080/requests/status.xml');
$sxe = new SimpleXMLElement($file);
$artist_xpath = $sxe->xpath('//info[#name="artist"]');
$album_xpath = $sxe->xpath('//info[#name="album"]');
$title_xpath = $sxe->xpath('//info[#name="title"]');
$artist = (string) $artist_xpath[0];
$album = (string) $album_xpath[0];
$title = (string) $title_xpath[0];
echo "<B>Artist: </B>".$artist."</br>";
echo "<B>Title: </B>".$title."</br>";
echo "<B>Album: </B>".$album."</br>";
?>
Instead of using a for loop, you can obtain the same result with XPath:
// Extraction splitted across two lines for clarity
$artist_xpath = $sxe->xpath('//info[#name="artist"]');
$artist = (string) $artist_xpath[0];
echo $artist;
You will have to adjust the xpath expression (i.e. change #name=... appropriately), but you get the idea. Also notice that [0] is necessary because xpath will return an array of matches (and you only need the first) and the cast (string) is used to extract text contained in the node.
Besides, your XML is invalid and will be rejected by the parser because of the literal & appearing in the <info name="album"> tag.
If you look at your code again, you are missing a function that turns the first result of the xpath expression into a string of a SimpleXMLElement (casting).
One way to write this once is to extend from SimpleXMLElement:
class BetterXMLElement extends SimpleXMLElement
{
public function xpathString($expression) {
list($result) = $this->xpath($expression);
return (string) $result;
}
}
You then create the more specific SimpleXMLElement like you did use the less specific before:
$file = file_get_contents('http://192.168.0.15:8080/requests/status.xml');
$sxe = new BetterXMLElement($file);
And then you benefit in your following code:
$artist = $sxe->xpathString('//info[#name="artist"]');
$album = $sxe->xpathString('//info[#name="album"]');
$title = $sxe->xpathString('//info[#name="title"]');
echo "<B>Artist: </B>".$artist."</br>";
echo "<B>Title: </B>".$title."</br>";
echo "<B>Album: </B>".$album."</br>";
This spares you some repeated code. This means as well less places you can make an error in :)
Sure you can further on optimize this by allowing to pass an array of multiple xpath queries and returning all values named then. But that is something you need to write your own according to your specific needs. So use what you learn in programming to make programming more easy :)
If you want some more suggestions, here is another, very detailed example using DOMDocument, the sister-library of SimpleXML. It is quite advanced but might give you some good inspiration, I think something similar is possible with SimpleXML as well and this is probably what you're looking for in the end:
Extracting data from HTML using PHP and xPath
I'm being given XML in the following format, and am parsing it with PHP's SimpleXML.
<?xml version="1.0" encoding="UTF-8"?>
<ManageMyBooking>
<BookingInfo>
<PartyDetails>
<Passenger>
<PaxNo>1</PaxNo>
<Title>Mrs</Title>
<Surname>Murphy</Surname>
</Passenger>
<Passenger>
<PaxNo>2</PaxNo>
<Title>Mr</Title>
<Surname>Murphy</Surname>
</Passenger>
<Passenger>
<PaxNo>3</PaxNo>
<Title>Miss</Title>
<Surname>Murphy</Surname>
</Passenger>
</PartyDetails>
<Accommodation>
<Units>
<Unit>
<UnitNo>1</UnitNo>
<UnitDesc>...</UnitDesc>
<PaxAssociated>1|2</PaxAssociated>
</Unit>
<Unit>
<UnitNo>2</UnitNo>
<UnitDesc>...</UnitDesc>
<PaxAssociated>3</PaxAssociated>
</Unit>
</Units>
</Accommodation>
</BookingInfo>
</ManageMyBooking>
I'm looping through the Units (Rooms) thus:
// $Accommodation is a SimpleXML Object defined earlier, and able to provide relevant info
<? foreach ($Accommodation->Units as $Units) {
foreach ($Units->Unit as $Unit) {
// (room/unit details echoed out here)
foreach ($Unit->xpath('//Passenger[contains(PaxAssociated,./PaxNo)]') as $RoomPax) { ?>
<?= $RoomPax->Title $RoomPax->Surname" ?><br />
<?= "$RoomPax->Title $RoomPax->Surname" ?><br />
<? }
}
} ?>
in an attempt to show the names off the Passengers (Pax) in each room.
But this xpath finds no-one, and the following gets everyone.
//Passenger[contains(PaxNo,./PaxAssociated)]
What's especially frustrating is that I've successfully used XPath elsewhere in the same PHP for a very similar purpose, with no problems.
Any help/advice/suggestions will be much appreciated.
Edit:
for completeness, and to answer a question from multiple people:
The following works elsewhere in the code, (though not 100% correctly given the possible matching on '22' vs '2'.
//Flight[contains(PaxAssociated,./PaxNo)]
This:
//Passenger[contains(PaxNo,./PaxAssociated)]
is: Find any <Passenger> with a child <PaxNo> who's value contains the value of the child <PaxAssociated>. It would only work with such a data structure (which you clearly don't have):
<ManageMyBooking>
<BookingInfo>
<PartyDetails>
<Passenger>
<PaxNo>1|2</PaxNo> <!-- note the exchanged value! -->
<PaxAssociated>1</PaxAssociated>
</Passenger>
</PartyDetails>
</BookingInfo>
</ManageMyBooking>
So this is wrong on multiple accounts. What you mean is probably a dynamic XPath expression, like this:
foreach ($Units->Unit as $Unit) {
$XPath = "//Passenger[contains('". $Unit->PaxAssociated . "', PaxNo)]";
foreach ($Unit->xpath($XPath) as $RoomPax) {
// ...
}
}
This works on first glance, but it is not fail-safe, because "22" contains "2" as well. So doing a contains() alone won't get you anywhere. Correct would be:
$XPath = "//Passenger[contains('|". $Unit->PaxAssociated ."|', concat('|', PaxNo, '|'))]";
This way you check "|22|" against "|2|", which would return false.