44
55namespace SimpleSAML \XML \Type ;
66
7+ use DOMElement ;
78use SimpleSAML \XML \Assert \Assert ;
89use SimpleSAML \XML \Exception \SchemaViolationException ;
10+ use SimpleSAML \XML \Type \{AnyURIValue , NCNameValue };
911
10- use function explode ;
12+ use function preg_match ;
1113
1214/**
1315 * @package simplesaml/xml-common
1416 */
1517class QNameValue extends AbstractValueType
1618{
19+ protected ?AnyURIValue $ namespaceURI ;
20+ protected ?NCNameValue $ namespacePrefix ;
21+ protected NCNameValue $ localName ;
22+
23+ private static string $ qname_regex = '/^
24+ (?:
25+ \{ # Match a literal {
26+ (\S+) # Match one or more non-whitespace character
27+ \} # Match a literal }
28+ (?:
29+ ([\w_][\w.-]*) # Match a-z or underscore followed by any word-character, dot or dash
30+ : # Match a literal :
31+ )?
32+ )? # Namespace and prefix are optional
33+ ([\w_][\w.-]*) # Match a-z or underscore followed by any word-character, dot or dash
34+ $/Dimx ' ;
35+
36+
1737 /**
1838 * Sanitize the value.
1939 *
@@ -35,8 +55,49 @@ protected function sanitizeValue(string $value): string
3555 */
3656 protected function validateValue (string $ value ): void
3757 {
38- // Note: value must already be sanitized before validating
39- Assert::validQName ($ this ->sanitizeValue ($ value ), SchemaViolationException::class);
58+ $ qName = $ this ->sanitizeValue ($ value );
59+
60+ /**
61+ * Split our custom format of {<namespaceURI>}<prefix>:<localName> into individual parts
62+ */
63+ $ result = preg_match (
64+ self ::$ qname_regex ,
65+ $ qName ,
66+ $ matches ,
67+ PREG_UNMATCHED_AS_NULL ,
68+ );
69+
70+ if ($ result && count ($ matches ) === 4 ) {
71+ list ($ qName , $ namespaceURI , $ namespacePrefix , $ localName ) = $ matches ;
72+
73+ $ this ->namespaceURI = ($ namespaceURI !== null ) ? AnyURIValue::fromString ($ namespaceURI ) : null ;
74+ $ this ->namespacePrefix = ($ namespacePrefix !== null ) ? NCNameValue::fromString ($ namespacePrefix ) : null ;
75+ $ this ->localName = NCNameValue::fromString ($ localName );
76+ } else {
77+ throw new SchemaViolationException (sprintf ('\'%s \' is not a valid xs:QName. ' , $ qName ));
78+ }
79+ }
80+
81+
82+ /**
83+ * Get the value.
84+ *
85+ * @return string
86+ */
87+ public function getValue (): string
88+ {
89+ return $ this ->getNamespacePrefix () . ': ' . $ this ->getLocalName ();
90+ }
91+
92+
93+ /**
94+ * Get the namespaceURI for this qualified name.
95+ *
96+ * @return \SimpleSAML\XML\Type\AnyURIValue|null
97+ */
98+ public function getNamespaceURI (): ?AnyURIValue
99+ {
100+ return $ this ->namespaceURI ;
40101 }
41102
42103
@@ -47,12 +108,7 @@ protected function validateValue(string $value): void
47108 */
48109 public function getNamespacePrefix (): ?NCNameValue
49110 {
50- $ qname = explode (': ' , $ this ->getValue (), 2 );
51- if (count ($ qname ) === 2 ) {
52- return NCNameValue::fromString ($ qname [0 ]);
53- }
54-
55- return null ;
111+ return $ this ->namespacePrefix ;
56112 }
57113
58114
@@ -63,11 +119,53 @@ public function getNamespacePrefix(): ?NCNameValue
63119 */
64120 public function getLocalName (): NCNameValue
65121 {
66- $ qname = explode (': ' , $ this ->getValue (), 2 );
67- if (count ($ qname ) === 2 ) {
68- return NCNameValue::fromString ($ qname [1 ]);
122+ return $ this ->localName ;
123+ }
124+
125+
126+ /**
127+ * @param \SimpleSAML\XML\Type\NCNameValue $localName
128+ * @param \SimpleSAML\XML\Type\AnyURIValue|null $namespaceURI
129+ * @param \SimpleSAML\XML\Type\NCNameValue|null $namespacePrefix
130+ * @return static
131+ */
132+ public static function fromParts (
133+ NCNameValue $ localName ,
134+ ?AnyURIValue $ namespaceURI ,
135+ ?NCNameValue $ namespacePrefix ,
136+ ): static {
137+ if ($ namespaceURI === null ) {
138+ // If we don't have a namespace, we can't have a prefix either
139+ Assert::null ($ namespacePrefix ->getValue (), SchemaViolationException::class);
140+ return new static ($ localName ->getValue ());
69141 }
70142
71- return NCNameValue::fromString ($ qname [0 ]);
143+ return new static (
144+ '{ ' . $ namespaceURI ->getValue () . '} '
145+ . ($ namespacePrefix ? ($ namespacePrefix ->getValue () . ': ' ) : '' )
146+ . $ localName ,
147+ );
148+ }
149+
150+
151+ /**
152+ * @param string $qName
153+ */
154+ public static function fromDocument (
155+ string $ qName ,
156+ DOMElement $ element ,
157+ ): static {
158+ $ namespacePrefix = null ;
159+ if (str_contains ($ qName , ': ' )) {
160+ list ($ namespacePrefix , $ localName ) = explode (': ' , $ qName , 2 );
161+ } else {
162+ // No prefix
163+ $ localName = $ qName ;
164+ }
165+
166+ // Will return the default namespace (if any) when prefix is NULL
167+ $ namespaceURI = $ element ->lookupNamespaceUri ($ namespacePrefix );
168+
169+ return new static ('{ ' . $ namespaceURI . '} ' . $ namespacePrefix . ': ' . $ localName );
72170 }
73171}
0 commit comments