PHP XML: How To Get The NodeValue by Its Siblings? - php

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

Related

How to use xquery to find node and addChild to it?

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>

xmldiff issues on php

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

PHP SimpleXMLElement, get element and its children as XML

Suppose I have the following XML:
<Book>
<bookname>thename</bookname>
<chapters>
<chapter>
<name>chapter1</name>
</chapter>
<chapter>
<name>chapter2</name>
</chapter>
</chapters>
</Book>
How can I get an XML as follows:
<chapters>
<chapter>
<name>chapter1</name>
</chapter>
<chapter>
<name>chapter2</name>
</chapter>
</chapters>
One way is to manually remove unwanted elements e.g
$resultXML = new SimpleXMLElement($inputXML);
unset($resultXML->bookname);
$resultXML = $resultXML->asXml();
echo format_result($resultXML,$format);
But if I have a large XML with many unwanted notes, this is tedious. Any idea who to extract the required element using its name?
The sub-nodes of your XML are themselves SimpleXMLElements – so you can just call the axXml method on them as well.
Since you only have one chapters node in your XML, simply
$resultXML->chapters->asXml()
will do.
Here we go:
<?php
$inputXML ="
<Book>
<bookname>thename</bookname>
<chapters>
<chapter>
<name>chapter1</name>
</chapter>
<chapter>
<name>chapter2</name>
</chapter>
</chapters>
</Book>
";
/* Load from file:
$parseXML = simplexml_load_file( $fileURL );
*/
$parseXML = simplexml_load_string( $inputXML );
$chapters = $parseXML->chapters->asXML();
echo $chapters;
?>
Note: Your xml must be well-formated, currently your <Book> has no closing tag.

php and xpath - loop through child elements of particular element

I would like to loop through each book_list in the following xml file and for each book_list loop through each book for that book_list.
<inventory>
<book_list>
<book>
<author>Rowling</author>
<title>Harry Potter</title>
</book>
<book>
<author>Blyton</author>
<title>Famous 5</title>
</book>
</book_list>
<book_list>
<book>
<author>Bloggs</author>
<title>Learning XML</title>
</book>
<book>
<author>Jones</author>
<title>Beginning PHP</title>
</book>
</book_list>
</inventory>
How can I, for each book_list, loop through each book, using xpath in a php simplexml script? Here is my code,
$booklistpath = $xml->xpath('//booklist');
foreach ($booklistpath as $booklist) {
$bookpath = $xml->xpath('//book');
foreach ($bookpath as $book) {
...
}
}
The first loop is fine, it goes through each book_list - but the nested loop, which is meant to go through each book for that particular book_list goes through each book in the entire document. I have also tried :-
'.//book' and
'descendant::book'
That's the right result since you're using the second xpath call on the original $xml which is the SimpleXMLElement for your whole XML document.
To get the books for each booklist just iterate them as follow:
$booklists = $sxe->xpath('//book_list');
foreach ($booklists as $booklist) {
foreach ($booklist->book as $book) {
echo $book->asXML();
}
}

cloneNode + appendChild + insertBefore dom xml 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.

Categories