XML count elements, If id exists increment by one - php

What i am trying to do is count the elements under the root element. Then check if one id on that same level has the id value. When this occurs it needs to increment by one.
The code
public function _generate_id()
{
$id = 0;
$xpath = new DOMXPath($this->_dom);
do{
$id++;
} while($xpath->query("/*/*[#id=$id]"));
return $id;
}
example xml
<?xml version="1.0"?>
<catalog>
<book id="0">
<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="1">
<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>

You can use the following xpath query to get the maximum value of the id attribute:
$result = $xpath->query('/*/*[not(../*/#id > #id)]/#id');
In your function you can return this value incremented by 1:
return intval($result->item(0)->nodeValue) + 1;
Update: You can do the increment operation using XPath as well. Note DOMXPath::evaluate():
return $xpath->evaluate('/*/*[not(../*/#id > #id)]/#id + 1');
|------- +1 in xpath
This will give you 2 - but as a double. I would suggest to convert to integer before returning the result:
return (integer) $xpath->evaluate('/*/*[not(../*/#id > #id)]/#id + 1');

I suggest you create an array of all existing ID values first (which is a single xpath query) and then you check against it:
$id = 0;
while(isset($ids[$id])) {
$id++;
}
echo $id; # 2
Creating such a list is trivial running the xpath on SimpleXML, however this can be easily ported to DOMXPath as well with iterator_to_array:
<?php
$buffer = <<<BUFFER
<?xml version="1.0"?>
<catalog>
<book id="0">
<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="1">
<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>
BUFFER;
$xml = simplexml_load_string($buffer);
$ids = array_flip(array_map('intval', $xml->xpath("/*/*/#id")));
Interactive Demo
Additionally I suggest you to not use 0 (zero) as ID value.

Use simplexml, try this
$xml = simplexml_load_string($this->_dom);
$id = is_array($xml->book) ? $xml->book[count($xml->book)-1]->attributes()->id : 0;
return $id;

Related

Getting Parent Node from XML file into a String in PHP

So, I'm parsing data from an XML feed in to php variables and everything is fine with the exception of the "link" element. It's not in a child like the others.
A cleaner, simpler example of the structure is below:
<bookstore>
<book category="children">
<title>Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
<link href="http://example.com">
</book>
<book category="web">
<title>Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<price>39.95</price>
<link href="http://example.com">
</book>
</bookstore>
<bookstore>
<book category="children">
<title>Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
<link href="http://example.com">
</book>
<book category="web">
<title>Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<price>39.95</price>
<link href="http://example.com">
</book>
</bookstore>
How do I read the link/href part of the XML in the parent node /bookstore/ and put it in to a string? It looks like it's been badly formatted, but I can't change it as it's supplied by a third party.
I thought I could load the entire /bookstore/ parent and search through it for the link and pull the value that way but it won't load the entire bookstore element.
My code is also extracting the other child tags fine and running through a loop to show the data in a list. Any help would be appreciated.
Edit: This is the link to the XML file I have to use: https://www.reddit.com/r/elderscrollsonline.xml
For SimpleXML - this code:
$rss = 'some_url_here';
$xml = simplexml_load_file($rss);
For you xml:
foreach($xml->bookstore as $bookstore) {
foreach ($bookastore as $book)
echo (string)$book->link['href'];
}
For links in https://www.reddit.com/r/elderscrollsonline.xml:
foreach($xml->entry as $book) echo (string)$book->link['href'];

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

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.

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

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

Simplexml is giving me the wrong results

I have a simple xml below:
<?xml version="1.0" encoding="utf-8"?>
<catalogue>
<category name="textbook" id="100" parent="books">
<product id="20000">
<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>
</product>
<product id="20001">
<author>Gambardellas, 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>
</product>
</category>
<category name="fiction" id="101" parent="books">
<product id="2001">
<author>Ralls, Kim</author>
<title>Midnight Rain</title>
<genre>Fantasy</genre>
<type>Fiction</type>
<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>
</product>
</category>
</catalogue>
I am using php simplexml library to parse it as follows: (note there are two category nodes. The first category contains two 'product' children. My aim is to get an array that contains those two children of first 'category'
$xml = simplexml_load_file($xml_file) or die ("unable to load XML File!".$xml_file);
//for each product, print out info
$cat = array();
foreach($xml->category as $category)
{
if($category['id'] == 100)
{
$cat = $category;
break;
}
}
$prod_arr = $category->product;
Here is the problem. I am expecting an array with two products children but its only returning one product. What am I doing wrong or is this a php bug? Please help!
You can use SimpleXMLElement::xpath() to get all product elements that are children of a specific category element. E.g.
// $catalogue is your $xml
$products = $catalogue->xpath('category[#id="100"]/product');
foreach($products as $p) {
echo $p['id'], ' ', $p->title, "\n";
}
prints
20000 XML Developer's Guide
20001 XML Developer's Guide
For start, your XML file is not well defined. You should probably start and end it with <categories> tag.
Replace the last assignment with the following:
$prod_array = array();
foreach ($cat->product as $p) {
$prod_array[] = $p;
}
$cat = array();
foreach ($xml->category as $category)
{
$attributes = $category->attributes();
if(isset($attributes['id']) && $attributes['id'] == 100)
{
$cat = $category;
break;
}
}

Categories