Skip to content

Commit 51a9d4e

Browse files
committed
Fix issue #3: attributes with no namespace prefix not shown
Oddly, the XML namespace spec leaves un-prefixed attributes as undefined behaviour, rather than inheriting the default namespace. While fixing this, I've also made most of the output ignore local aliases, because everyone should be using namespace URIs anyway.
1 parent 627c7d5 commit 51a9d4e

16 files changed

Lines changed: 140 additions & 107 deletions

File tree

src/simplexml_dump.php

Lines changed: 71 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -42,23 +42,23 @@ function simplexml_dump(SimpleXMLElement $sxml, $return=false)
4242
$dom_item = dom_import_simplexml($item);
4343

4444
// To what namespace does this element or attribute belong? Returns array( alias => URI )
45-
$ns_prefix = $dom_item->prefix;
46-
$ns_uri = $dom_item->namespaceURI;
45+
$item_ns_alias = $dom_item->prefix;
46+
$item_ns_uri = $dom_item->namespaceURI;
4747

4848
if ( $dom_item instanceOf DOMAttr )
4949
{
5050
$dump .= $indent . 'Attribute {' . PHP_EOL;
5151

52-
if ( ! is_null($ns_uri) )
52+
if ( ! is_null($item_ns_uri) )
5353
{
54-
$dump .= $indent . $indent . 'Namespace: \'' . $ns_uri . '\'' . PHP_EOL;
55-
if ( $ns_prefix == '' )
54+
$dump .= $indent . $indent . 'Namespace: \'' . $item_ns_uri . '\'' . PHP_EOL;
55+
if ( $item_ns_alias == '' )
5656
{
5757
$dump .= $indent . $indent . '(Default Namespace)' . PHP_EOL;
5858
}
5959
else
6060
{
61-
$dump .= $indent . $indent . 'Namespace Alias: \'' . $ns_prefix . '\'' . PHP_EOL;
61+
$dump .= $indent . $indent . 'Namespace Alias: \'' . $item_ns_alias . '\'' . PHP_EOL;
6262
}
6363
}
6464

@@ -71,16 +71,16 @@ function simplexml_dump(SimpleXMLElement $sxml, $return=false)
7171
{
7272
$dump .= $indent . 'Element {' . PHP_EOL;
7373

74-
if ( ! is_null($ns_uri) )
74+
if ( ! is_null($item_ns_uri) )
7575
{
76-
$dump .= $indent . $indent . 'Namespace: \'' . $ns_uri . '\'' . PHP_EOL;
77-
if ( $ns_prefix == '' )
76+
$dump .= $indent . $indent . 'Namespace: \'' . $item_ns_uri . '\'' . PHP_EOL;
77+
if ( $item_ns_alias == '' )
7878
{
7979
$dump .= $indent . $indent . '(Default Namespace)' . PHP_EOL;
8080
}
8181
else
8282
{
83-
$dump .= $indent . $indent . 'Namespace Alias: \'' . $ns_prefix . '\'' . PHP_EOL;
83+
$dump .= $indent . $indent . 'Namespace Alias: \'' . $item_ns_alias . '\'' . PHP_EOL;
8484
}
8585
}
8686

@@ -93,33 +93,32 @@ function simplexml_dump(SimpleXMLElement $sxml, $return=false)
9393
// This returns all namespaces used by this node and all its descendants,
9494
// whether declared in this node, in its ancestors, or in its descendants
9595
$all_ns = $item->getNamespaces(true);
96-
// If the default namespace is never declared, it will never show up using the below code
97-
if ( ! array_key_exists('', $all_ns) )
96+
$has_default_namespace = isset($all_ns['']);
97+
98+
// If the default namespace is never declared, we need to add a dummy entry for it
99+
// We also need to handle the odd fact that attributes are never assigned to the default namespace
100+
// The spec basically leaves their meaning undefined: https://www.w3.org/TR/xml-names/#defaulting
101+
if ( ! in_array(null, $all_ns, true) )
98102
{
99-
$all_ns[''] = NULL;
103+
$all_ns[] = null;
100104
}
101-
102-
foreach ( $all_ns as $ns_alias => $ns_uri )
105+
106+
// Prioritise "current" namespace by merging into onto the beginning of the list
107+
// (it will be added to the beginning and the duplicate entry dropped)
108+
$all_ns = array_unique(array_merge(
109+
array($item_ns_uri),
110+
$all_ns
111+
));
112+
113+
foreach ( $all_ns as $ns_uri )
103114
{
104115
$children = $item->children($ns_uri);
105116
$attributes = $item->attributes($ns_uri);
106-
107-
// Somewhat confusingly, in the case where a parent element is missing the xmlns declaration,
108-
// but a descendant adds it, SimpleXML will look ahead and fill $all_ns[''] incorrectly
109-
if (
110-
$ns_alias == ''
111-
&&
112-
! is_null($ns_uri)
113-
&&
114-
count($children) == 0
115-
&&
116-
count($attributes) == 0
117-
)
117+
118+
// Don't show children(null) if we have a default namespace defined
119+
if ( $has_default_namespace && $ns_uri === null )
118120
{
119-
// Try looking for a default namespace without a known URI
120-
$ns_uri = NULL;
121-
$children = $item->children($ns_uri);
122-
$attributes = $item->attributes($ns_uri);
121+
$children = array();
123122
}
124123

125124
// Don't show zero-counts, as they're not that useful
@@ -128,59 +127,56 @@ function simplexml_dump(SimpleXMLElement $sxml, $return=false)
128127
continue;
129128
}
130129

131-
$ns_label = (($ns_alias == '') ? 'Default Namespace' : "Namespace $ns_alias");
130+
$ns_label = ($ns_uri === null) ? 'Null Namespace' : "Namespace '$ns_uri'";
132131
$dump .= $indent . $indent . 'Content in ' . $ns_label . PHP_EOL;
133132

134-
if ( ! is_null($ns_uri) )
135-
{
136-
$dump .= $indent . $indent . $indent . 'Namespace URI: \'' . $ns_uri . '\'' . PHP_EOL;
137-
}
138-
139-
// Count occurrence of child element names, rather than listing them all out
140-
$child_names = array();
141-
foreach ( $children as $sx_child )
142-
{
143-
// Below is a rather clunky way of saying $child_names[ $sx_child->getName() ]++;
144-
// which avoids Notices about unset array keys
145-
$child_node_name = $sx_child->getName();
146-
if ( array_key_exists($child_node_name, $child_names) )
147-
{
148-
$child_names[$child_node_name]++;
149-
}
150-
else
151-
{
152-
$child_names[$child_node_name] = 1;
153-
}
154-
}
155-
ksort($child_names);
156-
$child_name_output = array();
157-
foreach ( $child_names as $name => $count )
158-
{
159-
$child_name_output[] = "$count '$name'";
160-
}
161-
162-
$dump .= $indent . $indent . $indent . 'Children: ' . count($children);
163-
// Don't output a trailing " - " if there are no children
164133
if ( count($children) > 0 )
165134
{
135+
// Count occurrence of child element names, rather than listing them all out
136+
$child_names = array();
137+
foreach ( $children as $sx_child )
138+
{
139+
// Below is a rather clunky way of saying $child_names[ $sx_child->getName() ]++;
140+
// which avoids Notices about unset array keys
141+
$child_node_name = $sx_child->getName();
142+
if ( array_key_exists($child_node_name, $child_names) )
143+
{
144+
$child_names[$child_node_name]++;
145+
}
146+
else
147+
{
148+
$child_names[$child_node_name] = 1;
149+
}
150+
}
151+
ksort($child_names);
152+
$child_name_output = array();
153+
foreach ( $child_names as $name => $count )
154+
{
155+
$child_name_output[] = "$count '$name'";
156+
}
157+
158+
$dump .= $indent . $indent . $indent . 'Children: ' . count($children);
166159
$dump .= ' - ' . implode(', ', $child_name_output);
160+
$dump .= PHP_EOL;
167161
}
168-
$dump .= PHP_EOL;
169-
170-
// Attributes can't be duplicated, but I'm going to put them in alphabetical order
171-
$attribute_names = array();
172-
foreach ( $attributes as $sx_attribute )
173-
{
174-
$attribute_names[] = "'" . $sx_attribute->getName() . "'";
175-
}
176-
ksort($attribute_names);
177-
$dump .= $indent . $indent . $indent . 'Attributes: ' . count($attributes);
178-
// Don't output a trailing " - " if there are no attributes
162+
179163
if ( count($attributes) > 0 )
180164
{
181-
$dump .= ' - ' . implode(', ', $attribute_names);
165+
// Attributes can't be duplicated, but I'm going to put them in alphabetical order
166+
$attribute_names = array();
167+
foreach ( $attributes as $sx_attribute )
168+
{
169+
$attribute_names[] = "'" . $sx_attribute->getName() . "'";
170+
}
171+
ksort($attribute_names);
172+
$dump .= $indent . $indent . $indent . 'Attributes: ' . count($attributes);
173+
// Don't output a trailing " - " if there are no attributes
174+
if ( count($attributes) > 0 )
175+
{
176+
$dump .= ' - ' . implode(', ', $attribute_names);
177+
}
178+
$dump .= PHP_EOL;
182179
}
183-
$dump .= PHP_EOL;
184180
}
185181

186182
$dump .= $indent . '}' . PHP_EOL;

src/simplexml_tree.php

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -145,32 +145,44 @@ function _simplexml_tree_recursively_process_node($item, $depth, $include_string
145145
// This returns all namespaces used by this node and all its descendants,
146146
// whether declared in this node, in its ancestors, or in its descendants
147147
$all_ns = $item->getNamespaces(true);
148-
// If the default namespace is never declared, it will never show up using the below code
149-
if ( ! array_key_exists('', $all_ns) )
148+
$has_default_namespace = isset($all_ns['']);
149+
150+
// If the default namespace is never declared, we need to add a dummy entry for it
151+
// We also need to handle the odd fact that attributes are never assigned to the default namespace
152+
// The spec basically leaves their meaning undefined: https://www.w3.org/TR/xml-names/#defaulting
153+
if ( ! in_array(null, $all_ns, true) )
150154
{
151-
$all_ns[''] = NULL;
155+
$all_ns[] = null;
152156
}
153157

154158
// Prioritise "current" namespace by merging into onto the beginning of the list
155-
// (it will be added to the beginning and the duplicate entry dropped)
156-
$all_ns = array_merge(
157-
array($item_ns_prefix => $item_ns_uri),
159+
// (it will be added to the beginning and the duplicate entry dropped)
160+
$all_ns = array_unique(array_merge(
161+
array($item_ns_uri),
158162
$all_ns
159-
);
163+
));
160164

161-
foreach ( $all_ns as $ns_alias => $ns_uri )
165+
foreach ( $all_ns as $ns_uri )
162166
{
163-
$children = $item->children($ns_alias, true);
164-
$attributes = $item->attributes($ns_alias, true);
165-
167+
$children = $item->children($ns_uri);
168+
$attributes = $item->attributes($ns_uri);
169+
170+
// Don't show children(null) if we have a default namespace defined
171+
if ( $has_default_namespace && $ns_uri === null )
172+
{
173+
$children = array();
174+
}
175+
166176
// If things are in the current namespace, display them a bit differently
167177
$is_current_namespace = ( $ns_uri == $item_ns_uri );
168178

179+
$force_attribute_namespace = ($ns_uri === null && $item_ns_uri !== null);
180+
169181
$ns_uri_quoted = (strlen($ns_uri) == 0 ? 'null' : "'$ns_uri'");
170182

171183
if ( count($attributes) > 0 )
172184
{
173-
if ( ! $is_current_namespace )
185+
if ( ! $is_current_namespace || $force_attribute_namespace )
174186
{
175187
$dump .= str_repeat($indent, $depth)
176188
. "->attributes($ns_uri_quoted)" . PHP_EOL;
@@ -179,7 +191,7 @@ function _simplexml_tree_recursively_process_node($item, $depth, $include_string
179191
foreach ( $attributes as $sx_attribute )
180192
{
181193
// Output the attribute
182-
if ( $is_current_namespace )
194+
if ( $is_current_namespace && ! $force_attribute_namespace )
183195
{
184196
// In current namespace
185197
// e.g. ['attribName']

tests/dump-output/basic

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ SimpleXML object (1 item)
55
String Content: '
66

77
'
8-
Content in Default Namespace
8+
Content in Null Namespace
99
Children: 1 - 1 'movie'
10-
Attributes: 0
10+
Attributes: 1 - 'test'
1111
}
1212
]

tests/dump-output/basic-default-ns

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ SimpleXML object (1 item)
77
String Content: '
88

99
'
10-
Content in Default Namespace
11-
Namespace URI: 'https://github.com/IMSoP/simplexml_debug'
10+
Content in Namespace 'https://github.com/IMSoP/simplexml_debug'
1211
Children: 1 - 1 'movie'
13-
Attributes: 0
12+
Content in Null Namespace
13+
Attributes: 1 - 'test'
1414
}
1515
]

tests/dump-output/basic-ns

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@ SimpleXML object (1 item)
55
String Content: '
66

77
'
8-
Content in Namespace test
9-
Namespace URI: 'https://github.com/IMSoP/simplexml_debug'
8+
Content in Namespace 'https://github.com/IMSoP/simplexml_debug'
109
Children: 1 - 1 'movie'
11-
Attributes: 0
10+
Attributes: 1 - 'test'
1211
}
1312
]

tests/dump-output/issue-11

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@ SimpleXML object (1 item)
33
Element {
44
Name: 'notinnamespace'
55
String Content: ''
6-
Content in Namespace test
7-
Namespace URI: 'http://example.com'
8-
Children: 0
6+
Content in Namespace 'http://example.com'
97
Attributes: 1 - 'isinnamespace'
108
}
119
]

tests/dump-output/issue-3

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
SimpleXML object (1 item)
2+
[
3+
Element {
4+
Namespace: 'http://example.com'
5+
(Default Namespace)
6+
Name: 'Foo'
7+
String Content: '
8+
9+
'
10+
Content in Namespace 'http://example.com'
11+
Children: 1 - 1 'MaskingChildElement'
12+
Content in Null Namespace
13+
Attributes: 1 - 'InvisibleAttribute'
14+
}
15+
]

tests/dump-output/soap

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@ SimpleXML object (1 item)
88

99

1010
'
11-
Content in Namespace soap
12-
Namespace URI: 'http://schemas.xmlsoap.org/soap/envelope/'
11+
Content in Namespace 'http://schemas.xmlsoap.org/soap/envelope/'
1312
Children: 2 - 1 'Body', 1 'Header'
14-
Attributes: 0
1513
}
1614
]

tests/input/basic-default-ns.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" ?>
2-
<movies xmlns="https://github.com/IMSoP/simplexml_debug">
2+
<movies xmlns="https://github.com/IMSoP/simplexml_debug" test="true">
33
<movie>
44
<title>PHP: Behind the Parser</title>
55
<characters>

tests/input/basic-ns.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" ?>
2-
<movies xmlns:test="https://github.com/IMSoP/simplexml_debug">
2+
<movies xmlns:test="https://github.com/IMSoP/simplexml_debug" test:test="true">
33
<test:movie>
44
<test:title>PHP: Behind the Parser</test:title>
55
<test:characters>

0 commit comments

Comments
 (0)