I have a requirement of generating a master XML file by integrating some XML files. It can be achieved if we have different child XML files stored at some where on the disk. But here in my case child XML files are dynamic XML data. They are not stored any where but generating dynamically. So how can i insert these child XML data to generate a master XML file.
My input is :
<root>
<element id="1">
</element>
<element id="2">
</element>
</root>
My output would be:
<root>
<element id="1">
<section>
<record>12</record>
</section>
</element>
<element id="2">
<section>
<input>menu</input>
</section>
</element>
</root>
Here in the above output XML data(<section><record>12</record></section>) should come from PHP variable.
You have to pass the two "dynamic" XML documents as parameters to the transformation.
Each of the "dynamic documents" should be already parsed as an XML document (in other XSLT processor APIs there is a method -- for example XmlDocument.LoadXml() -- that takes a string and parses it as XML and creates a (parsed) XML document).
Read your XSLT processor documentation to learn what API to use for passing external parameters to a transformation
This is an old thread, but no satisfying answer has been given; recently I was confronted with a similar situation and I believe the solution is general enough to apply to problems like this one.
Essentially: PHP and the XSLT processor communicate via DOMNode objects (parameters and return values). Therefore, it is possible to construct a DOMNode object using PHP, returning it at the request of the XSLT processor.
Given the above example, we would have the following XSLT:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:php="http://php.net/xsl">
<xsl:template match="/root">
<root>
<xsl:apply-templates select="element" />
</root>
</xsl:template>
<xsl:template match="element">
<element>
<!-- Pass the selected id attributes to a PHP callback,
then literally include the XML as returned from PHP.
Alternatively, one could use xsl:apply-templates
to further transform the result. -->
<xsl:copy-of select="php:function('xslt_callback', #id)" />
</element>
</xsl:template>
</xsl:stylesheet>
And the PHP function (this function should be exporteded with the registerPHPFunctions method [see php manual]) would be:
/**
* #param DOMAttr[] $attr_set An array of DOMAttr objects,
* passed by the XSLT processor.
* #return DOMElement The XML to be inserted.
*/
function xslt_callback ($attr_set) {
$id = $attr_set[0]->value;
return new DOMElement('section', $id); //whatever operation you fancy
}
Resulting in the following XML:
<root>
<element>
<section>1</section>
</element>
<element>
<section>2</section>
</element>
</root>
The php function xslt_callback can do whatever it likes with the selected id. We assume in this example that $attr_set always contains exactly one selected attribute. Depending on the situation, it might be advisable to perform some range or type checking; here however, this would only needlessly complicate the example skeleton.
Note: simply returning an XML string from PHP would result in < and > tags to be inserted for every < and >.
I'm still wondering what you wanted from php
I assumed that you could reprint and throw something like the following in a loop but I'm happy to dig further to the point of making something website front end with dynamic xml generation. Changing in this page what scalors option does by switching values is distinct in php there's lots that could be done please tell me further what you Want
<?php
$i=1;
$option = "default";
$elemental = 1;
$StrRecord = "12";
$StrInput = "menu";
$domdoc = new DOMDocument();
$domdoc->preserveWhiteSpaces = false;
$domdoc->load(realpath('xmlfile.xml'));
$root = new DOMElement('root');
$element = new DOMElement('element');
$section = new DOMElement('section');
$record = new DOMElement('record', $StrRecord);
$elementi = new DOMElement('element');
$sectioni = new DOMElement('section');
$inputi = new DOMElement('input', $StrInput);
$elementid= new DOMAttr('id', $elemental+1);
$elementidi= new DOMAttr('id', $elemental+1);
$domdoc->appendChild($root);
$root->appendChild($element);
$element->appendChild($section);
$section->appendChild($record);
$root->appendChild($elementi);
$elementi->appendChild($sectioni);
$sectioni->appendChild($inputi);
$sectioni->appendChild($inputi);
$element->appendChild($elementid);
$elementi->appendChild($elementidi);
$domdoc->formatOutput = true;
$domdoc->save('xmlfile.xml');
$xmldoc= new DOMDocument();
$xmldoc->load(realpath('xmlfile.xml'));
$xpath = new DOMXpath($xmldoc);
switch($option){
case "record":
$query = ('/root/element/section/record');
break;
case "input":
$query = ('/root/element/section/input');
break;
case "default":
$query = ('/root/element[#id="'. $i.'"]/');
break;
}
$nodeList = $xpath->query($query);
foreach($nodeList as $node){
echo($node->nodeValue);
}
?>
Related
I am looking to try and change a XML output so that the element structure changes and some CDATA becomes an attribute rather than an <element>
Given the XML stack.xml:
<root>
<item>
<name>name</name>
<type>Type</type>
<dateMade>Datemade</dateMade>
<desc>Desc</desc>
</item>
....(more Items)...
</root>
I would like to change the XML output to stacksaved.xml:
<root>
<item>
<name>name</name>
<Itemtype type="Type">
<Itemdate dateMade="Datemade">
<desc>Desc</desc>
</Itemdate>
<Itemtype>
</item>
....(next item)....
</root>
So far my PHP DOM looks like this:
<?php
//create and load
$doc = new DOMDocument();
$doc->load('stack.xml');
$types=$doc->getElementsByTagName("type");
foreach ($types as $type)
{
$attribute=$doc->getElementsByTagName("type");
$doc->getElementsByTagName("type").setAttribute("$attribute");
}
$doc->save('stacksaved.xml'); //save the final results into xml file
?>
I keep getting the error: Fatal error: Call to undefined function setAttribute() and the document is not saved or edited in anyway. I am really new to DOM/PHP and would greatly appreciate any advice!
How would I go about changing the child structure and the element to the desired output?
Thanks as always for the read!
EDIT: Parfait gave a great explanation and showed the great power of XSLT but I am trying to get this to run using pure php only as a learning exercise for php/DOM. Can anyone help with converting this using PHP only?
For a pure PHP DOM solution, consider creating a new DOMDocument iterating over values of old document using createElement, appendChild, and setAttribute methods. The multiple nested if logic is needed to check existence of a node before creating elements with items' node values, otherwise Undefined Warnings are raised.
$doc = new DOMDocument();
$doc->load('stack.xml');
// INITIALIZE NEW DOM DOCUMENT
$newdoc = new DOMDocument('1.0', 'UTF-8');
$newdoc->preserveWhiteSpace = false;
$newdoc->formatOutput = true;
// APPEND ROOT
$root= $newdoc->appendChild($newdoc->createElement("root"));
$items=$doc->getElementsByTagName("item");
// ITERATIVELY APPEND ITEM AND CHILDREN
foreach($items as $item){
$ItemNode = $newdoc->createElement("item");
$root->appendChild($ItemNode);
if (count($item->getElementsByTagName("name")->item(0)) > 0) {
$ItemNode->appendChild($newdoc->createElement('name', $item->getElementsByTagName("name")->item(0)->nodeValue));
}
if (count($item->getElementsByTagName("type")->item(0)) > 0) {
$ItemtypeNode = $ItemNode->appendChild($newdoc->createElement('Itemtype'));
$ItemtypeNode->setAttribute("type", $item->getElementsByTagName("type")->item(0)->nodeValue);
if (count($item->getElementsByTagName("dateMade")->item(0)) > 0) {
$ItemdateNode = $ItemtypeNode->appendChild($newdoc->createElement('Itemdate'));
$ItemdateNode->setAttribute("dateMade", $item->getElementsByTagName("dateMade")->item(0)->nodeValue);
if (count($item->getElementsByTagName("desc")->item(0)) > 0) {
$ItemdateNode->appendChild($newdoc->createElement('desc', $item->getElementsByTagName("desc")->item(0)->nodeValue));
}
}
}
}
// ECHO AND SAVE NEW DOC TREE
echo $newdoc->saveXML();
$newdoc->save($cd.'/ItemTypeDateMade_dom.xml');
Output
<?xml version="1.0" encoding="UTF-8"?>
<root>
<item>
<name>name</name>
<Itemtype type="Type">
<Itemdate dateMade="Datemade">
<desc>Desc</desc>
</Itemdate>
</Itemtype>
</item>
</root>
As mentioned in previous answer, here requires for and nested if that would not be required with XSLT. In fact, using microtime, we can compare script runtimes. Below enlargen stack.xml:
$time_start = microtime(true);
...
echo "Total execution time in seconds: " . (microtime(true) - $time_start) ."\n";
At 1,000 node lines, XSLT proves faster than DOM:
# XSLT VERSION
Total execution time in seconds: 0.0062189102172852
# DOM VERSION
Total execution time in seconds: 0.013695955276489
At 2,000 node lines, XSLT still remains about 2X faster than DOM:
# XSLT VERSION
Total execution time in seconds: 0.014697074890137
# DOM VERSION
Total execution time in seconds: 0.031282186508179
At 10,000 node lines, XSLT now becomes slightly faster than DOM. Reason for DOM's catch up might be due to the memory inefficiency XSLT 1.0 maintains for larger files, especially (> 100 MB). But arguably here for this use case, the XSLT approach is an easier PHP script to maintain and read:
# XSLT VERSION
Total execution time in seconds: 0.27568817138672
# DOM VERSION
Total execution time in seconds: 0.37149095535278
Consider XSLT, the special-purpose, declarative language designed to transform XML documents. PHP can run XSLT 1.0 scripts with the php-xsl extension (be sure to enable it in .ini file). With this approach, you avoid any need of foreach looping or if logic.
XSLT (save as .xsl file)
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output version="1.0" encoding="UTF-8" indent="yes" />
<xsl:template match="root">
<xsl:copy>
<xsl:apply-templates select="item"/>
</xsl:copy>
</xsl:template>
<xsl:template match="item">
<xsl:copy>
<xsl:copy-of select="name"/>
<Itemtype type="{type}">
<Itemdate dateMade="{dateMade}">
<xsl:copy-of select="desc"/>
</Itemdate>
</Itemtype>
</xsl:copy>
</xsl:template>
</xsl:transform>
PHP
$doc = new DOMDocument();
$doc->load('stack.xml');
$xsl = new DOMDocument;
$xsl->load('XSLTScript.xsl');
// CONFIGURE TRANSFORMER
$proc = new XSLTProcessor;
$proc->importStyleSheet($xsl);
// PROCESS TRANSFORMATION
$newXML = $proc->transformToXML($doc);
// ECHO STRING OUTPUT
echo $newXML;
// SAVE OUTPUT TO FILE
file_put_contents('Output.xml', $newXML);
Output
<?xml version="1.0" encoding="UTF-8"?>
<root>
<item>
<name>name</name>
<Itemtype type="Type">
<Itemdate dateMade="Datemade">
<desc>Desc</desc>
</Itemdate>
</Itemtype>
</item>
</root>
I've been battling with this all day :(
Although I found answers for similar questions they don't update an existing XML, they create a new XML.
Any help would be very much appreciated.
This is the XML I'm loading and trying to sort just the images->image nodes:
<?xml version="1.0"?>
<stuff>
<other_nodes>
</other_nodes>
<images>
<image><sorted_number><![CDATA[1]]></sorted_number></image>
<image><sorted_number><![CDATA[3]]></sorted_number></image>
<image><sorted_number><![CDATA[2]]></sorted_number></image>
</images>
</stuff>
//load the xml into a var
$theXML = //load the xml from the database
$imageNode = $theXML->images;
//sort the images into sorted order
$d = $imageNode;
// turn into array
$e = array();
foreach ($d->image as $image) {
$e[] = $image;
}
// sort the array
usort($e, function($a, $b) {
return $a->sorted_number - $b->sorted_number;
});
//now update the xml in the correct order
foreach ($e as $node) {
//???unsure how to update the images node in my XML
}
SimpleXML is too simple for your task. There is no easy way to reorder nodes. Basically, after your sorting routine, you have to reconstruct <image> nodes, but you have CDATA inside, and SimpleXML can't directly add CDATA value.
If you want try by this way, here you can find a cool SimpleXML class extension that add CDATA property, but also this solution use DOMDocument.
Basically, IMHO, since every solution require DOM, the best way is to use directly DOMDocument and — eventually — (re)load XML with SimpleXML after transformation:
$dom = new DOMDocument();
$dom->loadXML( $xml, LIBXML_NOBLANKS );
$dom->formatOutput = True;
$images = $dom->getElementsByTagName( 'image' );
/* This is the same as your array conversion: */
$sorted = iterator_to_array( $images );
/* This is your sorting routine adapted to DOMDocument: */
usort( $sorted, function( $a, $b )
{
return
$a->getElementsByTagName('sorted_number')->item(0)->nodeValue
-
$b->getElementsByTagName('sorted_number')->item(0)->nodeValue;
});
/* This is the core loop to “replace” old nodes: */
foreach( $sorted as $node ) $images->item(0)->parentNode->appendChild( $node );
echo $dom->saveXML();
ideone demo
The main routine add sorted nodes as child to existing <images> node. Please note that there is no need to pre-remove old childs: since we refer to same object, by appending a node in fact we remove it from its previous position.
If you want obtain a SimpleXML object, at the end of above code you can append this line:
$xml = simplexml_load_string( $dom->saveXML() );
Consider an XSLT solution using its <xsl:sort>. As information, XSLT (whose script is a well-formed XML file) is a declarative, special-purpose programming language (same type as SQL), used specifically to manipulate XML documents and sorting is one type of manipulation. Often used as a stylesheet to render XML content into HTML, XSLT is actually a language.
Most general-purpose languages including PHP (xsl extension), Python (lxml module), Java (javax.xml), Perl (libxml), C# (System.Xml), and VB (MSXML) maintain XSLT 1.0 processors. And various external executable processors like Xalan and Saxon (the latter of which can run XSLT 2.0 and recently 3.0) are also available -which of course PHP can call with exec(). Below embeds XSLT as a string variable but can very easily be loaded from an external .xsl or .xslt file.
// Load the XML source and XSLT file
$doc = new DOMDocument();
$doc->loadXML($xml);
$xsl = new DOMDocument;
$xslstr = '<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output version="1.0" encoding="UTF-8" indent="yes"
cdata-section-elements="sorted_number" />
<xsl:strip-space elements="*"/>
<!-- IDENTITY TRANSFORM (COPIES ALL CONTENT AS IS) -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!-- SORT IMAGE CHILDREN IN EACH IMAGES NODE -->
<xsl:template match="images">
<xsl:copy>
<xsl:apply-templates select="image">
<xsl:sort select="sorted_number" order="ascending" data-type="number"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:transform>';
$xsl->loadXML($xslstr);
// Configure the processor
$proc = new XSLTProcessor;
$proc->importStyleSheet($xsl);
// Transform XML source
$newXml = $proc->transformToXML($doc);
echo $newXml;
Result (notice <![CData[]]> being preserved)
<?xml version="1.0" encoding="UTF-8"?>
<stuff>
<other_nodes/>
<images>
<image>
<sorted_number><![CDATA[1]]></sorted_number>
</image>
<image>
<sorted_number><![CDATA[2]]></sorted_number>
</image>
<image>
<sorted_number><![CDATA[3]]></sorted_number>
</image>
</images>
</stuff>
Before going deeper, is a save of the sorted state really necessary? Like in a database, you can always sort items when retrieving them, same here with the code you have already written.
That said, "updating" in your case means delete all <image> nodes and add them back in order.
Update:
see fusion3k's answer, that it is not necessary to delete nodes, but just append them. I'd suggest to go with his solution.
You are using SimpleXml, which does not provide methods for copying nodes. You will need to re-create every single node, child-node, attribute.
Your XML looks simple, but I guess it is an example and your real XML is more complex. Then rather use DOM and its importNode() method, which can copy complex nodes, including all their attributes and children.
On the other hand, SimpleXml to me feels much easier, so I combine both:
$xml = simplexml_load_string($x); // assume XML in $x
$images = $xml->xpath("/stuff/images/image");
usort($images, function ($a, $b){
return strnatcmp($a->sorted_number, $b->sorted_number);
});
Comments:
xpath() is a quick way to get all items into an array of SimpleXml objects.
$images is sorted now, but we can't delete the original nodes, because $images holds references to these nodes.
This is why we need to save $images to a new, temporary document.
$tmp = new DOMDocument('1.0', 'utf-8');
$tmp->loadXML("<images />");
// add image to $tmp, then delete it from $xml
foreach($images as $image) {
$node = dom_import_simplexml($image); // make DOM from SimpleXml
$node = $tmp->importNode($node, TRUE); // import and append in $tmp
$tmp->getElementsByTagName("images")->item(0)->appendChild($node);
unset($image[0]); // delete image from $xml
}
Comments:
using DOM now, because I can copy nodes with importNode()
at this point, $tmp has all the <image> nodes in the desired order, $xml has none.
To copy nodes back from $tmp to $xml, we need to import $xml into DOM:
$xml = dom_import_simplexml($xml)->ownerDocument;
foreach($tmp->getElementsByTagName('image') as $image) {
$node = $xml->importNode($image, TRUE);
$xml->getElementsByTagName("images")->item(0)->appendChild($node);
}
// output...
echo $xml->saveXML();
see it in action: https://eval.in/535800
If I have the following structure in an XML File;
<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.008.001.02" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<CstmrDrctDbtInitn>
<GrpHdr>
<MsgID>rBYEqfjzEU</MsgID>
<CreDtTm>2014-07-01T12:36:15</CreDtTm>
<NbOfTxs>2</NbOfTxs>
<CtrlSum>400.4</CtrlSum>
<InitgPty>
<Id>
<PrvtId>
<Othr>
<Id>IA1234567</Id>
</Othr>
</PrvtId>
</Id>
</InitgPty>
</GrpHdr>
</CstmrDrctDbtInitn>
</Document>
The above code represents one transaction between the <CstmrDrctDbtInitn> and <\CstmrDrctDbtInitn> tags. This file will be appended to include more transactions which will all start and end with a <CstmrDrctDbtInitn> and <\CstmrDrctDbtInitn> tags. I need to count the number of transactions in the file, so i basically need to count the number of <CstmrDrctDbtInitn> tags in the file. Any suggestions? Sorry if I am explaining this badly, confused!
I altered the following PHP code as suggested but no luck :(
$filename = date('Y-W').'.xml'; //2014-26.xml
//Check if a file exists
if (file_exists($filename))
{
$dom = simplexml_load_string($xml);
global $NumberTransactions;
$NumberTransactions = count($dom->CstmrDrctDbtInitn);
// call xml appendFile function
appendFile($filename);
}
else
{
// call xml createFile function
createFile($filename);
}
Assuming you mean to count the number of child nodes under GrpHdr - you could use SimpleXML:
$xml = '<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.008.001.02" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<CstmrDrctDbtInitn>
<GrpHdr>
<MsgID>rBYEqfjzEU</MsgID>
<CreDtTm>2014-07-01T12:36:15</CreDtTm>
<NbOfTxs>2</NbOfTxs>
<CtrlSum>400.4</CtrlSum>
<InitgPty>
<Id>
<PrvtId>
<Othr>
<Id>IA1234567</Id>
</Othr>
</PrvtId>
</Id>
</InitgPty>
</GrpHdr>
</CstmrDrctDbtInitn>
</Document>';
$dom = simplexml_load_string($xml);
// var_dump($dom->CstmrDrctDbtInitn->GrpHdr);
echo count($dom->CstmrDrctDbtInitn->GrpHdr->children());
If you meant a variation you should be able to get a good clue of the structure by uncommenting that var_dump line and working through the object structure.
If you meant something else - give us a clue by telling us in detail what you wanted the count value to be based on your example data.
===================== UPDATED FOLLOWING CLARIFICATION BELOW=============
To count the number of CstmrDrctDbtInitn groups you can use the original example, but instead the count line would be:
echo count($dom->CstmrDrctDbtInitn);
DOMXpath::evaluate() can use Xpath to count the nodes.
// create and load
$dom= new DOMDocument();
$dom->loadXml($xml);
$xpath = new DOMXpath($dom);
// register an alias/prefix for the namespace
$xpath->registerNamespace('iso', 'urn:iso:std:iso:20022:tech:xsd:pain.008.001.02');
// get the count
var_dump($xpath->evaluate('count(//iso:CstmrDrctDbtInitn)'));
Demo: https://eval.in/169122
I'm trying to get html tags that start with uppercase using DOMDocument in PHP 5.3.
I'm using a php function registered in XPath to test it, but the function is receiving as first parameters tagNames in lowercase.
The xml:
<test>
<A>Match this</A>
<b>Dont match this</b>
</test>
The php function:
registerPhpFunctions - phpDoc
...
public function isUpper($name) {
return (bool)preg_match('/^[A-Z]/', $name);
}
...
Ant this is the Xpath:
//*[php:function("\Cdr\Dom\DOMXPath::isUpper", name())]
the function isUpper receives $name in lowercase so it don't works.
My questions are:
Why isn't case sensitive?
There is a better way to do this?
Load the code as XML and not HTML. The HTML is not case-sensitive.
$xmlDoc->loadXML('<html>');
instead of:
$xmlDoc->loadHTML('<html>');
A complete working example (test.php):
$doc = new DOMDocument;
$doc->load('test.xml');
$xpath = new DOMXPath($doc);
$xpath->registerNamespace("php", "http://php.net/xpath");
$xpath->registerPHPFunctions("isUpper");
function isUpper($name) {
return (bool)preg_match('/^[A-Z]/', $name);
}
$els = $xpath->query('//*[php:function("isUpper", name())]');
foreach ($els as $el) {
echo $el->nodeValue . "\n";
}
test.xml:
<test>
<A>Match this</A>
<b>Dont match this</b>
</test>
Output:
lwburk$ php test.php
Match this
Use this one-liner:
//*[contains('ABCDEFGHIJKLMNOPQRSTUVWXYZ', substring(name(),1,1))]
this selects any element in the XML document, the first character of whose name is contained in the string of all capital letters.
XSLT - based verification:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:copy-of select=
"//*[contains('ABCDEFGHIJKLMNOPQRSTUVWXYZ', substring(name(),1,1))]"/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the provided XML document:
<test>
<A>Match this</A>
<b>Dont match this</b>
</test>
the XPath expression is evaluated and the selected nodes (in this case just one) are copied to the output:
<A>Match this</A>
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>