Append an entry to an XML file using DOM and PHP - php

I'm trying to add entries to an XML file using DOM/PHP and I can't for the life of me get them to appear in the XML file.
The XML schema is as follows:
<alist>
<a>
<1>text a</1>
<2>text a</2>
</a>
<a>
<1>text b</1>
<2>text b</2>
</a>
</alist>
and the PHP is:
$xmlFile = "../../data/file.xml";
$dom = DOMDocument::load($xmlFile);
$v1 = "text c";
$v2 = "text c";
//create anchor
$alist = $dom->getElementsByTagName("alist");
//create elements and contents for <1> and <2>
$a1= $dom->createElement("1");
$a1->appendChild($dom->createTextNode($v1));
$a2= $dom->createElement("2");
$a2->appendChild($dom->createTextNode($v2));
//Create element <a>, add elements <1> and <2> to it.
$a= $dom->createElement("a");
$a->appendChild($v1);
$a->appendChild($v2);
//Add element <a> to <alist>
$alist->appendChild($a);
//Append entry?
$dom->save($xmlFile);

getElementsByTagName() returns a list of element nodes with that tag name. You can not append nodes to the list. You can only append them to elements nodes in the list.
You need to check if the list contains nodes and read the first one.
Numeric element names like 1 or 2 are not allowed. Digits can not be the first character of an xml qualified name. Even numbering them like e1, e2, ... is a bad idea, it makes definitions difficult. If the number is needed, put it into an attribute value.
$xml = <<<XML
<alist>
<a>
<n1>text a</n1>
<n2>text a</n2>
</a>
<a>
<n1>text b</n1>
<n2>text b</n2>
</a>
</alist>
XML;
$dom = new DOMDocument();
$dom->preserveWhiteSpace = FALSE;
$dom->formatOutput = TRUE;
$dom->loadXml($xml);
$v1 = "text c";
$v2 = "text c";
// fetch the list
$list = $dom->getElementsByTagName("alist");
if ($list->length > 0) {
$listNode = $list->item(0);
//Create element <a>, add it to the list node.
$a = $listNode->appendChild($dom->createElement("a"));
$child = $a->appendChild($dom->createElement("n1"));
$child->appendChild($dom->createTextNode($v1));
$child = $a->appendChild($dom->createElement("n2"));
$child->appendChild($dom->createTextNode($v2));
}
echo $dom->saveXml();
Output: https://eval.in/147562
<?xml version="1.0"?>
<alist>
<a>
<n1>text a</n1>
<n2>text a</n2>
</a>
<a>
<n1>text b</n1>
<n2>text b</n2>
</a>
<a>
<n1>text c</n1>
<n2>text c</n2>
</a>
</alist>

Related

How to add nodes to a multi-level XML from an array?

I have an array $arr=array("A-B-C-D","A-B-E","A-B-C-F") and my expected output of the XML should be
<root>
<A>
<B>
<C>
<D></D>
<F></F>
</C>
<E></E>
</B>
</A>
</root>
I have already done the code that creates a new node of the XML for the first array element i.e. A-B-C-D. But when I move to the second element I need to check how many nodes are already created (A-B) and then add the new node based on that in the proper position.
So how do I traverse the XML and find the exact position where the new node should be attached?
my current code looks like this
$arr=explode("-",$input);
$doc = new DomDocument();
$doc->formatOutput=true;
$doc->LoadXML('<root/>');
$root = $doc->documentElement;
$comm = $doc->createElement('comm');
$root->appendChild($comm);
foreach($arr as $a2) {
$newcomm = $doc->createElement($a2);
$community->appendChild($newcomm);
$community=$newcomm;
}
Should I use xpath or some other method will be easier?
To stick with using DOMDocument, I've added an extra loop to allow you to add all of the original array items in. The main thing is before adding a new item in, check if it's already there...
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
$set=array("A-B-C-D","A-B-E","A-B-C-F-G", "A-B-G-Q");
$doc = new DomDocument();
$doc->formatOutput=true;
$doc->LoadXML('<root/>');
foreach ( $set as $input ) {
$arr=explode("-",$input);
$base = $doc->documentElement;
foreach($arr as $a2) {
$newcomm = null;
// Decide if the element already exists.
foreach ( $base->childNodes as $nextElement ) {
if ( $nextElement instanceof DOMElement
&& $nextElement->tagName == $a2 ) {
$newcomm = $nextElement;
}
}
if ( $newcomm == null ) {
$newcomm = $doc->createElement($a2);
$base->appendChild($newcomm);
}
$base=$newcomm;
}
}
echo $doc->saveXML();
As there is no quick way ( as far as I know) to check for a child with a specific tag name, it just looks through all of the child elements for a DOMElement with the same name.
I started using getElementByTagName, but this finds any child node with the name and not just at the current level.
The output from above is...
<?xml version="1.0"?>
<root>
<A>
<B>
<C>
<D/>
<F>
<G/>
</F>
</C>
<E/>
<G>
<Q/>
</G>
</B>
</A>
</root>
I added a few other items in to show that it adds things in at the right place.

How do I get the names of a child element using PHP and DOM

I'm trying to get the names of certain elements in order to a populate a combo list, all the example I can find use the simpleXML lib which I do not have access to.
The current xml schema:
<alist>
<a>
<a1>text a</a1>
<a2>text a</a2>
</a>
<b>
<b1>text b</b1>
<b2>text b</b2>
</b>
</alist>
and the current PHP code:
$xmlFile = "file.xml";
$dom = DOMDocument::load($xmlFile);
What I want to do is get the name of the child elements of 'alist' (currently it would be a and b).
Be careful with tagName, it will contain a namespace prefix if the element has one. localName will be the name without a namespace prefix.
Xpath allows you to fetch and iterate the child element nodes directly:
$xml = <<<XML
<alist>
<a>
<a1>text a</a1>
<a2>text a</a2>
</a>
<b>
<b1>text b</b1>
<b2>text b</b2>
</b>
<foo:c xmlns:foo="bar"/>
</alist>
XML;
$dom = new DOMDocument();
$dom->loadXML($xml);
$xpath = new DOMXpath($dom);
foreach($xpath->evaluate('/alist/*') as $child) {
var_dump($child->localName);
}
Output: https://eval.in/149684
string(1) "a"
string(1) "b"
string(1) "c"
It's a property on DOMElement called tagName. E.g.:
<?php
$xml = <<<XML
<alist>
<a>
<a1>text a</a1>
<a2>text a</a2>
</a>
<b>
<b1>text b</b1>
<b2>text b</b2>
</b>
</alist>
XML;
$dom = new DOMDocument();
$dom->loadXML($xml);
$alist = $dom->getElementsByTagName('alist')->item(0);
foreach($alist->childNodes as $child) {
if ($child->nodeType === XML_ELEMENT_NODE) var_dump($child->tagName);
}

Removing the child node of an XML file using DOM and PHP

I'm trying to delete a child node within a XML document using DOM and PHP but I can't quite figure out how to do it. I do not have access to simpleXML.
XML Layout:
<list>
<as>
<a>
<a1>delete</a1>
</a>
<a>
<a1>keep</a1>
</a>
</as>
<list>
PHP Code:
$xml = "file.xml";
$dom = DOMDocument::load($xml);
$list = $dom->getElementsByTagName('as')->item(0);
//Cycle through <as> elements (there are multiple in the full file)
foreach($list->childNodes as $child) {
$subChild = substr($child->tagName, 0, -1);
$a = $dom->getElementsByTagName($subChild);
//Cycle through <a> elements
foreach($a as $node)
{
//Get status for status check
$check= $node->getElementsByTagName("a1")->item(0)->nodeValue;
if(strcmp($check,'delete')==0)
{
//code to delete here (I wish to delete the <a> that this triggers
}
}
}
http://www.php.net/manual/en/class.domnode.php
http://www.php.net/manual/en/domnode.removechild.php
You need the parent of a node to remove it, and you've got it as a property of the node that you want to remove, so no biggie. The result would be:
$node->parentNode->removeChild($node);

PHP DomDocument - How to replace a text from a Node with another node

I have a node:
<p>
This is a test node with Figure 1.
</p>
My goal is to replace "Figure 1" with a child node:
<xref>Figure 1</xref>
So that the final result will be:
<p>
This is a test node with <xref>Figure 1</xref>.
</p>
Thank you in advance.
Xpath allows you to fetch the text nodes containing the string from the document. Then you have to split it into a list of text and element (xref) nodes and insert that nodes before the text node. Last remove the original text node.
$xml = <<<'XML'
<p>
This is a test node with Figure 1.
</p>
XML;
$string = 'Figure 1';
$dom = new DOMDocument();
$dom->loadXml($xml);
$xpath = new DOMXpath($dom);
// find text nodes that contain the string
$nodes = $xpath->evaluate('//text()[contains(., "'.$string.'")]');
foreach ($nodes as $node) {
// explode the text at the string
$parts = explode($string, $node->nodeValue);
// add a new text node with the first part
$node->parentNode->insertBefore(
$dom->createTextNode(
// fetch and remove the first part from the list
array_shift($parts)
),
$node
);
// if here are more then one part
foreach ($parts as $part) {
// add a xref before it
$node->parentNode->insertBefore(
$xref = $dom->createElement('xref'),
$node
);
// with the string that we used to split the text
$xref->appendChild($dom->createTextNode($string));
// add the part from the list as new text node
$node->parentNode->insertBefore(
$dom->createTextNode($part),
$node
);
}
// remove the old text node
$node->parentNode->removeChild($node);
}
echo $dom->saveXml($dom->documentElement);
Output:
<p>
This is a test node with <xref>Figure 1</xref>.
</p>
You can first use getElementsByTagName() to find the node you're looking for and remove the search text from the nodeValue of that node. Now, create the new node, set the nodeValue as the search text and append the new node to the main node:
<?php
$dom = new DOMDocument;
$dom->loadHTML('<p>This is a test node with Figure 1</p>');
$searchFor = 'Figure 1';
// replace the searchterm in given paragraph node
$p_node = $dom->getElementsByTagName("p")->item(0);
$p_node->nodeValue = str_replace($searchFor, '', $p_node->nodeValue);
// create the new element
$new_node = $dom->createElement("xref");
$new_node->nodeValue = $searchFor;
// append the child element to paragraph node
$p_node->appendChild($new_node);
echo $dom->saveHTML();
Output:
<p>This is a test node with <xref>Figure 1</xref></p>
Demo.

detecting nodes in xml

for example, we have this xml:
<p>[TAG]
<span>foo 1</span>
<span>foo 2</span>
[/TAG]
<span>bar 1</span>
<span>bar 2</span>
</p>
how can i detect <span>-tags between words [TAG] and [/TAG] ("foo 1" and "foo 2" in this case)?
UPD. for example i need to change nodeValue of each span between [TAG] and [/TAG]
Assuming that you only have one set of [TAG]..[/TAG] per node (as in, if your document has two sets they're within separate <p> elements or whatever), and that they're always siblings:
You can use preceding-sibling and following-sibling to select only elements which are preceded by a [TAG] text node and followed by a [/TAG] text node:
//span[preceding-sibling::text()[normalize-space(.) = "[TAG]"]][following-sibling::text()[normalize-space(.) = "[/TAG]"]]
A full PHP example:
$doc = new DOMDocument();
$doc->loadHTMLFile('test.xml');
$xpath = new DOMXPath($doc);
foreach ($xpath->query('//span[preceding-sibling::text()[normalize-space(.) = "[TAG]"]][following-sibling::text()[normalize-space(.) = "[/TAG]"]]') as $el) {
$el->nodeValue = 'Changed!';
}
echo $doc->saveXML();

Categories