php xpath get node where attribute equals - php

I have an xml how can I get the node in levelone that has an attribute called myatt whose value is a and then access it's myval.
I tried referencing other posts to make it work but it doesn't seem to work what's wrong with my xpath
$this->myXmlObj->xpath("//levelone[myfield[attributes/myatt='a]]]"));
<myxml>
<levelone>
<myfield myatt="a" myval="aa" />
<myfield myatt="b" myval="bb" />
</levelone>
<leveltwo>
<myfield myatt="c" myval="dd" />
<myfield myatt="c" myval="dd" />
</leveltwo>
</myxml>
edit 1
array
0 =>
object(SimpleXMLElement)[41]
public '#attributes' =>
array
'myval' => string 'a' (length=40)
edit 2
$myVar = $this->myXmlObj->xpath("//levelone/myfield[#myatt='a']");
$myOutput = ((string)$myVar[0]->attributes()->myVal;

Attributes in XPATH are referenced with #attr syntax. So, you could retrieve aa with the following xpath
//levelone/myfield[#myatt='a']/#myval
Which means, grab all myfield elements that have attribute myatt equal to 'a'. Then, from those, select the value of their myval attributes. Note that this could be multiple results.
A handy place to test XPATH expressions is at http://chris.photobooks.com/xml/default.htm.

Related

PHP SimpleXMLElement object get data

I have the following XML:
<Root>
<personalData>
<userName>John Tom</userName>
<email>mail#example.com</email>
</personalData>
<profesionalData>
<job>engineer</job>
<jobId>16957</jobId>
</profesionalData>
</Root>
Doing in my debugger:
$myObject->xpath('//Root/profesionalData')
I have:
: array =
0: object(SimpleXMLElement) =
job: string = engineer
jobId: string = 16957
I cannot get hold of the jobId 16957.
What do I have to do?
$root = simplexml_load_file('file.xml');
$job_ids = $root->xpath('//profesionalData/jobId');
if (!$job_ids) {
die("Job IDs not found");
}
foreach ($job_ids as $id) {
// SimpleXmlElement implements __toString method, so
// you can fetch the vlaue by casting the object to string.
$id = (string)$id;
var_dump($id);
}
Sample Output
string(5) "16957"
Notes
You don't need to specify Root in the XPath expression, if you are going to fetch all profesionalData/jobId tags no matter where they are in the document, just use the double slash (//) expression. This approach may be convenient in cases, when you want to avoid registering the XML namespaces. Otherwise, you can use a strict expression like /Root/profesionalData/jobId (path from the root). By the way, your current expression (//Root/profesionalData/jobId) matches all occurrences of /Root/profesionalData/jobId in the document, e.g. /x/y/z/Root/profesionalData/jobId.
Since SimpleXmlElement::xpath function returns an array on success, or FALSE on failure, you should iterate the value with a loop, if it is a non-empty array.
SimpleXmlElement implements __toString method. The method is called when the object appears in a string context. In particular, you can cast the object to string in order to fetch string content of the node.

Insert string between quotation marks using RegEx

I am allowing the user to create XML elements with blank attribute values such as this:
<crimes id="" total="" />
at the same time I am also retrieving an associative array from a database to be used to fill up the attributes that the XML specifies. The array will look like this:
array(
theft => 123,
burglary => 456
)
After retrieving the array, I want to use that array to populate the attribute values of the XML which have been passed in. So as an example, the first array element combined with the example XML would look like this:
<crimes id="theft" total="123" />
Is there a way to use regular expression in PHP to insert the array values between the quotation marks of the XML attributes?
The only time this will work, is if the XML tags you receive are consistent. They need to be the same every time, otherwise there is no pattern, and regex is used for matching patterns.
If your tags will always have id and total, then I can write a regex for it. But if they sometimes have one or the other, it gets too complicated and you need a parser.
Although (as you've been already told) you should better use SimpleXML or DOM for this, here is a custom RegEx solution you asked for:
$list = array(
"theft" => 123,
"burglary" => 456
);
$template = '<crimes id="" total="" />';
foreach($list as $key => $value)
{
$s = preg_replace('/(id=")(")/', '${1}'.$key.'${2}', $template);
$s = preg_replace('/(total=")(")/', '${1}'.$value.'${2}', $s);
echo htmlentities($s)."<br>";
}
Here is its output:
<crimes id="theft" total="123" />
<crimes id="burglary" total="456" />
If "id" or "total" is missing - it just omits it.

SimpleXML, iteration on all element

I have a stock report file (coming from an outer source, therefore I can't modify in any way) and I would like to iterate over all elements (I have to save them into a MySQL table). As I see the $xml->Stockfile is an array of objects (2 items), so I tried to put it into an array.
For some reason the $myarray contains only the first element after the $myarray = $xml->StockFile assignment.
here is my code:
$xml = simplexml_load_file("../docs/stock.xml");
print_r($xml);
$myarray = $xml->StockFile;
print_r($myarray);
stock.xml:
<NewDataSet>
<StockFile>
<MatrixID>1533</MatrixID>
<Brand>myBrand</Brand>
<ProductCode>001</ProductCode>
<RRP>29.99</RRP>
<Image2Name />
<Image3Name />
</StockFile>
<StockFile>
<MatrixID>1534</MatrixID>
<Brand>myBrand</Brand>
<ProductCode>002</ProductCode>
<RRP>29.99</RRP>
<Image2Name />
<Image3Name />
</StockFile>
</NewDataSet>
Why I'm getting only one item instead of all?
What should I do do retrieve the whole array?
Take care with SimpleXMLElement. It has a lot of magic. Know the magic or get puzzled by print_r or var_dump or similar output. Your example extended:
$myarray = $xml->StockFile;
print_r($myarray); # shows one element
# foreach has both elements:
foreach($myarray as $name => $stockfile)
{
echo $name, ":\n", $stockfile->asXML(), "\n\n";
}
Even though it is the same variable ($myarray) it behaves differently depending on context it is used in. Inside a foreach the SimpleXMLElement (that is the type of that object) will provide an iterator over the child-elements named StockFile as specified here:
$myarray = $xml->StockFile;
However using that variable in some kind of single context, it will for example return the inner string of the first child-element with that name:
echo $myarray, "\n";
(which in your case is just some lines of whitespace).
See Demo: https://eval.in/83787
Running into this "trap" by SimpleXML is actually pretty common. I suggest to understand the basic usage by the example given in the manual:
Basic SimpleXML usage
change the last two lines to
foreach ($xml->StockFile as $nextStockFile) {
print_r ($nextStockFile);
}

PHP DOMDocument / XPath: Get HTML-text and surrounded tags

I am looking for this functionality:
Given is this html-Page:
<body>
<h1>Hello,
<b>world!</b>
</h1>
</body>
I want to get an array that only contains the DISTINCT text elements
(no duplicates) and an array of the tags that surround the text elements:
The result to the above "html" would be an array that looks like this:
array =>
"Hello," surrounded by => "h1" and "body"
"world!" surrounded by => "b", "h1" and "body"
I alreday do this:
$res=$xpath->query("//body//*/text()");
which gives me the distinct text-contents but that omits the html-tags.
When I just do this:
$res=$xpath->query("//body//*");
I get duplicate texts, one for each tag-constellation: e.g.: "world!" would show up 3 times,
one time for "body", one time for "h1" and one time for "b" but I don't seem to be able to
get the information which texts are acutally duplicates. Just checking for duplicate text is
not sufficient, as duplicate texts are sometimes just substrings of former texts or a website
could contain real duplicate text which would then be discarded which is wrong.
How could I solve this issue?
Thank you very much!!
Thomas
You can iterate over the parentNodes of the DOMText nodes:
$dom = new DOMDocument;
$dom->loadHTML($html);
$xpath = new DOMXPath($dom);
$textNodes = array();
foreach($xpath->query('/html/body//text()') as $i => $textNode) {
$textNodes[$i] = array(
'text' => $textNode->nodeValue,
'parents' => array()
);
for (
$currentNode = $textNode->parentNode;
$currentNode->parentNode;
$currentNode = $currentNode->parentNode
) {
$textNodes[$i]['parents'][] = $currentNode->nodeName;
}
}
print_r($textNodes);
demo
Note that loadHTML will add implied elements, e.g. it will add html and head elements which you will have to take into account when using XPath. Also note that any whitespace used for formatting is considered a DOMText so you will likely get more elements than you expect. If you only want to query for non-empty DOMText nodes use
/html/body//text()[normalize-space(.) != ""]
demo
In your sample code, $res=$xpath->query("//body//*/text()") is a DOMNodeList of DOMText nodes. For each DOMText, you can access the containing element via the parentNode property.

XPATH - get single value returned instead of array php

I am using Xpath in PHP - I know that my query will return either 0 or 1 results.
If 1 result is returned I do not want it as an array - which is what is returned right now. I simply want the value without having to access the [0] element of the result and cast to a string.
Is this possible?
If 1 result is returned I dont want it as an array - which is what is returned. I simply want the value without having to access the [0] element of the result and cast to a string.
That is possible with XPath's string function
A node-set is converted to a string by returning the string-value of the node in the node-set that is first in document order. If the node-set is empty, an empty string is returned.
and DOMXPath's evaluate method:
Returns a typed result if possible or a DOMNodeList containing all nodes matching the given XPath expression.
Example:
$dom = new DOMDocument;
$dom->loadXML('<root foo="bar"/>');
$xp = new DOMXPath($dom);
var_dump($xp->evaluate('string(/root/#foo)')); // string(3) "bar"
If there was a built in xpath way of grabbing the first and only the first node value then that would be much more preferable over writing a function to do it
You can use the position function:
The position function returns a number equal to the context position from the expression evaluation context.
Example:
$dom = new DOMDocument;
$dom->loadXML('<root><foo xml:id="f1"/><foo xml:id="f2"/></root>');
$xp = new DOMXPath($dom);
var_dump($xp->evaluate('string(/root/foo[position() = 1]/#xml:id)')); // string(2) "f1"
or the abbreviated syntax
$dom = new DOMDocument;
$dom->loadXML('<root><foo xml:id="f1"/><foo xml:id="f2"/></root>');
$xp = new DOMXPath($dom);
var_dump($xp->evaluate('string(/root/foo[1]/#xml:id)')); // string(2) "f1"
Note that when querying for descendants with // using the position function might yield multiple result due to the way the expression is evaluated.
Using 'evaluate' instead of 'query', you can do things like casting.
DOMXPath::evaluate()
Also, if you're just annoyed with doing stuff a lot of times, just write a function that does it ... that is the whole idea behind functions, right?
probably
if ($array[0]){
$string = $array[0];
}
?
if $array[0] is an array, you can rename string to new_array
if ($array[0]){
$new_array = $array[0];
}
Your question suggests that you are using SimpleXML because you talk about an array. However long-time ago you accepted an answer giving an answer with DOMDocument. In any case other users go here looking for a solution in SimpleXML it works a little differently:
list($first) = $xml->xpath('//element') + array(NULL);
The element in $first if not NULL (for no elements) then still will be of type SimpleXMLElement (either an element node or an attribute node depending on the xpath query), however you can just cast it to string in PHP and done or you just use it in string context, like with echo:
echo $first;
You can write it most simply like this:
$string = #$array[0];
The # operator will suppress errors, making $string null if $array is empty.

Categories