PHP DOMDocument::save() saves as ASCII instead of UTF-8 - php

I'm using DOMDocument and SimpleXMLElement to create a formatted XML file. While this all works, the resulting file is saved as ASCII, not as UTF-8. I can't find an answer as to how to change that.
The XML is created as so:
$XMLNS = "http://www.sitemaps.org/schemas/sitemap/0.9";
$rootNode = new \SimpleXMLElement("<?xml version='1.0' encoding='UTF-8'?><urlset></urlset>");
$rootNode->addAttribute('xmlns', $XMLNS);
$url = $rootNode->addChild('url');
$url->addChild('loc', "Somewhere over the rainbow");
//Turn it into an indented file needs a DOMDocument...
$dom = dom_import_simplexml($rootNode)->ownerDocument;
$dom->formatOutput = true;
$path = "C:\\temp";
// This saves an ASCII file
$dom->save($path.'/sitemap.xml');
The resulting XML looks like this (which is as it should be I think):
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>Somewhere over the rainbow</loc>
</url>
</urlset>
Unfortunately the file is ASCII encoded and not UTF-8.
How do I fix this?
Edit: Don't use notepad++ to check encoding
I've got it to work now thanks to the accepted answer below. There's one note: I used Notepad++ to open the file and check the encoding. However, when I re-generated the file, Notepad++ would update its tab and for some reason indicate ANSI as the encoding. Closing and reopening the same file in Notepad++ would then again indicate UTF-8 again. This caused me a load of confusion.

I think there are a couple of things going on here. For one, you need:
$dom->encoding = 'utf-8';
But also, I think we should try creating the DOMDocument manually specifying the proper encoding. So:
<?php
$XMLNS = "http://www.sitemaps.org/schemas/sitemap/0.9";
$rootNode = new \SimpleXMLElement("<?xml version='1.0' encoding='UTF-8'?><urlset></urlset>");
$rootNode->addAttribute('xmlns', $XMLNS);
$url = $rootNode->addChild('url');
$url->addChild('loc', "Somewhere over the rainbow");
// Turn it into an indented file needs a DOMDocument...
$domSxe = dom_import_simplexml($rootNode)->ownerDocument;
// Set DOM encoding to UTF-8.
$domSxe->encoding = 'UTF-8';
$dom = new DOMDocument('1.0', 'UTF-8');
$domSxe = $dom->importNode($domSxe, true);
$domSxe = $dom->appendChild($domSxe);
$path = "C:\\temp";
$dom->formatOutput = true;
$dom->save($path.'/sitemap.xml');
Also ensure that any elements or CData you're adding are actually UTF-8 (see utf8_encode()).
Using the example above, this works for me:
php > var_dump($utf8);
string(11) "ᙀȾᎵ⁸"
php > $XMLNS = "http://www.sitemaps.org/schemas/sitemap/0.9";
php > $rootNode = new \SimpleXMLElement("<?xml version='1.0' encoding='UTF-8'?><urlset></urlset>");
php > $rootNode->addAttribute('xmlns', $XMLNS);
php > $url = $rootNode->addChild('url');
php > $url->addChild('loc', "Somewhere over the rainbow $utf8");
php > $domSxe = dom_import_simplexml($rootNode);
php > $domSxe->encoding = 'UTF-8';
php > $dom = new DOMDocument('1.0', 'UTF-8');
php > $domSxe = $dom->importNode($domSxe, true);
php > $domSxe = $dom->appendChild($domSxe);
php > $dom->save('./sitemap.xml');
$ cat ./sitemap.xml
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"><url><loc>Somewhere over the rainbow ᙀȾᎵ⁸</loc></url></urlset>

Your data must not be in UTF-8. You can convert it like so:
utf8_encode($yourData);
Or, maybe:
iconv('ISO-8859-1', 'UTF-8', $yourData)

Related

Convert array to XML with both tags on empty value [duplicate]

I want to generate xml by using php simplexml.
$xml = new SimpleXMLElement('<xml/>');
$output = $xml->addChild('child1');
$output->addChild('child2', "value");
$output->addChild('noValue', '');
Header('Content-type: text/xml');
print($xml->asXML());
The output is
<xml>
<child1>
<child2>value</child2>
<noValue/>
</child1>
</xml>
What I want is if the tag has no value it should display like this
<noValue></noValue>
I've tried using LIBXML_NOEMPTYTAG from Turn OFF self-closing tags in SimpleXML for PHP?
I've tried $xml = new SimpleXMLElement('<xml/>', LIBXML_NOEMPTYTAG); and it doesn't work. So I don't know where to put the LIBXML_NOEMPTYTAG
LIBXML_NOEMPTYTAG does not work with simplexml, per the spec:
This option is currently just available in the DOMDocument::save and DOMDocument::saveXML functions.
To achieve what you're after, you need to convert the simplexml object to a DOMDocument object:
$xml = new SimpleXMLElement('<xml/>');
$child1 = $xml->addChild('child1');
$child1->addChild('child2', "value");
$child1->addChild('noValue', '');
$dom_sxe = dom_import_simplexml($xml); // Returns a DomElement object
$dom_output = new DOMDocument('1.0');
$dom_output->formatOutput = true;
$dom_sxe = $dom_output->importNode($dom_sxe, true);
$dom_sxe = $dom_output->appendChild($dom_sxe);
echo $dom_output->saveXML($dom_output, LIBXML_NOEMPTYTAG);
which returns:
<?xml version="1.0" encoding="UTF-8"?>
<xml>
<child1>
<child2>value</child2>
<noValue></noValue>
</child1>
</xml>
Something worth pointing out... the likely reason that the NOEMPTYTAG option is available for DOMDocument and not simplexml is that empty elements are not considered valid XML, while the DOM specification allows for them. You are banging your head against the wall to get invalid XML, which may suggest that the valid self-closing empty element would work just as well.
Despite the quite lengthy answers already given - which are not particularly wrong and do shed some light into some libxml library internals and it's PHP binding - you're most likely looking for:
$output->noValue = '';
To add an open tag, empty node-value and end tag (beautified, demo is here: http://3v4l.org/S2PKc):
<?xml version="1.0"?>
<xml>
<child1>
<child2>value</child2>
<noValue></noValue>
</child1>
</xml>
Just noting as it seems it has been overlooked with the existing answers.
Since Simple XML is proving troublesome, perhaps XMLWriter could do what you want.
Here's a fiddle
<?php
$oXMLWriter = new XMLWriter;
$oXMLWriter->openMemory();
$oXMLWriter->startDocument('1.0', 'UTF-8');
$oXMLWriter->startElement('xml');
$oXMLWriter->writeElement('child1', 'Hello world!!');
$oXMLWriter->writeElement('noValue', '');
$oXMLWriter->endElement();
$oXMLWriter->endDocument();
echo htmlentities($oXMLWriter->outputMemory(TRUE));
?>
Output:
<?xml version="1.0" encoding="UTF-8"?>
<xml>
<child1>Hello world!!</child1>
<noValue></noValue>
</xml>
Extending from my comments (some got deleted):
The behavior depends on your environment. This same code:
$xml = new SimpleXMLElement('<xml/>', LIBXML_NOEMPTYTAG);
$output = $xml->addChild('child1');
$output->addChild('child2', "value");
$output->addChild('noValue', '');
echo $xml->asXML();
In 3v4l.org and Ideone.com, it produces self-closing tag;
In eval.in, codepad.org and on my localhost (PHP 5.3/5.4, libXML 2.7, Windows), it produces empty tag.
Since according to PHP document, LIBXML_NOEMPTYTAG only (guarenteed to) work in DOM, you may want to try DOM for ensurance:
$dom=new DOMDocument("1.0","UTF-8");
$dom->formatOutput=true;
$root=$dom->createElement("xml");
$dom->appendChild($root);
$child1=$dom->createElement("child1");
$root->appendChild($child1);
$node=$dom->createElement("child2");
$node->appendChild($dom->createTextNode("value"));
$child1->appendChild($node);
$node=$dom->createElement("noValue");
$child1->appendChild($node);
echo $dom->saveXML($dom,LIBXML_NOEMPTYTAG);
3v4l.org demo.
Edit:
All the above online demo site uses Content-Type: text/plain by default. If you want to directly output XML to browser as a standalone resource, you can specify the header before output:
header("Content-Type: text/xml");
echo $dom->saveXML($dom,LIBXML_NOEMPTYTAG);
I had to get the child node and asign to the nodeValue property an empty string, and the xml close tag appears.
$output->childNodes[1]->nodeValue = '';
Greetings.

php export xml CDATA escaped

I am trying to export xml with CDATA tags. I use the following code:
$xml_product = $xml_products->addChild('product');
$xml_product->addChild('mychild', htmlentities("<![CDATA[" . $mytext . "]]>"));
The problem is that I get CDATA tags < and > escaped with < and > like following:
<mychild><![CDATA[My some long long long text]]></mychild>
but I need:
<mychild><![CDATA[My some long long long text]]></mychild>
If I use htmlentities() I get lots of errors like tag raquo is not defined etc... though there are no any such tags in my text. Probably htmlentities() tries to parse my text inside CDATA and convert it, but I dont want it either.
Any ideas how to fix that? Thank you.
UPD_1 My function which saves xml to file:
public static function saveFormattedXmlFile($simpleXMLElement, $output_file) {
$dom = new DOMDocument('1.0', 'UTF-8');
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->loadXML(urldecode($simpleXMLElement->asXML()));
$dom->save($output_file);
}
A short example of how to add a CData section, note the way it skips into using DOMDocument to add the CData section in. The code builds up a <product> element, $xml_product has a new element <mychild> created in it. This newNode is then imported into a DOMElement using dom_import_simplexml. It then uses the DOMDocument createCDATASection method to properly create the appropriate bit and adds it back into the node.
$xml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><Products />');
$xml_product = $xml->addChild('product');
$newNode = $xml_product->addChild('mychild');
$mytext = "<html></html>";
$node = dom_import_simplexml($newNode);
$cdata = $node->ownerDocument->createCDATASection($mytext);
$node->appendChild($cdata);
echo $xml->asXML();
This example outputs...
<?xml version="1.0" encoding="UTF-8"?>
<Products><product><mychild><![CDATA[<html></html>]]></mychild></product></Products>

SimpleXML changing file encoding

I am trying to write a function which could read an existing XML file and create a new one with all the data from the first one, but in a different encoding. As far I understand it, SimpleXML saves the file in UTF-8 encoding. My original XML file is Windows-1257.
Code:
public static function toUTF8()
{
$remote_file = "data/test/import/test.xml";
$xml = simplexml_load_file($remote_file);
$xml->asXml('data/test/import/utf8/test.xml');
echo var_dump('done');
exit;
}
This way the encoding of file is still not good. I wanted to try this:
$newXML = new SimpleXMLElement($xml);
But this takes only plain XML code as a parameter. How could I get the whole XML code from the object? Or how else could I create a new UTF-8 XML object and insert all the data from the old file?
I tried this out and saw problems importing the XML directly with SimpleXML. Despite the correct encoding declaration in the XML, it would output the wrong characters. So the alternative is to use a function like iconv which can do the conversion for you.
If you don't need to parse the XML, you can just do this directly:
<?php
$remote_file = "data/test/import/test.xml";
$new_file = "data/test/import/utf8/test.xml";
$baltic_xml = file_get_contents($remote_file);
$unicode_xml = iconv("CP1257", "UTF-8", $baltic_xml);
file_put_contents($new_file, $unicode_xml);
If you need to do stuff with the XML, it gets a little more complicated because you have to update the character set in the XML declaration.
<?php
$remote_file = "data/test/import/test.xml";
$new_file = "data/test/import/utf8/test.xml";
$baltic_xml = file_get_contents($remote_file);
$unicode_xml = iconv("CP1257", "UTF-8", $baltic_xml);
$unicode_xml = str_replace('encoding="CP1257"', 'encoding="UTF-8"', $unicode_xml);
$xml = new SimpleXMLElement($unicode_xml);
// do stuff with $xml
$xml->asXml($new_file);
I tested this out with the following file (saved as CP1257) and it worked fine:
<?xml version="1.0" encoding="CP1257"?>
<Root-Element>
<Test>Łų߯ĒČ</Test>
</Root-Element>
Unless I'm wrong, the SimpleXML extension will just use the same encoding all the way through. UTF-8 is the default if no encoding is given but, if the original document has encoding information such encoding will be used.
You can use DOMDocument as proxy:
$xml = simplexml_load_file(__DIR__ . '/test.xml');
$doc = dom_import_simplexml($xml)->ownerDocument;
$doc->encoding = 'UTF-8';
$xml->asXml('as-utf-8.xml');

Format output of $SimpleXML->asXML(); [duplicate]

This question already has answers here:
PHP simpleXML how to save the file in a formatted way?
(4 answers)
Closed 5 years ago.
The title pretty much says it all.
If I have something like (from PHP site examples):
$xmlstr = <<<XML
<?xml version='1.0' standalone='yes'?>
<movies></movies>
XML;
$sxe = new SimpleXMLElement($xmlstr);
$sxe->addAttribute('type', 'documentary');
$movie = $sxe->addChild('movie');
$movie->addChild('title', 'PHP2: More Parser Stories');
$movie->addChild('plot', 'This is all about the people who make it work.');
$characters = $movie->addChild('characters');
$character = $characters->addChild('character');
$character->addChild('name', 'Mr. Parser');
$character->addChild('actor', 'John Doe');
$rating = $movie->addChild('rating', '5');
$rating->addAttribute('type', 'stars');
echo("<pre>".htmlspecialchars($sxe->asXML())."</pre>");
die();
I end up outputing a long string like so:
<?xml version="1.0" standalone="yes"?>
<movies type="documentary"><movie><title>PHP2: More Parser Stories</title><plot>This is all about the people who make it work.</plot><characters><character><name>Mr. Parser</name><actor>John Doe</actor></character></characters><rating type="stars">5</rating></movie></movies>
This is fine for a program consumer, but for debugging/human tasks, does anyone know how to get this into a nice indented format?
There's a variety of solutions in the comments on the PHP manual page for SimpleXMLElement. Not very efficient, but certainly terse, is a solution by Anonymous
$dom = dom_import_simplexml($simpleXml)->ownerDocument;
$dom->formatOutput = true;
echo $dom->saveXML();
The PHP manual page comments are often good sources for common needs, as long as you filter out the patently wrong stuff first.
The above didn't work for me, I found this worked:
$dom = new DOMDocument("1.0");
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->loadXML($simpleXml->asXML());
echo $dom->saveXML();
Found a similar solution...to format raw xlm data..from my php SOAP requests __getLastRequest & __getLastResponse, for quick debugging the xml's i have combined it with google-code-prettify.
Its a good solution if you want to format sensitive xml data and don't want to do it online.
Some sample code below, may be helpful to others:
$dom = new DOMDocument;
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
$dom->loadXML($data); //=====$data has the raw xml data...you want to format
echo '<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></script>';
echo "<br/> <pre class=\"prettyprint\" >". htmlentities($dom->saveXML())."</pre>";
Below is a sample of the Formatted XML Output I got:
Note: The formatted XML is available in $dom->saveXML() and can be directly saved to a xml file using php file write.

Getting SimpleXMLElement to include the encoding in output

This:
$XML = new SimpleXMLElement("<foo />");
echo($XML->asXML());
...outputs this:
<?xml version="1.0"?>
<foo/>
But I want it to output the encoding, too:
<?xml version="1.0" encoding="UTF-8"?>
<foo/>
Is there some way to tell SimpleXMLElement to include the encoding attribute of the <?xml?> tag? Aside from doing this:
$XML = new SimpleXMLElement("<?xml version='1.0' encoding='utf-8'?><foo />");
echo($XML->asXML());
Which works, but it's annoying to have to manually specify the version and encoding.
Assume for the purposes of this question that I cannot use DOMDocument instead.
You can try this, but you must use simplexml_load_string for $xml
$xml // Your main SimpleXMLElement
$xml->addAttribute('encoding', 'UTF-8');
Or you can still use other means to add the encoding to your output.
Simple Replacement
$outputXML=str_replace('<?xml version="1.0"?>', '<?xml version="1.0" encoding="UTF-8"?>', $outputXML);
Regular Expressions
$outputXML=preg_replace('/<\?\s*xml([^\s]*)\?>/' '<?xml $1 encoding="UTF-8"?>', $outputXML);
DOMDocument - I know you said you don't want to use DOMDocument, but here is an example
$xml = dom_import_simplexml($simpleXML);
$xml->xmlEncoding = 'UTF-8';
$outputXML = $xml->saveXML();
You can wrap this code into a function that receives a parameter $encoding and adds it to the
Simple and clear only do this
$XMLRoot = new SimpleXMLElement('<?xml version="1.0" encoding="utf-8"?><element></element>');
OutPut
<?xml version="1.0" encoding="UTF-8"?>
<element></element>
to add attributes in element only use
$XMLRoot->addAttribute('name','juan');
to add child use
$childElement = $XMLRoot->addChild('elementChild');
$childElement->addAttribute('attribName','somthing');
The DOMDoc proposal of Cristian Toma seems a good approach if the document isn't too heavy. You could wrap it up in something like this:
private function changeEncoding(string $xml, string $encoding) {
$dom = new \DOMDocument();
$dom->loadXML($xml);
$dom->encoding = $encoding;
return $dom->saveXML();
}
Comes in useful when you don't have access to the serializer producing the xml.
I would say you will need to do this on creation of each XML object. Even if SimpleXMLElement had a way of setting it you would still need to set it as I guess it would be possible for the object to pick a valid default.
Maybe create a constant and Create objects like this
$XML = new SimpleXMLElement($XMLNamespace . "<foo />");
echo($XML->asXML());
If you don't specify an encoding, SimpleXML cannot (sanely) guess which one you intended.

Categories