Passed
Branch feature/php8.3 (4d3b0a)
by Tim
17:15
created

Schema::fromXML()   C

Complexity

Conditions 10
Paths 32

Size

Total Lines 97
Code Lines 75

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 75
nc 32
nop 1
dl 0
loc 97
rs 6.6787
c 0
b 0
f 0

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