Completed
Push — master ( f72cfb...4ef12f )
by Tim
21s queued 18s
created

Schema::toXML()   F

Complexity

Conditions 11
Paths 1024

Size

Total Lines 45
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

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