QNameValue   A
last analyzed

Complexity

Total Complexity 17

Size/Duplication

Total Lines 168
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 40
c 1
b 0
f 0
dl 0
loc 168
rs 10
wmc 17

8 Methods

Rating   Name   Duplication   Size   Complexity  
A getLocalName() 0 3 1
A sanitizeValue() 0 3 1
A getValue() 0 3 1
A fromParts() 0 15 3
A getNamespacePrefix() 0 3 1
A validateValue() 0 22 5
A getNamespaceURI() 0 3 1
A fromDocument() 0 20 4
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\XMLSchema\Type;
6
7
use DOMElement;
8
use SimpleSAML\XML\Assert\Assert;
9
use SimpleSAML\XML\DOMDocumentFactory;
10
use SimpleSAML\XMLSchema\Exception\SchemaViolationException;
11
use SimpleSAML\XMLSchema\Type\Interface\AbstractAnySimpleType;
12
13
use function preg_match;
14
15
/**
16
 * @package simplesaml/xml-common
17
 */
18
class QNameValue extends AbstractAnySimpleType
19
{
20
    /** @var string */
21
    public const SCHEMA_TYPE = 'QName';
22
23
24
    /** @var \SimpleSAML\XMLSchema\Type\AnyURIValue|null */
25
    protected ?AnyURIValue $namespaceURI;
26
27
    /** @var \SimpleSAML\XMLSchema\Type\NCNameValue|null */
28
    protected ?NCNameValue $namespacePrefix;
29
30
    /** @var \SimpleSAML\XMLSchema\Type\NCNameValue */
31
    protected NCNameValue $localName;
32
33
    /** @var string */
34
    private static string $qname_regex = '/^
35
        (?:
36
          \{                 # Match a literal {
37
            (\S+)            # Match one or more non-whitespace character
38
          \}                 # Match a literal }
39
          (?:
40
            ([\w_][\w.-]*)   # Match a-z or underscore followed by any word-character, dot or dash
41
            :                # Match a literal :
42
          )?
43
        )?                   # Namespace and prefix are optional
44
        ([\w_][\w.-]*)       # Match a-z or underscore followed by any word-character, dot or dash
45
        $/Dimx';
46
47
48
    /**
49
     * Sanitize the value.
50
     *
51
     * @param string $value  The unsanitized value
52
     * @return string
53
     */
54
    protected function sanitizeValue(string $value): string
55
    {
56
        return static::collapseWhitespace(static::normalizeWhitespace($value));
57
    }
58
59
60
    /**
61
     * Validate the value.
62
     *
63
     * @param string $value
64
     * @throws \SimpleSAML\XMLSchema\Exception\SchemaViolationException on failure
65
     * @return void
66
     */
67
    protected function validateValue(string $value): void
68
    {
69
        $qName = $this->sanitizeValue($value);
70
71
        /**
72
         * Split our custom format of {<namespaceURI>}<prefix>:<localName> into individual parts
73
         */
74
        $result = preg_match(
75
            self::$qname_regex,
76
            $qName,
77
            $matches,
78
            PREG_UNMATCHED_AS_NULL,
79
        );
80
81
        if ($result && count($matches) === 4) {
82
            list($qName, $namespaceURI, $namespacePrefix, $localName) = $matches;
83
84
            $this->namespaceURI = ($namespaceURI !== null) ? AnyURIValue::fromString($namespaceURI) : null;
85
            $this->namespacePrefix = ($namespacePrefix !== null) ? NCNameValue::fromString($namespacePrefix) : null;
86
            $this->localName = NCNameValue::fromString($localName);
87
        } else {
88
            throw new SchemaViolationException(sprintf('\'%s\' is not a valid xs:QName.', $qName));
89
        }
90
    }
91
92
93
    /**
94
     * Get the value.
95
     *
96
     * @return string
97
     */
98
    public function getValue(): string
99
    {
100
        return $this->getNamespacePrefix() . ':' . $this->getLocalName();
101
    }
102
103
104
    /**
105
     * Get the namespaceURI for this qualified name.
106
     *
107
     * @return \SimpleSAML\XMLSchema\Type\AnyURIValue|null
108
     */
109
    public function getNamespaceURI(): ?AnyURIValue
110
    {
111
        return $this->namespaceURI;
112
    }
113
114
115
    /**
116
     * Get the namespace-prefix for this qualified name.
117
     *
118
     * @return \SimpleSAML\XMLSchema\Type\NCNameValue|null
119
     */
120
    public function getNamespacePrefix(): ?NCNameValue
121
    {
122
        return $this->namespacePrefix;
123
    }
124
125
126
    /**
127
     * Get the local name for this qualified name.
128
     *
129
     * @return \SimpleSAML\XMLSchema\Type\NCNameValue
130
     */
131
    public function getLocalName(): NCNameValue
132
    {
133
        return $this->localName;
134
    }
135
136
137
    /**
138
     * @param \SimpleSAML\XMLSchema\Type\NCNameValue $localName
139
     * @param \SimpleSAML\XMLSchema\Type\AnyURIValue|null $namespaceURI
140
     * @param \SimpleSAML\XMLSchema\Type\NCNameValue|null $namespacePrefix
141
     * @return static
142
     */
143
    public static function fromParts(
144
        NCNameValue $localName,
145
        ?AnyURIValue $namespaceURI,
146
        ?NCNameValue $namespacePrefix,
147
    ): static {
148
        if ($namespaceURI === null) {
149
            // If we don't have a namespace, we can't have a prefix either
150
            Assert::null($namespacePrefix, SchemaViolationException::class);
151
            return new static($localName->getValue());
152
        }
153
154
        return new static(
155
            '{' . $namespaceURI->getValue() . '}'
156
            . ($namespacePrefix ? ($namespacePrefix->getValue() . ':') : '')
157
            . $localName,
158
        );
159
    }
160
161
162
    /**
163
     * @param string $qName
164
     * @param \DOMElement $element
165
     */
166
    public static function fromDocument(
167
        string $qName,
168
        DOMElement $element,
169
    ): static {
170
        $namespacePrefix = null;
171
        if (str_contains($qName, ':')) {
172
            list($namespacePrefix, $localName) = explode(':', $qName, 2);
173
        } else {
174
            // No prefix
175
            $localName = $qName;
176
        }
177
178
        // Will return the default namespace (if any) when prefix is NULL
179
        if ($namespacePrefix === null) {
180
            $namespaceURI = $element->lookupNamespaceURI($namespacePrefix);
181
        } else {
182
            $namespaceURI = DOMDocumentFactory::lookupNamespaceUri($element, $namespacePrefix);
183
        }
184
185
        return new static('{' . $namespaceURI . '}' . ($namespacePrefix ? $namespacePrefix . ':' : '') . $localName);
186
    }
187
}
188