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>
Related
Let's say I have the following .xml file:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<item>
<name>Foo</name>
</item>
<item>
<name>Bar</name>
</item>
</root>
In this sample file, I'm trying to append new nodes <item> to node <root> after the last node <item>.
I'm trying to append newly created <item> nodes after the last <item> node in the <root> node in the .xml file.
<?php
$file = new DOMDocument;
$file->load("xml.xml");
$file->loadXML($file->saveXML());
$root = $file->getElementsByTagName('root')->item(0);
foreach (["Foo_1", "Bar_2", "Foo_3", "Bar_4"] as $val) {
$item = new DOMElement('item');
$item->appendChild(new DOMElement('name', $val));
$root->appendChild(item);
}
?>
But I'm getting an error:
Fatal error: Uncaught Error: Call to a member function appendChild() on null in C:\Users\pfort\Desktop\p.php:12
Stack trace:
#0 {main}
thrown in C:\Users\user_acer\Desktop\p.php on line 12
What am I doing wrong?
There's multiple issues with your example code. I will address the error you received first:
There is no element <terminy> in your example XML, so
$root = $file->getElementsByTagName('terminy')->item(0);
will return null. That's why you are receiving the
Call to a member function appendChild() on null
error at
$root->appendChild(item);
Also, item is a typo, because it's not a valid variable name (but a name for a non-existent constant); you meant $item.
I'm assuming "terminy" means something similar to "root" in your native language and that you actually meant to write
$root = $file->getElementsByTagName('root')->item(0);
By the way: if you want a reference to the root node of an XML document, you can also use $file->docomentElement.
However, there are other issues with your example code:
$file->load("xml.xml");
$file->loadXML($file->saveXML()); // why are you reloading it in this way?
The last line is unnecessary. You are reloading the same XML again. Is it for formatting purposes? If so, there's a better option available:
$file->preserveWhiteSpace = false;
$file->formatOutput = true;
$file->load("xml.xml");
Lastly: you cannot append children to a node that has not been associated with a document yet. So, to create a new item and associate it with the document, you either do (recommended):
// automatically associate new nodes with document
$item = $file->createElement('item');
$item->appendChild($file->createElement('name', $val));
or (more cumbersome):
// import nodes to associate them with document
$item = $file->importNode(new DOMElement('item'));
$item->appendChild($file->importNode(new DOMElement('name', $val)));
So, putting it all together it becomes:
<?php
$xml = <<<'XML'
<?xml version="1.0" encoding="UTF-8"?>
<root>
<item>
<name>Foo</name>
</item>
<item>
<name>Bar</name>
</item>
</root>
XML;
$file = new DOMDocument;
$file->preserveWhiteSpace = false;
$file->formatOutput = true;
$file->loadXML($xml); // (for demo purpose loading above XML) replace this with $file->load("xml.xml"); in your actual code
$root = $file->documentElement;
foreach (["Foo_1", "Bar_2", "Foo_3", "Bar_4"] as $val) {
$item = $file->createElement('item');
$item->appendChild($file->createElement('name', $val));
$root->appendChild($item);
}
echo $file->saveXML();
**PROBLEM SOLVED**
I lost too much time on this problem. The good news is, I already know how to get what I need. Here I offer a solution - for everyone who will need to solve the same problem.
Perhaps this solution will be useful for anyone who needs it.
<?php
// snippet of xml temple
$xml = <<<XML
<item date="%s" status="%s">
<name>%s</name>
</item>
XML;
// prepare snippet
$xmlSnippet = sprintf($xml, "2022-11-21", 0, "Foo Bar");
// new DOMDocument
$dom = new DOMDocument;
$dom->preserveWhiteSpace = 0;
$dom->formatOutput = 1;
// load of .xml file content and load to DOMDocument object
$file = simplexml_load_file("xml.xml");
$dom->loadXML($file->asXML());
// creating of fragment from snippet
$fragment = $dom->createDocumentFragment();
$fragment->appendXML($xmlSnippet);
//append the snippet to the DOMDocument
// and save it to the xml.xml file
$dom->documentElement->appendChild($fragment);
$dom->save("xml.xml");
?>
Result:
using php domdocument, to import xml file, i can't have the list of "tags"
I have tried multiple way but i can't
xml document :
<resource>
<title>hello world</title>
<tags>
<resource>great</resource>
<resource>fun</resource>
<resource>omg</resource>
</resource>
php :
<?php
$url='test.xml';
$doc = new DOMDocument();
$doc->load($url);
$feed = $doc->getElementsByTagName("resource");
foreach($feed as $entry) {
echo $entry->getElementsByTagName("username")->item(0)->nodeValue;
echo '<br>';
echo $entry->getElementsByTagName("tags")->item(0)->nodeValue;
echo '<br>';
}
i expect the outpout to be a list like that :
hello world
great
fun
omg
but the actual output is NOT a list the result is a sentence without space :
hello world greatfunomg
DOMDocument::getElementsByTagName() returns all descendant element nodes with the specified name. DOMElement::$nodeValue will return the text content of an element node including all its descendants.
In your case echo $entry->getElementsByTagName("tags")->item(0)->nodeValue fetches all tags, access the first node of that list and outputs its text content. That is greatfunomg.
Using the DOM methods to access nodes is verbose and requires a lot of code and if you want to do it right a lot of conditions. It is a lot easier if you use Xpath expressions. The allow you to scalar values and lists of nodes from an DOM.
$xml = <<<'XML'
<_>
<resource>
<title>hello world</title>
<tags>
<resource>great</resource>
<resource>fun</resource>
<resource>omg</resource>
</tags>
</resource>
</_>
XML;
$document = new DOMDocument();
$document->loadXML($xml);
// create an Xpath instance for the document
$xpath = new DOMXpath($document);
// fetch resource nodes that are a direct children of the document element
$entries = $xpath->evaluate('/*/resource');
foreach($entries as $entry) {
// fetch the title node of the current entry as a string
echo $xpath->evaluate('string(title)', $entry), "\n";
// fetch resource nodes that are children of the tags node
// and map them into an array of strings
$tags = array_map(
function(\DOMElement $node) {
return $node->textContent;
},
iterator_to_array($xpath->evaluate('tags/resource', $entry))
);
echo implode(', ', $tags), "\n";
}
Output:
hello world
great, fun, omg
If you just need to output the first piece of text for each <resource> element - wherever it is, then using XPath and (making sure you ignore whitespace on load) pick the first child element of this and output the node value.
Ignoring the whitespace on load is important as the whitespace will create nodes for all the padding around each element and so the first child of each <resource> element may just be a new line or tab.
$xml = '<root>
<resource>
<title>hello world</title>
<tags>
<resource>great</resource>
<resource>fun</resource>
<resource>omg</resource>
</tags>
</resource>
</root>';
$doc = new DOMDocument();
$doc->preserveWhiteSpace = false;
$doc->loadXML($xml);
// $doc->load($filename); // If loading from a file
$xpath = new DOMXpath($doc);
$resources = $xpath->query("//resource");
foreach ( $resources as $resource ){
echo $resource->firstChild->nodeValue.PHP_EOL;
}
The output of which is
hello world
great
fun
omg
Or without using XPath...
$doc = new DOMDocument();
$doc->preserveWhiteSpace = false;
$doc->loadXML($xml);
//$doc->load($filename);
$resources = $doc->getElementsByTagName("resource");
foreach ( $resources as $resource ){
echo $resource->firstChild->nodeValue.PHP_EOL;
}
I have the xml below
<?xml version="1.0" encoding="UTF-8"?>
<!--Sample XML file generated by XMLSpy v2013 (http://www.altova.com)-->
<ftc:FATCA_OECD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ftc="urn:oecd:ties:fatca:v2" xmlns:sfa="urn:oecd:ties:stffatcatypes:v2" version="2.0" xsi:schemaLocation="urn:oecd:ties:fatca:v2 FatcaXML_v2.0.xsd">
<ftc:MessageSpec>
<sfa:SendingCompanyIN>S519K4.99999.SL.392</sfa:SendingCompanyIN>
<sfa:TransmittingCountry>JP</sfa:TransmittingCountry>
<sfa:ReceivingCountry>US</sfa:ReceivingCountry>
<sfa:MessageType>FATCA</sfa:MessageType>
<sfa:MessageRefId>DBA6455E-8454-47D9-914B-FEE48E4EF3AA</sfa:MessageRefId>
<sfa:ReportingPeriod>2016-12-31</sfa:ReportingPeriod>
<sfa:Timestamp>2017-01-17T09:30:47Z</sfa:Timestamp>
<ftc:SendingCompanyIN>testing</ftc:SendingCompanyIN></ftc:MessageSpec>
<ftc:FATCA>
<ftc:ReportingFI>
<sfa:ResCountryCode>JP</sfa:ResCountryCode>
<sfa:TIN>S519K4.99999.SL.392</sfa:TIN>
<sfa:Name>Bank of NN</sfa:Name>
<sfa:Address>
<sfa:CountryCode>JP</sfa:CountryCode>
<sfa:AddressFree>123 Main Street</sfa:AddressFree>
</sfa:Address>
<ftc:DocSpec>
<ftc:DocTypeIndic>FATCA1</ftc:DocTypeIndic>
<ftc:DocRefId>S519K4.99999.SL.392.50B80D2D-79DA-4AFD-8148-F06480FFDEB5</ftc:DocRefId>
</ftc:DocSpec>
</ftc:ReportingFI>
<ftc:ReportingGroup>
<ftc:NilReport>
<ftc:DocSpec>
<ftc:DocTypeIndic>FATCA1</ftc:DocTypeIndic>
<ftc:DocRefId>S519K4.99999.SL.392.CE54CA78-7C31-4EC2-B73C-E387C314F426</ftc:DocRefId>
</ftc:DocSpec>
<ftc:NoAccountToReport>yes</ftc:NoAccountToReport>
</ftc:NilReport>
</ftc:ReportingGroup>
</ftc:FATCA>
</ftc:FATCA_OECD>
I want to change node value, sfa:TIN and save the xml in a new file. How can this be accomplished in PHP? I got examples but none used namespaces.
One way you could do this is using DOMDocument and DOMXPath and find your elements using for example an xpath expression which will find the 'TIN' elements in the sfa namespace.
/ftc:FATCA_OECD/ftc:FATCA/ftc:ReportingFI/sfa:TIN
To update the value of the first found elemement you could take the first item from the DOMNodeList which is returned by query.
$doc = new DOMDocument();
$doc->loadXML($data);
$xpath = new DOMXPath($doc);
$res = $xpath->query("/ftc:FATCA_OECD/ftc:FATCA/ftc:ReportingFI/sfa:TIN");
if ($res->length > 0) {
$res[0]->nodeValue = "test";
}
$doc->save("yourfilename.xml");
Demo
You can use the following solution, using DOMDocument::getElementsByTagNameNS:
<?php
$dom = new DOMDocument();
$dom->load('old-file.xml');
//get all TIN nodes.
$nodesTIN = $dom->getElementsByTagNameNS('urn:oecd:ties:stffatcatypes:v2', 'TIN');
//check for existing TIN node.
if (count($nodesTIN) === 1) {
//update the first TIN node.
$nodesTIN->item(0)->nodeValue = 'NEWVALUE_OF_TIN';
}
//save the file to a new one.
$dom->save('new-file.xml');
I am building up an xml file and need to include a segment of xml saved in a database (yeah, I wish that wasn't the case too).
// parent element
$parent = $dom->createElement('RecipeIngredients');
// the xml string I want to include
$xmlStr = $row['ingredientSectionXml'];
// load xml string into domDocument
$dom->loadXML( $xmlStr );
// add all Ingredient Sections from xmlStr as children of $parent
$xmlList = $dom->getElementsByTagName( 'IngredientSection' );
for ($i = $xmlList->length; --$i >= 0; ) {
$elem = $xmlList->item($i);
$parent->appendChild( $elem );
}
// add the parent to the $dom doc
$dom->appendChild( $parent );
Right now, I get the following error when I hit the line $parent->appendChild( $elem );
Fatal error: Uncaught exception 'DOMException' with message 'Wrong Document Error'
The XML in the string might look something like the following example. An important point is that there may be multiple IngredientSections, all of which need to be appended to the $parent element.
<IngredientSection name="Herbed Cheese">
<RecipeIngredient>
<Quantity>2</Quantity>
<Unit>cups</Unit>
<Item>yogurt cheese</Item>
<Note>(see Tip)</Note>
<MeasureType/>
<IngredientBrand/>
</RecipeIngredient>
<RecipeIngredient>
<Quantity>2</Quantity>
<Unit/>
<Item>scallions</Item>
<Note>, trimmed and minced</Note>
<MeasureType/>
<IngredientBrand/>
</RecipeIngredient>
<IngredientSection name="Cracked-Wheat Crackers">
</IngredientSection>
<RecipeIngredient>
<Quantity>2</Quantity>
<Unit>teaspoon</Unit>
<Item>salt</Item>
<Note/>
<MeasureType/>
<IngredientBrand/>
</RecipeIngredient>
<RecipeIngredient>
<Quantity>1 1/4</Quantity>
<Unit>cups</Unit>
<Item>cracked wheat</Item>
<Note/>
<MeasureType/>
<IngredientBrand/>
</RecipeIngredient>
</IngredientSection>
Here a two possible solutions:
Import From A Source Document
This works only if the XML string is a valid document. You need to import the document element, or any descendant of it. Depends on the part you would like to add to the target document.
$xml = "<child>text</child>";
$source = new DOMDocument();
$source->loadXml($xml);
$target = new DOMDocument();
$root = $target->appendChild($target->createElement('root'));
$root->appendChild($target->importNode($source->documentElement, TRUE));
echo $target->saveXml();
Output:
<?xml version="1.0"?>
<root><child>text</child></root>
Use A Document Fragment
This works for any valid XML fragment. Even if it has no root node.
$xml = "text<child>text</child>";
$target = new DOMDocument();
$root = $target->appendChild($target->createElement('root'));
$fragment = $target->createDocumentFragment();
$fragment->appendXml($xml);
$root->appendChild($fragment);
echo $target->saveXml();
Output:
<?xml version="1.0"?>
<root>text<child>text</child></root>
You need to use ->importNode() instead of ->appendChild(). Your XML snippets are coming from a completely different XML document, and appendChild will only accept nodes which are part of the SAME xml tree. importNode() will accept "foreign" nodes and incorporate them into the main tree.
I would like to add a child, on a very specific place (so I'm also using DOM and not only simpleXML) for <domain:create> node.
I have tried to use the $ns attribute on simpleXML construct.
$nsNode = new SimpleXMLElement('<domain:ns>', $options = 0, $ns='urn:ietf:params:xml:ns:domain-1.0');
//transform the target into dom object for manipulation
$nodeRegistrantDom = dom_import_simplexml($nodeRegistrant);
But I'm getting:
I/O warning : failed to load external
entity "<domain:ns>"
I've tried to register the prefix after creating the element,
but I use no xpath after this, so this was quite a useless try...
//creates the simpleXML object node to be inserted.
$nsNode = new SimpleXMLElement('<ns/>');
//this will not work, because we will not use xpath after it :s
$nsNode->registerXPathNamespace('domain', 'urn:ietf:params:xml:ns:domain-1.0');
Since the xml is loaded from a file, and that file as this ns declared, maybe we should grab it from that file?
Here is an overall of the above, so that we can better understand the context:
We are loading a XML file that contains an overall structure:
$xmlObj = simplexml_load_file('EppCreateDomain.xml');
They we will grab an element that we will use as a target:
//grab the target.
$nodeRegistrant = $xmlObj->command->create->children(self::OBJ_URI_DOMAIN)->create->registrant;
//transform the target into a dom object for later manipulation
$nodeRegistrantDom = dom_import_simplexml($nodeRegistrant);
//we try to use simpleXML to create the node that we want to add after our target.
$nsNode = new SimpleXMLElement('<domain:ns>');
//grabs the node and all his children (none in this case), by importing the node we want to add,
//into the root object element that contains the <domain:registrant> node.
$nsNodeDom = $nodeRegistrantDom->ownerDocument->importNode(dom_import_simplexml($nsNode), true);
$nodeRegistrantDom->parentNode->insertBefore($nsNodeDom, $nodeRegistrantDom->nextSibling);
$simpleXmlNsNode = simplexml_import_dom($nsNodeDom);
Now we have our node placed on a proper place.
And converted to simpleXML so, we can now easily add some children and fill the rest of the xml file..
$hostAttr = $simpleXmlNsNode->addChild('domain:hostAttr');
$hostName = $hostAttr->addChild('domain:hostName');
Please advice,
MEM
Since the xml is loaded from a file, and that file as this ns declared, maybe we should grab it from that file?
If that file is a XML file, yes, you should load the whole the file, not just a portion.
Once the namespace is declared, adding a namespaced element is easy:
<?php
$xml = <<<XML
<epp>
<domain:create xmlns:domain="urn:someurn" xmlns:ietf="urn:thaturn">
<domain:name></domain:name>
<domain:registrant></domain:registrant>
<domain:contact></domain:contact>
</domain:create>
</epp>
XML;
$sxml = new SimpleXMLElement($xml);
$sxml->children("domain", true)->create->addChild("newElem", "value", "urn:thaturn");
echo $sxml->saveXML();
gives
<?xml version="1.0"?>
<epp>
<domain:create xmlns:domain="urn:someurn" xmlns:ietf="urn:thaturn">
<domain:name/>
<domain:registrant/>
<domain:contact/>
<ietf:newElem>value</ietf:newElem></domain:create>
</epp>
<?php
// test document, registrant as first/last element and somewhere in between
$xmlObj = new SimpleXMLElement('<epp>
<domain:create xmlns:domain="urn:someurn">
<domain:name></domain:name>
<domain:registrant></domain:registrant>
<domain:contact></domain:contact>
</domain:create>
<domain:create xmlns:domain="urn:someurn">
<domain:name></domain:name>
<domain:contact></domain:contact>
<domain:registrant></domain:registrant>
</domain:create>
<domain:create xmlns:domain="urn:someurn">
<domain:registrant></domain:registrant>
<domain:name></domain:name>
<domain:contact></domain:contact>
</domain:create>
</epp>');
foreach( $xmlObj->children("urn:someurn")->create as $create ) {
$registrant = $create->registrant;
insertAfter($registrant, 'domain:ns', 'some text');
}
echo $xmlObj->asXML();
function insertAfter(SimpleXMLElement $prevSibling, $qname, $val) {
$sd = dom_import_simplexml($prevSibling);
$newNode = $sd->ownerDocument->createElement($qname, $val);
$newNode = $sd->parentNode->insertBefore($newNode, $sd->nextSibling);
return simplexml_import_dom($newNode);
}
prints
<?xml version="1.0"?>
<epp>
<domain:create xmlns:domain="urn:someurn">
<domain:name/>
<domain:registrant/><domain:ns>some text</domain:ns>
<domain:contact/>
</domain:create>
<domain:create xmlns:domain="urn:someurn">
<domain:name/>
<domain:contact/>
<domain:registrant/><domain:ns>some text</domain:ns>
</domain:create>
<domain:create xmlns:domain="urn:someurn">
<domain:registrant/><domain:ns>some text</domain:ns>
<domain:name/>
<domain:contact/>
</domain:create>
</epp>