Schema::isEmptyElement()   B
last analyzed

Complexity

Conditions 11
Paths 11

Size

Total Lines 13
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 11
nc 11
nop 0
dl 0
loc 13
rs 7.3166
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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