How to compare similar XMLs with PHPUnit? - php

So let's say I want to compare two DOMDocument objects. They have the same content but order and formatting might be off. For example, first one outputs this XML:
<responses>
<response id="12">
<foo>bar</foo>
<lorem>ipsum</lorem>
<sit>dolor</sit>
</response></responses>
Other one outputs:
<responses>
<response id="12">
<lorem>ipsum</lorem><sit>dolor</sit>
<foo>bar</foo>
</response>
</responses>
As you can see, they contain the same XML structure but some elements might be in different order and formatting is completely random.
If I do:
$this->assertEquals();
The test will of course fail. I don't want to test just XML structure but also contents.
Any ideas?

This seems to have solved the problem:
https://phpunit.de/manual/current/en/appendixes.assertions.html#appendixes.assertions.assertXmlStringEqualsXmlString

Which version of PHPUnit is this? I'm pretty sure recent versions all support DomDocument comparisons.
Short version: Use the $doc->preserveWhiteSpace setting to remove the whitespace, and then use $doc->C14N() to strip comments and get a string you can compare.
OK, here's a script you can play with, note that the EOD; lines cannot have any trailing or leading whitespace.
$x1 = <<<EOD
<responses>
<response id="12">
<foo>bar</foo>
<lorem>ipsum</lorem>
<sit>dolor</sit>
<!--This is a comment -->
</response></responses>
EOD;
$x2 = <<<EOD
<responses>
<response id="12">
<lorem>ipsum</lorem><sit>dolor</sit>
<foo>bar</foo>
<!--This is another comment -->
</response>
</responses>
EOD;
// The next block is part of the same file, I'm just making this formatting-break so that the StackOverflow syntax-highlighting system doesn't choke.
$USE_C14N = true; // Try false, just to see the difference.
$d1 = new DOMDocument(1.0);
$d2 = new DOMDocument(1.0);
$d1->preserveWhiteSpace = false;
$d2->preserveWhiteSpace = false;
$d1->formatOutput = false; // Only useful for "pretty" output with saveXML()
$d2->formatOutput = false; // Only useful for "pretty" output with saveXML()
$d1->loadXML($x1); // Must be done AFTER preserveWhiteSpace and formatOutput are set
$d2->loadXML($x2); // Must be done AFTER preserveWhiteSpace and formatOutput are set
if($USE_C14N){
$s1 = $d1->C14N(true, false);
$s2 = $d2->C14N(true, false);
} else {
$s1 = $d1->saveXML();
$s2 = $d2->saveXML();
}
echo $s1 . "\n";
echo $s2 . "\n";
Output with $USE_C14N=true;
<responses><response id="12"><foo>bar</foo><lorem>ipsum</lorem><sit>dolor</sit></response></responses>
<responses><response id="12"><lorem>ipsum</lorem><sit>dolor</sit><foo>bar</foo></response></responses>
Output with $USE_C14N=false;
<?xml version="1.0"?>
<responses><response id="12"><foo>bar</foo><lorem>ipsum</lorem><sit>dolor</sit><!--This is a comment --></response></responses>
<?xml version="1.0"?>
<responses><response id="12"><lorem>ipsum</lorem><sit>dolor</sit><foo>bar</foo><!--This is another comment --></response></responses>
Note that $doc->C14N() might be slower, but I think it seems likely that stripping out comments is desirable. Note that all of this also assumes that whitespace in your XML isn't important, and there are probably some use-cases where that assumption isn't right...

I suggest you turn the XML into DOMDocuments and then use assertEquals with those. It's already supported by PHPUnit - However that might not cover all your needs already.
You can re-format the documents and re-load them as well, see PHP XML how to output nice format:
$doc->preserveWhiteSpace = false;
$doc->formatOutput = true;
Another idea is to sort then the children by their tagname - no idea if that has been done before.

You can use PHPUnit's assertXmlFileEqualsXmlFile(), assertXmlStringEqualsXmlFile() and assertXmlStringEqualsXmlString() functions; yet, they do not give informations on what's different, they only let the test fail with
Failed asserting that two DOM documents are equal.
So you might want to use PHP's XMLDiff PECL extension, or write your own recursive comparison function. If time matters, I'd recommend to not use DOM but SimpleXML instead because of the simpler API.

I've been playing with some of the notions presented here and figured I might as well post my end result. One of the things I wanted to be able to do was to compare the results of two nodes or two documents. (technically, this one can compare either or so long as the first child of a similar document is being compared to another)
Basically if I send in a DomDocument, it clones it using a $clone->loadXml($obj->saveXml) but if it's a node sent in, it does a $clone->importNode($obj);
The order of the if's becomes important because DomDocument is also a instance of DomNode.
/**
* #param \DOMDocument|\DOMNode $n1
* #param \DOMDocument|\DOMNode $n2
* #return bool
* #throws \Exception for invalid data
*/
function compareNode($n1, $n2)
{
$nd1 = new \DOMDocument('1.0', "UTF-8");
if ($n1 instanceof \DOMDocument) {
$nd1 = $n1->cloneNode(true);
$nd1->preserveWhiteSpace = false;
$nd1->formatOutput = false;
$nd1->loadXML($n1->saveXML());
} elseif ($n1 instanceof \DOMNode) {
$nd1->preserveWhiteSpace = false;
$nd1->formatOutput = false;
$nd1->importNode($n1);
} else {
throw new \Exception(__METHOD__ . " node 1 is invalid");
}
$nd2 = new \DOMDocument('1.0', "UTF-8");
if ($n2 instanceof \DOMDocument) {
$nd2 = $n2->cloneNode(true);
$nd2->preserveWhiteSpace = false;
$nd2->formatOutput = false;
$nd2->loadXML($n2->saveXML());
} elseif ($n1 instanceof \DOMNode) {
$nd2->preserveWhiteSpace = false;
$nd2->formatOutput = false;
$nd2->importNode($n2);
} else {
throw new \Exception(__METHOD__ . " node 2 is invalid");
}
return ($nd1->C14N(true, false) == $nd2->C14N(true, false));
}

Use the following assertion:
$this->assertXmlStringEqualsXmlString($expected, $actual);

Related

PHP return value after XML exploration

I got a PHP array with a lot of XML users-file URL :
$tab_users[0]=john.xml
$tab_users[1]=chris.xml
$tab_users[n...]=phil.xml
For each user a <zoom> tag is filled or not, depending if user filled it up or not:
john.xml = <zoom>Some content here</zoom>
chris.xml = <zoom/>
phil.xml = <zoom/>
I'm trying to explore the users datas and display the first filled <zoom> tag, but randomized: each time you reload the page the <div id="zoom"> content is different.
$rand=rand(0,$n); // $n is the number of users
$datas_zoom=zoom($n,$rand);
My PHP function
function zoom($n,$rand) {
global $tab_users;
$datas_user=new SimpleXMLElement($tab_users[$rand],null,true);
$tag=$datas_user->xpath('/user');
//if zoom found
if($tag[0]->zoom !='') {
$txt_zoom=$tag[0]->zoom;
}
... some other taff here
// no "zoom" value found
if ($txt_zoom =='') {
echo 'RAND='.$rand.' XML='.$tab_users[$rand].'<br />';
$datas_zoom=zoom($r,$n,$rand); } // random zoom fct again and again till...
}
else {
echo 'ZOOM='.$txt_zoom.'<br />';
return $txt_zoom; // we got it!
}
}
echo '<br />Return='.$datas_zoom;
The prob is: when by chance the first XML explored contains a "zoom" information the function returns it, but if not nothing returns... An exemple of results when the first one is by chance the good one:
// for RAND=0, XML=john.xml
ZOOM=Anything here
Return=Some content here // we're lucky
Unlucky:
RAND=1 XML=chris.xml
RAND=2 XML=phil.xml
// the for RAND=0 and XML=john.xml
ZOOM=Anything here
// content founded but Return is empty
Return=
What's wrong?
I suggest importing the values into a database table, generating a single local file or something like that. So that you don't have to open and parse all the XML files for each request.
Reading multiple files is a lot slower then reading a single file. And using a database even the random logic can be moved to SQL.
You're are currently using SimpleXML, but fetching a single value from an XML document is actually easier with DOM. SimpleXMLElement::xpath() only supports Xpath expression that return a node list, but DOMXpath::evaluate() can return the scalar value directly:
$document = new DOMDocument();
$document->load($xmlFile);
$xpath = new DOMXpath($document);
$zoomValue = $xpath->evaluate('string(//zoom[1])');
//zoom[1] will fetch the first zoom element node in a node list. Casting the list into a string will return the text content of the first node or an empty string if the list was empty (no node found).
For the sake of this example assume that you generated an XML like this
<zooms>
<zoom user="u1">z1</zoom>
<zoom user="u2">z2</zoom>
</zooms>
In this case you can use Xpath to fetch all zoom nodes and get a random node from the list.
$document = new DOMDocument();
$document->loadXml($xml);
$xpath = new DOMXpath($document);
$zooms = $xpath->evaluate('//zoom');
$zoom = $zooms->item(mt_rand(0, $zooms->length - 1));
var_dump(
[
'user' => $zoom->getAttribute('user'),
'zoom' => $zoom->textContent
]
);
Your main issue is that you are not returning any value when there is no zoom found.
$datas_zoom=zoom($r,$n,$rand); // no return keyword here!
When you're using recursion, you usually want to "chain" return values on and on, till you find the one you need. $datas_zoom is not a global variable and it will not "leak out" outside of your function. Please read the php's variable scope documentation for more info.
Then again, you're calling zoom function with three arguments ($r,$n,$rand) while the function can only handle two ($n and $rand). Also the $r is undiefined, $n is not used at all and you are most likely trying to use the same $rand value again and again, which obviously cannot work.
Also note that there are too many closing braces in your code.
I think the best approach for your problem will be to shuffle the array and then to use it like FIFO without recursion (which should be slightly faster):
function zoom($tab_users) {
// shuffle an array once
shuffle($tab_users);
// init variable
$txt_zoom = null;
// repeat until zoom is found or there
// are no more elements in array
do {
$rand = array_pop($tab_users);
$datas_user = new SimpleXMLElement($rand, null, true);
$tag=$datas_user->xpath('/user');
//if zoom found
if($tag[0]->zoom !='') {
$txt_zoom=$tag[0]->zoom;
}
} while(!$txt_zoom && !empty($tab_users));
return $txt_zoom;
}
$datas_zoom = zoom($tab_users); // your zoom is here!
Please read more about php scopes, php functions and recursion.
There's no reason for recursion. A simple loop would do.
$datas_user=new SimpleXMLElement($tab_users[$rand],null,true);
$tag=$datas_user->xpath('/user');
$max = $tag->length;
while(true) {
$test_index = rand(0, $max);
if ($tag[$test_index]->zoom != "") {
break;
}
}
Of course, you might want to add a bit more logic to handle the case where NO zooms have text set, in which case the above would be an infinite loop.

How to clean xml request before sending to web service

Good day i am trying to send an xml to a web service but would need to clean the xml before sending. So far I have tried different ways and now been stuck for a while.
I capture the data from a form and post it to my php file to process. If the user doesnt enter any data in the length/width/height then i would like to clean my xml and remove empty element so it can pass validation on the server where sending xml request too.
Here below is a snippet of the data cpatured from my post and build the xml file accordingly but what if the dimensions were omitted? Could I also clean other elements that are empty?
$xmlRequest = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<mailing-scenario xmlns="http://www.mysite.com/ws/ship/rate-v2">
<customer-number>{$mailedBy}</customer-number>
<parcel-characteristics>
<weight>{$weight}</weight>
<dimensions>
<length>{$length}</length>
<width>{$width}</width>
<height>{$height}</height>
</dimensions>
</parcel-characteristics>
<origin-postal-code>{$originPostalCode}</origin-postal-code>
<destination>
<domestic>
<postal-code>{$postalCode}</postal-code>
</domestic>
</destination>
</mailing-scenario>
XML;
$xmlRequest = phpquery::newDocument();
$xp = new DOMXPath($xmlRequest->getDOMDocument());
foreach($xp->query('//*[not(node()) or normalize-space() = ""]') as $node) {
$node->parentNode->removeChild($node);
}
Okay here an example with just simple dom. Maybe some points first, you'll have decide what to do if no customer number is given or a negative weight, ... .
So you have to clean the XML but sometimes cleaning it will make either the request invalid or the user might get some result he did not expect. For instance he might put 1kg as weight you remove the kg because the weight is set in g and a string there is just wrong. If you don't tell the user he might yell at you!
And also just because all nodes are valid does not mean the request is correct, as there might be some missing nodes, so you also have to check for the requirements!
One last word to efficency, if you can get all these fields from the user without XML because the user just sends one parcel at a time. Do it like that and just check if that data is correct.
If you have to use XML, put still just send one package at a time you can just fetch the data check the validity and reconstruct the validated XML.
I would just use this example if I know that these XML requests might be really extensive and/or have a complex format.
function cleanXML($data){
// ok the data is string! So get your node.
$node = simplexml_load_string($data);
// now we can iterate throught all child nodes:
foreach($node->children() as $child){
//so here we got the childrens
// All child nodes of the root should be mailing scenarios
if($child->getName() == "mailing-scenario"){
//otherwise we check if mailing scenario is valid
if(!validateMScenario($child)){
//This node seems not so valid
//you have to decide what to do now!
}
}
else{
//Here we remove all nodes that are different
unset($child[0]);
echo "Error: Needed to remove Node";
}
}
// Just give them their cleaned XML!
return $node->asXML();
}
function validateMScenario($ms){
// These var's safe if the requirements are fullfilled
$is_customer_number_set = 0
$is_weight_set = 0
$is_package_set = 0
// Again iterate over nodes
foreach($ms->children as $child){
//check for customer number
if($child->getName() == "customerNumber"){
if($is_customer_number_set == 1){
echo "You just need one customer number I guess?!"
return -1
}
value = (string) $child;
// Check if customer number is existing
if(strlen(value) == 0 || !is_int(value) || intval(value) == -1){
echo "Dude you need a number!";
return -1
}
$is_customer_number_set = 0;
}
else if($node->getName() == "parcel-characteristics"){
//Ok I hope it should be pretty clear what to do here!
//...
}
else{
//Remove node again?
}
}
// All requirements fullfilled?
return ($is_customer_number_set && $is_weight_set && $is_package_set);
}

removing elements from xml with php - unorthodox xml

I created an ios app that parses an xml document. If a user logs in, their information will be added to the xml file. I would like to be able to remove a user if they are logging out or cancelling their logins. Essentially, I need to figure out how to delete an xml object (a bartender, in this case) that looks like this:
<Bars>
<Bar>
<bar_id>0</bar_id>
<Bartenders>
<Bartender>
<imageURL>unique URL</imageURL>
<shift>20:30</shift>
</Bartender>
</Bartenders>
</Bar>
<Bar>
<bar_id>1</bar_id>
<Bartenders>
<Bartender>
<imageURL>aURL</imageURL>
<shift>a shift</shift>
</Bartender>
<Bartender>
<imageURL>aURL</imageURL>
<shift>a shift</shift>
</Bartender>
</Bartenders>
</Bar>
For example, I want to remove a user based on a unique imageURL... I will also know the bar_id. This is the php that I have so far, but I am completely open to suggestions on doing it a different way
$newimageURL = $row['imageURL'];
$newBar_ID = $row['Bar_ID'];
$xmlUrl = "Bars.xml"; // XML
$xmlStr = file_get_contents($xmlUrl);
$xml = new SimpleXMLElement($xmlStr);
$bartenders = $xml->xpath('//Bartenders');
// something needs to happen here to remove the child
$xml->asXML('Bars.xml');
I was told that I could use something of this sort:
$bartenders->removeChild($bartenders[$newBar_ID]);
Or xpath but I am not sure how to get the path right to the correct bartender with just the unique imageXML. I know I should have planned/designed this better, but I am crunched for time and this will have to do.
Sorry I am so bad with php...
Thanks for your help.
There is a very nice script at http://php.net/manual/fr/ref.simplexml.php that works perfectly fine
function removeNode($xml, $path, $multi='one')
{
$result = $xml->xpath($path);
# for wrong $path
if (!isset($result[0])) return false;
switch ($multi) {
case 'all':
$errlevel = error_reporting(E_ALL & ~E_WARNING);
foreach ($result as $r) unset ($r[0]);
error_reporting($errlevel);
return true;
case 'child':
unset($result[0][0]);
return true;
case 'one':
if (count($result[0]->children())==0 && count($result)==1) {
unset($result[0][0]);
return true;
}
default:
return false;
}
}
Please click on the link for more information

PHP xpath query on XML with default namespace binding

I have one solution to the subject problem, but it’s a hack and I’m wondering if there’s a better way to do this.
Below is a sample XML file and a PHP CLI script that executes an xpath query given as an argument. For this test case, the command line is:
./xpeg "//MainType[#ID=123]"
What seems most strange is this line, without which my approach doesn’t work:
$result->loadXML($result->saveXML($result));
As far as I know, this simply re-parses the modified XML, and it seems to me that this shouldn’t be necessary.
Is there a better way to perform xpath queries on this XML in PHP?
XML (note the binding of the default namespace):
<?xml version="1.0" encoding="utf-8"?>
<MyRoot
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.example.com/data http://www.example.com/data/MyRoot.xsd"
xmlns="http://www.example.com/data">
<MainType ID="192" comment="Bob's site">
<Price>$0.20</Price>
<TheUrl><![CDATA[http://www.example.com/path1/]]></TheUrl>
<Validated>N</Validated>
</MainType>
<MainType ID="123" comment="Test site">
<Price>$99.95</Price>
<TheUrl><![CDATA[http://www.example.com/path2]]></TheUrl>
<Validated>N</Validated>
</MainType>
<MainType ID="922" comment="Health Insurance">
<Price>$600.00</Price>
<TheUrl><![CDATA[http://www.example.com/eg/xyz.php]]></TheUrl>
<Validated>N</Validated>
</MainType>
<MainType ID="389" comment="Used Cars">
<Price>$5000.00</Price>
<TheUrl><![CDATA[http://www.example.com/tata.php]]></TheUrl>
<Validated>N</Validated>
</MainType>
</MyRoot>
PHP CLI Script:
#!/usr/bin/php-cli
<?php
$xml = file_get_contents("xpeg.xml");
$domdoc = new DOMDocument();
$domdoc->loadXML($xml);
// remove the default namespace binding
$e = $domdoc->documentElement;
$e->removeAttributeNS($e->getAttributeNode("xmlns")->nodeValue,"");
// hack hack, cough cough, hack hack
$domdoc->loadXML($domdoc->saveXML($domdoc));
$xpath = new DOMXpath($domdoc);
$str = trim($argv[1]);
$result = $xpath->query($str);
if ($result !== FALSE) {
dump_dom_levels($result);
}
else {
echo "error\n";
}
// The following function isn't really part of the
// question. It simply provides a concise summary of
// the result.
function dump_dom_levels($node, $level = 0) {
$class = get_class($node);
if ($class == "DOMNodeList") {
echo "Level $level ($class): $node->length items\n";
foreach ($node as $child_node) {
dump_dom_levels($child_node, $level+1);
}
}
else {
$nChildren = 0;
foreach ($node->childNodes as $child_node) {
if ($child_node->hasChildNodes()) {
$nChildren++;
}
}
if ($nChildren) {
echo "Level $level ($class): $nChildren children\n";
}
foreach ($node->childNodes as $child_node) {
if ($child_node->hasChildNodes()) {
dump_dom_levels($child_node, $level+1);
}
}
}
}
?>
The solution is using the namespace, not getting rid of it.
$result = new DOMDocument();
$result->loadXML($xml);
$xpath = new DOMXpath($result);
$xpath->registerNamespace("x", trim($argv[2]));
$str = trim($argv[1]);
$result = $xpath->query($str);
And call it as this on the command line (note the x: in the XPath expression)
./xpeg "//x:MainType[#ID=123]" "http://www.example.com/data"
You can make this more shiny by
finding out default namespaces yourself (by looking at the namespace property of the document element)
supporting more than one namespace on the command line and register them all before $xpath->query()
supporting arguments in the form of xyz=http//namespace.uri/ to create custom namespace prefixes
Bottom line is: In XPath you can't query //foo when you really mean //namespace:foo. These are fundamentally different and therefore select different nodes. The fact that XML can have a default namespace defined (and thus can drop explicit namespace usage in the document) does not mean you can drop namespace usage in XPath.
Just out of curiosity, what happens if you remove this line?
$e->removeAttributeNS($e->getAttributeNode("xmlns")->nodeValue,"");
That strikes me as the most likely to cause the need for your hack. You're basically removing the xmlns="http://www.example.com/data" part and then re-building the DOMDocument. Have you considered simply using string functions to remove that namespace?
$pieces = explode('xmlns="', $xml);
$xml = $pieces[0] . substr($pieces[1], strpos($pieces[1], '"') + 1);
Then continue on your way? It might even end up being faster.
Given the current state of the XPath language, I feel that the best answer is provided by Tomalek: to associate a prefix with the default namespace and to prefix all tag names. That’s the solution I intend to use in my current application.
When that’s not possible or practical, a better solution than my hack is to invoke a method that does the same thing as re-scanning (hopefully more efficiently): DOMDocument::normalizeDocument(). The method behaves “as if you saved and then loaded the document, putting the document in a ‘normal’ form.”
Also as a variant you may use a xpath mask:
//*[local-name(.) = 'MainType'][#ID='123']

PHP SimpleXML get innerXML

I need to get the HTML contents of answer in this bit of XML:
<qa>
<question>Who are you?</question>
<answer>Who who, <strong>who who</strong>, <em>me</em></answer>
</qa>
So I want to get the string "Who who, <strong>who who</strong>, <em>me</em>".
If I have the answer as a SimpleXMLElement, I can call asXML() to get "<answer>Who who, <strong>who who</strong>, <em>me</em></answer>", but how to get the inner XML of an element without the element itself wrapped around it?
I'd prefer ways that don't involve string functions, but if that's the only way, so be it.
function SimpleXMLElement_innerXML($xml)
{
$innerXML= '';
foreach (dom_import_simplexml($xml)->childNodes as $child)
{
$innerXML .= $child->ownerDocument->saveXML( $child );
}
return $innerXML;
};
This works (although it seems really lame):
echo (string)$qa->answer;
To the best of my knowledge, there is not built-in way to get that. I'd recommend trying SimpleDOM, which is a PHP class extending SimpleXMLElement that offers convenience methods for most of the common problems.
include 'SimpleDOM.php';
$qa = simpledom_load_string(
'<qa>
<question>Who are you?</question>
<answer>Who who, <strong>who who</strong>, <em>me</em></answer>
</qa>'
);
echo $qa->answer->innerXML();
Otherwise, I see two ways of doing that. The first would be to convert your SimpleXMLElement to a DOMNode then loop over its childNodes to build the XML. The other would be to call asXML() then use string functions to remove the root node. Attention though, asXML() may sometimes return markup that is actually outside of the node it was called from, such as XML prolog or Processing Instructions.
most straightforward solution is to implement custom get innerXML with simple XML:
function simplexml_innerXML($node)
{
$content="";
foreach($node->children() as $child)
$content .= $child->asXml();
return $content;
}
In your code, replace $body_content = $el->asXml(); with $body_content = simplexml_innerXML($el);
However, you could also switch to another API that offers distinction between innerXML (what you are looking for) and outerXML (what you get for now). Microsoft Dom libary offers this distinction but unfortunately PHP DOM doesn't.
I found that PHP XMLReader API offers this distintion. See readInnerXML(). Though this API has quite a different approach to processing XML. Try it.
Finally, I would stress that XML is not meant to extract data as subtrees but rather as value. That's why you running into trouble finding the right API. It would be more 'standard' to store HTML subtree as a value (and escape all tags) rather than XML subtree. Also beware that some HTML synthax are not always XML compatible ( i.e. vs , ). Anyway in practice, you approach is definitely more convenient for editing the xml file.
I would have extend the SimpleXmlElement class:
class MyXmlElement extends SimpleXMLElement{
final public function innerXML(){
$tag = $this->getName();
$value = $this->__toString();
if('' === $value){
return null;
}
return preg_replace('!<'. $tag .'(?:[^>]*)>(.*)</'. $tag .'>!Ums', '$1', $this->asXml());
}
}
and then use it like this:
echo $qa->answer->innerXML();
<?php
function getInnerXml($xml_text) {
//strip the first element
//check if the strip tag is empty also
$xml_text = trim($xml_text);
$s1 = strpos($xml_text,">");
$s2 = trim(substr($xml_text,0,$s1)); //get the head with ">" and trim (note that string is indexed from 0)
if ($s2[strlen($s2)-1]=="/") //tag is empty
return "";
$s3 = strrpos($xml_text,"<"); //get last closing "<"
return substr($xml_text,$s1+1,$s3-$s1-1);
}
var_dump(getInnerXml("<xml />"));
var_dump(getInnerXml("<xml / >faf < / xml>"));
var_dump(getInnerXml("<xml >< / xml>"));
var_dump(getInnerXml("<xml>faf < / xml>"));
var_dump(getInnerXml("<xml > faf < / xml>"));
?>
After I search for a while, I got no satisfy solution. So I wrote my own function.
This function will get exact the innerXml content (including white-space, of course).
To use it, pass the result of the function asXML(), like this getInnerXml($e->asXML()). This function work for elements with many prefixes as well (as my case, as I could not find any current methods that do conversion on all child node of different prefixes).
Output:
string '' (length=0)
string '' (length=0)
string '' (length=0)
string 'faf ' (length=4)
string ' faf ' (length=6)
function get_inner_xml(SimpleXMLElement $SimpleXMLElement)
{
$element_name = $SimpleXMLElement->getName();
$inner_xml = $SimpleXMLElement->asXML();
$inner_xml = str_replace('<'.$element_name.'>', '', $inner_xml);
$inner_xml = str_replace('</'.$element_name.'>', '', $inner_xml);
$inner_xml = trim($inner_xml);
return $inner_xml;
}
If you don't want to strip CDATA section, comment out lines 6-8.
function innerXML($i){
$text=$i->asXML();
$sp=strpos($text,">");
$ep=strrpos($text,"<");
$text=trim(($sp!==false && $sp<=$ep)?substr($text,$sp+1,$ep-$sp-1):'');
$sp=strpos($text,'<![CDATA[');
$ep=strrpos($text,"]]>");
$text=trim(($sp==0 && $ep==strlen($text)-3)?substr($text,$sp+9,-3):$text);
return($text);
}
You can just use this function :)
function innerXML( $node )
{
$name = $node->getName();
return preg_replace( '/((<'.$name.'[^>]*>)|(<\/'.$name.'>))/UD', "", $node->asXML() );
}
Here is a very fast solution i created:
function InnerHTML($Text)
{
return SubStr($Text, ($PosStart = strpos($Text,'>')+1), strpos($Text,'<',-1)-1-$PosStart);
}
echo InnerHTML($yourXML->qa->answer->asXML());
using regex you could do this
preg_match(’/<answer(.*)?>(.*)?<\/answer>/’, $xml, $match);
$result=$match[0];
print_r($result);

Categories