views:

981

answers:

5

I need to print arbitrary SimpleXML objects in a specific manner, with special handling of attribute nodes.

The problem is that SimpleXML elements and attributes seem to use exactly the same class, attribute node even pretends to support attributes() method, and SimpleXML hides its internals, so there doesn't seem to be any way to tell type of node (short of generating XML and reparsing it).

Both give identical result:

$element = new SimpleXMLElement('<foo>test</foo>');
echo $element;
print_r($element);

$element = new SimpleXMLElement('<foo attr="test" />');
echo $element['attr'];
print_r($element['attr']);

Is there a hidden property/method that allows identifying type of node in SimpleXML? Equivalent of DOM's $node->nodeType or $node instanceof DOMAttr? (I can't use DOM instead, support for SimpleXML is core requirement).

+1  A: 

You need SimpleXMLElement::attributes:

function xml_out($el) {
    $name = $el->getName();
    echo '<'. $name;

    if (count($el->attributes())) {
        foreach ($el->attributes() as $a=>$v) {
            echo ' '. ((string)$a) . '="' . htmlspecialchars((string)$v) . '"';
        }
    }

    echo '>'. (string)$el;

    if (count($el->children())) {
        foreach($el->children() as $c) {
            xml_out($c);
        }
    }
    echo '</'. $name . '>';

}

May need a bit of tweaking.

Milen A. Radev
Unfortunately this assumes my code will get element from the start. I cannot control whether input to the function will be an element or attribute.
porneL
A: 

Unfortunately there is no hidden property or method that allows identifying the type of node in SimpleXML. SimpleXML only uses one Class and elements do not have anything that point to their parents. If you try the below reflection you will see that there is nothing to distinguish element or attribute.

$element = new SimpleXMLElement('<foo>test</foo>');
echo ReflectionObject::export($element);

$element = new SimpleXMLElement('<foo attr="test">test</foo>');
echo ReflectionObject::export($element['attr']);

However if an element has attributes you can detect that. So you would have to assume that all elements passed in have attributes. With that assumption you could tell them apart.

$element = new SimpleXMLElement('<foo attr="test">test</foo>');

echo ReflectionObject::export($element);
echo ReflectionObject::export($element['attr']);

This is the best I can come up with. Remember it's possible for an element with no attributes to still return false with this.

function isNotAttribute($simpleXML)
{
  return (count($simpleXML->attributes()) > 0);
}

$element = new SimpleXMLElement('<foo attr="test">test</foo>');
var_dump(isNotAttribute($element));
var_dump(isNotAttribute($element['attr']));

// returns
bool(true)
bool(false)
gradbot
You can additionally check to see if the element has any children with ->children()If the element has any attributes OR children, it's not an attribute.
Frank Farmer
Hey gradbot,The proposed isNotAttribute function is not going to work. It will return false for an element with no attributes.BTW, naming functions that way (isNotX) is HIGHLY not recommended :)
palako
yes palako, I stated above that it will fail with elements that have no attributes.
gradbot
+2  A: 

Yes there is a way. Well, nothing elegant that you can retrieve via the API, but somewhere in the guts of the SimpleXML is keeping track of what it is and it makes a differences, for example, when you call functions such as getName() or asXML().

$element = new SimpleXMLElement('<foo>test</foo>');
print_r($element->getName());
print_r($element->asXML());
echo "------------------\n";
$element = new SimpleXMLElement('<foo attr="test" />');
$at = $element['attr'];
print_r($at->getName());
print_r($at->asXML());

foo
<?xml version="1.0"?>
<foo>test</foo>
------------------
attr 
attr="test"

Your code won't win a beauty contest, but at least you can do it.

palako
Interesting I wonder how asXML knows it's an attribute
gradbot
+2  A: 

Using what palako pointed out, a function like this might work:

function is_attribute($node) {
    return !($node->asXML()[0] == "<")
}
Doomspork
+1  A: 

Is there a hidden property/method that allows identifying type of node in SimpleXML? Equivalent of DOM's $node->nodeType or $node instanceof DOMAttr? (I can't use DOM instead, support for SimpleXML is core requirement)

The answer is no... and yes. SimpleXML doesn't have such a property but here's the good news: SimpleXML and DOM are two faces of the same coin. (the coin is libxml ;)) You don't have to choose one or the other, you can use both! You can turn SimpleXMLElement into DOMNode and vice versa. In your case, you could do something like:

$element = new SimpleXMLElement('<foo attr="test" />');
$is_attr = (dom_import_simplexml($element['attr'])->nodeType === XML_ATTRIBUTE_NODE);

If that's something you do often, or you don't want to handle the DOM/SimpleXML juggling, you can take a look at SimpleDOM.

$element = new SimpleDOM('<foo attr="test" />');
$is_attr = ($element['attr']->nodeType() === XML_ATTRIBUTE_NODE);
Josh Davis