Skip to content

Commit c339200

Browse files
committed
Import QNameElementTrait
1 parent 4250cc5 commit c339200

15 files changed

+269
-1
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
},
2424
"autoload-dev": {
2525
"psr-4": {
26-
"SimpleSAML\\Test\\XML\\": ["tests/src", "tests/PHPUnit/", "tests/Utils"]
26+
"SimpleSAML\\Test\\XML\\": ["tests/XML", "tests/PHPUnit/", "tests/Utils"]
2727
}
2828
},
2929
"require": {

src/QNameElementTrait.php

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SimpleSAML\XML;
6+
7+
use DOMElement;
8+
use SimpleSAML\Assert\Assert;
9+
use SimpleSAML\XML\Constants;
10+
use SimpleSAML\XML\Exception\InvalidDOMElementException;
11+
use SimpleSAML\XML\Exception\SchemaViolationException;
12+
13+
use function preg_split;
14+
15+
/**
16+
* Trait grouping common functionality for simple elements with QName textContent
17+
*
18+
* @package simplesamlphp/xml-common
19+
*/
20+
trait QNameElementTrait
21+
{
22+
/** @var string */
23+
protected string $content;
24+
25+
/** @var string|null */
26+
protected ?string $namespaceUri;
27+
28+
29+
/**
30+
* Set the content of the element.
31+
*
32+
* @param string $content The value to go in the XML textContent
33+
*/
34+
protected function setContent(string $content): void
35+
{
36+
Assert::validQName($content, SchemaViolationException::class);
37+
$this->content = $content;
38+
}
39+
40+
41+
/**
42+
* Get the content of the element.
43+
*
44+
* @return string
45+
*/
46+
public function getContent(): string
47+
{
48+
return $this->content;
49+
}
50+
51+
52+
/**
53+
* Set the namespaceUri.
54+
*
55+
* @param string|null $namespaceUri
56+
*/
57+
protected function setContentNamespaceUri(?string $namespaceUri): void
58+
{
59+
Assert::nullOrValidURI($namespaceUri, SchemaViolationException::class);
60+
$this->namespaceUri = $namespaceUri;
61+
}
62+
63+
64+
/**
65+
* Get the namespace URI.
66+
*
67+
* @return string|null
68+
*/
69+
public function getContentNamespaceUri(): ?string
70+
{
71+
return $this->namespaceUri;
72+
}
73+
74+
75+
/**
76+
* Splits a QName into an array holding the prefix (or null if no prefix is available) and the localName
77+
*
78+
* @param string $qName The qualified name
79+
* @return string[]
80+
*/
81+
private static function parseQName(string $qName): array
82+
{
83+
Assert::validQName($qName);
84+
85+
@list($prefix, $localName) = preg_split('/:/', $qName, 2);
86+
if ($localName === null) {
87+
$prefix = null;
88+
$localName = $qName;
89+
}
90+
91+
Assert::nullOrValidNCName($prefix);
92+
Assert::validNCName($localName);
93+
94+
return [$prefix, $localName];
95+
}
96+
97+
98+
/**
99+
* Convert XML into a class instance
100+
*
101+
* @param \DOMElement $xml The XML element we should load
102+
* @return static
103+
*
104+
* @throws \SimpleSAML\XML\Exception\InvalidDOMElementException
105+
* If the qualified name of the supplied element is wrong
106+
*/
107+
public static function fromXML(DOMElement $xml): static
108+
{
109+
Assert::same($xml->localName, static::getLocalName(), InvalidDOMElementException::class);
110+
Assert::same($xml->namespaceURI, static::NS, InvalidDOMElementException::class);
111+
112+
list($prefix, $localName) = self::parseQName($xml->textContent);
113+
if ($localName === null) {
114+
// We don't have a prefixed value here; use target namespace
115+
$namespace = $xml->lookupNamespaceUri(null);
116+
} else {
117+
$namespace = $xml->lookupNamespaceUri($prefix);
118+
}
119+
120+
return new static($xml->textContent, $namespace);
121+
}
122+
123+
124+
/**
125+
* Convert this element to XML.
126+
*
127+
* @param \DOMElement|null $parent The element we should append this element to.
128+
* @return \DOMElement
129+
*/
130+
public function toXML(DOMElement $parent = null): DOMElement
131+
{
132+
$e = $this->instantiateParentElement($parent);
133+
134+
list($prefix, $localName) = self::parseQName($this->content);
135+
if ($this->namespaceUri !== null && $prefix !== null) {
136+
if ($e->lookupNamespaceUri($prefix) === null && $e->lookupPrefix($this->namespaceUri) === null) {
137+
// The namespace is not yet available in the document - insert it
138+
$e->setAttribute('xmlns:' . $prefix, $this->namespaceUri);
139+
}
140+
}
141+
142+
$e->textContent = ($prefix === null) ? $localName : ($prefix . ':' . $localName);
143+
144+
return $e;
145+
}
146+
147+
148+
/** @return string */
149+
abstract public static function getLocalName(): string;
150+
151+
152+
/**
153+
* Create a document structure for this element
154+
*
155+
* @param \DOMElement|null $parent The element we should append to.
156+
* @return \DOMElement
157+
*/
158+
abstract public function instantiateParentElement(DOMElement $parent = null): DOMElement;
159+
}

tests/Utils/QNameElement.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SimpleSAML\Test\XML;
6+
7+
use SimpleSAML\XML\QNameElementTrait;
8+
use SimpleSAML\XML\AbstractElement;
9+
10+
/**
11+
* Empty shell class for testing QNameElement.
12+
*
13+
* @package simplesaml/xml-common
14+
*/
15+
final class QNameElement extends AbstractElement
16+
{
17+
use QNameElementTrait;
18+
19+
/** @var string */
20+
public const NS = 'urn:x-simplesamlphp:namespace';
21+
22+
/** @var string */
23+
public const NS_PREFIX = 'ssp';
24+
25+
26+
/**
27+
* @param string $qname
28+
* @param string|null $namespaceUri
29+
*/
30+
public function __construct(string $qname, ?string $namespaceUri = null)
31+
{
32+
$this->setContent($qname);
33+
$this->setContentNamespaceUri($namespaceUri);
34+
}
35+
}
File renamed without changes.
File renamed without changes.

tests/XML/QNameElementTraitTest.php

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SimpleSAML\Test\XML;
6+
7+
use PHPUnit\Framework\TestCase;
8+
use SimpleSAML\Assert\Assert;
9+
use SimpleSAML\Test\XML\QNamelement;
10+
use SimpleSAML\Test\XML\SerializableElementTestTrait;
11+
use SimpleSAML\XML\AbstractElement;
12+
use SimpleSAML\XML\DOMDocumentFactory;
13+
use SimpleSAML\XML\Exception\SchemaViolationException;
14+
15+
use function dirname;
16+
use function strval;
17+
18+
/**
19+
* Class \SimpleSAML\Test\XML\QNameElementTraitTest
20+
*
21+
* @covers \SimpleSAML\XML\QNameElementTrait
22+
*
23+
* @package simplesamlphp\xml-common
24+
*/
25+
final class QNameElementTraitTest extends TestCase
26+
{
27+
use SerializableElementTestTrait;
28+
29+
/**
30+
*/
31+
public function setup(): void
32+
{
33+
$this->testedClass = QNameElement::class;
34+
35+
$this->xmlRepresentation = DOMDocumentFactory::fromFile(
36+
dirname(dirname(__FILE__)) . '/resources/xml/ssp_QNameElement.xml',
37+
);
38+
}
39+
40+
41+
/**
42+
*/
43+
public function testMarshalling(): void
44+
{
45+
$qnameElement = new QNameElement('env:Sender', 'http://www.w3.org/2003/05/soap-envelope');
46+
47+
$this->assertEquals(
48+
$this->xmlRepresentation->saveXML($this->xmlRepresentation->documentElement),
49+
strval($qnameElement)
50+
);
51+
}
52+
53+
54+
/**
55+
*/
56+
public function testMarshallingInvalidQualifiedNameThrowsException(): void
57+
{
58+
$this->expectException(SchemaViolationException::class);
59+
60+
new QNameElement('0:Sender', 'http://www.w3.org/2003/05/soap-envelope');
61+
}
62+
63+
64+
/**
65+
*/
66+
public function testUnmarshalling(): void
67+
{
68+
$qnameElement = QNameElement::fromXML($this->xmlRepresentation->documentElement);
69+
70+
$this->assertEquals('env:Sender', $qnameElement->getContent());
71+
$this->assertEquals('http://www.w3.org/2003/05/soap-envelope', $qnameElement->getContentNamespaceUri());
72+
}
73+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<ssp:QNameElement xmlns:ssp="urn:x-simplesamlphp:namespace" xmlns:env="http://www.w3.org/2003/05/soap-envelope">env:Sender</ssp:QNameElement>

0 commit comments

Comments
 (0)