Skip to content

Fix GH-12168: Include leading non-element nodes in SimpleXML children() properties - #24791

Open
Truta446 wants to merge 2 commits into
php:masterfrom
Truta446:gh12168-sxe-children
Open

Truta446 wants to merge 2 commits into
php:masterfrom
Truta446:gh12168-sxe-children

Conversation

@Truta446

Copy link
Copy Markdown

Fixes GH-12168.

For an element returned by children() (SXE_ITER_CHILD), sxe_get_prop_hash() started at the first element child (via php_sxe_iterator_fetch()), but then walked the following siblings with plain node->next. So processing instructions and comments were included in var_dump() / (array) / json_encode() only when they came after the first element:

XML (array) $root (array) $root->children() before after
<c><x/><?hello world?></c> x, hello x, hello x, hello
<c><?hello world?><x/></c> hello, x x hello, x
<c><!-- c --><x/></c> comment, x x comment, x
<c>text</c> 0 (empty) (empty)

The fix starts at the first child, like the SXE_ITER_NONE case does, so children() now matches the element itself. Text nodes are skipped for SXE_ITER_CHILD; otherwise an element containing only text would start exposing that text through children(). Iteration (foreach, count()) is unchanged, since it goes through the iterator, not the property table.

Targeting master because this changes var_dump()/json_encode() output for documents with a PI or comment before the first child element; I can retarget to PHP-8.4 if you consider it a plain bug fix.

Tests: new ext/simplexml/tests/gh12168.phpt (fails before, passes after). ext/simplexml, ext/dom and ext/libxml tests pass on master (debug NTS, and debug + ASan) and on PHP-8.4 (debug + ASan, and debug ZTS).

…erties

The property table of a SimpleXMLElement returned by children() was built
starting at the first element child and then walking all following
siblings. Processing instructions and comments were therefore included only
when they appeared after the first element, unlike for the element itself.
Start at the first child instead. Text nodes are still skipped, so an element
containing only text keeps having no children.
@Truta446
Truta446 force-pushed the gh12168-sxe-children branch from a410473 to 1428f37 Compare October 27, 2026 15:44
@devnexen

Copy link
Copy Markdown
Member

Hi @Truta446, there is an inconsistency sxe_prop_is_empty() was left as is, so for
<c><?pi a?></c> (array) $c->children() gives ["pi"] while (bool) $c->children() is false.

Can we add the following as test case ?

foreach ([
    '<c><?pi a?></c>',
    '<c><!-- c --></c>',
    '<c>t<?pi a?></c>',
    '<!DOCTYPE c [<!ENTITY e "v">]><c>&e;<x/></c>',
] as $xml) {
    $sxe = simplexml_load_string($xml);
    echo $xml, ': ', json_encode(array_keys((array) $sxe->children())), PHP_EOL;
}
$sxe = simplexml_load_string('<c xmlns:a="urn:a"><?pi a?><y/><a:x/></c>');
var_dump(array_keys((array) $sxe->children('urn:a')));

Keep the property table of children() consistent with sxe_prop_is_empty():
an element whose only children are PIs or comments (or with no child in the
requested namespace) is still empty, so (bool) and (array) agree.
@Truta446

Copy link
Copy Markdown
Author

Thanks, good catch. I went the other way around rather than changing sxe_prop_is_empty(): making (bool) $el->children() true for an element that only has a PI or comment would change the common if ($el->children()) check (and count() would still be 0). So non-element children are now only included when there is at least one matching element child, which is the same condition sxe_prop_is_empty() uses; (array) is empty exactly when (bool) is false, including for children('urn:a') with no matching element.

I added your cases to gh12168.phpt, together with the (bool) checks. If you'd prefer making (bool) true instead, I can switch it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Mixing processing instructions and element nodes gives inconsistent results in SimpleXML

2 participants