Merge two DOMNodeLists in xpath - php

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;
}

Related

PHP find the node by value and remove it XML

I have all properties listed in XML file with this structure
<property>
<details>
<object>25.5 m2 Flat in New York</object>
</details>
</property>
<property>
<details>
<object>95.6 m2 House in New Jersey</object>
</details>
</property>
Now I want to use PHP to find the node with a specific <object> value and to remove the parent node (<property>). How can I do it?
I tried by doing the code below but I cannot manage to work.
$doc = new DOMDocument;
$doc->load('../openimmo/xml-import1.xml');
$thedocument = $doc->documentElement;
$list = $thedocument->getElementsByTagName('property');
$nodeToRemove = null;
foreach ($list as $domElement) {
$attrValue = $domElement->getElementsByTagName('object');
foreach ($attrValue as $item) {
if ($item->nodeValue == $_GET['delete']) {
$nodeToRemove = $domElement;
}
}
}
if ($nodeToRemove != null)
$thedocument->removeChild($nodeToRemove);
echo $doc->saveXML();
You can use Xpath expressions to fetch nodes. This allows you to use conditions.
$document = new DOMDocument;
//$document->load('../openimmo/xml-import1.xml');
$document->loadXML($xml);
$xpath = new DOMXpath($document);
$objectText = '25.5 m2 Flat in New York';
$properties = $xpath->evaluate('//property[details/object = "'.$objectText.'"]');
foreach ($properties as $property) {
// remove the node (PHP 8)
$property->remove();
}
echo $document->saveXML();
The other difference is that the result of DOMXpath::evaluate() is not live. Unlike the result from DOMNode::getElementsByTagName() it does not change if the DOM changes.
PHP 8 adds DOM Living Standard methods. In PHP 7 you would have to use $property->parentNode->removeChild($property).

Xpath nodeValue/textContent unable to see <BR> tag

HTML is as follows:
ABC<BR>DEF
However, both nodeValue and textContent attributes show "ABCDEF" as the value.
Any way to show or parse the <BR>?
Maybe this'll help you: DOMNode::C14N
It'll return the HTML of the node.
<?php
$a = 'ABC<BR>DEF';
$doc = new DOMDocument();
#$doc->loadHTML($a);
$finder = new DomXPath($doc);
$nodes = $finder->query("//a");
foreach ($nodes as $node) {
var_dump($node->c14n());
}
Demo
I know you have already solved your problem, but I wanted to add a more direct way of solving it...
$a = 'ABC<BR>DEF';
$doc = new DOMDocument();
$doc->loadHTML($a);
$xp = new DomXPath($doc);
$nodes = $xp->query("//a/node()");
$text = '';
foreach ($nodes as $node) {
$text .= $doc->saveHTML($node);
}
echo $text;
Outputs...
ABC<br>DEF

Get xml nodes using wildcard (php)

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>

Undefined property: DOMNodeList::$textContent when to parse web

In my code1,it can parse the web to get the td content for me.
code1
<?php
$url='http://www.sse.com.cn/marketservices/tradingservice/shhksc/eligible/';
$html = file_get_contents($url);
$dom = new DOMDocument();
#$dom->loadHTML($html);
$xpath = new DOMXPath($dom);
$nodes = $xpath->query('//div[#id="hk_view"]//table[#class="tablestyle"]//tr//td[position()<4 and position()>1]');
foreach($nodes as $node){
echo $node->textContent.'</br>';}
?>
Now i change other format to parse the web.
code2
<?php
$url='http://www.sse.com.cn/marketservices/tradingservice/shhksc/eligible/';
$html = file_get_contents($url);
$dom = new DOMDocument();
#$dom->loadHTML($html);
$xpath = new DOMXPath($dom);
$nodes = $xpath->query('//div[#id="hk_view"]//table[#class="tablestyle"]//tr');
foreach($nodes as $node){
$sub =$xpath->query('//td[position()<4 and position()>1]' ,$node);
echo $sub->textContent.'</br>';}
?>
Is the xpath expression wrong ?
$sub =$xpath->query('//td[position()<4 and position()>1]' ,$node);
It is the result of my code1.
According to har07's answer ,code2 was rewrite as code3,there is another problem remain,please test it with my code3 .
code3
<?php
$url='http://www.sse.com.cn/marketservices/tradingservice/shhksc/eligible/';
$html = file_get_contents($url);
$dom = new DOMDocument();
#$dom->loadHTML($html);
$xpath = new DOMXPath($dom);
$nodes = $xpath->query('//div[#id="hk_view"]//table[#class="tablestyle"]//tr');
foreach($nodes as $node){
$sub =$xpath->query('//td[position()<4 and position()>1]' ,$node);
foreach($sub as $s){
echo $s->textContent.'</br>';
}
}
?>
The problem isn't in the xpath expression you use. As the error message suggests, query() returns DOMNodeList which doesn't have textContent property. It is DOMNode that have textContent.
You need to iterate through the DOMNodeList to access it's individual DOMNode member, and access textContent property on each DOMNode :
foreach($nodes as $node){
$sub = $xpath->query('.//td[position()<4 and position()>1]' ,$node);
foreach($sub as $s){
echo $s->textContent;
}
}

how to use the sum-function in xpath?

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.

Categories