How to tell apart SimpleXML objects representing e

2019-01-12 10:44发布

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).

6条回答
Melony?
2楼-- · 2019-01-12 11:00

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)
查看更多
劳资没心,怎么记你
3楼-- · 2019-01-12 11:01

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

function is_attribute($node) {
    return !($node->asXML()[0] == "<")
}
查看更多
【Aperson】
4楼-- · 2019-01-12 11:01

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.

查看更多
疯言疯语
5楼-- · 2019-01-12 11:05

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.

查看更多
仙女界的扛把子
6楼-- · 2019-01-12 11:11

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);
查看更多
小情绪 Triste *
7楼-- · 2019-01-12 11:13

There are no built-in properties in SimpleXMLElement which would allow you to tell these apart.

As others have suggested dom_import_simplexml can be appropriate, however, that function can change nodes on the fly sometimes, for example, if you pass in a list of childnodes or named childnodes, it will take those and turn them into the first element.

If it's an empty list, for example no attributes returned from attributes() or non-existing named childnodes, it will give a warning telling you an invalid nodetype has been given:

Warning: dom_import_simplexml(): Invalid Nodetype to import

So if you need this precise with a snappy boolean true/false, here is how it works with Simplexml:

$isElement   = $element->xpath('.') == array($element);

$isAttribute = $element[0] == $element
               and $element->xpath('.') != array($element);

It works similar with attribute lists and element lists, I've just blogged about this in the morning, you need to have some specific knowledge about what to evaluate for what, so I created a cheatsheet for it:

+------------------+---------------------------------------------+
| TYPE             | TEST                                        |
+------------------+---------------------------------------------+
| Element          | $element->xpath('.') == array($element)     |
+------------------+---------------------------------------------+
| Attribute        | $element[0] == $element                     |
|                  | and $element->xpath('.') != array($element) |
+------------------+---------------------------------------------+
| Attributes       | $element->attributes() === NULL             |
+------------------+---------------------------------------------+
| Elements         | $element[0] != $element                     |
|                  | and $element->attributes() !== NULL         |
+------------------+---------------------------------------------+
| Single           | $element[0] == $element                     |
+------------------+---------------------------------------------+
| Empty List       | $element[0] == NULL                         |
+------------------+---------------------------------------------+
| Document Element | $element->xpath('/*') == array($element)    |
+------------------+---------------------------------------------+
查看更多
登录 后发表回答