Completed
Push — master ( b4a32a...a386bd )
by Tim
18s queued 14s
created

QNameValue::getLocalName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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