Skip to content

Commit efb02cb

Browse files
committed
Add code to normalize document namespace-tags
1 parent 6d237b7 commit efb02cb

10 files changed

+85
-7
lines changed

src/XML/Chunk.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,6 @@ public function toXML(?DOMElement $parent = null): DOMElement
255255

256256
$parent->appendChild($doc->importNode($this->getXML(), true));
257257

258-
return $doc->documentElement;
258+
return DOMDocumentFactory::normalizeDocument($doc)->documentElement;
259259
}
260260
}

src/XML/DOMDocumentFactory.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,22 @@
55
namespace SimpleSAML\XML;
66

77
use DOMDocument;
8+
use DOMElement;
9+
use DOMXPath;
810
use SimpleSAML\XML\Assert\Assert;
911
use SimpleSAML\XML\Exception\IOException;
1012
use SimpleSAML\XML\Exception\RuntimeException;
1113
use SimpleSAML\XML\Exception\UnparseableXMLException;
1214

15+
use function count;
1316
use function file_get_contents;
1417
use function func_num_args;
1518
use function libxml_clear_errors;
1619
use function libxml_set_external_entity_loader;
1720
use function libxml_use_internal_errors;
1821
use function sprintf;
22+
use function strpos;
23+
use function substr;
1924

2025
/**
2126
* @package simplesamlphp/xml-common
@@ -115,4 +120,48 @@ public static function create(string $version = '1.0', string $encoding = 'UTF-8
115120
{
116121
return new DOMDocument($version, $encoding);
117122
}
123+
124+
125+
public static function normalizeDocument(DOMDocument $doc): DOMDocument
126+
{
127+
// Get the root element
128+
$root = $doc->documentElement;
129+
130+
// Collect all xmlns attributes from the document
131+
$xpath = new DOMXPath($doc);
132+
$xmlnsAttributes = [];
133+
134+
// Register all namespaces to ensure XPath can handle them
135+
foreach ($xpath->query('//namespace::*') as $node) {
136+
$name = $node->nodeName === 'xmlns' ? 'xmlns' : $node->nodeName;
137+
$xmlnsAttributes[$name] = $node->nodeValue;
138+
}
139+
140+
// If no xmlns attributes found, return early with debug info
141+
if (empty($xmlnsAttributes)) {
142+
return $doc->saveXML();
143+
}
144+
145+
// Remove xmlns attributes from all elements
146+
$nodes = $xpath->query('//*[namespace::*]');
147+
foreach ($nodes as $node) {
148+
$attributesToRemove = [];
149+
foreach ($node->attributes as $attr) {
150+
if (strpos($attr->nodeName, 'xmlns') === 0 || $attr->nodeName === 'xmlns') {
151+
$attributesToRemove[] = $attr->nodeName;
152+
}
153+
}
154+
foreach ($attributesToRemove as $attrName) {
155+
$node->removeAttribute($attrName);
156+
}
157+
}
158+
159+
// Add all collected xmlns attributes to the root element
160+
foreach ($xmlnsAttributes as $name => $value) {
161+
$root->setAttribute($name, $value);
162+
}
163+
164+
// Return the normalized XML
165+
return static::fromString($root->ownerDocument->saveXML());
166+
}
118167
}

src/XML/TypedTextContentTrait.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ public function toXML(?DOMElement $parent = null): DOMElement
105105

106106
$e->textContent = strval($this->getContent());
107107

108+
if (self::getContextType() === QNameValue::class) {
109+
return DOMDocumentFactory::normalizeDocument($e->ownerDocument)->documentElement;
110+
}
108111
return $e;
109112
}
110113

tests/Helper/Element.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use DOMElement;
88
use SimpleSAML\Assert\Assert;
99
use SimpleSAML\XML\AbstractElement;
10+
use SimpleSAML\XML\DOMDocumentFactory;
1011
use SimpleSAML\XMLSchema\Exception\InvalidDOMElementException;
1112
use SimpleSAML\XMLSchema\Type\BooleanValue;
1213
use SimpleSAML\XMLSchema\Type\IntegerValue;
@@ -133,6 +134,6 @@ public function toXML(?DOMElement $parent = null): DOMElement
133134
$e->setAttribute('otherText', strval($this->getOtherString()));
134135
}
135136

136-
return $e;
137+
return DOMDocumentFactory::normalizeDocument($e->ownerDocument)->documentElement;
137138
}
138139
}

tests/Helper/ExtendableAttributesElement.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use DOMElement;
88
use SimpleSAML\Assert\Assert;
99
use SimpleSAML\XML\AbstractElement;
10+
use SimpleSAML\XML\DOMDocumentFactory;
1011
use SimpleSAML\XML\ExtendableAttributesTrait;
1112
use SimpleSAML\XML\SchemaValidatableElementInterface;
1213
use SimpleSAML\XML\SchemaValidatableElementTrait;
@@ -85,6 +86,6 @@ public function toXML(?DOMElement $parent = null): DOMElement
8586
$attr->toXML($e);
8687
}
8788

88-
return $e;
89+
return DOMDocumentFactory::normalizeDocument($e->ownerDocument)->documentElement;
8990
}
9091
}

tests/Helper/ExtendableElement.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use DOMElement;
88
use SimpleSAML\XML\AbstractElement;
9+
use SimpleSAML\XML\DOMDocumentFactory;
910
use SimpleSAML\XML\ExtendableElementTrait;
1011
use SimpleSAML\XML\SchemaValidatableElementInterface;
1112
use SimpleSAML\XML\SchemaValidatableElementTrait;
@@ -76,14 +77,14 @@ public static function fromXML(DOMElement $xml): static
7677
*/
7778
public function toXML(?DOMElement $parent = null): DOMElement
7879
{
79-
$e = $this->instantiateParentElement();
80+
$e = $this->instantiateParentElement($parent);
8081

8182
foreach ($this->getElements() as $elt) {
8283
if (!$elt->isEmptyElement()) {
8384
$elt->toXML($e);
8485
}
8586
}
8687

87-
return $e;
88+
return DOMDocumentFactory::normalizeDocument($e->ownerDocument)->documentElement;
8889
}
8990
}

tests/XML/DOMDocumentFactoryTest.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,17 @@ public function testEmptyStringIsNotValid(): void
114114
);
115115
DOMDocumentFactory::fromString('');
116116
}
117+
118+
119+
public function testNormalizeDocument(): void
120+
{
121+
$normalized = DOMDocumentFactory::fromFile('tests/resources/xml/domdocument_normalized.xml');
122+
$notNormalized = DOMDocumentFactory::fromFile('tests/resources/xml/domdocument_not_normalized.xml');
123+
$normalizedDoc = DOMDocumentFactory::normalizeDocument($notNormalized);
124+
125+
$this->assertEquals(
126+
$normalized->saveXML($normalized),
127+
$normalizedDoc->saveXML($normalizedDoc),
128+
);
129+
}
117130
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<root xmlns:xml="http://www.w3.org/XML/1998/namespace" xmlns:ns1="http://example.com/ns1" xmlns:ns2="http://example.com/ns2" xmlns:ns3="http://example.com/ns3">
2+
<ns1:element1>Content</ns1:element1>
3+
<ns2:element2>Content in ns2</ns2:element2>
4+
<ns3:element3>Content in ns3</ns3:element3>
5+
</root>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<root xmlns:xml="http://www.w3.org/XML/1998/namespace" xmlns:ns1="http://example.com/ns1" xmlns:ns2="http://example.com/ns2">
2+
<ns1:element1 xmlns:ns1="http://example.com/ns1">Content</ns1:element1>
3+
<ns2:element2>Content in ns2</ns2:element2>
4+
<ns3:element3 xmlns:ns3="http://example.com/ns3">Content in ns3</ns3:element3>
5+
</root>
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<ssp:ExtendableElement xmlns:ssp="urn:x-simplesamlphp:namespace">
1+
<ssp:ExtendableElement xmlns:ssp="urn:x-simplesamlphp:namespace" xmlns:dummy="urn:custom:dummy">
22
<ssp:Chunk>some</ssp:Chunk>
3-
<dummy:Chunk xmlns:dummy="urn:custom:dummy">some</dummy:Chunk>
3+
<dummy:Chunk>some</dummy:Chunk>
44
</ssp:ExtendableElement>

0 commit comments

Comments
 (0)