There's the sum-function in xpath:
<list>
<a>1</a>
<a>3</a>
<a>4</a>
</list>
Now with SimpleXml...
var_dump($xml->xpath("sum(/list/a)"));
delivers NULL instead of 8
What's wrong here?
see it not working: https://eval.in/135558
EDIT: I've used this workaround for SimpleXml to avoid iterating:
$sum = $xml->xpath("/list/a");
$sum = array_sum(array_walk("intval", $sum));
If you're using SimpleXML, you're going to have to do it manually. However, you can use DOMDocument to achieve this. Just use evaluate() method to evaluate the XPath expression:
$dom = new DOMDocument;
$dom->loadXML($xml); // $xml is the XML string
$xpath = new DOMXPath($dom);
$sum = (int) $xpath->evaluate('sum(/list/a)'); // => 8
Demo
SimpleXML solution:
$xml = simplexml_load_string($xml);
$sum = 0;
foreach ($xml->a as $node) {
$sum += (int) $node;
}
echo $sum; // => 8
Demo
Use DOMXPath::evaluate() here:
$str = <<<XML
<list>
<a>1</a>
<a>3</a>
<a>4</a>
</list>
XML;
$output = new SimpleXMLElement($str);
$doc = new DOMDocument();
$doc->loadXML($str);
$selector = new DOMXPath($doc);
var_dump($selector->evaluate('sum(//list/a/text())'));
// double(8)
Side-note: It will return a double not an integer. This might be surprising in the first place.
Related
Is the getElementsByTagName function capable of returning the children as a XML blob?
DOMDocument::getElementsByTagName() return a DOMNodeList instance. You can iterate through the DOMNodes contained in it, using DOMDocument::saveXML() to keep concatenating the raw XML to a string.
<?php
$a = '<root>
<prueba>
<testtag value="1"/>
</prueba>
<notprueba>
<testtag value="1"/>
</notprueba>
<prueba>
<testtag value="2"/>
</prueba>
</root>';
$doc = new DOMDocument;
$doc->loadXML($a);
$x = new DOMXPath($doc);
$nodes = $doc->getElementsByTagName("prueba");
$xml = "";
foreach ($nodes as $node) {
$xml .= $doc->saveXML($node);
}
var_dump(htmlentities($xml));
Demo
I have xml file
<root>
<param1_2014>1</param1_2014>
<param2_2014>2</param2_2014>
<param2_2015>3</param2_2015>
<param2_2015>4</param2_2015>
<param3_2015>5</param3_2015>
</root>
How I can get all nodes, which contains "2015" substring in tags? I can't search it in manual.
Thank you!
The xpath function contains is what you're after, just check the element names against your string.
Example:
$xml = <<<'XML'
<root>
<param1_2014>1</param1_2014>
<param2_2014>2</param2_2014>
<param2_2015>3</param2_2015>
<param2_2015>4</param2_2015>
<param3_2015>5</param3_2015>
</root>
XML;
$dom = new DOMDocument();
$dom->loadXML($xml);
$xpath = new DOMXPath($dom);
foreach ($xpath->query("//*[contains(local-name(),'2015')]") as $node) {
echo $dom->saveXML($node), "\n";
}
Output:
<param2_2015>3</param2_2015>
<param2_2015>4</param2_2015>
<param3_2015>5</param3_2015>
If you want to limit it specifically to tags ending in "2015" it's a little more work. Sadly xpath 2 isn't supported or the xpath function ends-with would be perfect here, so you'll have to make do with substring.
Example:
$dom = new DOMDocument();
$dom->loadXML($xml);
$xpath = new DOMXPath($dom);
$search = "2015";
$query = "//*[
substring(
local-name(),
string-length(local-name()) - string-length('$search') + 1
) = '$search'
]";
foreach ($xpath->query($query) as $node) {
echo $dom->saveXML($node), "\n";
}
Output:
<param2_2015>3</param2_2015>
<param2_2015>4</param2_2015>
<param3_2015>5</param3_2015>
Hi I am trying to clean up xml file out of positions I dont need. Here is my code so far:
<?php
$doc = new DOMDocument;
$doc->load('merg.xml');
$xpath = new DOMXPath($doc);
$products = $xpath->query('//offer/products/*');
printf('There is %d products<br /><br />', $products->length);
function findStopPointByName($xml, $query) {
$upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZĄŻŚĆŹĆÓŁ";
$lower = "abcdefghijklmnopqrstuvwxyzążśćźńół";
$arg_query = "translate('$query', '$upper', '$lower')";
return $xml->query("//offer/products/product/description/name[contains(text(),$arg_query)]");
}
foreach(findStopPointByName($xpath,'Skór') as $node)
{
$node->parentNode->removeChild($node);
}
$doc->save('merg_fixed.xml');
?>
Structure of XML:
<offer>
<products>
<product>
<description>
<name>Name of the product</name>
...
</name>
...
</description>
</product>
</products>
</offer>
I am trying to remove all PRODUCT where its NAME contains 'Skór' in any case (Skór, skór, SKÓR - is enough). Funcion findStopPointByName returns DOMNodeList of correct length, but nothing is removed from actual XML file, please help.
First, you can directly find node product with the condition
Second, to make search case insensitive, you can translate node text in any case but should use pattern in the same case. As the result, your code may be so
function findStopPointByName($xml, $query) {
$upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZĄŻŚĆŹĆÓŁ";
$lower = "abcdefghijklmnopqrstuvwxyzążśćźńół";
$arg_query = "translate(text(), '$upper', '$lower')";
$q = "//product[description/name[contains($arg_query, '$query')]]" ."\n";
return $xml->query($q);
}
$doc = new DOMDocument;
$doc->load('merg.xml');
$xpath = new DOMXPath($doc);
foreach(findStopPointByName($xpath,'skór') as $node)
$node->parentNode->removeChild($node);
echo $doc->saveXML();
Demo on eval.in
I have this XML file and I need to replace te value of qteStock using the php DOM,but I still can't get the concept.Can anyone help me please?
<?xml version="1.0" standalone="no"?>
<?xml-stylesheet type="text/xsl" href="Marque.xsl" ?>
<Marques xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="Marques.xsd">
<Marque>
<codeMarque>00003</codeMarque>
<nomMarque>Diesel</nomMarque>
<paysOrigine>USA</paysOrigine>
<qteStock>50</qteStock>
<qteLimite>5</qteLimite>
</Marque>
</Marques>
This is the php code I've been trying to manipulate:
<?php
$marque=$_POST['nomMarque'];
$qte=$_POST['qte'];
$xmlstring = 'entities/Marques.xml';
$dom = new DOMDocument;
$dom->load($xmlstring);
$xpath = new DOMXPath($dom);
$query = "//Marque[nomMarque='".$marque."']/qteStock";
$qteStock = $xpath->query($query);
$query = "//Marque[nomMarque='".$marque."']/qteLimite";
$qteLimite = $xpath->query($query);
$nouvelleQuantite = $qteStock->item(0)->nodeValue-$qte ;
$newQuantity = $dom->createTextNode($nouvelleQuantite);
$return = ($qteStock->replaceChild($newQuantity,$qteStock);
$dom->save('entities/Marques.xml');
?>
You can set the nodeValue property of the element node - integers do not need escaping in XML. If you need to write text that could contain &, set the nodeValue to an empty string (deletes all child nodes) and insert a new text node.
$changeValue = 5;
$dom = new DOMDocument();
$dom->loadXml($xml);
$xpath = new DOMXpath($dom);
$nodes = $xpath->evaluate('/Marques/Marque[1]/qteStock');
// node found?
if ($nodes->length > 0) {
$stock = $nodes->item(0);
$newValue = $stock->nodeValue - $changeValue;
// just set the content (an int does not need any escaping)
$stock->nodeValue = (int)$newValue;
}
echo $dom->saveXml();
Demo: https://eval.in/149098
I have two DOMNodeLists
$textNodes = $xpath->query('//text()');
and
$titleNodes = $xpath->query('//#title');
How can I merge those to DOMNodeLists so I can use it with a foreach loop?
XPath supports the | operator for combining two node sets:
$textNodes = $xpath->query('//text() | //#title');
Imagine this simple example :
$xml = '<?xml version="1.0"?>
<person>
<name>joe</name>
<age>99</age>
</person>';
$doc = new DOMDocument();
$doc->loadXml($xml);
$selector = new DOMXPath($doc);
$nodes = $selector->query('//name | //age');
foreach($nodes as $node) {
echo $node->nodeName, PHP_EOL;
}