2

If the contents of my XML file is:

<query>
    <results/>
</query>

I'm using $obj = simplexml_load_file( $filepath ) so have the file contents stored in $obj.

As you can see, the results tag is empty, it has no text or attributes.

How can I check if the results tag is empty?

I thought about doing this if ( empty( $obj->results ) ) but I don't think that's the right syntax.

2
  • if((string)$obj->results == '') Commented Apr 13, 2015 at 9:52
  • 1
    @treegarden That would check that it had no text, but not if it had no children or attributes. Commented Apr 13, 2015 at 20:33

1 Answer 1

5

As you can see, the results tag is empty, it has no text or attributes.

How can I check if the results tag is empty?

In XML an element is considered empty by definition if:

[Definition: An element with no content is said to be empty.] The representation of an empty element is either a start-tag immediately followed by an end-tag, or an empty-element tag. [Definition: An empty-element tag takes a special form:] [... it follows the definition of the empty-element tag with "/>" at then end]

So whether it is <results/> or <results></results>, in XML that is called an empty element. And note here that <results foo="bar"/> also is an empty element. Important is the content of the element here, not the attributes.

So now in SimpleXML, how can you find out if an element has no content? It's actually pretty cumbersome. I would prefer to lean on the DOM sister library for that, then it's rather straight forward:

<?php

$buffer = <<<XML
<query>
    <results/>
    <results></results>
</query>
XML;

$xml = simplexml_load_string($buffer);

$element = $xml->results[0];
var_dump(dom_import_simplexml($element)->childNodes->length);

$element = $xml->results[1];
var_dump(dom_import_simplexml($element)->childNodes->length);

This example just parses the little XML document in $buffer with SimpleXML into $xml. Then the first and the second results element is checked for the number of children via the DOM sister library of SimpleXML. If the number of children is zero, then the element is empty.

$isEmpty = !dom_import_simplexml($element)->childNodes->length;

With "pure" SimpleXML you've got a problem because of the limited model the simple in SimpleXML offers here. SimpleXML doesn't take comments into account for example and has problems accessing text-nodes if they are not leaf-nodes. You can (but must not as I've shown with DOM) work around with an xpath query around some of the limitations in SimpleXML. For example only match the element if it is not empty, so again a count of 0 signals an empty element:

var_dump(count($element->xpath('(.)[./node()]')));

The xpath here

(.)[./node()]

means: get only the current element if it has child-nodes. So query returns one element if not empty and zero elements if empty. This is one way to check for an empty element in SimpleXML. You can extend this principle to also check for attributes:

(.)[./node()|./@*]

Perhaps a bit cryptic. But to answer your question, that is the way to go:

$isEmpty = !count($element->xpath('(.)[./node()|./@*]'));

That is: empty XML element with zero attributes.

Hope this is helpful and sheds some light into areas you haven't explored so far.


Here is some example code sandbox style I created while writing the answer (try it online as well):

<?php
/**
 * How to check if XML tag is empty using PHP?
 * @link http://stackoverflow.com/a/29614074/367456
 */

$buffer = <<<XML
<query>
    <results/>
    <results afri="cloa&lt;a"/>
    <results><!-- help --></results>
    <results><!-- help -->1<test>222</test></results>
</query>
XML;

$xml = simplexml_load_string($buffer);

$element = $xml->results[0];

// $path   = '*';
// $path   = '.';
// $path   = 'self::*';
// $path   = 'self::*[1]';
// $path   = 'descendant::node()';
// $path   = 'self::*[descendant::node()]';
// $path   = '(.)[descendant::node()]';
// $path   = '(.)[.//node()]';
// $path   = '(.)[child::node()]';
$path   = '(.)[./node()]';
$result = $element->xpath($path);
var_dump(count($result));
foreach ($result as $node) {
    echo $node->asXML(), "\n";
}
echo "------\n";

var_dump(count($element->xpath('(.)[./node()]')));
var_dump(count($element->xpath('(.)[./node()|./@*]')));

var_dump(strlen($element) + $element->children()->count());


$element = $xml->results[1];
var_dump(dom_import_simplexml($element)->childNodes->length);

$dom = dom_import_simplexml($element);
$dom->appendChild($dom->ownerDocument->createTextNode(""));

var_dump($element->children()->count());
Sign up to request clarification or add additional context in comments.

7 Comments

As ever, a brilliantly comprehensive answer! One thing, your XPath examples switch back and forth between ./node() and .//node(); I'm guessing there's no difference in this context, but it might be best to be consistent.
I haven't edited the sandbox code, right. .//node() is all descendants, not only children, so not necessary here as a check on direct child-nodes is enough (I edited it later and there you caught me in the act :)) - thanks for comment! - now fixed.
@IMSoP: Thx for review and fix.
Well, I can't hope to write a better answer than you, so I figured I'd make your answer a tiny bit better instead. ;)
Thank you @hakre for taking the time to write this, is an interesting read. +1 from me :)
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.