I have an XML file and a variable. I want to write a function to replace a specific node's value. Example:
write_xml("->system_settings->settings->hostname",'Helloooooooo');
function write_xml($tag_address, $value) {
$xml = simplexml_load_file("test.xml")
or die("Error: Cannot create object");
$xml->system_settings->settings->hostname = $value;
$xml->asXML("test.xml");
}
In this example, the node ->system_settings->settings if helloooooo should be replaced with hostname.
My problem is: in the above code, I entered the path manually and it works. But if I assign my path dynamically (like below), it doesn't work:
write_xml("->system_settings->settings->hostname",'Helloooooooo');
function write_xml($tag_address, $value) {
...
$xml->$tag_address = $value; // <---- doesn't work
$xml->asXML("test.xml");
}
What should i do?
Related
The following php script gives count of elements in a single xml file in the folder uploads. But I have number of xml files in the folder. What to modify in the following script so that I get result in tabular format with the file name and element count for all the xml files in the folder.
<?php
$doc = new DOMDocument;
$xml = simplexml_load_file("uploads/test.xml");
//file to SimpleXMLElement
$xml = simplexml_import_dom($xml);
print("Number of elements: ".$xml->count());
?>
You're first loading the XML file into a SimpleXMLElement then import it into a DOMElement and call the method count() on it. This method does not exists on DOMElement - only on SimpleXMLElement. So the import would not be necessary.
You can use a GlobIterator to iterate the files:
$directory = __DIR__.'/uploads';
// get an iterator for the XML files
$files = new GlobIterator(
$directory.'/*.xml', FilesystemIterator::CURRENT_AS_FILEINFO
);
$results = [];
foreach ($files as $file) {
// load file using absolute file path
// the returned SimpleXMLElement wraps the document element node
$documentElement = simplexml_load_file($file->getRealPath());
$results[] = [
// file name without path
'file' => $file->getFilename(),
// "SimpleXMLElement::count()" returns the number of children of an element
'item-count' => $documentElement->count(),
];
}
var_dump($results);
With DOM you can use Xpath to fetch specific values from the XML.
$directory = __DIR__.'/uploads';
// get an iterator for the XML files
$files = new GlobIterator(
$directory.'/*.xml', FilesystemIterator::CURRENT_AS_FILEINFO
);
// only one document instance is needed
$document = new DOMDocument();
$results = [];
foreach ($files as $file) {
// load the file into the DOM document
$document->load($file->getRealPath());
// create an Xpath processor for the loaded document
$xpath = new DOMXpath($document);
$results[] = [
'file' => $file->getFilename(),
// use an Xpath expression to fetch the value
'item-count' => $xpath->evaluate('count(/*/*)'),
];
}
var_dump($results);
The Xpath Expression
Get the document element /*
Get the child elements of the document element /*/*
Count them count(/*/*)
* is an universal selector for any element node. If you can you should be more specific and use the actual element names (e.g. /list/item).
First, create a function with the logic you have:
function getXML($path) {
$doc = new DOMDocument;
$xml = simplexml_load_file($path);
//file to SimpleXMLElement
$xml = simplexml_import_dom($xml);
return $xml;
}
Note that I:
have converted the path into a parameter, so you can reuse the same logic for your files
separated the parsing of XML from showing it
returned the XML itself, so you can get the count or you can do whatever else you may want with it
This is how you can get the files of a given path:
$files = array_diff(scandir('uploads'), array('.', '..'));
we get all files except for . and .., which are surely not of interest here. Read more about scandir here: https://www.php.net/manual/en/function.scandir.php
You received an array of filenames on success, so, let's loop it and perform the logic you need:
$xmls = [];
foreach ($files as $file) {
if (str_ends_with($file, '.xml')) {
$xmls[] = $file . "\t" . getXML('uploads/' . $file)->count();
}
}
echo implode("\n", $xmls);
EDIT
As #Juan kindly explained in the comment section, one can use
$files = glob("./uploads/*.xml");
instead of scandir and that would ensure that we no longer need a call for array_diff and later we can avoid the if inside the loop:
$xmls = [];
foreach ($files as $file) {
$xmls[] = $file . "\t" . getXML('uploads/' . $file)->count();
}
echo implode("\n", $xmls);
I'm trying to update 1 particular node in an pre-existing xml file from php. The problem i'm having is that it doesn't seem to be saving to the xml file. Not sure why and any help here will be appreciated!
<?php
$itemNumber = $_GET["itemNumberField"];
$xmlFile = "items.xml";
if(file_exists($xmlFile))
{
// $doc = new DOMDocument('1.0');
// $doc->load($xmlFile);
$doc = DOMDocument::load($xmlFile);
$item = $doc->getElementsByTagName("item");
foreach($item as $node)
{
$itemNumberNode = $node->getElementsByTagName("itemNumber");
$itemNumberNode = $itemNumberNode->item(0)->nodeValue;
$qtyNode = $node->getElementsByTagName("quantity");
$qtyNode = $qtyNode->item(0)->nodeValue;
if ($itemNumberNode == $itemNumber)
{
$qtyNode++;
echo $qtyNode;
}
}
}
else
{
echo "file doesn't exist! <br/>";
}
$doc->save($xmlFile);
?>
Edit: Just to clarify, the adding to the node seems to be fine.
Solution: Turns out the reason it didn't save was because node of the adding. i had to either directly update it or assign an updated value to it.
$qtyNode = $node->getElementsByTagName("quantity");
$qtyNode = $qtyNode->item(0);
...
$qtyNode->nodeValue++;
I've just coded something similar, and found out that $doc->saveXML() just builds up the outgoing xml string. I added a file_put_contents for writing to the xml file and worked fine.
file_put_contents($xmlFile, $doc->saveXML());
You COPY the scalar value (string) into a variable. Then you change the variable.
$qtyNode = $qtyNode->item(0)->nodeValue;
if ($itemNumberNode == $itemNumber)
{
$qtyNode++;
echo $qtyNode;
}
$qtyNode is not a DOMNode object, but a string variable.
You will have to change the DOMNode::$nodeValue property directly or assign the variable to it.
$qtyNode = $qtyNode->item(0);
if ($itemNumberNode == $itemNumber)
{
$qtyNode->nodeValue++;
echo $qtyNode->nodeValue;
}
Chmod the folder in which you want to save xml file: set permissions to maxiamally 775.
If this not works try using Curl
I am trying here to set the vale of the first child element of $map. I need to do this by referencing the path of the array i.e [0]['child'].
The function returns the path value ok if set but I am having trouble changing the value of that of that element. So in this case I want $map[0]['child'] to equal "new".
function getArrayPath($arr,$path) {
foreach($path as $item){
$arr = $arr[$item];
}
return $arr;
}
$map='[{"child":""},{"child":""},{"child":""}]';
$map=json_decode($map,true);
$path = array("0","child");
$target = getArrayPath($map,$path);
if($target==""){
$target="new";
}
var_dump($map);
You can solve this by using references:
function &getArrayPath(&$arr,$path) {
// ^--return ^--receive
// reference reference
foreach($path as $item){
$arr =& $arr[$item];
// ^--assign reference
}
return $arr;
}
$map = json_decode('[{"child":"1"},{"child":"2"},{"child":"3"}]', true);
$path = array("0","child");
$target =& getArrayPath($map,$path);
// ^--assign reference
as you can see in this demo (your code slightly changed).
Since PHP does not assign/return by reference as default, you always need to explicitly do that by adding the & operator.
So, I have this code that searches for a particular node in my XML file, unsets an existing node and inserts a brand new child node with the correct data. Is there a way of getting this new data to save within the actual XML file with simpleXML? If not, is there another efficient method for doing this?
public function hint_insert() {
foreach($this->hints as $key => $value) {
$filename = $this->get_qid_filename($key);
echo "$key - $filename - $value[0]<br>";
//insert hint within right node using simplexml
$xml = simplexml_load_file($filename);
foreach ($xml->PrintQuestion as $PrintQuestion) {
unset($xml->PrintQuestion->content->multichoice->feedback->hint->Passage);
$xml->PrintQuestion->content->multichoice->feedback->hint->addChild('Passage', $value[0]);
echo("<pre>" . print_r($PrintQuestion) . "</pre>");
return;
}
}
}
Not sure I understand the issue. The asXML() method accepts an optional filename as param that will save the current structure as XML to a file. So once you have updated your XML with the hints, just save it back to file.
// Load XML with SimpleXml from string
$root = simplexml_load_string('<root><a>foo</a></root>');
// Modify a node
$root->a = 'bar';
// Saving the whole modified XML to a new filename
$root->asXml('updated.xml');
// Save only the modified node
$root->a->asXml('only-a.xml');
If you want to save the same, you can use dom_import_simplexml to convert to a DomElement and save:
$dom = new DOMDocument('1.0');
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->loadXML($simpleXml->asXML());
echo $dom->saveXML();
I am trying to figure out what I can do to create a code that appends data in my XML file not rewrite the XML file continuously.
I need to be able to save all the form entries and as of right now every time the form is submitted. it creates a new XML file and erases the old one.
This may be really easy to fix or I am just really dumb but I have looked at DOM syntax and do not see what I could change to change the outcome.
// define configuration file name and path
$configFile = 'abook.xml';
// if form not yet submitted
// display form
if (!isset($_POST['submit'])) {
// set up array with default parameters
$data = array();
$data['name'] = null;
$data['email'] = null;
$data['caddress'] = null;
$data['city'] = null;
$data['state'] = null;
$data['zipcode'] = null;
$data['phone'] = null;
$data['pug'] = null;
$data['comment'] = null;
$data['subscribe'] = null;
// read current configuration values
// use them to pre-fill the form
if (file_exists($configFile)) {
$doc = new DOMDocument();
$doc->preserveWhiteSpace = false;
$doc->load($configFile);
$address = $doc->getElementsByTagName('address');
foreach ($address->item(0)->childNodes as $node) {
$data[$node->nodeName] = $node->nodeValue;
}
}
In between is a PHP form and validation code and at the end I use XML tags again:
// generate new XML document
$doc = new DOMDocument();
// create and attach root element <configuration>
$root = $doc->createElement('addressbook');
$configuration = $doc->appendChild($root);
// create and attach <oven> element under <configuration>
$address = $doc->createElement('address');
$configuration->appendChild($address);
// write each configuration value to the file
foreach ($config as $key => $value) {
if (trim($value) != '') {
$elem = $doc->createElement($key);
$text = $doc->createTextNode($value);
$address->appendChild($elem);
$elem->appendChild($text);
}
}
// format XML output
// save XML file
$doc->formatOutput = true;
$doc->save($configFile) or die('ERROR: Cannot write configuration file');
echo 'Thank you for filling out an application.';
}
I am really new at this so I am sorry if my code is pretty messy.
The second part I am dealing with an XSL file which I have linked to my XML file but no matter what syntax I have used to transform, nothing works to save it in a table.
Again, I don't know if this could be caused by the way I have set up my PHP to write the XML.