44
55namespace SimpleSAML \XML \Type ;
66
7+ use DOMElement ;
8+ use PREG_UNMATCHED_AS_NULL ;
79use SimpleSAML \XML \Assert \Assert ;
810use SimpleSAML \XML \Exception \SchemaViolationException ;
11+ use SimpleSAML \XML \Type \{AnyURIValue , NCNameValue };
912
10- use function explode ;
13+ use function preg_match ;
1114
1215/**
1316 * @package simplesaml/xml-common
1417 */
1518class QNameValue extends AbstractValueType
1619{
20+ protected ?AnyURIValue $ namespaceURI ;
21+ protected ?NCNameValue $ namespacePrefix ;
22+ protected NCNameValue $ localName ;
23+
24+ private static string $ qname_regex = '/^
25+ (?:
26+ \{ # Match a literal {
27+ (\S+) # Match one or more non-whitespace character
28+ \} # Match a literal }
29+ (?:
30+ ([\w_][\w.-]*) # Match a-z or underscore followed by any word-character, dot or dash
31+ : # Match a literal :
32+ )?
33+ )? # Namespace and prefix are optional
34+ ([\w_][\w.-]*) # Match a-z or underscore followed by any word-character, dot or dash
35+ $/Dimx ' ;
36+
37+
1738 /**
1839 * Sanitize the value.
1940 *
@@ -35,8 +56,49 @@ protected function sanitizeValue(string $value): string
3556 */
3657 protected function validateValue (string $ value ): void
3758 {
38- // Note: value must already be sanitized before validating
39- Assert::validQName ($ this ->sanitizeValue ($ value ), SchemaViolationException::class);
59+ $ qName = $ this ->sanitizeValue ($ value );
60+
61+ /**
62+ * Split our custom format of {<namespaceURI>}<prefix>:<localName> into individual parts
63+ */
64+ $ result = preg_match (
65+ self ::$ qname_regex ,
66+ $ qName ,
67+ $ matches ,
68+ PREG_UNMATCHED_AS_NULL ,
69+ );
70+
71+ if ($ result && count ($ matches ) === 4 ) {
72+ list ($ qName , $ namespaceURI , $ namespacePrefix , $ localName ) = $ matches ;
73+
74+ $ this ->namespaceURI = ($ namespaceURI !== null ) ? AnyURIValue::fromString ($ namespaceURI ) : null ;
75+ $ this ->namespacePrefix = ($ namespacePrefix !== null ) ? NCNameValue::fromString ($ namespacePrefix ) : null ;
76+ $ this ->localName = NCNameValue::fromString ($ localName );
77+ } else {
78+ throw new SchemaViolationException (sprintf ('\'%s \' is not a valid xs:QName. ' , $ qName ));
79+ }
80+ }
81+
82+
83+ /**
84+ * Get the value.
85+ *
86+ * @return string
87+ */
88+ public function getValue (): string
89+ {
90+ return $ this ->getNamespacePrefix () . ': ' . $ this ->getLocalName ();
91+ }
92+
93+
94+ /**
95+ * Get the namespaceURI for this qualified name.
96+ *
97+ * @return \SimpleSAML\SAML11\Type\AnyURIValue|null
98+ */
99+ public function getNamespaceURI (): ?AnyURIValue
100+ {
101+ return $ this ->namespaceURI ;
40102 }
41103
42104
@@ -47,12 +109,7 @@ protected function validateValue(string $value): void
47109 */
48110 public function getNamespacePrefix (): ?NCNameValue
49111 {
50- $ qname = explode (': ' , $ this ->getValue (), 2 );
51- if (count ($ qname ) === 2 ) {
52- return NCNameValue::fromString ($ qname [0 ]);
53- }
54-
55- return null ;
112+ return $ this ->namespacePrefix ;
56113 }
57114
58115
@@ -63,11 +120,53 @@ public function getNamespacePrefix(): ?NCNameValue
63120 */
64121 public function getLocalName (): NCNameValue
65122 {
66- $ qname = explode (': ' , $ this ->getValue (), 2 );
67- if (count ($ qname ) === 2 ) {
68- return NCNameValue::fromString ($ qname [1 ]);
123+ return $ this ->localName ;
124+ }
125+
126+
127+ /**
128+ * @param \SimpleSAML\XML\Type\NCNameValue $localName
129+ * @param \SimpleSAML\XML\Type\AnyURIValue|null $namespaceURI
130+ * @param \SimpleSAML\XML\Type\NCNameValue|null $namespacePrefix
131+ * @return static
132+ */
133+ public static function fromParts (
134+ NCNameValue $ localName ,
135+ ?AnyURIValue $ namespaceURI ,
136+ ?NCNameValue $ namespacePrefix ,
137+ ): static {
138+ if ($ namespaceURI === null ) {
139+ // If we don't have a namespace, we can't have a prefix either
140+ Assert::null ($ namespacePrefix , SchemaViolationException::class);
141+ return new static ($ localName );
69142 }
70143
71- return NCNameValue::fromString ($ qname [0 ]);
144+ return new static (
145+ '{ ' . $ namespaceURI ->getValue () . '} '
146+ . ($ namespacePrefix ? ($ namespacePrefix ->getValue () . ': ' ) : '' )
147+ . $ localName ,
148+ );
149+ }
150+
151+
152+ /**
153+ * @param string $qname
154+ */
155+ public static function fromDocument (
156+ string $ qName ,
157+ DOMElement $ element ,
158+ ) {
159+ $ namespacePrefix = null ;
160+ if (str_contains ($ qName , ': ' )) {
161+ list ($ namespacePrefix , $ localName ) = explode (': ' , $ qName , 2 );
162+ } else {
163+ // No prefix
164+ $ localName = $ qName ;
165+ }
166+
167+ // Will return the default namespace (if any) when prefix is NULL
168+ $ namespaceURI = $ element ->lookupNamespaceUri ($ namespacePrefix );
169+
170+ return new static ('{ ' . $ namespaceURI . '} ' . $ namespacePrefix . ': ' . $ localName );
72171 }
73172}
0 commit comments