Is it possible to use xpath/xquery to query for a specific xml node, and then import/add a child node to it?
Example (code taken from http://codepad.org/gJ1Y2LjM , that was presented in similar question, but not the same):
1.
I want to add an array
$book = array('isbn'=>123456789099, 'title' => 'Harry Potter 3', 'author' => 'J K. Rowling', 'edition' => '2007');
2.
To an existing XML.
$doc = new DOMDocument();
$doc->loadXML('<?xml version="1.0"?>
<books>
<book>
<isbn>123456789098</isbn>
<title>Harry Potter</title>
<author>J K. Rowling</author>
<edition>2005</edition>
</book>
<book>
<placeItHere></placeItHere>
<isbn>1</isbn>
<title>stuffs</title>
<author>DA</author>
<edition>2014</edition>
</book>
</books>');
3.
To do that it is used the following fragment.
$fragment = $doc->createDocumentFragment();
$fragment->appendXML(" <book>
<isbn>{$book['isbn']}</isbn>
<title>{$book['title']}</title>
<author>{$book['author']}</author>
<edition>{$book['edition']}</edition>
</book>
");
4.
But instead of appending it to the root node, has almost every example i found on internet:
$doc->documentElement->appendChild($fragment);
5.
I want to append it (p.ex) to the node found in /books/book/placeItHere and not using getElementbyId or tagName, but a xpath/xquery. I tryed
$xp = new domxpath($doc);
$parent = $xp->query("books/book/placeItHere");
to reach the node, but never managed to use it as the parent.
Question: How to use that location to appendChild $fragment? Is it possible?
**YOUR BEAUTIFUL MAGIC**
In the end I will just save it.
echo $doc->saveXML();
Thank you for any help you can give to me.
A couple problems:
Your xpath expression should be /books/book/placeItHere (with the leading /).
DOMXPath::query() returns a DOMNodeList rather than a DOMNode, so you'll need to grab your item() from it.
I rarely recommend using document fragments as playing fast and loose with raw XML regularly leads to problems. For example if your book title included an ampersand appendXML() would crap out with a parser error.
Instead I suggest createElement() and createTextNode(), which will handle transforming things like & into & automatically.
Example:
$xml = <<<'XML'
<?xml version="1.0"?>
<books>
<book>
<isbn>123456789098</isbn>
<title>Harry Potter</title>
<author>J K. Rowling</author>
<edition>2005</edition>
</book>
<book>
<placeItHere></placeItHere>
<isbn>1</isbn>
<title>stuffs</title>
<author>DA</author>
<edition>2014</edition>
</book>
</books>
XML;
$book = [
'isbn' => 123456789099,
'title' => 'Harry Potter 3',
'author' => 'J K. Rowling',
'edition' => '2007'
];
$dom = new DOMDocument();
$dom->formatOutput = true;
$dom->preserveWhiteSpace = false;
$dom->loadXML($xml);
$xpath = new DOMXPath($dom);
$placeItHere = $xpath->query('/books/book/placeItHere')->item(0);
$newBook = $placeItHere->appendChild($dom->createElement('book'));
foreach ($book as $part => $value) {
$element = $newBook->appendChild($dom->createElement($part));
$element->appendChild($dom->createTextNode($value));
}
echo $dom->saveXML();
Output:
<?xml version="1.0"?>
<books>
<book>
<isbn>123456789098</isbn>
<title>Harry Potter</title>
<author>J K. Rowling</author>
<edition>2005</edition>
</book>
<book>
<placeItHere>
<book>
<isbn>123456789099</isbn>
<title>Harry Potter 3</title>
<author>J K. Rowling</author>
<edition>2007</edition>
</book>
</placeItHere>
<isbn>1</isbn>
<title>stuffs</title>
<author>DA</author>
<edition>2014</edition>
</book>
</books>
Related
i want to store xml Dom object in array and retrieve them back from array using array index
for example
arrayoftags[index] = $this->dom->createElement("plist");
index++;
// and retrive back
$dict = $this->dom->createElement("dict");
arrayoftags[index]->appendChild($dict);
/* some thing like that
<plist>
<dict>
</dict>
</plist>
*/
what i am doing wrong please guide me in right direction and thanks in advance
I am not sure why you want to use an array. So the answer is a little more generic. But yes you can store XML nodes in variables including arrays.
$dom = new DOMDocument();
$created = [];
$created['plist'] = $dom->appendChild($dom->createElement('plist'));
$created['dict'] = $dom->createElement('dict');
$created['plist']->appendChild($created['dict']);
echo $dom->saveXml();
Output:
<?xml version="1.0"?>
<plist><dict/></plist>
appendChild() returns the node it appended. So it is possible to use it directly on a createElement() (or other create* call) and assign the result to a variable. So if the parent node is just stored in a variable the example will be cleaner.
$dom = new DOMDocument();
$plist = $dom->appendChild($dom->createElement('plist'));
$plist->appendChild($dom->createElement('dict'));
echo $dom->saveXml();
Now the DOM already is a data structure, you can use Xpath to fetch some nodes from it, why store the nodes in a second structure (the array)?
$dom = new DOMDocument();
$plist = $dom->appendChild($dom->createElement('plist'));
$plist->appendChild($dom->createElement('dict'));
$xpath = new DOMXpath($dom);
foreach ($xpath->evaluate('//*') as $node) {
var_dump($node->nodeName);
}
Output:
string(5) "plist"
string(4) "dict"
Please refer this code, I think this will help you.
<!-- suppose this is book.xml file -->
<?xml version="1.0"?>
<catalog>
<book id="bk101">
<author>Gambardella, Matthew</author>
<title>XML Developer's Guide</title>
<genre>Computer</genre>
<price>44.95</price>
<publish_date>2000-10-01</publish_date>
<description>An in-depth look at creating applications
with XML.</description>
</book>
<book id="bk102">
<author>Ralls, Kim</author>
<title>Midnight Rain</title>
<genre>Fantasy</genre>
<price>5.95</price>
<publish_date>2000-12-16</publish_date>
<description>A former architect battles corporate zombies,
an evil sorceress, and her own childhood to become queen
of the world.</description>
</book>
<catalog>
//PHP file
$dom = new DOMDocument();
$dom->loadXml('book.xml');
$xpath = new DOMXpath($dom);
$result = [];
foreach ($xpath->evaluate('//book') as $book) {
$result[] = [
'id' => $xpath->evaluate('string(#id)', $book),
'Author' => $xpath->evaluate('string(author)', $book),
'Title' => $xpath->evaluate('string(title)', $book),
'Genre' => $xpath->evaluate('string(genre)', $book),
'Price' => $xpath->evaluate('number(price)', $book),
'Publish Date' => $xpath->evaluate('string(publish_date)', $book),
'Description' => $xpath->evaluate('string(description)', $book)
];
}
var_dump($result);
I am having some issues using xmldiff package. I'm using xmldiff package 0.9.2; PHP 5.4.17; Apache 2.2.25.
For example I have two xml files: "from.xml" & "to.xml".
File "from.xml" contains:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<rott>
<NDC>321</NDC>
<NDC>123</NDC>
</rott>
</root>
File "to.xml" contains:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<rott>
<NDC>123</NDC>
<NDC>321</NDC>
</rott>
</root>
I'm using code:
$zxo = new XMLDiff\File;
$dir1 = dirname(__FILE__) . "/upload/from.xml";
$dir2 = dirname(__FILE__) . "/upload/to.xml";
$diff = $zxo->diff($dir1, $dir2);
$file = 'differences.xml';
file_put_contents($file, $diff);
I get result in "differences.xml" file:
<?xml version="1.0"?>
<dm:diff xmlns:dm="http://www.locus.cz/diffmark">
<root>
<rott>
<dm:delete>
<NDC/>
</dm:delete>
<dm:copy count="1"/>
<dm:insert>
<NDC>321</NDC>
</dm:insert>
</rott>
</root>
</dm:diff>
Could you please comment from where this:
<dm:delete>
<NDC/>
</dm:delete>
comes?
Also please kindly inform me if there is a method which differs two xml files without matter of xml nodes order?
What you see is the diff in the libdiffmark format. Right from that page:
<copy/> is used in places where the input subtrees are the same
The documents from your snippet have partially identical sub trees. Effectively the instructions libdiffmark will execute are
delete the whole subtree
copy 1 nodes, that means the node is the same in the both documents, so don't touch it
insert 1 new node
The order of the nodes matters. Please think about how a diff would look like, if the node order were ignored. Say you had 42 nodes and some of those were the same, how it would apply the copy instruction with the count? Much easier for a diff to use the exact node order of two documents. One interesting reading I've found here about why node order can be important.
Thanks.
If the document structure is known, I think you can simply sort the necessary parts. Here's a useful acticle about it. Based on it, I've poked on some examples and could sort a document by node values (just for example), please look here
document library.xml
<?xml version="1.0"?>
<library>
<book id="1003">
<title>Jquery MVC</title>
<author>Me</author>
<price>500</price>
</book>
<book id="1001">
<title>Php</title>
<author>Me</author>
<price>600</price>
</book>
<book id="1002">
<title>Where to use IFrame</title>
<author>Me</author>
<price>300</price>
</book>
<book id="1002">
<title>American dream</title>
<author>Hello</author>
<price>300</price>
</book>
</library>
The PHP code, sorting by the <title>
<?php
$dom = new DOMDocument();
$dom->load('library.xml');
$xp = new DOMXPath($dom);
$booklist = $xp->query('/library/book');
$books = iterator_to_array($booklist);
function sort_by_title_node($a, $b)
{
$x = $a->getElementsByTagName('title')->item(0);
$y = $b->getElementsByTagName('title')->item(0);
return strcmp($x->nodeValue, $y->nodeValue) > 0;
}
usort($books, 'sort_by_title_node');
$newdom = new DOMDocument("1.0");
$newdom->formatOutput = true;
$root = $newdom->createElement("library");
$newdom->appendChild($root);
foreach ($books as $b) {
$node = $newdom->importNode($b,true);
$root->appendChild($newdom->importNode($b,true));
}
echo $newdom->saveXML();
And here's the result:
<?xml version="1.0"?>
<library>
<book id="1002">
<title>American dream</title>
<author>Hello</author>
<price>300</price>
</book>
<book id="1003">
<title>Jquery MVC</title>
<author>Me</author>
<price>500</price>
</book>
<book id="1001">
<title>Php</title>
<author>Me</author>
<price>600</price>
</book>
<book id="1002">
<title>Where to use IFrame</title>
<author>Me</author>
<price>300</price>
</book>
</library>
This way you can sort the parts of the document before comparing. After that you can even use the DOM comparison directly. Even you could reorder the nodes, it were a similar approach.
I'm not sure it'll be very useful in the case if you have a variable node number. Say if the <NDC> tag were repeated some random number of times and it's values were completely different.
And after all, I still think the simplest way were to ask your supplicant to create some more predictable document structure :)
Thanks
Anatol
I need to retrieve the value of the value of "TotalBooks" from an xml file that is structured like the example below.
I can get the equivalent of the "MatchesFound" value by doing a count of "book" and I can successfully get the information for each book.
However, I cannot get the actual value shown in the xml file for "MatchesFound", "TotalBooks", and "Page".
I'm using php with simplexml_load_file. Any help I can get is appreciated. Thanks.
<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
<MatchesFound>2</MatchesFound>
<TotalBooks>563</TotalBooks>
<Page>1</Page>
<book>
<title>Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price currency="USD">30.00</price>
</book>
<book>
<title>Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price currency="USD">29.99</price>
</book>
</bookstore>
$xml = new SimpleXMLElement($xmlString);
echo $xml->TotalBooks;
Using xpath :
<?php
$string = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
<MatchesFound>2</MatchesFound>
<TotalBooks>563</TotalBooks>
<Page>1</Page>
<book>
<title>Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price currency="USD">30.00</price>
</book>
<book>
<title>Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price currency="USD">29.99</price>
</book>
</bookstore>
XML;
$xml = new SimpleXMLElement($string);
$result = $xml->xpath('//TotalBooks');
while(list( , $node) = each($result)) {
echo "$node\n";
}
?>
See http://php.net/manual/en/simplexmlelement.xpath.php
I have an xml and I would be cloning father and leave under the cloned node.
More giving this error.
I wonder how
Fatal error: Call to a member function insertBefore() on a non-object in C:\xampp\htdocs\xml2\cloneNew.php on line 32
$xmla = <<<XML
<?xml version="1.0" ?>
<library>
<book isbn="1001" pubdate="1943-01-01">
<title><![CDATA[The Fountainhead]]></title>
<author>Ayn Rand</author>
<price>300</price>
</book>
<book isbn="1002" pubdate="1954-01-01">
<title><![CDATA[The Lord of the Rings]]></title>
<author>J.R.R.Tolkein</author>
<price>500</price>
</book>
<book isbn="1006" pubdate="1982-01-01">
<title><![CDATA[The Dark - Tower San]]></title>
<author>Stephen King</author>
<price>200</price>
</book>
</library>
XML;
$xmlb = <<<XML
<?xml version="1.0" ?>
<library>
<book isbn="1004" pubdate="1943-01-01">
<title><![CDATA[The Fountainhead]]></title>
<author>Ayn Rand</author>
<price>300</price>
</book>
</library>
XML;
$dom_01 = new DOMDocument();
$dom_01->loadXML($xmla);
$library_01 = $dom_01->documentElement;
$dom_02 = new DOMDocument();
$dom_02->loadXML($xmlb);
$library_02 = $dom_02->documentElement;
$xpath = new DOMXPath($dom_02);
$result = $xpath->query('/library/book[translate(#pubdate,"-","")>translate("1980-01-01","-","")]');
$library_02 = $library_02->cloneNode(true);
$newElement = $library_01->appendChild($result->item(0));
$library_01->parentNode->insertBefore($newElement, $result->item(0));
header("Content-type: text/xml");
echo $dom->saveXML();
Result:
$xmla = <<<XML
<?xml version="1.0" ?>
<library>
<book isbn="1001" pubdate="1943-01-01">
<title><![CDATA[The Fountainhead]]></title>
<author>Ayn Rand</author>
<price>300</price>
</book>
<book isbn="1002" pubdate="1954-01-01">
<title><![CDATA[The Lord of the Rings]]></title>
<author>J.R.R.Tolkein</author>
<price>500</price>
</book>
<book isbn="1004" pubdate="1943-01-01">
<title><![CDATA[The Fountainhead]]></title>
<author>Ayn Rand</author>
<price>300</price>
</book>
<book isbn="1006" pubdate="1982-01-01">
<title><![CDATA[The Dark - Tower San]]></title>
<author>Stephen King</author>
<price>200</price>
</book>
</library>
XML;
You are trying to get the parentNode of a documentElement no such node exists.
Also if you want to place a node from one document into another use DOMDocument.importNode instead of cloneNode.
Example of the xml:
<books>
<book>
<title>Hip Hop Hippo</title>
<released>31-12-9999</released>
</book>
<book>
<title>Bee In A Jar</title>
<released>01-01-0001</released>
</book>
</books>
I want to make a function that return the released date of a book title.
Ex: I want to get released date of the 'Hip Hop Hippo' book.
I know I can use simplexml and write ->book[0]->released. But that's only works when I have a static XML and I know where the ->book[$i]->title that match 'Hip Hop Hippo'. But not in dynamic case. I can't predict every changes, since it came from an API provider. It can be book[1], book[2], and so on.
What should I write in my function?
Check out the xpath functions http://php.net/manual/en/simplexmlelement.xpath.php
You will then be able to write a query like: /books/book[title="Hip Hop Hippo"]
$string = <<<XML
<books>
<book>
<title>Hip Hop Hippo</title>
<released>31-12-9999</released>
</book>
<book>
<title>Hip Hop Hippo</title>
<released>31-12-2000</released>
</book>
<book>
<title>Bee In A Jar</title>
<released>01-01-0001</released>
</book>
</books>
XML;
$xml = new SimpleXMLElement($string);
$result = $xml->xpath('/books/book[title="Hip Hop Hippo"]');
foreach($result as $key=>$node)
{
echo '<li>';
echo $node->title . ' / ' . $node->released;
echo '</li>';
}