Adding a block of XML as child of a SimpleXMLElement object - php

I have this SimpleXMLElement object with a XML setup similar to the following...
$xml = <<<EOX
<books>
<book>
<name>ABCD</name>
</book>
</books>
EOX;
$sx = new SimpleXMLElement( $xml );
Now I have a class named Book that contains info. about each book. The same class can also spit out the book info. in XML format akin the the above (the nested block).. example,
$book = new Book( 'EFGH' );
$book->genXML();
... will generate
<book>
<name>EFGH</name>
</book>
Now I'm trying to figure out a way by which I can use this generated XML block and append as a child of so that now it looks like... for example..
// Non-existent member method. For illustration purposes only.
$sx->addXMLChild( $book->genXML() );
...XML tree now looks like:
<books>
<book>
<name>ABCD</name>
</book>
<book>
<name>EFGH</name>
</book>
</books>
From what documentation I have read on SimpleXMLElement, addChild() won't get this done for you as it doesn't support XML data as tag value.

Two solutions. First, you do it with the help of libxml / DOMDocument / SimpleXML: you have to import your $sx object to DOM, create a DOMDocumentFragment and use DOMDocumentFragment::appendXML():
$doc = dom_import_simplexml($sx)->ownerDocument;
$fragment = $doc->createDocumentFragment();
$fragment->appendXML($book->genXML());
$doc->documentElement->appendChild($fragment);
// your original $sx is now already modified.
See the Online Demo.
You can also extend from SimpleXMLElement and add a method that is providing this. Using this specialized object then would allow you to create the following easily:
$sx = new MySimpleXMLElement($xml);
$sx->addXML($book->genXML());
Another solution is to use an XML library that already has this feature built-in like SimpleDOM. You grab SimpleDOM and you use insertXML(), which works like the addXMLChild() method you were describing.
include 'SimpleDOM.php';
$books = simpledom_load_string(
'<books>
<book>
<name>ABCD</name>
</book>
</books>'
);
$books->insertXML(
'<book>
<name>EFGH</name>
</book>'
);

Have a look at my code:
$doc = new DOMDocument();
$doc->loadXML("<root/>");
$fragment = $doc->createDocumentFragment();
$fragment->appendXML("<foo>text</foo><bar>text2</bar>");
$doc->documentElement->appendChild($fragment);
echo $doc->saveXML();
This modifies the XML document by adding an XML fragment. Online Demo.

Related

createCDATASection is undefined when generating XML

I'm trying to generate an xml file using PHP, with the the description element being placed on a CDATA.
<?php
$title = "Volvo";
$description = "this is a test description";
$xml = new SimpleXMLElement('<xml/>');
$track = $xml->addChild('blog');
$post = $track->addChild('post');
$post->addChild('title',$title);
$cdata = createDATASection($description);
$post->addChild('description',$cdata);
$xml->asXml();
?>
Am I using createDATASection correctly? I have also tried other ways but I am still not getting it.
createCDATASection() is a method of the DOMDocument. SimpleXML itself is limited. If you need that much control (like creating specific node types) you will have to use DOM. SimpleXML treats the XML as a tree of just elements. In DOM everything is a node, elements, texts, attributes, comments, ...
In DOM the create and the append are separate. You create an new node (of any type) with the corresponding method of DOMDocument then you append it using the method of the parent node. The append methods will return the node, so you can nest calls.
Here is your example source converted to DOM API calls:
$title = "Volvo";
$description = "this is a test description";
$document = new DOMDocument();
$xml = $document
->appendChild($document->createElement('xml'));
$blog = $xml
->appendChild($document->createElement('blog'));
$track = $blog
->appendChild($document->createElement('track'));
$post = $track
->appendChild($document->createElement('post'));
$post
->appendChild($document->createElement('title'))
->appendChild($document->createTextNode($title));
$post
->appendChild($document->createElement('description'))
->appendChild($document->createCDATASection($description));
$document->formatOutput = TRUE;
echo $document->saveXml();
Output:
<?xml version="1.0"?>
<xml>
<blog>
<track>
<post>
<title>Volvo</title>
<description><![CDATA[this is a test description]]></description>
</post>
</track>
</blog>
</xml>

How to get into into another child than 'firstChild' of XML file in PHP?

How can I get through another child of XML file than firstChild in PHP?
I have a code like this:
$root = $xmldoc->firstChild;
Can I simply get into second child or another?
A possible solution to your problem could be something like this. First your XML structure. You asked how to add an item node to the data node.
$xml = <<< XML
<?xml version="1.0" encoding="utf-8"?>
<xmldata>
<data>
<item>item 1</item>
<item>item 2</item>
</data>
</xmldata>
XML;
In PHP one possible solution is the DomDocument object.
$doc = new \DomDocument();
$doc->loadXml($xml);
// fetch data node
$dataNode = $doc->getElementsByTagName('data')->item(0);
// create new item node
$newItemNode = $doc->createElement('item', 'item 3');
// append new item node to data node
$dataNode->appendChild($newItemNode);
// save xml node
$doc->saveXML();
This code example is not tested. Have fun. ;)

Get all children from certain xml child element using SimpleXMLElement and xpath

I have xml like:
<root xmlns="urn:test:apis:baseComponents">
<books>
<book>
<name>50 shades of grey</name>
</book>
</books>
<disks>
<disk>
<name>Britney Spears</name>
</disk>
</disks>
</root>
And such php code:
$xml = new SimpleXMLElement($xml);
$books = $xml->books;
$disks = $xml->disks;
$disks->registerXPathNamespace('x', 'urn:test:apis:baseComponents');
$books->registerXPathNamespace('x', 'urn:test:apis:baseComponents');
$b_names = $books->xpath('//x:name');
b_names contains array with 2 values instead of 1. First holds books->book->name, second holds disks->disk->name.
Can you please explain what am I doing wrong and how could I find children of only one element?
The reason that I am using xpath instead of taking manually values using SimpleXMLElement, is that I don't know what value, which I want to search in advance.
Use $books->xpath('.//x:name') to search descendants of your $books variable and not descendants of the root node/document node (which the path //x:name does).

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>

Categories