Schema::getFinalDefault()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\XMLSchema\XML;
6
7
use DOMElement;
8
use SimpleSAML\XML\Assert\Assert;
9
use SimpleSAML\XML\Constants as C_XML;
10
use SimpleSAML\XML\SchemaValidatableElementInterface;
11
use SimpleSAML\XML\SchemaValidatableElementTrait;
12
use SimpleSAML\XML\Type\LangValue;
13
use SimpleSAML\XMLSchema\Constants as C_XS;
14
use SimpleSAML\XMLSchema\Exception\InvalidDOMElementException;
15
use SimpleSAML\XMLSchema\Exception\SchemaViolationException;
16
use SimpleSAML\XMLSchema\Type\AnyURIValue;
17
use SimpleSAML\XMLSchema\Type\IDValue;
18
use SimpleSAML\XMLSchema\Type\Schema\BlockSetValue;
19
use SimpleSAML\XMLSchema\Type\Schema\FormChoiceValue;
20
use SimpleSAML\XMLSchema\Type\Schema\FullDerivationSetValue;
21
use SimpleSAML\XMLSchema\Type\TokenValue;
22
use SimpleSAML\XMLSchema\XML\Interface\RedefinableInterface;
23
use SimpleSAML\XPath\XPath;
24
25
use function array_merge;
26
use function strval;
27
28
/**
29
 * Class representing the schema-element
30
 *
31
 * @package simplesamlphp/xml-common
32
 */
33
final class Schema extends AbstractOpenAttrs implements SchemaValidatableElementInterface
34
{
35
    use SchemaValidatableElementTrait;
36
37
38
    /** @var string */
39
    public const LOCALNAME = 'schema';
40
41
    /** The exclusions for the xs:anyAttribute element */
42
    public const XS_ANY_ATTR_EXCLUSIONS = [
43
        [C_XML::NS_XML, 'lang'],
44
    ];
45
46
47
    /**
48
     * Schema constructor
49
     *
50
     * @param (
51
     *     \SimpleSAML\XMLSchema\XML\XsInclude|
52
     *     \SimpleSAML\XMLSchema\XML\Import|
53
     *     \SimpleSAML\XMLSchema\XML\Redefine|
54
     *     \SimpleSAML\XMLSchema\XML\Annotation
55
     * )[] $topLevelElements
56
     * @param (
57
     *     \SimpleSAML\XMLSchema\XML\Interface\RedefinableInterface|
58
     *     \SimpleSAML\XMLSchema\XML\TopLevelAttribute|
59
     *     \SimpleSAML\XMLSchema\XML\TopLevelElement|
60
     *     \SimpleSAML\XMLSchema\XML\Notation|
61
     *     \SimpleSAML\XMLSchema\XML\Annotation
62
     * )[] $schemaTopElements
63
     * @param \SimpleSAML\XMLSchema\Type\AnyURIValue $targetNamespace
64
     * @param \SimpleSAML\XMLSchema\Type\TokenValue $version
65
     * @param \SimpleSAML\XMLSchema\Type\Schema\FullDerivationSetValue $finalDefault
66
     * @param \SimpleSAML\XMLSchema\Type\Schema\BlockSetValue $blockDefault
67
     * @param \SimpleSAML\XMLSchema\Type\Schema\FormChoiceValue|null $attributeFormDefault
68
     * @param \SimpleSAML\XMLSchema\Type\Schema\FormChoiceValue|null $elementFormDefault
69
     * @param \SimpleSAML\XMLSchema\Type\IDValue|null $id
70
     * @param \SimpleSAML\XML\Type\LangValue|null $lang
71
     * @param array<\SimpleSAML\XML\Attribute> $namespacedAttributes
72
     */
73
    public function __construct(
74
        protected array $topLevelElements = [],
75
        protected array $schemaTopElements = [],
76
        protected ?AnyURIValue $targetNamespace = null,
77
        protected ?TokenValue $version = null,
78
        protected ?FullDerivationSetValue $finalDefault = null,
79
        protected ?BlockSetValue $blockDefault = null,
80
        protected ?FormChoiceValue $attributeFormDefault = null,
81
        protected ?FormChoiceValue $elementFormDefault = null,
82
        protected ?IDValue $id = null,
83
        protected ?LangValue $lang = null,
84
        array $namespacedAttributes = [],
85
    ) {
86
        Assert::maxCount($topLevelElements, C_XML::UNBOUNDED_LIMIT);
87
        Assert::allIsInstanceOfAny(
88
            $topLevelElements,
89
            [XsInclude::class, Import::class, Redefine::class, Annotation::class],
90
            SchemaViolationException::class,
91
        );
92
93
        Assert::maxCount($schemaTopElements, C_XML::UNBOUNDED_LIMIT);
94
        Assert::allIsInstanceOfAny(
95
            $schemaTopElements,
96
            [
97
                RedefinableInterface::class,
98
                TopLevelAttribute::class,
99
                TopLevelElement::class,
100
                Notation::class,
101
                Annotation::class,
102
            ],
103
            SchemaViolationException::class,
104
        );
105
106
        parent::__construct($namespacedAttributes);
107
    }
108
109
110
    /**
111
     * Collect the value of the topLevelElements-property
112
     *
113
     * @return (
114
     *     \SimpleSAML\XMLSchema\XML\XsInclude|
115
     *     \SimpleSAML\XMLSchema\XML\Import|
116
     *     \SimpleSAML\XMLSchema\XML\Redefine|
117
     *     \SimpleSAML\XMLSchema\XML\Annotation
118
     * )[]
119
     */
120
    public function getTopLevelElements(): array
121
    {
122
        return $this->topLevelElements;
123
    }
124
125
126
    /**
127
     * Collect the value of the schemaTopElements-property
128
     *
129
     * @return (
130
     *     \SimpleSAML\XMLSchema\XML\Interface\RedefinableInterface|
131
     *     \SimpleSAML\XMLSchema\XML\TopLevelAttribute|
132
     *     \SimpleSAML\XMLSchema\XML\TopLevelElement|
133
     *     \SimpleSAML\XMLSchema\XML\Notation|
134
     *     \SimpleSAML\XMLSchema\XML\Annotation
135
     * )[]
136
     */
137
    public function getSchemaTopElements(): array
138
    {
139
        return $this->schemaTopElements;
140
    }
141
142
143
    /**
144
     * Collect the value of the targetNamespace-property
145
     *
146
     * @return \SimpleSAML\XMLSchema\Type\AnyURIValue|null
147
     */
148
    public function getTargetNamespace(): ?AnyURIValue
149
    {
150
        return $this->targetNamespace;
151
    }
152
153
154
    /**
155
     * Collect the value of the version-property
156
     *
157
     * @return \SimpleSAML\XMLSchema\Type\TokenValue|null
158
     */
159
    public function getVersion(): ?TokenValue
160
    {
161
        return $this->version;
162
    }
163
164
165
    /**
166
     * Collect the value of the blockDefault-property
167
     *
168
     * @return \SimpleSAML\XMLSchema\Type\Schema\BlockSetValue|null
169
     */
170
    public function getBlockDefault(): ?BlockSetValue
171
    {
172
        return $this->blockDefault;
173
    }
174
175
176
    /**
177
     * Collect the value of the finalDefault-property
178
     *
179
     * @return \SimpleSAML\XMLSchema\Type\Schema\FullDerivationSetValue|null
180
     */
181
    public function getFinalDefault(): ?FullDerivationSetValue
182
    {
183
        return $this->finalDefault;
184
    }
185
186
187
    /**
188
     * Collect the value of the attributeFormDefault-property
189
     *
190
     * @return \SimpleSAML\XMLSchema\Type\Schema\FormChoiceValue|null
191
     */
192
    public function getAttributeFormDefault(): ?FormChoiceValue
193
    {
194
        return $this->attributeFormDefault;
195
    }
196
197
198
    /**
199
     * Collect the value of the elementFormDefault-property
200
     *
201
     * @return \SimpleSAML\XMLSchema\Type\Schema\FormChoiceValue|null
202
     */
203
    public function getElementFormDefault(): ?FormChoiceValue
204
    {
205
        return $this->elementFormDefault;
206
    }
207
208
209
    /**
210
     * Collect the value of the id-property
211
     *
212
     * @return \SimpleSAML\XMLSchema\Type\IDValue|null
213
     */
214
    public function getID(): ?IDValue
215
    {
216
        return $this->id;
217
    }
218
219
220
    /**
221
     * Collect the value of the lang-property
222
     *
223
     * @return \SimpleSAML\XML\Type\LangValue|null
224
     */
225
    public function getLang(): ?LangValue
226
    {
227
        return $this->lang;
228
    }
229
230
231
    /**
232
     * Test if an object, at the state it's in, would produce an empty XML-element
233
     *
234
     * @return bool
235
     */
236
    public function isEmptyElement(): bool
237
    {
238
        return parent::isEmptyElement() &&
239
            empty($this->getTopLevelElements()) &&
240
            empty($this->getSchemaTopElements()) &&
241
            empty($this->getTargetNamespace()) &&
242
            empty($this->getVersion()) &&
243
            empty($this->getFinalDefault()) &&
244
            empty($this->getBlockDefault()) &&
245
            empty($this->getAttributeFormDefault()) &&
246
            empty($this->getElementFormDefault()) &&
247
            empty($this->getId()) &&
248
            empty($this->getLang());
249
    }
250
251
252
    /**
253
     * Create an instance of this object from its XML representation.
254
     *
255
     * @param \DOMElement $xml
256
     * @return static
257
     *
258
     * @throws \SimpleSAML\XMLSchema\Exception\InvalidDOMElementException
259
     *   if the qualified name of the supplied element is wrong
260
     */
261
    public static function fromXML(DOMElement $xml): static
262
    {
263
        Assert::same($xml->localName, static::getLocalName(), InvalidDOMElementException::class);
264
        Assert::same($xml->namespaceURI, static::NS, InvalidDOMElementException::class);
265
266
        $xpCache = XPath::getXPath($xml);
267
268
        $beforeAllowed = [
269
            'annotation' => Annotation::class,
270
            'import' => Import::class,
271
            'include' => XsInclude::class,
272
            'redefine' => Redefine::class,
273
        ];
274
        $beforeSchemaTopElements = XPath::xpQuery(
275
            $xml,
276
            '('
277
            . '/xs:schema/xs:group|'
278
            . '/xs:schema/xs:attributeGroup|'
279
            . '/xs:schema/xs:complexType|'
280
            . '/xs:schema/xs:simpleType|'
281
            . '/xs:schema/xs:element|'
282
            .  '/xs:schema/xs:attribute'
283
            . ')[1]/preceding-sibling::xs:*',
284
            $xpCache,
285
        );
286
287
        $topLevelElements = [];
288
        foreach ($beforeSchemaTopElements as $node) {
289
            if ($node instanceof DOMElement) {
290
                if ($node->namespaceURI === C_XS::NS_XS && array_key_exists($node->localName, $beforeAllowed)) {
291
                    $topLevelElements[] = $beforeAllowed[$node->localName]::fromXML($node);
292
                }
293
            }
294
        }
295
296
        $afterAllowed = [
297
            'annotation' => Annotation::class,
298
            'attribute' => TopLevelAttribute::class,
299
            'attributeGroup' => NamedAttributeGroup::class,
300
            'complexType' => TopLevelComplexType::class,
301
            'element' => TopLevelElement::class,
302
            'notation' => Notation::class,
303
            'simpleType' => TopLevelSimpleType::class,
304
        ];
305
        $afterSchemaTopElementFirstHit = XPath::xpQuery(
306
            $xml,
307
            '('
308
            . '/xs:schema/xs:group|'
309
            . '/xs:schema/xs:attributeGroup|'
310
            . '/xs:schema/xs:complexType'
311
            . '/xs:schema/xs:simpleType|'
312
            . '/xs:schema/xs:element|'
313
            . '/xs:schema/xs:attribute'
314
            . ')[1]',
315
            $xpCache,
316
        );
317
318
        $afterSchemaTopElementSibling = XPath::xpQuery(
319
            $xml,
320
            '('
321
            . '/xs:schema/xs:group|'
322
            . '/xs:schema/xs:attributeGroup|'
323
            . '/xs:schema/xs:complexType'
324
            . '/xs:schema/xs:simpleType|'
325
            . '/xs:schema/xs:element|'
326
            . '/xs:schema/xs:attribute'
327
            . ')[1]/following-sibling::xs:*',
328
            $xpCache,
329
        );
330
331
        $afterSchemaTopElements = array_merge($afterSchemaTopElementFirstHit, $afterSchemaTopElementSibling);
332
333
        $schemaTopElements = [];
334
        foreach ($afterSchemaTopElements as $node) {
335
            if ($node instanceof DOMElement) {
336
                if ($node->namespaceURI === C_XS::NS_XS && array_key_exists($node->localName, $afterAllowed)) {
337
                    $schemaTopElements[] = $afterAllowed[$node->localName]::fromXML($node);
338
                }
339
            }
340
        }
341
342
        $lang = $xml->hasAttributeNS(C_XML::NS_XML, 'lang')
343
            ? LangValue::fromString($xml->getAttributeNS(C_XML::NS_XML, 'lang'))
344
            : null;
345
346
        return new static(
347
            $topLevelElements,
348
            $schemaTopElements,
349
            self::getOptionalAttribute($xml, 'targetNamespace', AnyURIValue::class, null),
350
            self::getOptionalAttribute($xml, 'version', TokenValue::class, null),
351
            self::getOptionalAttribute($xml, 'finalDefault', FullDerivationSetValue::class, null),
352
            self::getOptionalAttribute($xml, 'blockDefault', BlockSetValue::class, null),
353
            self::getOptionalAttribute($xml, 'attributeFormDefault', FormChoiceValue::class, null),
354
            self::getOptionalAttribute($xml, 'elementFormDefault', FormChoiceValue::class, null),
355
            self::getOptionalAttribute($xml, 'id', IDValue::class, null),
356
            $lang,
357
            self::getAttributesNSFromXML($xml),
358
        );
359
    }
360
361
362
    /**
363
     * Add this Schema to an XML element.
364
     *
365
     * @param \DOMElement|null $parent The element we should append this Schema to.
366
     * @return \DOMElement
367
     */
368
    public function toXML(?DOMElement $parent = null): DOMElement
369
    {
370
        $e = parent::toXML($parent);
371
372
        if ($this->getTargetNamespace() !== null) {
373
            $e->setAttribute('targetNamespace', strval($this->getTargetNamespace()));
374
        }
375
376
        if ($this->getVersion() !== null) {
377
            $e->setAttribute('version', strval($this->getVersion()));
378
        }
379
380
        if ($this->getFinalDefault() !== null) {
381
            $e->setAttribute('finalDefault', strval($this->getFinalDefault()));
382
        }
383
384
        if ($this->getBlockDefault() !== null) {
385
            $e->setAttribute('blockDefault', strval($this->getBlockDefault()));
386
        }
387
388
        if ($this->getAttributeFormDefault() !== null) {
389
            $e->setAttribute('attributeFormDefault', strval($this->getAttributeFormDefault()));
390
        }
391
392
        if ($this->getElementFormDefault() !== null) {
393
            $e->setAttribute('elementFormDefault', strval($this->getElementFormDefault()));
394
        }
395
396
        if ($this->getId() !== null) {
397
            $e->setAttribute('id', strval($this->getId()));
398
        }
399
400
        $this->getLang()?->toAttribute()->toXML($e);
401
402
        foreach ($this->getTopLevelElements() as $tle) {
403
            $tle->toXML($e);
404
        }
405
406
        foreach ($this->getSchemaTopElements() as $ste) {
407
            $ste->toXML($e);
408
        }
409
410
        return $e;
411
    }
412
}
413