Plesk XML formatting - php

In PHP i have this code for making a XML header for the plesk API.
$request = <<<EOF
<packet version="1.6.7.0">
<mail>
<update>
<set>
<filter>
<site-id>$site_id</site-id>
<mailname>
<name>$name</name>
<autoresponder>
<enabled>true</enabled>
<subject>$subject</subject>
<text>$mail_body</text>
<end_date>$date</end_date>
</autoresponder>
</mailname>
</filter>
</set>
</update>
</mail>
</packet>
EOF;
However i get this response: 1014 Parser error: Cannot parse the XML from the source specified
I have put the xml into a formatting of 2, 3 ,4 and tab spacing and it doesnt seem to be able to parse it.
What am i doing wrong?

You can't guess to create a valid XML by string concatenation, especially when you have complex contents like an email text.
No all characters are allowed inside XML tags: you have to properly escape not-allowed characters. Fortunately, php have some parser that do this job for you.
First of all, create an empty XML template (check its validity using a XML validator):
$xml = '<?xml version="1.0" encoding="utf-8" ?>
<packet version="1.6.7.0">
<mail>
<update>
<set>
<filter>
<site-id/>
<mailname>
<name/>
<autoresponder>
<enabled/>
<subject/>
<text/>
<end_date/>
</autoresponder>
</mailname>
</filter>
</set>
</update>
</mail>
</packet>
';
Then, load it into a DOMDocument object and init a DOMXPath object:
$dom = new DomDocument();
$dom->loadXML( $xml );
$xpath = new DOMXPath( $dom );
Then, find each node that you want to change and set/update its node value:
$nodes = $xpath->query( 'mail/update/set/filter/site-id' );
$nodes->item(0)->nodeValue = $site_id;
$nodes = $xpath->query( 'mail/update/set/filter/mailname/name' );
$nodes->item(0)->nodeValue = $name;
For the <autoresponder> children, you can perform a loop through each child, using * at the end of your search pattern:
$nodes = $xpath->query( 'mail/update/set/filter/mailname/autoresponder/*' );
foreach( $nodes as $node )
{
if( 'enabled' == $node->nodeName )
{
$node->nodeValue = 'true';
}
elseif( 'subject' == $node->nodeName )
{
$node->nodeValue = $subject;
}
elseif( 'text' == $node->nodeName )
{
$cdata = $dom->createCDATASection( $mail_body );
$node->appendChild( $cdata );
}
elseif( 'end_date' == $node->nodeName )
{
$node->nodeValue = $date;
}
}
Note the different syntax adopted for mail body: I use a CDATA node here: if your XML doesn't allow CDATA, replace it with standard ->nodeValue syntax. Or — instead — you can have to use CDATA method for all the nodes.
When the XML is ready, you can echo it by:
echo $dom->saveXML();
DOMXPath allow to perform complex searches in the XML tree: it's not mandatory in your case, because you start from a short, empty, unambiguous template. I use it for demonstration purpose, but you can replace a line like this:
$nodes = $xpath->query( 'mail/update/set/filter/site-id' );
with:
$nodes = $dom->getElementsByTagName( 'site-id' );
and it will work fine.
Read more about DOMDocument
Read more about DOMXPath

Related

PHP XML parser CDATA keyword missing after parse

I have the following XML code which I want to read and get the value inside "content" tag.
"<?xml version='1.0' encoding='ISO-8859-1'?>
<ad modelVersion='0.9'>
<richmediaAd>
<content>
<![CDATA[<script src=\"mraid.js\"></script>
<div class=\"celtra-ad-v3\">
<img src=\"data: image/png, celtra\" style=\"display: none\"onerror=\"(function(img){ varparams={ 'channelId': '45f3f23c','clickUrl': 'http%3a%2f%2fexamplehost.com%3a53766%2fCloudMobRTBWeb%2fClickThroughHandler.ashx%3fadid%3de6983c95-9292-4e16-967d-149e2e77dece%26cid%3d352%26crid%3d850'};varreq=document.createElement('script');req.id=params.scriptId='celtra-script-'+(window.celtraScriptIndex=(window.celtraScriptIndex||0)+1);params.clientTimestamp=newDate/1000;req.src=(window.location.protocol=='https: '?'https': 'http')+': //ads.celtra.com/e7f5ce18/mraid-ad.js?';for(varkinparams){req.src+='&'+encodeURIComponent(k)+'='+encodeURIComponent(params[ k ]); }img.parentNode.insertBefore(req, img.nextSibling);})(this);\"/>
</div>]]>
</content>
<width>320</width>
<height>50</height>
</richmediaAd>
</ad>"
I tried 2 methods (SimpleXML and DOM). I managed to get the value but found the keyword "CDATA" missing. What I got inside "content" tag was:
<script src="mraid.js"></script>
<div class="celtra-ad-v3">
<img src="data: image/png, celtra" style="display: none"onerror="(function(img){ varparams={ 'channelId': '45f3f23c','clickUrl': 'http%3a%2f%2fexamplehost.com%3a53766%2fCloudMobRTBWeb%2fClickThroughHandler.ashx%3fadid%3de6983c95-9292-4e16-967d-149e2e77dece%26cid%3d352%26crid%3d850'};varreq=document.createElement('script');req.id=params.scriptId='celtra-script-'+(window.celtraScriptIndex=(window.celtraScriptIndex||0)+1);params.clientTimestamp=newDate/1000;req.src=(window.location.protocol=='https: '?'https': 'http')+': //ads.celtra.com/e7f5ce18/mraid-ad.js?';for(varkinparams){req.src+='&'+encodeURIComponent(k)+'='+encodeURIComponent(params[ k ]); }img.parentNode.insertBefore(req, img.nextSibling);})(this);"/>
</div>
I know the parser was trying to sort of "beautify" the XML by removing CDATA. But what I want is just the raw data with "CDATA" tag in it. Is there any way to achieve this?
Appreciate your help.
And below is my 2 methods for your reference:
Method 1:
$type = simplexml_load_string($response['adm']) or die("Error: Cannot create object");
$data = $type->richmediaAd[0]->content;
Yii::warning((string) $data);
Yii::warning(strpos($data, 'CDATA'));
Method 2:
$doc = new \DOMDocument();
$doc->loadXML($response['adm']);
$richmediaAds = ($doc->getElementsByTagName("richmediaAd"));
foreach($richmediaAds as $richmediaAd){
$contents = $richmediaAd->getElementsByTagName("content");
foreach($contents as $content){
Yii::warning($content->nodeValue);
}
}
I'll improve this if I can, but you can target explicitly the "CDATA Section" node of your content element and use $doc->saveXML( $node ) with the node as the parameter to get that exact XML element structure.
$doc = new \DOMDocument();
$doc->loadXML( $xml );
$xpath = new \DOMXPath( $doc );
$nodes = $xpath->query( '/ad/richmediaAd/content');
foreach( $nodes[0]->childNodes as $node )
{
if( $node->nodeType === XML_CDATA_SECTION_NODE )
{
echo $doc->saveXML( $node ); // string content
}
}
Edit: You may wish to support some redundancy if there is no CDATA found.
Without XPATH
$doc = new \DOMDocument();
$doc->loadXML( $xml );
$doc->normalize();
foreach( $doc->getElementsByTagName('content')->item(0)->childNodes as $node )
{
if( $node->nodeType === XML_CDATA_SECTION_NODE )
{
echo $doc->saveXML( $node ); // string content
}
}

Repeating XML parsing with PHP

I asked before, and got the answers for one instance, now i have to parse multiple repeating instances of XML file into PHP variables; XML file looks like this:
<status>
<client type="s" name="root" desc="" protocol="server" protocolext="" au="0" thid="0x15e9190">
<request="0000" srvid="0000" time="" history="" answered=""></request>
<times login="2013-04-16T10:59:16+0200" online="7001" idle="0"></times>
<connection ip="127.0.0.1" port="0">OK</connection>
</client>
<client type="p" name="user1" desc="" protocol="run1" protocolext="" au="-1" thid="0x15f1790">
<request="0000" srvid="0000" time="" history="2667" answered=""></request>
<times login="2013-04-16T10:59:16+0200" online="7001" idle="6999"></times>
<connection ip="127.0.2.2" port="10002">CONNECTED</connection>
</client>
<client type="p" name="user2" desc="" protocol="run2" protocolext="" au="-1" thid="0x15f32b0">
<request="0000" srvid="0000" time="" history="" answered=""></request>
<times login="2013-04-16T10:59:16+0200" online="7001" idle="7001"></times>
<connection ip="127.0.3.1" port="12001">CONNECTED</connection>
</client>
<client type="p" name="user3" desc="" protocol="run1" protocolext="" au="-1" thid="0x1631170">
<request="0000" srvid="0000" time="" history="" answered=""></request>
<times login="2013-04-16T10:59:16+0200" online="7001" idle="7001"></times>
<connection ip="127.0.4.1" port="9600">CONNECTED</connection>
</client>
</status>
when i do it with Xpath, it works OK, but fetches only first data part into variables;
$dom = new DOMDocument();
$dom->loadXml($xml);
$xpath = new DOMXpath($dom);
$client_type = $xpath->evaluate('string(/status/client/#type)');
$name = $xpath->evaluate('string(/status/client/#name)');;
$conn_ip = $xpath->evaluate('string(/status/client/connection/#ip)');
and echoing variables:
echo $client_type;
echo $name ;
echo $conn_ip;
it returns only first values:
What would be the best way to pull ALL DATA from the file above?
Getting all the <client> nodes and then looping over them is a good way to make the dom traversal clearer. Here is an example of getting all the clients infos
$dom = new DOMDocument();
$dom->loadXml($xml);
$xpath = new DOMXpath($dom);
// use the double // to find ALL clients in the document
$clientXpath = "//client";
$clients = $xpath->evaluate($clientXpath);
// foreach client node
foreach ($clients as $ii=>$client) {
// get the type attribute of client node
echo $client->getAttribute('type') . "\n";
// get the name attribute of client node
echo $client->getAttribute('name') . "\n";
// get clients children
$children = $client->childNodes;
foreach ($children as $child) {
// ignore textnodes
if ($child instanceof DomText) {
continue;
}
// now concern ourself only with the connection tag, which
// contains the ip
if ($child->tagName == 'connection') {
print $child->getAttribute('ip') . "\n";
}
}
}
Xpath expression used in DOMXpath, return a DOMNodelist unless the result is casted to a scalar. So the example can be expanded to iterate over a list of nodes. Inside the loop the node is used as a context for the expressions.
$dom = new DOMDocument();
$dom->loadXml($xml);
$xpath = new DOMXpath($dom);
foreach ($xpath->evaluate('/status/client') as $client) {
var_dump(
$xpath->evaluate('string(#type)', $client),
$xpath->evaluate('string(#name)', $client),
$xpath->evaluate('string(connection/#ip)', $client)
);
}
Demo: https://eval.in/125083

DOMDocument parsing right way

I have an xml from an exernal server like this:
<?xml version="1.0" encoding="UTF-8"?>
<Response ResponseReference="200002">
<ResponseDetails Language="en">
<SearchCurrencyResponse>
<CurrencyDetails>
<Currency Code="ARS"><![CDATA[Argentine Peso]]></Currency>
<Currency Code="AUD"><![CDATA[Australian Dollar]]></Currency>
</CurrencyDetails>
</SearchCurrencyResponse>
</ResponseDetails>
</Response>
What is the right way to take all currency (code and text inside)?
I have tried in this way and works but is there a way to make it more fast? Because I can have thousands of elements:
$searchCurrencyReponseElements = $xpath->query( 'ResponseDetails/SearchCurrencyResponse', $responseElement );
foreach( $searchCurrencyReponseElements as $searchCurrencyReponseElement ) {
$CurrencyElements = $xpath->query( 'CurrencyDetails/Currency', $searchCurrencyReponseElement );
foreach( $CurrencyElements as $CurrencyElement ) {
echo '<p>Currency: '.$CurrencyElement->textContent.'</p>';
}
}
And how to take the code attribute? Thanks
If you need to select all nodes by a specific node name then use DOMDocument::getElementsByTagName(). Try this:
$doc = new DOMDocument();
$doc->load($path);
// get all Currency nodes
$currencyNodes = $doc->getElementsByTagName('Currency');
// iterate over them
foreach($currencyNodes as $node) {
echo $node->getAttribute('Code') . ' ' . $node->nodeValue . PHP_EOL;
}

PHP - parse data from a SOAP response

I'm using the W3 validator API, and I get this kind of response:
<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope">
<env:Body>
<m:markupvalidationresponse env:encodingStyle="http://www.w3.org/2003/05/soap-encoding" xmlns:m="http://www.w3.org/2005/10/markup-validator">
<m:uri>http://myurl.com/</m:uri>
<m:checkedby>http://validator.w3.org/</m:checkedby>
<m:doctype>-//W3C//DTD XHTML 1.1//EN</m:doctype>
<m:charset>utf-8</m:charset>
<m:validity>false</m:validity>
<m:errors>
<m:errorcount>1</m:errorcount>
<m:errorlist>
<m:error>
<m:line>7</m:line>
<m:col>80</m:col>
<m:message>character data is not allowed here</m:message>
<m:messageid>63</m:messageid>
<m:explanation> <![CDATA[
PAGE HTML IS HERE
]]>
</m:explanation>
<m:source><![CDATA[ HTML AGAIN ]]></m:source>
</m:error>
...
</m:errorlist>
</m:errors>
<m:warnings>
<m:warningcount>0</m:warningcount>
<m:warninglist>
</m:warninglist>
</m:warnings>
</m:markupvalidationresponse>
</env:Body>
</env:Envelope>
How can I extract some variables from there?
I need validity, errorcount and if possible from the list of errors: line, col, and message :)
Is there a easy way to do this?
You can load the XML string into a SimpleXMLElement with simplexml_load_string and then find the attributes using XPath. It's important to register the namespaces involved with registerXPathNamespace before using XPath.
$xml = file_get_contents('example.xml'); // $xml should be the XML source string
$doc = simplexml_load_string($xml);
$doc->registerXPathNamespace('m', 'http://www.w3.org/2005/10/markup-validator');
$nodes = $doc->xpath('//m:markupvalidationresponse/m:validity');
$validity = strval($nodes[0]);
echo 'is valid: ', $validity, "\n";
$nodes = $doc->xpath('//m:markupvalidationresponse/m:errors/m:errorcount');
$errorcount = strval($nodes[0]);
echo 'total errors: ', $errorcount, "\n";
$nodes = $doc->xpath('//m:markupvalidationresponse/m:errors/m:errorlist/m:error');
foreach ($nodes as $node) {
$nodes = $node->xpath('m:line');
$line = strval($nodes[0]);
$nodes = $node->xpath('m:col');
$col = strval($nodes[0]);
$nodes = $node->xpath('m:message');
$message = strval($nodes[0]);
echo 'line: ', $line, ', column: ', $col, ' message: ', $message, "\n";
}
You should be using a SOAP library to get this in the first place. There are various options you can try for this; nusoap, http://php.net/manual/en/book.soap.php, the zend framework also has SOAP client and server which you can use. Whatever implementation you use will allow you to get the data in some way. Doing a var_dump() on whatever holds the initial response should aid you in navigating through it.
If you rather use the DOMDocument class from php. You don't have to know Xpath to get this working. An example:
$url = "http://www.google.com";
$xml = new DOMDocument();
$xml->load("http://validator.w3.org/check?uri=".urlencode($url)."&output=soap12");
$doctype = $xml->getElementsByTagNameNS('http://www.w3.org/2005/10/markup-validator', 'doctype')->item(0)->nodeValue;
$valid = $xml->getElementsByTagNameNS('http://www.w3.org/2005/10/markup-validator', 'validity')->item(0)->nodeValue;
$errorcount = $xml->getElementsByTagNameNS('http://www.w3.org/2005/10/markup-validator', 'errorcount')->item(0)->nodeValue;
$warningcount = $xml->getElementsByTagNameNS('http://www.w3.org/2005/10/markup-validator', 'warningcount')->item(0)->nodeValue;
$errors = $xml->getElementsByTagNameNS('http://www.w3.org/2005/10/markup-validator', 'error');
foreach ($errors as $error) {
echo "<br>line: ".$error->childNodes->item(1)->nodeValue;
echo "<br>col: ".$error->childNodes->item(3)->nodeValue;
echo "<br>message: ".$error->childNodes->item(5)->nodeValue;
}
// item() arguments are uneven because the empty text between tags is counted as an item.

How do I remove a specific node using its attribute value in PHP XML Dom?

My question is best phrase as:
Remove a child with a specific attribute, in SimpleXML for PHP
except I'm not using simpleXML.
I'm new to XML for PHP so I may not be doing the best way
I have a xml created using the $dom->save($xml) for each individual user. (not placing all in one xml due to undisclosed reasons)
It gives me that xml declaration <?xml version="1.0"?> (no idea how to make it to others, but that's not the point, hopefully)
<?xml version="1.0"?>
<details>
<person>name</person>
<data1>some data</data1>
<data2>some data</data2>
<data3>some data</data3>
<category id="0">
<categoryName>Cat 1</categoryName>
<categorydata1>some data</categorydata1>
</category>
<category id="1">
<categoryName>Cat 2</categoryName>
<categorydata1>some data</categorydata1>
<categorydata2>some data</categorydata2>
<categorydata3>some data</categorydata3>
<categorydata4>some data</categorydata4>
</category>
</details>
And I want to remove a category that has a specific attribute named id with the DOM class in php when i run a function activated from using a remove button.
the following is the debug of the function im trying to get to work. Can i know what I'm doing wrong?
function CatRemove($myXML){
$xmlDoc = new DOMDocument();
$xmlDoc->load( $myXML );
$categoryArray = array();
$main = $xmlDoc->getElementsByTagName( "details" )->item(0);
$mainElement = $xmlDoc->getElementsByTagName( "details" );
foreach($mainElement as $details){
$currentCategory = $details->getElementsByTagName( "category" );
foreach($currentCategory as $category){
$categoryID = $category->getAttribute('id');
array_push($categoryArray, $categoryID);
if($categoryID == $_POST['categorytoremoveValue']) {
return $categoryArray;
}
}
}
$xmlDoc->save( $myXML );
}
Well the above prints me an array of [0]->0 all the time when i slot the return outside the if.
is there a better way? I've tried using getElementbyId as well but I've no idea how to work that.
I would prefer not to use an attribute though if that would make things easier.
Ok, let’s try this complete example of use:
function CatRemove($myXML, $id) {
$xmlDoc = new DOMDocument();
$xmlDoc->load($myXML);
$xpath = new DOMXpath($xmlDoc);
$nodeList = $xpath->query('//category[#id="'.(int)$id.'"]');
if ($nodeList->length) {
$node = $nodeList->item(0);
$node->parentNode->removeChild($node);
}
$xmlDoc->save($myXML);
}
// test data
$xml = <<<XML
<?xml version="1.0"?>
<details>
<person>name</person>
<data1>some data</data1>
<data2>some data</data2>
<data3>some data</data3>
<category id="0">
<categoryName>Cat 1</categoryName>
<categorydata1>some data</categorydata1>
</category>
<category id="1">
<categoryName>Cat 2</categoryName>
<categorydata1>some data</categorydata1>
<categorydata2>some data</categorydata2>
<categorydata3>some data</categorydata3>
<categorydata4>some data</categorydata4>
</category>
</details>
XML;
// write test data into file
file_put_contents('untitled.xml', $xml);
// remove category node with the id=1
CatRemove('untitled.xml', 1);
// dump file content
echo '<pre>', htmlspecialchars(file_get_contents('untitled.xml')), '</pre>';
So you want to remove the category node with a specific id?
$node = $xmlDoc->getElementById("12345");
if ($node) {
$node->parentNode->removeChild($node);
}
You could also use XPath to get the node, for example:
$xpath = new DOMXpath($xmlDoc);
$nodeList = $xpath->query('//category[#id="12345"]');
if ($nodeList->length) {
$node = $nodeList->item(0);
$node->parentNode->removeChild($node);
}
I haven’t tested it but it should work.
Can you try with this modified version:
function CatRemove($myXML, $id){
$doc = new DOMDocument();
$doc->loadXML($myXML);
$xpath = new DOMXpath($doc);
$nodeList = $xpath->query("//category[#id='$id']");
foreach ($nodeList as $element) {
$element->parentNode->removeChild($element);
}
echo htmlentities($doc->saveXML());
}
It's working for me. Just adapt it to your needs. It's not intended to use as-is, but just a proof of concept.
You also have to remove the xml declaration from the string.
the above funciton modified to remove an email from a mailing list
function CatRemove($myXML, $id) {
$xmlDoc = new DOMDocument();
$xmlDoc->load($myXML);
$xpath = new DOMXpath($xmlDoc);
$nodeList = $xpath->query('//subscriber[#email="'.$id.'"]');
if ($nodeList->length) {
$node = $nodeList->item(0);
$node->parentNode->removeChild($node);
}
$xmlDoc->save($myXML);
}
$xml = 'list.xml';
$to = $_POST['email'];//user already submitted they email using a form
CatRemove($xml,$to);

Categories