is there an option with DomDocument to remove the first line:
<?xml version="1.0" encoding="UTF-8"?>
The class instantiation automatically adds it to the output, but is it possible to get rid of it?
I think using DOMDocument is a universal solution for valid XML files:
If you have XML already loaded in a variable:
$t_xml = new DOMDocument();
$t_xml->loadXML($xml_as_string);
$xml_out = $t_xml->saveXML($t_xml->documentElement);
For XML file from disk:
$t_xml = new DOMDocument();
$t_xml->load($file_path_to_xml);
$xml_out = $t_xml->saveXML($t_xml->documentElement);
This comment helped: http://www.php.net/manual/en/domdocument.savexml.php#88525
If you want to output HTML, use the saveHTML() function. It automatically avoids a whole lot of XML idiom and handles closed/unclosed HTML idiom properly.
If you want to output XML you can use the fact that DOMDocument is a DOMNode (namely: '/' in XPath expression), thus you can use DOMNode API calls on it to iterate over child nodes and call saveXML() on each child node. This does not output the XML declaration, and it outputs all other XML content properly.
Example:
$xml = get_my_document_object();
foreach ($xml->childNodes as $node) {
echo $xml->saveXML($node);
}
For me, none of the answers above worked:
$dom = new \DOMDocument();
$dom->loadXXX('<?xml encoding="utf-8" ?>' . $content); // loadXML or loadHTML
$dom->saveXML($dom->documentElement);
The above didn't work for me if I had partial HTML, e.g.
<p>Lorem</p>
<p>Ipsum</p>
As it then removed the everything after <p>Lorem</p>.
The only solution that worked for me was:
foreach ($doc->childNodes as $xx) {
if ($xx instanceof \DOMProcessingInstruction) {
$xx->parentNode->removeChild($xx);
}
}
I had the same problem, but I am using symfony/serializer for XML creation. If you also want to achieve this with Symfony serializer you can do in this way:
$encoder = new \Symfony\Component\Serializer\Encoder\XmlEncoder();
$encoder->encode($nodes[$rootNodeName], 'xml', [
XmlEncoder::ROOT_NODE_NAME => $rootNodeName,
XmlEncoder::ENCODING => $encoding,
XmlEncoder::ENCODER_IGNORED_NODE_TYPES => [
XML_PI_NODE, //this flag is the solution
],
]);
You can use output buffering to remove it. A bit of a hack but it works.
ob_start();
// dom stuff
$output = ob_get_contents();
ob_end_clean();
$clean = preg_replace("/(.+?\n)/","",$output);
Related
I'm sending the following XML to an api using cURL:
$xml = "<request type='auth' timestamp='$timestamp'>
<merchantid>$merchantid</merchantid>
<account>$account</account>
<orderid>$orderid</orderid>
<amount currency='$currency'>$amount</amount>
<card>
<number>$cardnumber</number>
<expdate>$expdate</expdate>
<type>$cardtype</type>
<chname>$cardname</chname>
</card>
<sha1hash>$sha1hash</sha1hash>
</request>";
What is the best way to avoid hard coding this XML? I was thinking of using XMLWriter but seems strange as it won't be changing.
Should I use a template? Or generate it using XMLWriter / Simple XML?
As I mentioned in the comments, there's not necessarily a right answer to this but I recently had to write a project around an XML API Feed as well. I decided to go with XMLWriter and it's still very easy to interchange into others easily by using their respected .loadXML() functions.
class SomeApi extends XMLwriter {
public function __construct() {
$this->openMemory();
$this->setIndent( true );
$this->setIndentString ( " " );
$this->startDocument( '1.0', 'UTF-8', 'no' );
$this->startElement( 'root' );
}
public function addNode( $Name, $Contents ) {
$this->startElement( $Name );
$this->writeCData( $Contents );
$this->endElement();
}
public function output() {
$this->endElement();
$this->endDocument();
}
//Returns a String of Xml.
public function render() {
return $this->outputMemory();
}
}
$newRequest = new SomeApi();
$newRequest->addNode( 'some', 'Some Lots of Text' );
$Xml = $newRequest->render();
I think it's a nice clean way writing an XML Feed in PHP, furthermore as you can add internal functions such as:
$this->addHeader();
private function addHeader() {
$this->addNode( 'login', 'xxxxx' );
$this->addNode( 'password', 'xxxxx' );
}
Which then appends nodes that you'll use over & over again. Then if you suddenly need to use a DOMDocument object for example (As I needed too for XSL).
$Dom = new DOMDocument();
$Dom->loadXML( $Xml );
Should I use a template?
You actually already did use a template here.
Or generate it using XMLWriter / Simple XML?
XMLWriter and also SimpleXMLElement are components that allow you to create XML easily. For your specific case I'd use SimpleXML for a start:
$xml = new SimpleXMLElement('<request type="auth"/>');
$xml['timestamp'] = $timestamp;
$xml->merchantid = $merchantid;
$xml->account = $account;
$xml->orderid = $orderid;
$xml->addChild('amount', $amount)['currency'] = $currency;
$card = $xml->addChild('card');
$card->number = $cardnumber;
$card->expdate = $expdate;
$card->type = $cardtype;
$card->chname = $cardname;
$xml->sha1hash = $sha1hash;
See that the XML is not hardcoded any longer, only the names used are. The SimpleXML library takes care to create the XML (demo, here the output is beautified for better readability):
<?xml version="1.0"?>
<request type="auth" timestamp="">
<merchantid></merchantid>
<account></account>
<orderid></orderid>
<amount currency=""/>
<card>
<number></number>
<expdate></expdate>
<type></type>
<chname></chname>
</card>
<sha1hash></sha1hash>
</request>
Thanks to the library, the output is always valid XML and you don't need to care about the details here. You can further simplify it by wrapping it more, but I don't think this is of much use with your very little XML you have here.
I want to integrate an XSL file in an XML string gived me by php CURL command.
I tryed this
$output = XML gived me by curl option;
$hotel = simplexml_load_string($output);
$hotel->addAttribute('?xml-stylesheet type=”text/xsl” href=”css/stile.xsl”?');
echo $hotel->asXML();
Doing this when I see the XML on browser, I receive the file without the stylesheet.
Where is my error?
A SimpleXMLElement does not allow you by default to create and add a Processing Instruction (PI) to a node. However the sister library DOMDocument allows this. You can marry the two by extending from SimpleXMLElement and create a function to provide that feature:
class MySimpleXMLElement extends SimpleXMLElement
{
public function addProcessingInstruction($target, $data = NULL) {
$node = dom_import_simplexml($this);
$pi = $node->ownerDocument->createProcessingInstruction($target, $data);
$result = $node->appendChild($pi);
return $this;
}
}
This then is easy to use:
$output = '<hotel/>';
$hotel = simplexml_load_string($output, 'MySimpleXMLElement');
$hotel->addProcessingInstruction('xml-stylesheet', 'type="text/xsl" href="style.xsl"');
$hotel->asXML('php://output');
Exemplary output (beautified):
<?xml version="1.0"?>
<hotel>
<?xml-stylesheet type="text/xsl" href="style.xsl"?>
</hotel>
Another way is to insert an XML chunk to a simplexml element: "PHP SimpleXML: insert node at certain position" or "Insert XML into a SimpleXMLElement".
Here's the response from a WSDL
<return code='6000'></return>
I would like to return the code value. Can i use simplexml_load_string() ?
Yes, you can.
$xml = simplexml_load_string($str);
$code = (int) $xml->attributes()->code;
CodePad.
You can use DOMDocument() to get node values as well as attribute values.
$dom_boj=new DOMDocument(); //Creating object to the class DOMDocument()
$dom_boj->loadXML($XMLResponse); // loading your response using loadXML
//Traversing all return tags.
foreach($dom_boj->getElementByTagName('return') as $tagName)
{
echo $tagName->getAttribute('code');
}
My question is a rather simple one for anyone familiar with the DOM* classes in PHP.
Basically i have different classes that i want to return to me something that I can append in my xml document
Following pseudo-code should demonstrate better
Class ChildObject{ function exportToXML( return a DOMNode ? ) }
Class ContainerObject{
function exportToXML(){
$domSomething = new DOM*SOMETHING*;
foreach($children as $child) $domSomething->appendChild($child->exportToXML);
return $domSomething ;
}
}
Now i want to create the entire DOMDocument
$xml = new DOMDocument();
$root = $xml->createElement('root');
foreach($containers as $container) $root->appendChild($container->exportToXML());
I tried sending the DOMDocument object as a reference, did not work. I tried creating DOMNodes but didn't work as well....so i'm looking at a simple answer: what datatypes do i need to return in order for me to achieve the above functionality?
<?php
$xml = new DOMDocument();
$h = $xml->createElement('hello');
$node1 = new DOMNode('aaa');
$node1->appendChild(new DOMText('new text content'));
//node1 is being returned by a function
$node2 = new DOMNode('bbb');
$node2->appendChild(new DOMText('new text content'));
//node2 is being returned by some other function
$h->appendChild($node1);//append to this element the returned node1
$h->appendChild($node2);//append to this element the returned node2
$xml->appendChild($h);//append to the document the root node
$content = $xml->saveXML();
file_put_contents('xml.xml', $content);//output to an xml file
?>
The above code should do the following:
consider that i want to build the following xml
<hello>
<node1>aaa</node1>
<node2>bbb</node2>
</hello>
node1 could be again a node that has multiple children so node1 could be as well as something like this:
<node1>
<child1>text</child1>
<child2>text</child2>
<child3>
<subchild1>text</subchild1>
</child3>
</node1>
Basically when i call exportToXML() something should be returned, call it $x that i can append in my document using $xml->appendChild($x);
I want to create the above structure and return the object that can be appended in the DOMDocument
The following code:
<?php
$xml = new DOMDocument();
$h = $xml->appendChild($xml->createElement('hello'));
$node1 = $h->appendChild($xml->createElement('aaa'));
$node1->appendChild($xml->createTextNode('new text content'));
$node2 = $h->appendChild($xml->createElement('bbb'));
$node2->appendChild($xml->createTextNode('new text content'));
$xml->save("xml.xml");
?>
will produce:
<?xml version="1.0"?>
<hello>
<aaa>new text content</aaa>
<bbb>new text content</bbb>
</hello>
Your example XML showed <node1>aaa</node1> but I think your various code snippet examples went out of sync when you were editing =) In case you need that output, try:
<?php
$xml = new DOMDocument();
$h = $xml->appendChild($xml->createElement('hello'));
$node1 = $h->appendChild($xml->createElement('node1'));
$node1->appendChild($xml->createTextNode('aaa'));
$node2 = $h->appendChild($xml->createElement('node2'));
$node2->appendChild($xml->createTextNode('bbb'));
$xml->save("xml.xml");
?>
Is there any way I can insert an HTML template to existing DOMNode without content being encoded?
I have tried to do that with:
$dom->createElement('div', '<h1>Hello world</h1>');
$dom->createTextNode('<h1>Hello world</h1>');
The output is pretty much the same, with only difference that first code would wrap it in a div.
I have tried to loadHTML from string but I have no idea how can I append it's body content to another DOMDocument.
In javascript, this process seems to be quite simple and obvious.
You can use
DOMDocumentFragment::appendXML — Append raw XML data
Example:
// just some setup
$dom = new DOMDocument;
$dom->loadXml('<html><body/></html>');
$body = $dom->documentElement->firstChild;
// this is the part you are looking for
$template = $dom->createDocumentFragment();
$template->appendXML('<h1>This is <em>my</em> template</h1>');
$body->appendChild($template);
// output
echo $dom->saveXml();
Output:
<?xml version="1.0"?>
<html><body><h1>This is <em>my</em> template</h1></body></html>
If you want to import from another DOMDocument, replace the three lines with
$tpl = new DOMDocument;
$tpl->loadXml('<h1>This is <em>my</em> template</h1>');
$body->appendChild($dom->importNode($tpl->documentElement, TRUE));
Using TRUE as the second argument to importNode will do a recursive import of the node tree.
If you need to import (malformed) HTML, change loadXml to loadHTML. This will trigger the HTML parser of libxml (what ext/DOM uses internally):
libxml_use_internal_errors(true);
$tpl = new DOMDocument;
$tpl->loadHtml('<h1>This is <em>malformed</em> template</h2>');
$body->appendChild($dom->importNode($tpl->documentElement, TRUE));
libxml_use_internal_errors(false);
Note that libxml will try to correct the markup, e.g. it will change the wrong closing </h2> to </h1>.
It works with another DOMDocument for parsing the HTML code. But you need to import the nodes into the main document before you can use them in it:
$newDiv = $dom->createElement('div');
$tmpDoc = new DOMDocument();
$tmpDoc->loadHTML($str);
foreach ($tmpDoc->getElementsByTagName('body')->item(0)->childNodes as $node) {
$node = $dom->importNode($node, true);
$newDiv->appendChild($node);
}
And as a handy function:
function appendHTML(DOMNode $parent, $source) {
$tmpDoc = new DOMDocument();
$tmpDoc->loadHTML($source);
foreach ($tmpDoc->getElementsByTagName('body')->item(0)->childNodes as $node) {
$node = $parent->ownerDocument->importNode($node, true);
$parent->appendChild($node);
}
}
Then you can simply do this:
$elem = $dom->createElement('div');
appendHTML($elem, '<h1>Hello world</h1>');
As I do not want to struggle with XML, because it throws errors faster and I am not a fan of prefixing an # to prevent error output. The loadHTML does the better job in my opinion and it is quite simple as that:
$doc = new DOMDocument();
$div = $doc->createElement('div');
// use a helper to load the HTML into a string
$helper = new DOMDocument();
$helper->loadHTML('This is my HTML Link.');
// now the magic!
// import the document node of the $helper object deeply (true)
// into the $div and append as child.
$div->appendChild($doc->importNode($helper->documentElement, true));
// add the div to the $doc
$doc->appendChild($div);
// final output
echo $doc->saveHTML();
Here is simple example by using DOMDocumentFragment:
$doc = new DOMDocument();
$doc->loadXML("<root/>");
$f = $doc->createDocumentFragment();
$f->appendXML("<foo>text</foo><bar>text2</bar>");
$doc->documentElement->appendChild($f);
echo $doc->saveXML();
Here is helper function for replacing DOMNode:
/**
* Helper function for replacing $node (DOMNode)
* with an XML code (string)
*
* #var DOMNode $node
* #var string $xml
*/
public function replaceNodeXML(&$node, $xml) {
$f = $this->dom->createDocumentFragment();
$f->appendXML($xml);
$node->parentNode->replaceChild($f,$node);
}
Source: Some old "PHP5 Dom Based Template" article.
And here is another suggestion posted by Pian0_M4n to use value attribute as workaround:
$dom = new DomDocument;
// main object
$object = $dom->createElement('div');
// html attribute
$attr = $dom->createAttribute('value');
// ugly html string
$attr->value = "<div> this is a really html string ©</div><i></i> with all the © that XML hates!";
$object->appendChild($attr);
// jquery fix (or javascript as well)
$('div').html($(this).attr('value')); // and it works!
$('div').removeAttr('value'); // to clean-up
No ideal, but at least it works.
Gumbo's code works perfectly! Just a little enhancement that adding the TRUE parameter so that it works with nested html snippets.
$node = $parent->ownerDocument->importNode($node);
$node = $parent->ownerDocument->importNode($node, **TRUE**);