Adding in new XML root node - php

I need to add in a new root node to the following XML
<?xml version="1.0"?>
<unit>
<source>
<id>ANCH02</id>
<uri>http://www.hamiltonisland.biz/tabid/339/Default.aspx</uri>
</source>
</unit>
to become
<?xml version="1.0"?>
<units>
<unit>
<source>
<id>ANCH02</id>
<uri>http://www.hamiltonisland.biz/tabid/339/Default.aspx</uri>
</source>
</unit>
</units>
How could I do this? It doesn't seem like SimpleXMLElement has this functionality. I have also looked at this DomNode example http://php.net/manual/en/domnode.insertbefore.php but it doesnt seem to be able to add in a new root node.

This seem to work
$units = $dom->createElement('units');
$units->appendChild($dom->documentElement);
$dom->appendChild($units);
DEMO

DOMDocument:
$yourDOMDOMDocument ... <--- already loaded XML
$doc = new DOMDocument();
$doc->appendChild($doc->createElement('Units'));
$doc->documentElement->appendChild($doc->importNode($yourDOMDocument->documentElement));
Or. if you have your XML as SimpleXMLElement already:
$yourSimpleXML ... <--- already loaded XML
$doc = new DOMDocument();
$doc->appendChild($doc->createElement('Units'));
$domnode = dom_import_simplexml($yourSimpleXML);
$doc->documentElement->appendChild($doc->importNode($domnode));
//if you want it back as SXE:
$newSimpleXMLElement = simplexml_import_dom($doc);

Related

How to count nodes of an xml file with php?

I know that maybe is a duplicate but doesn't works....
I have this xml
<?xml version="1.0" encoding="UTF-8"?><kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2">
<Document>
<name>EMSC - Last 2 Weeks earthquakes worldwide</name>
<Folder>
<name>2017 7 - 13</name>
</Folder>
<Folder>
<name>2017 7 - 12</name>
</Folder>
</Document>
</kml>
i want count node Folder, i tied this
$pars_emsc= simplexml_load_file('/file');
$count_folder_emsc= $pars_emsc-> Document -> getElementsByTagName('Folder')->length;
but doesn't work...
You can't use the simple XML parser as a DOM document. Quite simply getElementsByTagName doesn't exist in SimpleXML. Use this instead:
$pars_emsc = new DOMDocument( "1.0", "ISO-8859-15" );
$pars_emsc->load("/file");
$count_folder_emsc= $pars_emsc->getElementsByTagName("Document")[0]->getElementsByTagName('Folder')->length;
print_r($count_folder_emsc);
Alternatively just do:
$pars_emsc= simplexml_load_file("/file");
$count_folder_emsc= $pars_emsc-> Document -> Folder -> count();
Xpath expressions can count nodes, too.
$document = new DOMDocument();
$document->loadXml($xml);
$xpath = new DOMXpath($document);
$xpath->registerNamespace('kml', 'http://www.opengis.net/kml/2.2');
var_dump($xpath->evaluate('count(//kml:Folder)'));
Output:
float(2)
You XML uses a default namespace, so you have to register a prefix for it.

Set the namespace for an XML tag with PHP

I'd like to create an XML document with a very specific format. It should look similar to this:
<?xml version="1.0" encoding="UTF-8"?>
<ram:FLOW xmlns:ram=\"http://MY_LIBRARY\" xmlns:mar=\"http://ANOTHER_LIBRARY\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">
<Header>
<Source>Application1</Source>
<Time>2014-11-12T12:46:39</Time>
<Environment>TEST</Environment>
<Sequence>537</Sequence>
</Header>
<Data>
<mar:OC_DC>
<DC_elements>
<Unit>
<Unit_ID>089789</Unit_ID>
<State>active</State>
</Unit>
<Unit>
<Unit_ID>459008</Unit_ID>
<State>inactive</State>
</Unit>
</DC_elements>
</mar:OC_DC>
</Data>
</ram:FLOW>
I wrote a PHP/MySQL script to generate this document:
<?php
$xml = new SimpleXMLElement("<?xml version=\"1.0\" encoding=\"UTF-8\"?><ram:FLOW xmlns:ram=\"http://MY_LIBRARY\" xmlns:mar=\"http://ANOTHER_LIBRARY\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"></ram:FLOW>");
$header = $xml->addChild('Header');
$header->addChild('Source', $source);
$header->addChild('Time', $time);
$header->addChild('Environment', $env);
$header->addChild('Sequence', $sequence);
$data=$xml->addChild('Data');
$mar_oc_dc=$data->addChild('mar:OC_DC');
$dc_elements=$mar_oc_dc->addChild('DC_elements');
while($condition)
{
// some MySQL code here to extract unit_id and state
$unit=$dc_elements->addChild('Unit');
$unit_id=$unit->addChild('Unit_ID', $unit_id);
$state=$unit->addChild('State', $state);
}
$dom = new DOMDocument();
$dom->preserveWhiteSpace = FALSE;
$dom->formatOutput = TRUE;
$dom->loadXML($xml->asXML());
$handle = fopen("backup/" . $file_name . ".xml", "w");
fwrite($handle, $dom->saveXML());
fclose($handle);
?>
But the result was a little bit different from what I expected:
<?xml version="1.0" encoding="UTF-8"?>
<FLOW xmlns:ram=\"http://MY_LIBRARY\" xmlns:mar=\"http://ANOTHER_LIBRARY\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">
<Header>
<Source>Application1</Source>
<Time>2014-11-12T12:46:39</Time>
<Environment>TEST</Environment>
<Sequence>537</Sequence>
</Header>
<Data>
<OC_DC>
<DC_elements>
<Unit>
<Unit_ID>089789</Unit_ID>
<State>active</State>
</Unit>
<Unit>
<Unit_ID>459008</Unit_ID>
<State>inactive</State>
</Unit>
</DC_elements>
</OC_DC>
</Data>
</FLOW>
As you can see, the ram:FLOW tag became FLOW, and the mar:OC_DC tag became OC_DC.
I looked on Stack Overflow and other websites for a solution and didn't manage to find one. Could you please give me a hand with this?
Thank you in advance.
The xmlns:* attributes are namespace definitions (not libraries). The value of that attributes is a unique string that identifies the format/standard the elements belong to.
The attributes define a prefix for the unique string so that the XML document is smaller and more readable.
If you want to create an element (or attribute) inside a namespace you have to provide the namespace. In SimpleXMlElement the third argument is the namespace.
It seems to add the elements to the namespace of the parent node, if no namespace is provided. That means that you have to provide an empty string for any element without a namespace.
$root = new SimpleXMlElement('<ram:FLOW xmlns:ram="http://MY_LIBRARY" xmlns:mar="http://ANOTHER_LIBRARY"/>');
$root->addChild('header', null, '');
$data = $root->addChild('data', null, '');
$data->addChild('mar:OC_DC', null, 'http://ANOTHER_LIBRARY');
echo $root->asXml();
Output:
<?xml version="1.0"?>
<ram:FLOW xmlns:ram="http://MY_LIBRARY" xmlns:mar="http://ANOTHER_LIBRARY">
<header xmlns=""/>
<data xmlns="">
<mar:OC_DC/>
</data>
</ram:FLOW>
I haven't found a way to avoid the empty xmlns attributes.
DOM is more explicit. The create and append logic is separate.
const XMLNS_RAM = 'http://MY_LIBRARY';
const XMLNS_MAR = 'http://ANOTHER_LIBRARY';
$dom = new DOMDocument();
// appending an element with a namespace with define it if needed
$root = $dom->appendChild($dom->createElementNS(XMLNS_RAM, 'ram:FLOW'));
// setting the xmlns attribute explicit avoids the definition in descendant nodes
$root->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:mar', XMLNS_MAR);
$root->appendChild($dom->createElement('header'));
$data = $root->appendChild($dom->createElement('data'));
$data->appendChild($dom->createElementNS(XMLNS_MAR, 'mar:OC_DC'));
$dom->formatOutput = true;
echo $dom->saveXml();
Output:
<?xml version="1.0"?>
<ram:FLOW xmlns:ram="http://MY_LIBRARY" xmlns:mar="http://ANOTHER_LIBRARY">
<header/>
<data>
<mar:OC_DC/>
</data>
</ram:FLOW>

Append DOMDocument root element to another DOMDocument

I have 2 "DOMDocument" objects - $original and $additional. What I want is to take all children from $additional DOMDocument and append it to the end of $original document.
My plan was to take root element of the $additional document. I tried to use:
$root = $additional->documentElement;
$original->appendChild($root)
But I receive error that appendChild expect DOMNode object as an argument.
I tried to access each children of the document through:
$additional->childNodes->item(0);
But it returns object of DOMElement. Can you advice how to get object of DOMNode class? What is the most convenient way to provide this import operation?
$original XML looks like:
<?xml version="1.0" encoding="utf-8"?>
<Product>
<RecordReference>345345</RecordReference>
<NotificationType>03</NotificationType>
<NumberOfPages>100</NumberOfPages
</Product>
$additional XML looks like:
<?xml version="1.0" encoding="utf-8"?>
<MainSubject>
<SubjectScheme>10</SubjectScheme>
</MainSubject>
What I want to have:
<?xml version="1.0" encoding="utf-8"?>
<Product>
<RecordReference>345345</RecordReference>
<NotificationType>03</NotificationType>
<NumberOfPages>100</NumberOfPages>
<MainSubject>
<SubjectScheme>10</SubjectScheme>
</MainSubject>
</Product>
A DOMElement is a DOMNode, DOMNode is the superclass. Here are several child classes for element, text and other nodes. Just iterate, import and append them.
$targetDom = new DOMDocument();
$targetDom->loadXML('<root/>');
$sourceDom = new DOMDocument();
$sourceDom->loadXml('<items><child/>TEXT</items>');
foreach ($sourceDom->documentElement->childNodes as $child) {
$targetDom->documentElement->appendChild(
$targetDom->importNode($child, TRUE)
);
}
This works with the document element, too.
$targetDom = new DOMDocument();
$targetDom->loadXML('<root/>');
$sourceDom = new DOMDocument();
$sourceDom->loadXml('<items><child/>TEXT</items>');
$targetDom->documentElement->appendChild(
$targetDom->importNode($sourceDom->documentElement, TRUE)
);
echo $targetDom->saveXml();
DOMDocument::importNode() creates a copy of the provided node in the context of the document. Only nodes belonging to a document can be appended to it.

Hide XML declaration in files generated using PHP

I was tesing with a simple example of how to display XML in browser using PHP and found this example which works good
<?php
$xml = new DOMDocument("1.0");
$root = $xml->createElement("data");
$xml->appendChild($root);
$id = $xml->createElement("id");
$idText = $xml->createTextNode('1');
$id->appendChild($idText);
$title = $xml->createElement("title");
$titleText = $xml->createTextNode('Valid');
$title->appendChild($titleText);
$book = $xml->createElement("book");
$book->appendChild($id);
$book->appendChild($title);
$root->appendChild($book);
$xml->formatOutput = true;
echo "<xmp>". $xml->saveXML() ."</xmp>";
$xml->save("mybooks.xml") or die("Error");
?>
It produces the following output:
<?xml version="1.0"?>
<data>
<book>
<id>1</id>
<title>Valid</title>
</book>
</data>
Now I have got two questions regarding how the output should look like.
The first line in the xml file '', should not be displayed, that is it should be hidden
How can I display the TextNode in the next line. In total I am exepecting an output in this fashion
<data>
<book>
<id>1</id>
<title>
Valid
</title>
</book>
</data>
Is that possible to get the desired output, if so how can I accomplish that.
Thanks
To skip the XML declaration you can use the result of saveXML on the root node:
$xml_content = $xml->saveXML($root);
file_put_contents("mybooks.xml", $xml_content) or die("cannot save XML");
Please note that saveXML(node) has a different output from saveXML().
First question:
here is my post where all usable threads with answers are listed: How do you exclude the XML prolog from output?
Second question:
I don't know of any PHP function that outputs text nodes like that.
You could:
read xml using DomDocument and save each node as string
iterate trough nodes
detect text nodes and add new lines to xml string manually
At the end you would have the same XML with text node values in new line:
<node>
some text data
</node>

SimpleXML get node value

Say I have this following XML structure:
<?xml version="1.0" encoding="UTF-8"?>
<main>
<parent>
<child1>some value</child1>
<child2>another value</child2>
</parent>
</main>
I made a variable of the XML and now I want to get the values of child1, so I use SimpleXML:
$xml = new SimpleXMLElement($xml);
$this->xmlcode = (string) $xml->main->parent->child1;
But I get this message: Notice: Trying to get property of non-object in /x.php on line x
I also tried it with $xml->parent->child1, but no success.
Anyone??
$xml = new SimpleXMLElement($xml);
$this->xmlcode = (string) $xml->parent[0]->child1;
A good example of using XPath with php for the SimpleXMLElement can be found here
http://www.php.net/manual/en/class.simplexmlelement.php#95229
// Find the topmost element of the domDocument
$xpath = new DOMXPath($xml);
$child1 = $xpath->evaluate('/main/parent/child1')->item(0);
Variant for xpath (Also how to get content of node having dashes in name):
<?xml version="1.0" encoding="UTF-8"?> <main>
<parent>
<child-1>some value</child-1>
<child-2>another value</child-2>
</parent> </main>
$xml = simplexml_load_string($content);
$node_value= (string)$xml->xpath('parent/child-1')[0];
result of $node_value:
"some value"

Categories