I want to create some XML dynamically and I wonder how I could add an XMLElement to an XMLElement?
Here's my code:
$table = new SimpleXMLElement("<table></table>");
$tableRow = new SimpleXMLElement("<tr></tr>");
$count = count($this->dataSource->columns);
for ($i = 0; $i < $count; $i++)
{
$tableRow->addChild("<th></th>","Hi!")
}
$table->addChild($tableRow); // Not good, but this is what I want to do.
Take a look at this: Add Children with SimpleXML
Example:
<?php
$xml = <<<XML
<books>
<book title="Fahrenheit 451" author="Ray Bradbury"/>
<book title="Stranger in a Strange Land" author="Robert Heinlein"/>
</books>
XML;
$sxe = new SimpleXMLElement($xml);
$new_book = $sxe->addChild('book');
$new_book->addAttribute('title', '1984');
$new_book->addAttribute('author', 'George Orwell');
echo $sxe->asXML();
?>
I haven't found the answer to my question.
I've coded a class which inherit from SimpleXmlElement and added a method "addChildElement(XElement $element)". The method take an XElement in parameter and add it (and its childs) recursively.
I don't know if this is the best way to do it but it seems to work pretty well for me.
Tell me if something is wrong with this function.
Note that you can't modify an XElement after you've added it to the xml, but I'm working on it.
class XElement extends SimpleXMLElement
{
/**
* Add an XElement to an XElement.
* #param XElement $element
* #return void
*/
public function addChildElement(XElement $element)
{
// TODO Handle namespaces
$addedElement = $this->addChild($element->getName(), (string)$element);
foreach ($element->children() as $node)
{
$addedElement->addChildElement($node);
}
}
}
How to use:
$tableRow = new XElement("<tr></tr>");
$asd = new XElement("<tr2></tr2>");
$asd2 = new XElement("<tr3></tr3>");
$asd->addChildElement($asd2);
$tableRow->addChildElement($asd);
This wont work, but I'm working on it:
$tableRow = new XElement("<tr></tr>");
$asd = new XElement("<tr2></tr2>");
$asd2 = new XElement("<tr3></tr3>");
$tableRow->addChildElement($asd);
$asd->addChildElement($asd2);
Expanding off of the accepted answer I created a static method for more of a XElement from C# feel. It also copied attributes when adding the child XElement node.
class XElement extends SimpleXMLElement
{
/**
* Add an XElement to an XElement.
*
* #param XElement $element A SimpleXmlElement
*
* #return void
*/
public function add(XElement $element)
{
// TODO Handle namespaces
$addedElement = $this->addChild($element->getName(), (string)$element);
$this->_copyAttributes($element, $addedElement);
foreach ($element->children() as $node) {
$addedElement->add($node);
}
}
private function _copyAttributes($from, $to)
{
foreach ($from->attributes() as $n => $v) {
$to->addAttribute($n, $v);
}
}
public static function Node(string $name, $content, $attributes = null)
{
$content_type = gettype($content);
$element = null;
if ($content_type == 'string') {
$element = new XElement("<$name />");
$element[0] = $content;
} else {
if (substr($name, 0, 1) === "<") {
$element = new XElement($name);
} else {
$element = new XElement("<$name />");
}
if ($content_type == 'object' && get_class($content) == 'XElement') {
$element->add($content);
} else if ($content_type == 'array') {
foreach ($content as $c) {
$element->add($c);
}
}
}
if (!empty($attributes)) {
foreach ($attributes as $n => $v) {
$element->addAttribute($n, $v);
}
}
return $element;
}
}
To use:
//auto close tags, add content text
$xml = XElement::Node('cXML', 'nothing');
//nested elements
$xml = XElement::Node('cXML',
XElement::Node('Header',
XElement::Node('Data', 'Your Text Here')
)
);
//attributes
$xml = XElement::Node('cXML',
XElement::Node('Header', '', ['type' => 'no_data'])
);
//multiple nodes (sibling nodes)
$header = XElement::Node('Header', '', ['type' => 'no_data']);
$request = XElement::Node('Request',
XElement::Node('Order',
//inline array
[
XElement::Node('Item',
XElement::Node('Qty', '1')
),
XElement::Node('Item',
XElement::Node('Qty', '2')
),
]
),
['type' => 'data']
);
$nodes = [ $header, $request ];
$xml = XElement::Node('cXML', $nodes);
Output:
<!-- auto close tags, add content text -->
<?xml version="1.0"?>
<cXML>nothing</cXML>
<!-- nested elements -->
<?xml version="1.0"?>
<cXML>
<Header>
<Data>Your Text Here</Data>
</Header>
</cXML>
<!-- attributes -->
<?xml version="1.0"?>
<cXML>
<Header type="no_data"/>
</cXML>
<!-- multiple nodes (sibling nodes) -->
<?xml version="1.0"?>
<cXML>
<Header type="no_data"/>
<Request type="data">
<Order>
<Item>
<Qty>1</Qty>
</Item>
<Item>
<Qty>2</Qty>
</Item>
</Order>
</Request>
</cXML>
Related
I have tried various methods as seen in here
and in here and many more.
I even tried the function in here.
The XML looks something like this:
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing"><s:Header><a:Action s:mustUnderstand="1">http://tempuri.org/IFooEntryOperation/SaveFooStatusResponse</a:Action></s:Header><s:Body><SaveFooStatusResponse xmlns="http://htempuri.org/"><SaveFooStatusResult xmlns:b="http://schemas.datacontract.org/2004/07/FooAPI.Entities.Foo" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><b:AWBNumber>999999999</b:AWBNumber><b:IsError>true</b:IsError><b:Status><b:FooEntryStatus><b:StatusCode>Foo_ENTRY_FAILURE</b:StatusCode><b:StatusInformation>InvalidEmployeeCode</b:StatusInformation></b:FooEntryStatus></b:Status></SaveFooStatusResult></SaveFooStatusResponse></s:Body></s:Envelope>
And here's one example of my code (I have a dozen variations):
$ReturnData = $row["ReturnData"]; // string frm a database
if (strpos($ReturnData, "s:Envelope") !== false){
$ReturnXML = new SimpleXMLElement($ReturnData);
$xml = simplexml_load_string($ReturnXML);
$StatusCode = $xml["b:StatusCode"];
echo "<br>StatusCode: " . $StatusCode;
$IsError = $xml["b:IsError"];
echo "<br>IsError: " . $IsError;
}
Another option I tried:
$test = json_decode(json_encode($xml, 1); //this didn't work either
I either get an empty array or I get errors like:
"Fatal error: Uncaught exception 'Exception' with message 'String
could not be parsed as XML"
I have tried so many things, I may lost track of where my code is right now. Please help - I am really stuck...
I also tried:
$ReturnXML = new SimpleXMLElement($ReturnData);
foreach( $ReturnXML->children('b', true)->entry as $entries ) {
echo (string) 'Summary: ' . simplexml_load_string($entries->StatusCode->children()->asXML(), null, LIBXML_NOCDATA) . "<br />\n";
}
Method 1.
You can try the below code snippet to parse it an array
$p = xml_parser_create();
xml_parse_into_struct($p, $xml, $values, $indexes);// $xml containing the XML
xml_parser_free($p);
echo "Index array\n";
print_r($indexes);
echo "\nVals array\n";
print_r($values);
Method 2.
function XMLtoArray($xml) {
$previous_value = libxml_use_internal_errors(true);
$dom = new DOMDocument('1.0', 'UTF-8');
$dom->preserveWhiteSpace = false;
$dom->loadXml($xml);
libxml_use_internal_errors($previous_value);
if (libxml_get_errors()) {
return [];
}
return DOMtoArray($dom);
}
function DOMtoArray($root) {
$result = array();
if ($root->hasAttributes()) {
$attrs = $root->attributes;
foreach ($attrs as $attr) {
$result['#attributes'][$attr->name] = $attr->value;
}
}
if ($root->hasChildNodes()) {
$children = $root->childNodes;
if ($children->length == 1) {
$child = $children->item(0);
if (in_array($child->nodeType,[XML_TEXT_NODE,XML_CDATA_SECTION_NODE]))
{
$result['_value'] = $child->nodeValue;
return count($result) == 1
? $result['_value']
: $result;
}
}
$groups = array();
foreach ($children as $child) {
if (!isset($result[$child->nodeName])) {
$result[$child->nodeName] = DOMtoArray($child);
} else {
if (!isset($groups[$child->nodeName])) {
$result[$child->nodeName] = array($result[$child->nodeName]);
$groups[$child->nodeName] = 1;
}
$result[$child->nodeName][] = DOMtoArray($child);
}
}
}
return $result;
}
You can get an array using print_r(XMLtoArray($xml));
I don't know how you would do this using SimpleXMLElement but judging by the fact you have tried so many things I trust that the actual method employed is not important so you should therefore find the following, which uses DOMDocument and DOMXPath, of interest.
/* The SOAP response */
$strxml='<?xml version="1.0" encoding="UTF-8"?>
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
<s:Header>
<a:Action s:mustUnderstand="1">http://tempuri.org/IFooEntryOperation/SaveFooStatusResponse</a:Action>
</s:Header>
<s:Body>
<SaveFooStatusResponse xmlns="http://htempuri.org/">
<SaveFooStatusResult xmlns:b="http://schemas.datacontract.org/2004/07/FooAPI.Entities.Foo" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<b:AWBNumber>999999999</b:AWBNumber>
<b:IsError>true</b:IsError>
<b:Status>
<b:FooEntryStatus>
<b:StatusCode>Foo_ENTRY_FAILURE</b:StatusCode>
<b:StatusInformation>InvalidEmployeeCode</b:StatusInformation>
</b:FooEntryStatus>
</b:Status>
</SaveFooStatusResult>
</SaveFooStatusResponse>
</s:Body>
</s:Envelope>';
/* create the DOMDocument and manually control errors */
libxml_use_internal_errors( true );
$dom=new DOMDocument;
$dom->validateOnParse=true;
$dom->recover=true;
$dom->strictErrorChecking=true;
$dom->loadXML( $strxml );
libxml_clear_errors();
/* Create the XPath object */
$xp=new DOMXPath( $dom );
/* Register the various namespaces found in the XML response */
$xp->registerNamespace('b','http://schemas.datacontract.org/2004/07/FooAPI.Entities.Foo');
$xp->registerNamespace('i','http://www.w3.org/2001/XMLSchema-instance');
$xp->registerNamespace('s','http://www.w3.org/2003/05/soap-envelope');
$xp->registerNamespace('a','http://www.w3.org/2005/08/addressing');
/* make XPath queries for whatever pieces of information you need */
$Action=$xp->query( '//a:Action' )->item(0)->nodeValue;
$StatusCode=$xp->query( '//b:StatusCode' )->item(0)->nodeValue;
$StatusInformation=$xp->query( '//b:StatusInformation' )->item(0)->nodeValue;
printf(
"<pre>
%s
%s
%s
</pre>",
$Action,
$StatusCode,
$StatusInformation
);
The output from the above:
http://tempuri.org/IFooEntryOperation/SaveFooStatusResponse
Foo_ENTRY_FAILURE
InvalidEmployeeCode
I've made a class that reads value and node name of value and combines it into array to use it as a simple and quick access to config in XML. But this solution works only if ill give bottom node, XML looks like this.
<?xml version="1.0" encoding="ISO-8859-2"?>
<settings>
<const>
<inscript>true</inscript>
<title>Template</title>
</const>
<meta>
</meta>
<db>
<user>user</user>
<pass>pass</pass>
<host>host</host>
<name>name</name>
</db>
<path>
<style>
<css>/Template/view/www/style/</css>
<img>/Template/view/www/style/img</img>
</style>
</path>
</settings>
Now I want to get for example whole db node and return it as an array where node name wold be the key and node value, value. but im stuck at this. Heres what i made so far.
class config {
private static $xml = "lib/config/settings.xml";
private static $xmlRoot = "settings";
public static function loadConfig($value) {
$domDocument = new DOMDocument();
$domDocument->load(self::$xml);
$settings = $domDocument->getElementsByTagName(self::$xmlRoot);
try {
foreach ($settings as $setting) {
$configValue = $setting->getElementsByTagName($value)->item(0)->nodeValue;
$configNode = $setting->getElementsByTagName($value)->item(0)->nodeName;
$test = $setting->getElementsByTagName("path")->item(0)->childNodes->item(2)->nodeName;
var_dump($test);
}
$configValue = explode(' ', trim(preg_replace( '/\s+/', ' ', $configValue)));
$configNode = explode(' ', trim(preg_replace( '/\s+/', ' ', $configNode)));
$configArray = array_combine($configNode, $configValue);
return $configArray;
}
catch(Exception $e) {
echo '<h1>Błąd - '.$e->getMessage().'</h1>';
}
}
}
You could use XPath like this:
$xp = new DOMXPath($domDocument);
$config = array();
foreach ($xp->query('./db/*') as $node) {
$config[$node->nodeName] = $node->textContent;
}
return $config;
How can I find a node with a value of other node in the same level in XML?
XML:
<config>
<module>
<idJS >001</idJS>
<addressPLC>41000</addressPLC>
</module>
<module>
<idJS >002</idJS>
<addressPLC>42000</addressPLC>
</module>
</config>
PHP:
<?php
$doc = new DOMDocument();
$doc->load( 'file.xml' );
$config = $doc->getElementsByTagName( "module" );
$ids = $doc->getElementsByTagName('idJS');
foreach ($ids as $id) {
if ($id->nodeValue == '001') {
echo $addressPLC;
}
}
?>
How get the nodeValue of "addressPLC" with "idJS"?
I think a preferable way to go is to iterate through the <module> nodes (instead of the idJS nodes) and retrieve both the idJS and addressPLC from that point.
It looks like there isn't an easy way to get a node's child elements by name, but you could add this convenience function (from here: PHP DOMElement::getElementsByTagName - Anyway to get just the immediate matching children?):
/**
* Traverse an elements children and collect those nodes that
* have the tagname specified in $tagName. Non-recursive
*
* #param DOMElement $element
* #param string $tagName
* #return array
*/
function getImmediateChildrenByTagName(DOMElement $element, $tagName)
{
$result = array();
foreach($element->childNodes as $child)
{
if($child instanceof DOMElement && $child->tagName == $tagName)
{
$result[] = $child;
}
}
return $result;
}
Then you'd have:
foreach ($config as $module) {
$idJS = getImmediateChildrenByTagName($module, "idJS")[0];
if ($idJS->nodeValue == '001') {
echo getImmediateChildrenByTagName($module, "addressPLC")[0]->nodeValue;
}
}
To get addressPLC having idJS you can get parent and find elements in parent:
$addressPLC = $id->parentNode->getElementsByTagName("addressPLC");
echo $addressPLC->nodeValue;
There is no direct method in PHP to retrieve all the siblings to a given node. You need to select the parent element via $node->parentNode and then select the desired element starting from that parent element and using the methods you already know (e.g. getElementsByTagName()).
There is also a user comment in the DOM documentation over at php.net, which has an implementation to find any siblings to a given node: http://php.net/dom#60188
You really should use xpath for that:
$xp = new DOMXpath($doc);
echo $xp->evaluate('string(//module[./idJS[. = "001"]]/addressPLC[1])');
Done. Also it does work as well with getElementsByTagName. See the online Demo:
<?php
$buffer = <<<BUFFER
<config>
<module>
<idJS >001</idJS>
<addressPLC>41000</addressPLC>
</module>
<module>
<idJS >002</idJS>
<addressPLC>42000</addressPLC>
</module>
</config>
BUFFER;
$doc = new DOMDocument();
$doc->loadXML( $buffer );
$modules = $doc->getElementsByTagName( "module" );
var_dump($modules);
foreach ($modules as $module)
{
$ids = $module->getElementsByTagName('idJS');
var_dump($ids);
foreach ($ids as $id) {
var_dump($id->nodeValue);
if ($id->nodeValue == '001') {
# ...
}
}
}
$xp = new DOMXpath($doc);
echo $xp->evaluate('string(//module[./idJS[. = "001"]]/addressPLC[1])');
I have an XML file that looks something like this:
<product>
<modelNumber>Data</modelNumber>
<salePrice>Data</salePrice>
</product>
<product>
<modelNumber>Data</modelNumber>
<salePrice>Data</salePrice>
</product>
Is there a simple way to change the tag names , to something else such as model, price.
Essentially, I have a bunch of XML files containing similar data, but in different formats, so I'm looking for a simple way to parse the XML file, change certain tag names, and write a new XML file with the changed tag names.
There are two issues with Kris and dfsq code:
Only first child node will be copied - solved with temporary copy of $childNodes)
Children will get xmlns tag - solved by replacing node at the beginning - so it's connected to the document
A corrected renaming function is:
function renameTag( DOMElement $oldTag, $newTagName ) {
$document = $oldTag->ownerDocument;
$newTag = $document->createElement($newTagName);
$oldTag->parentNode->replaceChild($newTag, $oldTag);
foreach ($oldTag->attributes as $attribute) {
$newTag->setAttribute($attribute->name, $attribute->value);
}
foreach (iterator_to_array($oldTag->childNodes) as $child) {
$newTag->appendChild($oldTag->removeChild($child));
}
return $newTag;
}
Next function will do the trick:
/**
* #param $xml string Your XML
* #param $old string Name of the old tag
* #param $new string Name of the new tag
* #return string New XML
*/
function renameTags($xml, $old, $new)
{
$dom = new DOMDocument();
$dom->loadXML($xml);
$nodes = $dom->getElementsByTagName($old);
$toRemove = array();
foreach ($nodes as $node)
{
$newNode = $dom->createElement($new);
foreach ($node->attributes as $attribute)
{
$newNode->setAttribute($attribute->name, $attribute->value);
}
foreach ($node->childNodes as $child)
{
$newNode->appendChild($node->removeChild($child));
}
$node->parentNode->appendChild($newNode);
$toRemove[] = $node;
}
foreach ($toRemove as $node)
{
$node->parentNode->removeChild($node);
}
return $dom->saveXML();
}
// Load XML from file data.xml
$xml = file_get_contents('data.xml');
$xml = renameTags($xml, 'modelNumber', 'number');
$xml = renameTags($xml, 'salePrice', 'price');
echo '<pre>'; print_r(htmlspecialchars($xml)); echo '</pre>';
There is some sample code that works in my question over here, but there is no direct way of changing a tag name through DOMDocument/DOMElement, you can however copy elements with a new tagname as shown.
basically you have to:
function renameTag(DOMElement $oldTag, $newTagName)
{
$document = $oldTag->ownerDocument;
$newTag = $document->createElement($newTagName);
foreach($oldTag->attributes as $attribute)
{
$newTag->setAttribute($attribute->name, $attribute->value);
}
foreach($oldTag->childNodes as $child)
{
$newTag->appendChild($oldTag->removeChild($child));
}
$oldTag->parentNode->replaceChild($newTag, $oldTag);
return $newTag;
}
I want to modify an RSS feed. I want to remove X items from feed and then return the new feed as XML.
<?php
class RSS {
private $simpleXML;
public function __construct($address) {
$xml = file_get_contents($address);
$this->simpleXML = simplexml_load_string($xml);
}
private function getRssInformation() {
// Here I want to get the Rss Head information ...
}
// Here I get X items ...
private function getItems($itemsNumber, $UTF8) {
$xml = null;
$items = $this->simpleXML->xpath('/rss/channel/item');
if(count($items) > $itemsNumber) {
$items = array_slice($items, 0, $itemsNumber, true);
}
foreach($items as $item) {
$xml .= $item->asXML() . "\n";
}
return $xml;
}
public function getFeed($itemsNumber = 5, $UTF8 = true) {
// Here i will join rss information with X items ...
echo $this->getItems($itemsNumber, $UTF8);
}
}
?>
Is it possible with XPath?
Thank you.
Another way to do it (but could be cleaner) :
private function getItems($itemsNumber, $UTF8) {
$xml = '<?xml version="1.0" ?>
<rss version="2.0">
<channel>';
$i = 0;
if (count($this->simpleXML->rss->channel->item)>0){
foreach ($this->simpleXML->rss->channel->item as $item) {
$xml .= str_replace('<?xml version="1.0" ?>', '', $item->asXML());
$i++;
if($i==$itemsNumber) break;
}
}
$xml .=' </channel></rss> ';
return $xml;
}
But I think asXML(); add the XML declaration :
<?xml version="1.0" ?>
I edited my code. It's dirty but it should work. Any better idea?