Passed
Push — master ( 8413b2...00f101 )
by Tim
02:19
created

Schema::fromXML()   C

Complexity

Conditions 10
Paths 32

Size

Total Lines 99
Code Lines 75

Duplication

Lines 0
Ratio 0 %

Importance

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