For XPath escaping use the following method (of course it could be more efficient).
<?php
public function xpathescape($string)
{
$result = 'concat(';
for($i=0, $j=strlen($string); $i<$j; ++$i)
{
if($i > 0)
$result .= ",";
if($string[$i] == '\'')
$result .= "\"".$string[$i]."\"";
else
$result .= '\''.$string[$i].'\'';
}
$result .= ')';
return $result;
}
?>
Use it this way:
<php
$xpath->query('//example[sub='.xpathescape($acomplexstring).']');
?>
DOMXPath::query
(PHP 5)
DOMXPath::query — Evaluates the given XPath expression
Description
Executes the given XPath expression .
Parameters
- expression
-
The XPath expression to execute.
- contextnode
-
The optional contextnode can be specified for doing relative XPath queries. By default, the queries are relative to the root element.
Return Values
Returns a DOMNodeList containing all nodes matching the given XPath expression . Any expression which do not return nodes will return an empty DOMNodeList.
Examples
Example #1 Getting all the english books
<?php
$doc = new DOMDocument;
// We don't want to bother with white spaces
$doc->preserveWhiteSpace = false;
$doc->Load('book.xml');
$xpath = new DOMXPath($doc);
// We starts from the root element
$query = '//book/chapter/para/informaltable/tgroup/tbody/row/entry[. = "en"]';
$entries = $xpath->query($query);
foreach ($entries as $entry) {
echo "Found {$entry->previousSibling->previousSibling->nodeValue}," .
" by {$entry->previousSibling->nodeValue}\n";
}
?>
The above example will output:
Found The Grapes of Wrath, by John Steinbeck Found The Pearl, by John Steinbeck
We can also use the contextnode parameter to shorten our expression:
<?php
$doc = new DOMDocument;
$doc->preserveWhiteSpace = false;
$doc->load('book.xml');
$xpath = new DOMXPath($doc);
$tbody = $doc->getElementsByTagName('tbody')->item(0);
// our query is relative to the tbody node
$query = 'row/entry[. = "en"]';
$entries = $xpath->query($query, $tbody);
foreach ($entries as $entry) {
echo "Found {$entry->previousSibling->previousSibling->nodeValue}," .
" by {$entry->previousSibling->nodeValue}\n";
}
?>
See Also
- DOMXPath::evaluate - Evaluates the given XPath expression and returns a typed result if possible.
DOMXPath::query
Niklas
27-Aug-2008 11:36
27-Aug-2008 11:36
adam dot prall at thinkingman dot com
20-Jul-2008 06:01
20-Jul-2008 06:01
If you're wondering, like I was, why your XPath queries are not returning any of the new DOMElements you create in your (X)HTML documents, and only the ones originally loaded in with (for example) loadXML(), this is why; if you're doing things right, you have registered the nameSpace 'html' after creating your DOMXPath object thus:
<?php
class XPathQueryLength {
private $nameSpace = '';
function __construct(DOMDocument $doc) {
$this->xpath = new DOMXPath($this->doc);
$this->xpath->registerNamespace(
'html','http://www.w3.org/1999/xhtml' );
}
function queryLength($query) {
return $this->xpath->query($query)->length;
}
}
?>
...but don't forget that when adding new elements to the above DOMDocument $doc, to use createElementNS() instead of createElement(), otherwise you'll have this problem:
<?php
//$doc is a previously loaded XHTML document containing a normal html, head and body structure
//$body is the first selected tag using $doc->getElementsByTagName('body');
$pTag = $doc->createElement('p','This is a new paragraph!');
$body->appendChild($pTag);
$xPath = new XPathQueryLength($doc);
print $xPath->queryLength('//html:p');
output: 0
print $xPath->queryLength('//p');
output: 1
?>
So do this instead:
<?php
//$doc is a previously loaded XHTML document containing a normal html, head and body structure
//$body is the first selected tag using $doc->getElementsByTagName('body');
$pTag = $doc->createElementNS('http://www.w3.org/1999/xhtml','p','This is a new paragraph!');
$body->appendChild($pTag);
$xPath = new XPathQueryLength($doc);
print $xPath->queryLength('//html:p');
output: 2
print $xPath->queryLength('//p');
output: 0
?>
The resulting XHTML file from both example scripts looks much like this:
<html>
<head></head>
<body>
<p>This is a hardcoded paragraph.</p>
<p>This is a new paragraph!</p>
</body>
</html>
...so you would think that a paragraph is a paragraph is a paragraph, since you never see the prefix, as in "<html:p>This is a new paragraph!</html:p>".
This may seem glaringly obvious, but I was writing a class that converts CSS queries into XPath queries, and the fact that a namespace had been registered was rather buried in the code.
We love the DOM, the DOM is good to us.
Nibinaear
03-Mar-2008 03:15
03-Mar-2008 03:15
I've searched the entire web looking for a way to update / modify/ change/ alter the elements of an xml file and found NOTHING!
So here it is, the defninitive way to "Change XML elements with PHP" rather than adding / appending new ones. This uses XPATH:
<?php
// Create a DOMDocument instance
$xml = new DOMDocument;
// Ignore whitespace between nodes (default: true)
$xml->preserveWhiteSpace = false;
$file='about.xml';
// Load the XML data source
$xml->Load($file);
$xpath = new DOMXPath($xml);
$query='/regions/branch';
$entries = $xpath->query($query);
foreach ($entries as $entry)
{
$entry->firstChild->nodeValue="like this!";
echo $entry->firstChild->nodeValue;
}
$xml->save($file);
?>
ondrej dot fischer at 4internet dot cz
25-Oct-2007 03:46
25-Oct-2007 03:46
Unfortunately PHP's DOM extension doesn't support use of:
<?xml-stylesheet type="text/xsl" ... ?>
processing instruction.
Here is an example, how to implement it using XPath query and extending DOMDocument by a method output().
<?php
// This simple function adds missing direct usage of anonymous instances
// in PHP5's reference model
function a($var) {
return $var;
}
// Extended DOMDocument class
class MyDOMDocument extends DOMDocument
{
public function output()
{
$stylesheets = array();
$PIs = a(new DOMXPath($this))
->query('/processing-instruction("xml-stylesheet")');
foreach($PIs as $PI)
{
// This might be implemented cleaner by regular parsing
// of DOMProcessingInstruction::data property
if(ereg('type *= *"text/xsl" +href *= *"([^"]+)"', $PI->data, $mem))
{
// Here should be verified, that XSL file exists.
a($stylesheets[] = new DOMDocument())->load($mem[1]);
}
}
if($stylesheets)
{
$processor = new XSLTProcessor();
foreach($stylesheets as $stylesheet)
$processor->importStylesheet($stylesheet);
return $processor->transformToDoc($this);
}
// If no stylesheet instructions present, return self directly
else return $this;
}
}
?>
Usage:
<?php
$document = new MyDOMDocument();
$document->load('my.xml');
echo $document->output()->saveXML();
?>
With following file my.xml:
<?xml version="1.0" ?>
<?xml-stylesheet type="text/xsl" href="my.xsl" ?>
<my-root />
and existing file my.xsl that code will transform the xml file using my.xsl and output the result.
jbarnett at flowershopnetwork dot com
11-Sep-2007 10:46
11-Sep-2007 10:46
The order of nodes in the return value is not guaranteed.
When my code was on an old server, the returned DOMNodeList was in document order. On the new server, the returned DOMNodeList is in a consistent order, but it is not in document order.
PHP passes this function call off to the xmlXPathEvalExpression() function in libxml. That function in libxml only accepts two arguments - the same two this PHP function accepts. There must have been a change in the libxml version from the old server to the new server, and that libxml behaves differently.
This would be okay if PHP had a way to compare nodes so I can resort the nodes manually, but there is not.
So, there is no guaranteed way to get an ordered list of nodes like DOM 3 XPath provides.
Hayley Watson
13-Aug-2007 02:43
13-Aug-2007 02:43
Note that if your DOMDocument was loaded from HTML, where element and attribute names are case-insensitive, the DOM parser converts them all to lower-case, so your XPath queries will have to as well; '//A/@HREF' won't find anything even if the original HTML contained "<A HREF='example.com'>".
nicolas_rainardNOSPAM at yahoo dot fr
10-Jul-2007 11:04
10-Jul-2007 11:04
Please note that what clochix says is valid for *any* document which has a default namespace (as it is the case for XHTML).
This document :
<?xml version="1.0" encoding="UTF-8" ?>
<root xmlns="http://www.exemple.org/namespace">
<element id="1">
...
</element>
<element id="2">
...
</element>
</element>
must be accessed this way :
$document = new DOMDocument();
$document->load('document.xml');
$xpath = new DOMXPath($document);
$xpath->registerNameSpace('fakeprefix', 'http://www.exemple.org/namespace');
$elements = $xpath->query('//fakeprefix:element');
Of course, there is no prefix in the original document, but the DOMXPath class *needs* one, whatever it is, if you use a default namespace. It *doesn't work* if you specify an empty prefix like this :
$xpath->registerNameSpace('', 'http://www.exemple.org/namespace');
Hope this help to spare some time...
clochix at clochix dot net
01-Mar-2007 01:05
01-Mar-2007 01:05
If you want to perform queries on XHTML documents, you must fix a default namespace:
$doc = new DOMDocument;
$doc->preserveWhiteSpace = true;
$doc->resolveExternals = true; // for character entities
$doc->load("http://www.w3.org/");
$xpath = new DOMXPath($doc);
// won't work
$entries = $xpath->query("//div");
// you should use :
$xpath->registerNamespace("html", "http://www.w3.org/1999/xhtml");
$entries = $xpath->query("//html:div");
ckrack at i-z dot de
23-Dec-2005 04:27
23-Dec-2005 04:27
tried finding a node by it's text content?
// Get all elements that equal the string "test"
$query = "//*[.='test']";
jakob dot voss at nichtich dot de
14-Nov-2005 09:33
14-Nov-2005 09:33
You can transform the result nodes into new DOMDocument objects this way:
<?php
$result = $xpath->query($query);
$resultNode = $result->item(0);
$newDom = new DOMDocument;
$newDom->appendChild($newDom->importNode($resultNode,1));
print "<pre>" . htmlspecialchars($newDom->saveXML()) . "</pre>";
?>
Eric Hanson
13-Jul-2005 02:40
13-Jul-2005 02:40
Two great XPath references follow.
XPath in Five Paragraphs (finally!):
http://www.rpbourret.com/xml/XPathIn5.htm
The w3c spec actually has a bunch of helpful examples:
http://www.w3.org/TR/xpath#location-paths
