PHP, XML -> Create XML from Node Set + Data - php

Am wondering if someone can help in the best method to accomplish this.
We have a XML with various data sets, Within the XML is a Set of Data per Channel.
Each Channel runs its own program.
What i was hoping to do, is if ChannelA was to process the XML
And it had a error for one reason or another, Can i simply extract that NODE set it was processing and build a XML from it.
or do i have to declare each NODE set to build the XML then essentially import it?
The XML has around 30/40 Nodes (Am posting like this as an example) so typing these out in PHP is just going to be ugly, and down the line as we add more sets, its just going to be horrible to maintain it.
<data>
<MainUpdate>
<Chan>5</Chan>
<Data1></<Data1>
<Data2></<Data2>
<Data3></<Data3>
<Data4></<Data4>
<Data5></<Data5>
</MainUpdate>
<MainUpdate>
<Chan>8</Chan>
<Data1></<Data1>
<Data2></<Data2>
<Data3></<Data3>
<Data4></<Data4>
<Data5></<Data5>
</MainUpdate>
<MainUpdate>
<Chan>10</Chan>
<Data1></<Data1>
<Data2></<Data2>
<Data3></<Data3>
<Data4></<Data4>
<Data5></<Data5>
</MainUpdate>
</data>
If Channel8 processes this XML, and its only processing Channel8 Data
I want to be able to create a XML with just:
<data>
<MainUpdate>
<Chan>8</Chan>
<Data1></<Data1>
<Data2></<Data2>
<Data3></<Data3>
<Data4></<Data4>
<Data5></<Data5>
</MainUpdate>
</data>
Without delcaring all the nodes

use xpath to select the desired node (and its children) and
importNode() to get it to a new DOMDocument
code example:
$xpath = new DOMXpath($doc); // assume original XML in $doc
$e = $xpath->query("/data/MainUpdate[Chan = '8']")->item(0);
Note the condition in [], assuming <Chan> as unique, the resulting DOMNodeList will have 1 DOMElement, of which we grab the first and only and store it in $e.
$e is NULL if there is no such <Chan>, check this before proceeding.
Now create a new document with <data></data> as root and import $e:
$newdoc = new DOMDocument();
$newdoc->loadXML("<data />");
$e = $newdoc->importNode($e, true);
$newdoc->documentElement->appendChild($e);
see it working: https://eval.in/513276

Related

PHP - DOMXpath does not return any nodes when it should

I am pulling HTML from Selenium, and then extracting data from the HTML using Xpaths.
This is the Xpath:
/html/body/div[2]/div[1]/div/div/div/div/ul/li/div[1]/h3/a
This is my code:
$data = $webdriver->getPageSource();
d($data, $urltemplate);
$doc = new DOMDocument();
$doc->loadHTML($data);
$xp = "/html/body/div[2]/div[1]/div/div/div/div/ul/li/div[1]/h3/a";
$xpatho = new DOMXpath($doc);
$elementsn = $xpatho->query($xp);
d(get_class($elementsn),$elementsn->count(),$xp,$name);
// d() is a custom function like var_dump().
I always get $elementsn->count() = 0.
This is $data:
https://pastebin.com/ahuvkJfN
I am trying to extract those strings like "NAD M10 BLUOS...", "NAD M12 DIRECT DIGITAL..." and so on...
I saved the HTML into a file, and opened it in my browser. I am attaching screenshot of what data I was looking to retrieve (highlighted in blue):
Basically, the HTML page is a product listing, and I am looking to extract all the product names. To confirm, I used Chrome Developer tools, and used the copy full Xpath function. I have the following Xpaths for some of the product names:
/html/body/div[2]/div[1]/div/div/div/div/ul/li[1]/div[1]/h3/a
/html/body/div[2]/div[1]/div/div/div/div/ul/li[3]/div[1]/h3/a
I would guess that this would generalise to:
/html/body/div[2]/div[1]/div/div/div/div/ul/li/div[1]/h3/a
However, I keep on getting a DOMNodeList with count = 0. Why is this so, and how can I check what the error is, if any?
P.S.: This is the original webpage: http://lenbrook.com.sg/3-shop-by-brand#/page-4/price-49-8667
Try changing your $xp
$xp = '//a[#class="product_link"]/text()'

XML file content not updating using PHP

Each time I run the code, file updates and I can see the file last edited date and time are updated but the content in the XML file is not updated.
I just tried to update the following XML Code
<?xml version="1.0" encoding="utf-8"?>
<topcont>
<sitenondualtraining>
<title>The Heart of Awakening</title>
<descripition>nondual</descripition>
<link>www.test.com/post/latestpost</link>
</sitenondualtraining>
</topcont>
using PHP code
$topcont = new DOMDocument();
$topcont->load("http://fenner.tk/topcont.xml");
$topcont->topcont->sitenondualtraining->title = 'test';
$topcont->sitenondualtraining->descripition = $_POST['nd2'];
$topcont->sitenondualtraining->link = $_POST['nd3'];
$topcont->Save("topcont.xml");
I also tried
$topcont = new SimpleXmlElement('http://fenner.tk/topcont.xml',null, true);
$topcont->sitenondualtraining->title = $_POST['nd1'];
$topcont->sitenondualtraining->descripition = $_POST['nd2'];
$topcont->sitenondualtraining->link = $_POST['nd3'];
$topcont->asXml('topcont.xml');
But none of these are working. Can anyone point where the issue is? Thanks.
File permission are set to 777 but still not working
NO ERRORS BUT WARNINGS ARE
Warning: Creating default object from empty value in /home/fenner/public_html/topads.php on line 20
Warning: Creating default object from empty value in /home/fenner/public_html/topads.php on line 21 /home/fenner/public_html/
Using DomDocument, you were almost there. You can do it like this:
$topcont = new DOMDocument();
$topcont->load("topcont.xml");
$topcont->getElementsByTagName("title")->item(0)->nodeValue = $_POST['nd2'];
$topcont->getElementsByTagName("description")->item(0)->nodeValue = $_POST['nd2'];
$topcont->getElementsByTagName("link")->item(0)->nodeValue = $_POST['nd3'];
$topcont->save("topcont.xml");
Just remember to sanitize your inputs before storing your data ;)
Also worth looking into is creating cdata sections and using replaceData, depending on what you intend to store in each node.
EDIT
In response to your comment below, you might want to change your xml structure a little if you are going to be handling multiple child nodes. This way it is easier to loop through and update the node you are interested in. You will see below that I moved 'sitenondualtraining' and 'siteradiantmind' to be id's of an 'item" node, though you could easily change this to something like <site id="nodualtraining> if that's more like what you were looking for.
<?xml version="1.0" encoding="utf-8"?>
<topcont>
<item id="sitenondualtraining">
<title>test</title>
<description>hello test</description>
<link>hello</link>
</item>
<item id="siteradiantmind">
<title>The Heart of Awakening</title>
<description>radiantmind</description>
<link>www.radiantmind.com/post/latestpost</link>
</item>
</topcont>
Your PHP code would then be something like this, again this is quite basic and could be tidied up, but is a good start:
$items = $topcont->getElementsByTagName("item");
// loop through each item
foreach ($items as $item) {
$id = $item->getAttribute('id');
// check the item id to make sure we edit the correct one
if ($id == "sitenondualtraining") {
$item->getElementsByTagName("title")->item(0)->nodeValue = $_POST['nd1'];
$item->getElementsByTagName("link")->item(0)->nodeValue = $_POST['nd2'];
$item->getElementsByTagName("description")->item(0)->nodeValue = $_POST['nd3];
}
}
If you were feeling a little adventurous, you could have a look at xpath and xpath query, you can find some sample code in most php docs to get you started and the comments from other users can be helpful as well.
For reference: getAttribute, getElementsByTagName.

How do you rename a tag in SimpleXML through a DOM object?

The problem seems straightforward, but I'm having trouble getting access to the tag name of a SimpleXMLElement.
Let's say I have the follow XML structure:
<xml>
<oldName>Stuff</oldName>
</xml>
And I want it to look like this:
<xml>
<newName>Stuff</newName>
</xml>
Is this possible to do without doing a copy of the entire object?
I've started to realize the errors of the ways I am approaching this problem. It seems that I need to convert my SimpleXMLElement into a DOM object. Upon doing so I find it very hard to manipulate the object in the way I want (apparently renaming tags in a DOM isn't easy to do for a reason).
So... I am able to import my SimpleXMLElement into a DOM object with the import, but I am finding it difficult to do the clone.
Is the following the right thinking behind cloning a DOM object or am I still way off:
$old = $dom->getElementsByTagName('old')->item(0); // The tag is unique in my case
$new = $dom->createElement('new');
/* ... some recursive logic to copy attributes and children of the $old node ... */
$old->ownerDocument->appendChild($new);
$new->ownerDocument->removeChild($old);
Here's what's probably the simplest way to copy a node's children and attributes without using XSLT:
function clonishNode(DOMNode $oldNode, $newName, $newNS = null)
{
if (isset($newNS))
{
$newNode = $oldNode->ownerDocument->createElementNS($newNS, $newName);
}
else
{
$newNode = $oldNode->ownerDocument->createElement($newName);
}
foreach ($oldNode->attributes as $attr)
{
$newNode->appendChild($attr->cloneNode());
}
foreach ($oldNode->childNodes as $child)
{
$newNode->appendChild($child->cloneNode(true));
}
$oldNode->parentNode->replaceChild($newNode, $oldNode);
}
Which you can use this way:
$dom = new DOMDocument;
$dom->loadXML('<foo><bar x="1" y="2">x<baz/>y<quux/>z</bar></foo>');
$oldNode = $dom->getElementsByTagName('bar')->item(0);
clonishNode($oldNode, 'BXR');
// Same but with a new namespace
//clonishNode($oldNode, 'newns:BXR', 'http://newns');
die($dom->saveXML());
It will replace the old node with a clone with a new name.
Attention though, this is a copy of the original node's content. If you had any variable pointing to the old nodes, they are now invalid.
Maybe easier way would be to replace the tags using preg functions for the XML source string?
Cleaner way
Create XSLT XML transformation file and use xsl PHP extension to translate it.
For this see this answer – Rename nodes with XSLT.
PHP code part could be found in PHP documentation.
Is this possible to do without doing a copy of the entire object?
Nope, it's not.
You could do it in XSLT via an "identity transform". If you search around for "rename tag" and "identity transform" you should find a few examples, assuming XSLT is an option.
Rather late but I came up with the fallowing solution by replacing the hell out of the xml. I thought this might help some people as I couldnt find any good solution on the web without copying all children.
function RenameNode(SimpleXMLElement $Entire_XML, SimpleXMLElement $Node, $New_Title)
{
$Full_XML = $Entire_XML->asXML();
$Old_Title = $Node->getName();
$XML_String = $Node->asXML();
$Replaced = preg_replace("/$Old_Title/", $New_Title, $XML_String, 1);
if (count($Node->children())>0)
{
$Replaced = strrev(
preg_replace(
strrev("/$Old_Title/"),
strrev($New_Title),
strrev($Replaced),
1
)
);
}
$Full_XML = str_replace($XML_String, $Replaced, $Full_XML);
return simplexml_load_string($Full_XML);
}
Sidenote: This function can be simplified but I quickly rewrote this function in order to post this here. The original function I use looks a little bit different

Read XML file and write to a php array/file

I need to update the country list of my website and I want to automate the process. Country list can be found here
http://www.iso.org/iso/country_codes...code_lists.htm // Edit : Can't find the good link...
I tried it this way –
http://www.w3schools.com/php/php_xml_parser_expat.asp (PHP XML Expat Parser)
However, this didn't seem to work well as I was confused where to actually 'get' the data and print it to my own array for later use.
Now I want to try it using XML DOM.
Just want to check with everyone, if I had a simple XML file to read, that contained a country code and country name as follows:
<Entry>
<Country_name>AFGHANISTAN</Country_name>
<Code_element>AF</Code_element>
</Entry>
I want to read this file (DOM method), and then feed the data into a separate file/array of mine that will be accessed by my website. What PHP xml functions would YOU use/recommend to do this simple task?
Any help in this regards is appreciated.
Use SimpleXML
how about
$dom = new DOMDOcument();
$dom->loadXML($xml);
$xpath = new DOMXpath($dom);
$res = $xpath->query("/CODE");
$allres = array();
foreach($res as $node){
$result = array();
$result['country'] = ($node->getElementsByTagName("Country_name")->item(0)->nodeValue);
$result['code'] = ($node->getElementsByTagName("Code_element")->item(0)->nodeValue);
$allres[] = $res
}
in the end $allres array would contain all your country codes and names

Updating the XML file using PHP script

I'm making an interface-website to update a concert-list on a band-website.
The list is stored as an XML file an has this structure :
I already wrote a script that enables me to add a new gig to the list, this was relatively easy...
Now I want to write a script that enables me to edit a certain gig in the list.
Every Gig is Unique because of the first attribute : "id" .
I want to use this reference to edit the other attributes in that Node.
My PHP is very poor, so I hope someone could put me on the good foot here...
My PHP script :
Well i dunno what your XML structure looks like but:
<gig id="someid">
<venue></venue>
<day></day>
<month></month>
<year></year>
</gig>
$xml = new SimpleXmlElement('gig.xml',null, true);
$gig = $xml->xpath('//gig[#id="'.$_POST['id'].'"]');
$gig->venue = $_POST['venue'];
$gig->month = $_POST['month'];
// etc..
$xml->asXml('gig.xml)'; // save back to file
now if instead all these data points are attributes you can use $gig->attributes()->venue to access it.
There is no need for the loop really unless you are doing multiple updates with one post - you can get at any specific record via an XPAth query. SimpleXML is also a lot lighter and a lot easier to use for this type of thing than DOMDOcument - especially as you arent using the feature of DOMDocument.
You'll want to load the xml file in a domdocument with
<?
$xml = new DOMDocument();
$xml->load("xmlfile.xml");
//find the tags that you want to update
$tags = $xml->getElementsByTagName("GIG");
//find the tag with the id you want to update
foreach ($tags as $tag) {
if($tag->getAttribute("id") == $id) { //found the tag, now update the attribute
$tag->setAttribute("[attributeName]", "[attributeValue]");
}
}
//save the xml
$xml->save();
?>
code is untested, but it's a general idea

Categories