Passed
Pull Request — master (#71)
by Axel
09:56
created

SchemaReader::loadChoice()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 11
c 1
b 0
f 0
dl 0
loc 14
rs 9.9
ccs 10
cts 10
cp 1
cc 1
nc 1
nop 2
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace GoetasWebservices\XML\XSDReader;
6
7
use Closure;
8
use DOMDocument;
9
use DOMElement;
10
use DOMNode;
11
use GoetasWebservices\XML\XSDReader\Documentation\DocumentationReader;
12
use GoetasWebservices\XML\XSDReader\Documentation\StandardDocumentationReader;
13
use GoetasWebservices\XML\XSDReader\Exception\IOException;
14
use GoetasWebservices\XML\XSDReader\Exception\TypeException;
15
use GoetasWebservices\XML\XSDReader\Schema\Attribute\Attribute;
16
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeContainer;
17
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeDef;
18
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeItem;
19
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeSingle;
20
use GoetasWebservices\XML\XSDReader\Schema\Attribute\Group as AttributeGroup;
21
use GoetasWebservices\XML\XSDReader\Schema\Element\AbstractElementSingle;
22
use GoetasWebservices\XML\XSDReader\Schema\Element\Choice;
23
use GoetasWebservices\XML\XSDReader\Schema\Element\Element;
24
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementContainer;
25
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementDef;
26
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementRef;
27
use GoetasWebservices\XML\XSDReader\Schema\Element\Group;
28
use GoetasWebservices\XML\XSDReader\Schema\Element\GroupRef;
29
use GoetasWebservices\XML\XSDReader\Schema\Element\InterfaceSetDefault;
30
use GoetasWebservices\XML\XSDReader\Schema\Element\InterfaceSetFixed;
31
use GoetasWebservices\XML\XSDReader\Schema\Element\InterfaceSetMinMax;
32
use GoetasWebservices\XML\XSDReader\Schema\Exception\TypeNotFoundException;
33
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Base;
34
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Extension;
35
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Restriction;
36
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\RestrictionType;
37
use GoetasWebservices\XML\XSDReader\Schema\Item;
38
use GoetasWebservices\XML\XSDReader\Schema\Schema;
39
use GoetasWebservices\XML\XSDReader\Schema\SchemaItem;
40
use GoetasWebservices\XML\XSDReader\Schema\Type\BaseComplexType;
41
use GoetasWebservices\XML\XSDReader\Schema\Type\ComplexType;
42
use GoetasWebservices\XML\XSDReader\Schema\Type\ComplexTypeSimpleContent;
43
use GoetasWebservices\XML\XSDReader\Schema\Type\SimpleType;
44
use GoetasWebservices\XML\XSDReader\Schema\Type\Type;
45
use GoetasWebservices\XML\XSDReader\Utils\UrlUtils;
46
47
class SchemaReader
48
{
49
    public const XSD_NS = 'http://www.w3.org/2001/XMLSchema';
50
51
    public const XML_NS = 'http://www.w3.org/XML/1998/namespace';
52
53
    private DocumentationReader $documentationReader;
54
55
    /**
56
     * @var Schema[]
57
     */
58
    private array $loadedFiles = [];
59
60
    /**
61
     * @var Schema[][]
62
     */
63
    private array $loadedSchemas = [];
64
65
    /**
66
     * @var string[]
67
     */
68
    protected array $knownLocationSchemas = [
69
        'http://www.w3.org/2001/xml.xsd' => (
70
            __DIR__ . '/Resources/xml.xsd'
71
        ),
72
        'http://www.w3.org/2001/XMLSchema.xsd' => (
73
            __DIR__ . '/Resources/XMLSchema.xsd'
74
        ),
75
        'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' => (
76
            __DIR__ . '/Resources/oasis-200401-wss-wssecurity-secext-1.0.xsd'
77
        ),
78
        'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd' => (
79
            __DIR__.'/Resources/oasis-200401-wss-wssecurity-utility-1.0.xsd'
80
        ),
81
        'https://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd' => (
82
            __DIR__.'/Resources/xmldsig-core-schema.xsd'
83
        ),
84
        'http://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd' => (
85
            __DIR__.'/Resources/xmldsig-core-schema.xsd'
86
        ),
87
    ];
88
89
    /**
90
     * @var string[]
91
     */
92
    protected array $knownNamespaceSchemaLocations = [
93
        'http://www.w3.org/2000/09/xmldsig#' => (
94
            __DIR__ . '/Resources/xmldsig-core-schema.xsd'
95
        ),
96
    ];
97
98
    /**
99
     * @var string[]
100 2
     */
101
    protected static array $globalSchemaInfo = [
102 2
        self::XML_NS => 'http://www.w3.org/2001/xml.xsd',
103
        self::XSD_NS => 'http://www.w3.org/2001/XMLSchema.xsd',
104 2
    ];
105 1
106
    private function extractErrorMessage(): \Exception
107 2
    {
108 2
        $errors = [];
109
110 2
        foreach (libxml_get_errors() as $error) {
111
            $errors[] = sprintf("Error[%s] code %s: %s in '%s' at position %s:%s", $error->level, $error->code, trim($error->message), $error->file, $error->line, $error->column);
112
        }
113 85
        $e = new \Exception(implode('; ', $errors));
114
        libxml_use_internal_errors(false);
115 85
116 85
        return $e;
117
    }
118 85
119 85
    public function __construct(DocumentationReader $documentationReader = null)
120
    {
121
        if (null === $documentationReader) {
122
            $documentationReader = new StandardDocumentationReader();
123
        }
124
        $this->documentationReader = $documentationReader;
125
    }
126
127 1
    /**
128
     * Override remote location with a local file.
129 1
     *
130 1
     * @param string $remote remote schema URL
131
     * @param string $local  local file path
132
     */
133
    public function addKnownSchemaLocation(string $remote, string $local): void
134
    {
135
        $this->knownLocationSchemas[$remote] = $local;
136
    }
137
138
    /**
139 1
     * Specify schema location by namespace.
140
     * This can be used for schemas which import namespaces but do not specify schemaLocation attributes.
141 1
     *
142 1
     * @param string $namespace namespace
143
     * @param string $location  schema URL
144 74
     */
145
    public function addKnownNamespaceSchemaLocation(string $namespace, string $location): void
146
    {
147
        $this->knownNamespaceSchemaLocations[$namespace] = $location;
148 74
    }
149 74
150 74
    private function loadAttributeGroup(
151
        Schema $schema,
152
        DOMElement $node
153 74
    ): Closure {
154 74
        $attGroup = new AttributeGroup($schema, $node->getAttribute('name'));
155
        $attGroup->setDoc($this->getDocumentation($node));
156
        $schema->addAttributeGroup($attGroup);
157
158
        return function () use ($schema, $node, $attGroup): void {
159 74
            SchemaReader::againstDOMNodeList(
160 74
                $node,
161
                function (
162 74
                    DOMElement $node,
163 74
                    DOMElement $childNode
164 74
                ) use (
165 74
                    $schema,
166 74
                    $attGroup
167 74
                ): void {
168
                    switch ($childNode->localName) {
169 74
                        case 'attribute':
170 74
                            $attribute = $this->getAttributeFromAttributeOrRef(
171 74
                                $childNode,
172 1
                                $schema,
173 1
                                $node
174 1
                            );
175 1
                            $attGroup->addAttribute($attribute);
176 1
                            break;
177
                        case 'attributeGroup':
178 1
                            $this->findSomethingLikeAttributeGroup(
179
                                $schema,
180 74
                                $node,
181
                                $childNode,
182 74
                                $attGroup
183
                            );
184
                            break;
185 74
                    }
186
                }
187
            );
188
        };
189
    }
190 74
191 74
    private function getAttributeFromAttributeOrRef(
192
        DOMElement $childNode,
193
        Schema $schema,
194
        DOMElement $node
195
    ): AttributeItem {
196 74
        if ($childNode->hasAttribute('ref')) {
197
            $attribute = $this->findAttributeItem($schema, $node, $childNode->getAttribute('ref'));
198
        } else {
199 74
            /**
200
             * @var Attribute
201
             */
202 74
            $attribute = $this->loadAttribute($schema, $childNode);
203
        }
204
205
        return $attribute;
206 74
    }
207 74
208 74
    private function loadAttribute(Schema $schema, DOMElement $node): Attribute
209
    {
210 74
        $attribute = new Attribute($schema, $node->getAttribute('name'));
211 1
        $attribute->setDoc($this->getDocumentation($node));
212
        $this->fillItem($attribute, $node);
213 74
        $this->fillAttribute($attribute, $node);
214 1
215
        return $attribute;
216 74
    }
217 74
218
    private function fillAttribute(AttributeSingle $attribute, DOMElement $node): void
219
    {
220 74
        if ($node->hasAttribute('fixed')) {
221
            $attribute->setFixed($node->getAttribute('fixed'));
0 ignored issues
show
Bug introduced by
The method setFixed() does not exist on GoetasWebservices\XML\XS...tribute\AttributeSingle. Since it exists in all sub-types, consider adding an abstract or default implementation to GoetasWebservices\XML\XS...tribute\AttributeSingle. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

221
            $attribute->/** @scrutinizer ignore-call */ 
222
                        setFixed($node->getAttribute('fixed'));
Loading history...
222
        }
223 74
        if ($node->hasAttribute('default')) {
224
            $attribute->setDefault($node->getAttribute('default'));
0 ignored issues
show
Bug introduced by
The method setDefault() does not exist on GoetasWebservices\XML\XS...tribute\AttributeSingle. Since it exists in all sub-types, consider adding an abstract or default implementation to GoetasWebservices\XML\XS...tribute\AttributeSingle. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

224
            $attribute->/** @scrutinizer ignore-call */ 
225
                        setDefault($node->getAttribute('default'));
Loading history...
225
        }
226
        if ($node->hasAttribute('nillable')) {
227
            $attribute->setNil('true' === $node->getAttribute('nillable'));
228 74
        }
229 74
        if ($node->hasAttribute('form')) {
230 74
            $attribute->setQualified('qualified' === $node->getAttribute('form'));
231 74
        }
232
        if ($node->hasAttribute('use')) {
233 74
            $attribute->setUse($node->getAttribute('use'));
234 74
        }
235 74
    }
236
237
    private function loadAttributeOrElementDef(
238
        Schema $schema,
239 74
        DOMElement $node,
240 74
        DOMElement $childNode,
241
        bool $isAttribute
242
    ): Closure {
243 74
        $name = $childNode->getAttribute('name');
244
        if ($isAttribute) {
245 74
            $attribute = new AttributeDef($schema, $name);
246
            $attribute->setDoc($this->getDocumentation($childNode));
247
            $this->fillAttribute($attribute, $childNode);
248 74
            $schema->addAttribute($attribute);
249
        } else {
250 74
            $attribute = new ElementDef($schema, $name);
251
            $attribute->setDoc($this->getDocumentation($childNode));
252
            $this->fillElement($attribute, $childNode);
253
            $schema->addElement($attribute);
254
            $this->resolveSubstitutionGroup($schema, $node, $childNode, $attribute);
255
        }
256 74
257
        return function () use ($attribute, $childNode): void {
258 74
            $this->fillItem($attribute, $childNode);
259 74
        };
260
    }
261 74
262 74
    private function loadElementDef(Schema $schema, DOMElement $node, DOMElement $childNode): Closure
263
    {
264
        return $this->loadAttributeOrElementDef($schema, $node, $childNode, false);
265
    }
266
267 74
    private function loadAttributeDef(Schema $schema, DOMElement $node, DOMElement $childNode): Closure
268 74
    {
269
        return $this->loadAttributeOrElementDef($schema, $node, $childNode, true);
270 74
    }
271
272 74
    private function getDocumentation(DOMElement $node): string
273 74
    {
274 74
        return $this->documentationReader->get($node);
275 74
    }
276 74
277 74
    /**
278 74
     * @return Closure[]
279 74
     */
280 74
    private function schemaNode(Schema $schema, DOMElement $node, Schema $parent = null): array
281 74
    {
282 74
        $this->setSchemaThingsFromNode($schema, $node, $parent);
283 74
        $functions = [];
284 74
285 74
        self::againstDOMNodeList(
286 74
            $node,
287 74
            function (DOMElement $node, DOMElement $childNode) use ($schema, &$functions): void {
288 74
                $callback = null;
289 74
290 74
                switch ($childNode->localName) {
291 74
                    case 'attributeGroup':
292 74
                        $callback = $this->loadAttributeGroup($schema, $childNode);
293 74
                        break;
294 74
                    case 'include':
295
                    case 'import':
296
                        $callback = $this->loadImport($schema, $childNode);
297 74
                        break;
298 74
                    case 'element':
299
                        $callback = $this->loadElementDef($schema, $node, $childNode);
300 74
                        break;
301
                    case 'attribute':
302
                        $callback = $this->loadAttributeDef($schema, $node, $childNode);
303 74
                        break;
304
                    case 'group':
305
                        $callback = $this->loadGroup($schema, $childNode);
306 74
                        break;
307
                    case 'choice':
308 74
                        $callback = $this->loadChoice($schema, $childNode);
309 74
                        break;
310
                    case 'complexType':
311 74
                        $callback = $this->loadComplexType($schema, $childNode);
312 74
                        break;
313
                    case 'simpleType':
314 74
                        $callback = $this->loadSimpleType($schema, $childNode);
315
                        break;
316
                }
317 74
318
                if ($callback instanceof Closure) {
319 74
                    $functions[] = $callback;
320 74
                }
321
            }
322 74
        );
323
324 74
        return $functions;
325
    }
326 74
327 74
    private function createGroupRef(Group $referenced, DOMElement $node): GroupRef
328 74
    {
329 7
        $ref = new GroupRef($referenced);
330
        $ref->setDoc($this->getDocumentation($node));
331
332 74
        self::maybeSetMax($ref, $node);
333
        self::maybeSetMin($ref, $node);
334 74
335
        return $ref;
336 74
    }
337 1
338
    private static function maybeSetMax(InterfaceSetMinMax $ref, DOMElement $node): void
339 74
    {
340
        if ($node->hasAttribute('maxOccurs')) {
341 74
            $ref->setMax('unbounded' === $node->getAttribute('maxOccurs') ? -1 : (int) $node->getAttribute('maxOccurs'));
342
        }
343
    }
344
345 74
    private static function maybeSetMin(InterfaceSetMinMax $ref, DOMElement $node): void
346 74
    {
347 74
        if ($node->hasAttribute('minOccurs')) {
348
            $ref->setMin((int) $node->getAttribute('minOccurs'));
349 74
            if (-1 !== $ref->getMax() && $ref->getMin() > $ref->getMax()) {
350 74
                $ref->setMax($ref->getMin());
351
            }
352
        }
353 74
    }
354 74
355
    private static function maybeSetFixed(InterfaceSetFixed $ref, DOMElement $node): void
356 74
    {
357 74
        if ($node->hasAttribute('fixed')) {
358
            $ref->setFixed($node->getAttribute('fixed'));
359 74
        }
360 74
    }
361
362
    private static function maybeSetDefault(InterfaceSetDefault $ref, DOMElement $node): void
363
    {
364
        if ($node->hasAttribute('default')) {
365 74
            $ref->setDefault($node->getAttribute('default'));
366 74
        }
367 74
    }
368
369 74
    private function loadSequence(ElementContainer $elementContainer, DOMElement $node, int $max = null, int $min = null): void
370 74
    {
371 74
        $max =
372 74
            (
373 74
                (is_int($max) && (bool) $max) ||
374 74
                'unbounded' === $node->getAttribute('maxOccurs') ||
375
                1 < $node->getAttribute('maxOccurs')
376 74
            )
377
                ? 2
378 74
                : null;
379
        $min =
380 74
            (
381
                null === $min &&
382
                !$node->hasAttribute('minOccurs')
383
            )
384
                ? null
385
                : (int) max((int) $min, $node->getAttribute('minOccurs'));
386
387 74
        self::againstDOMNodeList(
388 74
            $node,
389 74
            function (DOMElement $node, DOMElement $childNode) use ($elementContainer, $max, $min): void {
390 74
                $this->loadSequenceChildNode(
391 74
                    $elementContainer,
392 74
                    $node,
393 74
                    $childNode,
394 74
                    $max,
395 74
                    $min
396
                );
397 74
            }
398 74
        );
399 74
    }
400 74
401 74
    private function loadSequenceChildNode(
402 74
        ElementContainer $elementContainer,
403 74
        DOMElement $node,
404 74
        DOMElement $childNode,
405
        ?int $max,
406 74
        ?int $min = null
407 74
    ): void {
408 74
        switch ($childNode->localName) {
409 74
            case 'sequence':
410 74
            case 'all':
411 74
                $this->loadSequence(
412 74
                    $elementContainer,
413
                    $childNode,
414 74
                    $max,
415
                    $min
416 74
                );
417
                break;
418 74
            case 'choice':
419
                $this->loadChoiceWithChildren($elementContainer->getSchema(), $childNode, $elementContainer);
420
                break;
421
            case 'element':
422
                $this->loadSequenceChildNodeLoadElement(
423
                    $elementContainer,
424
                    $node,
425 74
                    $childNode,
426 74
                    $max,
427 74
                    $min
428 74
                );
429
                break;
430 74
            case 'group':
431 74
                $this->addGroupAsElement(
432
                    $elementContainer->getSchema(),
433 74
                    $node,
434 74
                    $childNode,
435
                    $elementContainer
436 74
                );
437 74
                break;
438
        }
439
    }
440 74
441
    private function loadSequenceChildNodeLoadElement(
442
        ElementContainer $elementContainer,
443 74
        DOMElement $node,
444 74
        DOMElement $childNode,
445
        ?int $max,
446
        ?int $min
447 74
    ): void {
448 74
        $schema = $elementContainer->getSchema();
449 74
        if ($childNode->hasAttribute('ref')) {
450
            $elementDef = $this->findElement($schema, $node, $childNode->getAttribute('ref'));
451
            $element = new ElementRef($elementDef);
452
            $element->setDoc($this->getDocumentation($childNode));
453 74
            $this->fillElement($element, $childNode);
454 74
455
            if ($node->hasAttribute('name')) {
456
                $element->setName($node->getAttribute('name'));
457 74
            }
458
        } else {
459
            $element = $this->createElement($schema, $childNode);
460
        }
461
462
        $this->resolveSubstitutionGroup($schema, $node, $childNode, $element);
463 74
464
        if (null !== $min) {
465 74
            $element->setMin($min);
466 74
        }
467
468 74
        if (1 < $max) {
469
            /*
470
             * although one might think the typecast is not needed with $max being `? int $max` after passing > 1,
471
             * phpstan@a4f89fa still thinks it's possibly null.
472
             * see https://github.com/phpstan/phpstan/issues/577 for related issue
473
             */
474 74
            $element->setMax((int) $max);
475 74
        }
476 74
        $elementContainer->addElement($element);
477 74
    }
478
479
    private function resolveSubstitutionGroup(
480 74
        Schema $schema,
481 74
        DOMElement $node,
482 74
        DOMElement $childNode,
483
        AbstractElementSingle $element
484 74
    ): void {
485
        if ($childNode->hasAttribute('substitutionGroup')) {
486 74
            $substitutionGroup = $childNode->getAttribute('substitutionGroup');
487 74
            $elementDef = $this->findElement($schema, $node, $substitutionGroup);
488 74
            $elementDef->addSubstitutionCandidate($element);
489
        }
490 74
    }
491 1
492
    private function addGroupAsElement(
493 1
        Schema $schema,
494 1
        DOMElement $node,
495
        DOMElement $childNode,
496 1
        ElementContainer $elementContainer
497 1
    ): void {
498
        $referencedGroup = $this->findGroup(
499
            $schema,
500
            $node,
501 74
            $childNode->getAttribute('ref')
502
        );
503
504 74
        $group = $this->createGroupRef($referencedGroup, $childNode);
505 74
        $elementContainer->addElement($group);
506
    }
507 74
508 74
    private function loadChoiceWithChildren(
509 74
        Schema $schema,
510 74
        DOMElement $node,
511 74
        ElementContainer $elementContainer
512 74
    ): void {
513
        $choice = $this->createChoice($schema, $node);
514 74
        $elementContainer->addElement($choice);
515
516 74
        self::againstDOMNodeList(
517
            $node,
518
            function (DOMElement $node, DOMElement $childNode) use ($choice): void {
519 74
                $this->loadSequenceChildNode(
520
                    $choice,
521
                    $node,
522
                    $childNode,
523
                    null,
524 74
                    null
525
                );
526 74
            }
527 74
        );
528
    }
529
530
    private function loadGroup(Schema $schema, DOMElement $node): Closure
531
    {
532 74
        $group = new Group($schema, $node->getAttribute('name'));
533
        $group->setDoc($this->getDocumentation($node));
534 74
        $groupOriginal = $group;
535 1
536
        if ($node->hasAttribute('maxOccurs') || $node->hasAttribute('minOccurs')) {
537 74
            $group = $this->createGroupRef($group, $node);
538 2
        }
539
540 74
        $schema->addGroup($group);
541
542
        return function () use ($groupOriginal, $node): void {
543 74
            static::againstDOMNodeList(
544
                $node,
545 74
                function (DOMelement $node, DOMElement $childNode) use ($groupOriginal): void {
546 74
                    switch ($childNode->localName) {
547 74
                        case 'sequence':
548
                        case 'all':
549
                            $this->loadSequence($groupOriginal, $childNode);
550
                            break;
551 74
                        case 'choice':
552
                            $this->loadChoiceWithChildren($groupOriginal->getSchema(), $childNode, $groupOriginal);
553 74
                    }
554 74
                }
555
            );
556
        };
557
    }
558
559 74
    private function loadChoice(Schema $schema, DOMElement $node): Closure
560 74
    {
561
        $choice = $this->createChoice($schema, $node);
562 74
563 74
        return function () use ($choice, $node): void {
564 74
            self::againstDOMNodeList(
565 74
                $node,
566 74
                function (DOMElement $node, DOMElement $childNode) use ($choice): void {
567
                    $this->loadSequenceChildNode(
568 74
                        $choice,
569
                        $node,
570
                        $childNode,
571 74
                        null,
572 74
                        null
573
                    );
574 74
                }
575
            );
576
        };
577 74
    }
578
579
    private function createChoice(Schema $schema, DOMElement $node): Choice
580
    {
581
        $choice = new Choice($schema, '');
582
        $choice->setDoc($this->getDocumentation($node));
583 74
        self::maybeSetMax($choice, $node);
584 74
        self::maybeSetMin($choice, $node);
585 74
586 74
        return $choice;
587 74
    }
588 74
589 74
    private function loadComplexType(Schema $schema, DOMElement $node, Closure $callback = null): Closure
590 74
    {
591
        /**
592
         * @var bool
593 74
         */
594 74
        $isSimple = false;
595 74
596 74
        self::againstDOMNodeList(
597 74
            $node,
598 74
            function (DOMElement $node, DOMElement $childNode) use (&$isSimple): void {
599 74
                if ($isSimple) {
600
                    return;
601 74
                }
602 74
                if ('simpleContent' === $childNode->localName) {
603 2
                    $isSimple = true;
604 2
                }
605 2
            }
606 2
        );
607 2
608
        $type = $isSimple ? new ComplexTypeSimpleContent($schema, $node->getAttribute('name')) : new ComplexType($schema, $node->getAttribute('name'));
609 2
610 74
        $type->setDoc($this->getDocumentation($node));
611
        if ($node->getAttribute('name')) {
612 1
            $schema->addType($type);
613
        }
614 1
615 1
        return function () use ($type, $node, $schema, $callback): void {
616 1
            $this->fillTypeNode($type, $node, true);
617 1
618 1
            self::againstDOMNodeList(
619
                $node,
620
                function (DOMElement $node, DOMElement $childNode) use ($schema, $type): void {
621 1
                    $this->loadComplexTypeFromChildNode($type, $node, $childNode, $schema);
622
                }
623 74
            );
624
625 74
            if ($callback instanceof Closure) {
626
                call_user_func($callback, $type);
627 74
            }
628 74
        };
629 74
    }
630 74
631
    private function loadComplexTypeFromChildNode(
632
        BaseComplexType $type,
633
        DOMElement $node,
634 74
        DOMElement $childNode,
635
        Schema $schema
636 74
    ): void {
637 74
        switch ($childNode->localName) {
638
            case 'sequence':
639 74
            case 'all':
640 74
                if ($type instanceof ElementContainer) {
641 74
                    $this->loadSequence($type, $childNode);
642 74
                }
643 74
                break;
644 74
            case 'choice':
645 74
                if ($type instanceof ComplexType) {
646
                    $this->loadChoiceWithChildren($schema, $childNode, $type);
647 74
                }
648
                break;
649
            case 'attribute':
650 74
                $this->addAttributeFromAttributeOrRef($type, $childNode, $schema, $node);
651 74
                break;
652
            case 'attributeGroup':
653 74
                $this->findSomethingLikeAttributeGroup($schema, $node, $childNode, $type);
654
                break;
655
            case 'group':
656 74
                if ($type instanceof ComplexType) {
657
                    $this->addGroupAsElement($schema, $node, $childNode, $type);
658 74
                }
659
                break;
660
        }
661
    }
662 74
663 74
    private function loadSimpleType(Schema $schema, DOMElement $node, Closure $callback = null): Closure
664
    {
665 74
        $type = new SimpleType($schema, $node->getAttribute('name'));
666 74
        $type->setDoc($this->getDocumentation($node));
667
        if ($node->getAttribute('name')) {
668
            $schema->addType($type);
669
        }
670
671 74
        return function () use ($type, $node, $callback): void {
672
            $this->fillTypeNode($type, $node, true);
673 74
674 74
            self::againstDOMNodeList(
675 74
                $node,
676
                function (DOMElement $node, DOMElement $childNode) use ($type): void {
677 74
                    switch ($childNode->localName) {
678 74
                        case 'union':
679
                            $this->loadUnion($type, $childNode);
680 74
                            break;
681
                        case 'list':
682
                            $this->loadList($type, $childNode);
683 74
                            break;
684
                    }
685 74
                }
686
            );
687
688
            if ($callback instanceof Closure) {
689
                call_user_func($callback, $type);
690 74
            }
691 74
        };
692 74
    }
693 74
694
    private function loadList(SimpleType $type, DOMElement $node): void
695
    {
696
        if ($node->hasAttribute('itemType')) {
697 74
            /**
698
             * @var SimpleType
699
             */
700
            $listType = $this->findSomeType($type, $node, 'itemType');
701
            $type->setList($listType);
702 74
        } else {
703 74
            self::againstDOMNodeList(
704 74
                $node,
705 74
                function (DOMElement $node, DOMElement $childNode) use ($type): void {
706
                    $this->loadTypeWithCallback(
707
                        $type->getSchema(),
708 74
                        $childNode,
709
                        function (SimpleType $list) use ($type): void {
710
                            $type->setList($list);
711 74
                        }
712
                    );
713 74
                }
714 74
            );
715 74
        }
716
    }
717
718
    private function findSomeType(
719 74
        SchemaItem $fromThis,
720 74
        DOMElement $node,
721 74
        string $attributeName
722 74
    ): SchemaItem {
723
        return $this->findSomeTypeFromAttribute(
724 74
            $fromThis,
725
            $node,
726
            $node->getAttribute($attributeName)
727 74
        );
728 74
    }
729
730
    private function findSomeTypeFromAttribute(
731
        SchemaItem $fromThis,
732
        DOMElement $node,
733 74
        string $attributeName
734
    ): SchemaItem {
735 74
        return $this->findType(
736 74
            $fromThis->getSchema(),
737 74
            $node,
738
            $attributeName
739 74
        );
740 74
    }
741
742 74
    private function loadUnion(SimpleType $type, DOMElement $node): void
743
    {
744 74
        if ($node->hasAttribute('memberTypes')) {
745
            $types = preg_split('/\s+/', $node->getAttribute('memberTypes'));
746 74
            foreach ($types as $typeName) {
747
                /**
748 74
                 * @var SimpleType
749 74
                 */
750
                $unionType = $this->findSomeTypeFromAttribute($type, $node, $typeName);
751
                $type->addUnion($unionType);
752 74
            }
753 74
        }
754
        self::againstDOMNodeList(
755 74
            $node,
756 74
            function (DOMElement $node, DOMElement $childNode) use ($type): void {
757 74
                $this->loadTypeWithCallback(
758 74
                    $type->getSchema(),
759 74
                    $childNode,
760 74
                    function (SimpleType $unType) use ($type): void {
761 74
                        $type->addUnion($unType);
762
                    }
763 74
                );
764 74
            }
765 74
        );
766 74
    }
767 74
768
    private function fillTypeNode(Type $type, DOMElement $node, bool $checkAbstract = false): void
769 74
    {
770
        if ($checkAbstract) {
771 74
            $type->setAbstract('true' === $node->getAttribute('abstract') || '1' === $node->getAttribute('abstract'));
772
        }
773 74
774
        self::againstDOMNodeList(
775 74
            $node,
776 74
            function (DOMElement $node, DOMElement $childNode) use ($type): void {
777
                switch ($childNode->localName) {
778 74
                    case 'restriction':
779 74
                        $this->loadRestriction($type, $childNode);
780 74
                        break;
781 74
                    case 'extension':
782 74
                        if ($type instanceof BaseComplexType) {
783
                            $this->loadExtension($type, $childNode);
784
                        }
785 74
                        break;
786 74
                    case 'simpleContent':
787
                    case 'complexContent':
788 74
                        $this->fillTypeNode($type, $childNode);
789
                        break;
790
                }
791
            }
792
        );
793
    }
794
795
    private function loadExtension(BaseComplexType $type, DOMElement $node): void
796 74
    {
797 74
        $extension = new Extension();
798 74
        $type->setExtension($extension);
799
800 74
        if ($node->hasAttribute('base')) {
801
            $this->findAndSetSomeBase($type, $extension, $node);
802
        }
803
        $this->loadExtensionChildNodes($type, $node);
804 74
    }
805 74
806
    private function findAndSetSomeBase(Type $type, Base $setBaseOnThis, DOMElement $node): void
807
    {
808
        /**
809
         * @var Type
810 74
         */
811
        $parent = $this->findSomeType($type, $node, 'base');
812 74
        $setBaseOnThis->setBase($parent);
813 74
    }
814 74
815 74
    private function loadExtensionChildNodes(
816 74
        BaseComplexType $type,
817 74
        DOMElement $node
818 74
    ): void {
819 74
        self::againstDOMNodeList(
820
            $node,
821
            function (DOMElement $node, DOMElement $childNode) use ($type): void {
822 74
                switch ($childNode->localName) {
823 74
                    case 'sequence':
824 74
                    case 'choice':
825 74
                    case 'all':
826 74
                        if ($type instanceof ElementContainer) {
827 74
                            $this->loadSequence($type, $childNode);
828 74
                        }
829
                        break;
830 74
                }
831 74
                $this->loadChildAttributesAndAttributeGroups($type, $node, $childNode);
832 74
            }
833 74
        );
834 74
    }
835 74
836 74
    private function loadChildAttributesAndAttributeGroups(
837
        BaseComplexType $type,
838 74
        DOMElement $node,
839
        DOMElement $childNode
840 74
    ): void {
841
        switch ($childNode->localName) {
842 74
            case 'attribute':
843
                $this->addAttributeFromAttributeOrRef(
844 74
                    $type,
845
                    $childNode,
846 74
                    $type->getSchema(),
847 74
                    $node
848 74
                );
849 74
                break;
850
            case 'attributeGroup':
851 74
                $this->findSomethingLikeAttributeGroup(
852 74
                    $type->getSchema(),
853
                    $node,
854
                    $childNode,
855
                    $type
856
                );
857 74
                break;
858 74
        }
859
    }
860 74
861 74
    private function loadRestriction(Type $type, DOMElement $node): void
862 74
    {
863
        $restriction = new Restriction();
864 74
        $type->setRestriction($restriction);
865 74
        if ($node->hasAttribute('base')) {
866
            $this->findAndSetSomeBase($type, $restriction, $node);
867 74
        } else {
868
            self::againstDOMNodeList(
869
                $node,
870 74
                function (
871 74
                    DOMElement $node,
872
                    DOMElement $childNode
873
                ) use (
874
                    $type,
875
                    $restriction
876 74
                ): void {
877
                    $this->loadTypeWithCallback(
878
                        $type->getSchema(),
879 74
                        $childNode,
880 74
                        function (Type $restType) use ($restriction): void {
881
                            $restriction->setBase($restType);
882 74
                        }
883
                    );
884
                }
885
            );
886
        }
887
        $this->loadRestrictionChildNodes($type, $restriction, $node);
888
    }
889
890
    private function loadRestrictionChildNodes(
891
        Type $type,
892
        Restriction $restriction,
893
        DOMElement $node
894
    ): void {
895 74
        self::againstDOMNodeList(
896
            $node,
897
            function (DOMElement $node, DOMElement $childNode) use ($type, $restriction): void {
898 74
                if ($type instanceof BaseComplexType) {
899 74
                    $this->loadChildAttributesAndAttributeGroups($type, $node, $childNode);
900
                }
901 74
                if (null !== ($restrictionType = RestrictionType::tryFrom($childNode->localName))) {
902 74
                    $restriction->addCheck(
903
                        $restrictionType,
904
                        [
905
                            'value' => $childNode->getAttribute('value'),
906 74
                            'doc' => $this->getDocumentation($childNode),
907
                        ]
908 74
                    );
909
                }
910
            }
911
        );
912
    }
913 74
914
    /**
915 74
     * @return mixed[]
916 74
     */
917 74
    private static function splitParts(DOMElement $node, string $typeName): array
918 74
    {
919
        $prefix = null;
920
        $name = $typeName;
921
        if (str_contains($typeName, ':')) {
922
            [$prefix, $name] = explode(':', $typeName);
923
        }
924 74
925
        /**
926
         * @psalm-suppress PossiblyNullArgument
927 74
         */
928 74
        $namespace = $node->lookupNamespaceUri($prefix);
929 74
930
        return [
931
            $name,
932
            $namespace,
933 74
            $prefix,
934
        ];
935 74
    }
936
937
    private function findAttributeItem(Schema $schema, DOMElement $node, string $typeName): AttributeItem
938
    {
939
        [$name, $namespace] = static::splitParts($node, $typeName);
940 74
941
        /**
942
         * @var string|null $namespace
943
         */
944
        $namespace = $namespace ?: $schema->getTargetNamespace();
945
946 74
        try {
947
            /**
948 74
             * @var AttributeItem $out
949
             */
950
            $out = $schema->findAttribute((string) $name, $namespace);
951
952
            return $out;
953
        } catch (TypeNotFoundException $e) {
954 74
            throw new TypeException(sprintf("Can't find %s named {%s}#%s, at line %d in %s ", 'attribute', $namespace, $name, $node->getLineNo(), $node->ownerDocument->documentURI), 0, $e);
955
        }
956 74
    }
957
958
    private function findAttributeGroup(Schema $schema, DOMElement $node, string $typeName): AttributeGroup
959
    {
960
        [$name, $namespace] = static::splitParts($node, $typeName);
961 74
962
        /**
963
         * @var string|null $namespace
964
         */
965
        $namespace = $namespace ?: $schema->getTargetNamespace();
966
967 74
        try {
968
            /**
969 74
             * @var AttributeGroup $out
970
             */
971
            $out = $schema->findAttributeGroup((string) $name, $namespace);
972
973
            return $out;
974
        } catch (TypeNotFoundException $e) {
975 74
            throw new TypeException(sprintf("Can't find %s named {%s}#%s, at line %d in %s ", 'attributegroup', $namespace, $name, $node->getLineNo(), $node->ownerDocument->documentURI), 0, $e);
976
        }
977 74
    }
978
979
    private function findElement(Schema $schema, DOMElement $node, string $typeName): ElementDef
980
    {
981
        [$name, $namespace] = static::splitParts($node, $typeName);
982 74
983
        /**
984
         * @var string|null $namespace
985 74
         */
986
        $namespace = $namespace ?: $schema->getTargetNamespace();
987
988
        try {
989
            return $schema->findElement((string) $name, $namespace);
990
        } catch (TypeNotFoundException $e) {
991 74
            throw new TypeException(sprintf("Can't find %s named {%s}#%s, at line %d in %s ", 'element', $namespace, $name, $node->getLineNo(), $node->ownerDocument->documentURI), 0, $e);
992
        }
993 74
    }
994
995
    private function findGroup(Schema $schema, DOMElement $node, string $typeName): Group
996
    {
997
        [$name, $namespace] = static::splitParts($node, $typeName);
998 74
999
        /**
1000
         * @var string|null $namespace
1001
         */
1002
        $namespace = $namespace ?: $schema->getTargetNamespace();
1003
1004 74
        try {
1005
            /**
1006 74
             * @var Group $out
1007
             */
1008
            $out = $schema->findGroup((string) $name, $namespace);
1009
1010
            return $out;
1011
        } catch (TypeNotFoundException $e) {
1012 74
            throw new TypeException(sprintf("Can't find %s named {%s}#%s, at line %d in %s ", 'group', $namespace, $name, $node->getLineNo(), $node->ownerDocument->documentURI), 0, $e);
1013
        }
1014 74
    }
1015
1016
    private function findType(Schema $schema, DOMElement $node, string $typeName): SchemaItem
1017
    {
1018
        [$name, $namespace] = static::splitParts($node, $typeName);
1019 74
1020
        /**
1021
         * @var string|null $namespace
1022
         */
1023 74
        $namespace = $namespace ?: $schema->getTargetNamespace();
1024 1
1025 1
        $tryFindType = static function (Schema $schema, string $name, ?string $namespace): ?SchemaItem {
1026
            try {
1027 74
                return $schema->findType($name, $namespace);
1028
            } catch (TypeNotFoundException $e) {
1029 74
                return null;
1030 74
            }
1031 74
        };
1032 74
1033
        $interestingSchemas = array_merge([$schema], $this->loadedSchemas[$namespace] ?? []);
1034
        foreach ($interestingSchemas as $interestingSchema) {
1035
            if ($result = $tryFindType($interestingSchema, $name, $namespace)) {
1036
                return $result;
1037
            }
1038
        }
1039 74
1040
        throw new TypeException(sprintf("Can't find %s named {%s}#%s, at line %d in %s ", 'type', $namespace, $name, $node->getLineNo(), $node->ownerDocument->documentURI));
1041 74
    }
1042
1043
    private function fillItem(Item $element, DOMElement $node): void
1044 74
    {
1045
        /**
1046
         * @var bool
1047
         */
1048
        $skip = false;
1049 74
        self::againstDOMNodeList(
1050 74
            $node,
1051 74
            function (DOMElement $node, DOMElement $childNode) use ($element, &$skip): void {
1052
                if (
1053
                    !$skip &&
1054
                    in_array(
1055
                        $childNode->localName,
1056 74
                        [
1057 74
                            'complexType',
1058
                            'simpleType',
1059
                        ],
1060 74
                        true
1061 74
                    )
1062 74
                ) {
1063
                    $this->loadTypeWithCallback(
1064 74
                        $element->getSchema(),
1065
                        $childNode,
1066
                        function (Type $type) use ($element): void {
1067 74
                            $element->setType($type);
1068
                        }
1069
                    );
1070 74
                    $skip = true;
1071 74
                }
1072 74
            }
1073
        );
1074 74
        if ($skip) {
1075 74
            return;
1076
        }
1077 74
        $this->fillItemNonLocalType($element, $node);
1078
    }
1079 74
1080
    private function fillItemNonLocalType(Item $element, DOMElement $node): void
1081 74
    {
1082 74
        if ($node->getAttribute('type')) {
1083
            /**
1084 74
             * @var Type
1085 74
             */
1086
            $type = $this->findSomeType($element, $node, 'type');
1087 74
        } else {
1088
            /**
1089 74
             * @var Type
1090
             */
1091
            $type = $this->findSomeTypeFromAttribute(
1092
                $element,
1093 74
                $node,
1094
                ($node->lookupPrefix(self::XSD_NS).':anyType')
1095
            );
1096
        }
1097
1098 74
        $element->setType($type);
1099 74
    }
1100 74
1101 74
    private function loadImport(
1102
        Schema $schema,
1103
        DOMElement $node
1104
    ): Closure {
1105 74
        $namespace = $node->getAttribute('namespace');
1106 74
        $schemaLocation = $node->getAttribute('schemaLocation');
1107
        if (!$schemaLocation && isset($this->knownNamespaceSchemaLocations[$namespace])) {
1108 74
            $schemaLocation = $this->knownNamespaceSchemaLocations[$namespace];
1109
        }
1110
1111
        // postpone schema loading
1112 74
        if ($namespace && !$schemaLocation && !isset(self::$globalSchemaInfo[$namespace])) {
1113 74
            return function () use ($schema, $namespace): void {
1114 74
                if (!empty($this->loadedSchemas[$namespace])) {
1115 1
                    foreach ($this->loadedSchemas[$namespace] as $s) {
1116
                        $schema->addSchema($s, $namespace);
1117
                    }
1118
                }
1119 74
            };
1120
        }
1121 3
1122 3
        if ($namespace && !$schemaLocation && !empty($this->loadedSchemas[$namespace])) {
1123 3
            foreach ($this->loadedSchemas[$namespace] as $s) {
1124
                $schema->addSchema($s, $namespace);
1125
            }
1126 3
        }
1127 74
1128 1
        $base = urldecode($node->ownerDocument->documentURI);
0 ignored issues
show
Bug introduced by
It seems like $node->ownerDocument->documentURI can also be of type null; however, parameter $string of urldecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1128
        $base = urldecode(/** @scrutinizer ignore-type */ $node->ownerDocument->documentURI);
Loading history...
1129 1
        $file = UrlUtils::resolveRelativeUrl($base, $schemaLocation);
1130
1131
        if (isset($this->loadedFiles[$file])) {
1132
            $schema->addSchema($this->loadedFiles[$file]);
1133 74
1134 74
            return function (): void {
1135
            };
1136 74
        }
1137 74
1138
        return $this->loadImportFresh($namespace, $schema, $file);
1139
    }
1140 74
1141
    private function createOrUseSchemaForNs(Schema $schema, string $namespace): Schema
1142
    {
1143 3
        if ('' !== trim($namespace)) {
1144
            $newSchema = new Schema();
1145
            $newSchema->addSchema($this->getGlobalSchema());
1146 3
            $schema->addSchema($newSchema);
1147
        } else {
1148
            $newSchema = $schema;
1149
        }
1150 3
1151 2
        return $newSchema;
1152 2
    }
1153 2
1154
    private function loadImportFresh(
1155 1
        string $namespace,
1156
        Schema $schema,
1157
        string $file
1158 3
    ): Closure {
1159
        return function () use ($namespace, $schema, $file): void {
1160
            $dom = $this->getDOM(
1161 3
                $this->knownLocationSchemas[$file]
1162
                    ?? $file
1163
            );
1164
1165
            $schemaNew = $this->createOrUseSchemaForNs($schema, $namespace);
1166
1167 3
            $this->setLoadedFile($file, $schemaNew);
1168 3
1169 3
            $callbacks = $this->schemaNode($schemaNew, $dom->documentElement, $schema);
1170
1171
            foreach ($callbacks as $callback) {
1172 3
                $callback();
1173
            }
1174 3
        };
1175
    }
1176 3
1177
    /**
1178 3
     * @var Schema|null
1179 3
     */
1180
    protected $globalSchema;
1181 3
1182
    public function getGlobalSchema(): Schema
1183
    {
1184
        if (!($this->globalSchema instanceof Schema)) {
1185
            $callbacks = [];
1186
            $globalSchemas = [];
1187
            /**
1188
             * @var string $namespace
1189 74
             */
1190
            foreach (self::$globalSchemaInfo as $namespace => $uri) {
1191 74
                $this->setLoadedFile(
1192 74
                    $uri,
1193 74
                    $globalSchemas[$namespace] = $schema = new Schema()
1194
                );
1195
                $this->setLoadedSchema($namespace, $schema);
1196
                if ($namespace === self::XSD_NS) {
1197 74
                    $this->globalSchema = $schema;
1198 74
                }
1199 74
                $xml = $this->getDOM($this->knownLocationSchemas[$uri]);
1200 74
                $callbacks = array_merge($callbacks, $this->schemaNode($schema, $xml->documentElement));
1201
            }
1202 74
1203 74
            $globalSchemas[(string) static::XSD_NS]->addType(new SimpleType($globalSchemas[(string) static::XSD_NS], 'anySimpleType'));
1204 74
            $globalSchemas[(string) static::XSD_NS]->addType(new SimpleType($globalSchemas[(string) static::XSD_NS], 'anyType'));
1205
1206 74
            $globalSchemas[(string) static::XML_NS]->addSchema(
1207 74
                $globalSchemas[(string) static::XSD_NS],
1208
                (string) static::XSD_NS
1209
            );
1210 74
            $globalSchemas[(string) static::XSD_NS]->addSchema(
1211 74
                $globalSchemas[(string) static::XML_NS],
1212
                (string) static::XML_NS
1213 74
            );
1214 74
1215 74
            /**
1216
             * @var Closure $callback
1217 74
             */
1218 74
            foreach ($callbacks as $callback) {
1219 74
                $callback();
1220
            }
1221
        }
1222
1223
        if (!($this->globalSchema instanceof Schema)) {
1224
            throw new TypeException('Global schema not discovered');
1225 74
        }
1226 74
1227
        return $this->globalSchema;
1228
    }
1229
1230 74
    /**
1231
     * @param DOMNode[] $nodes
1232
     */
1233
    public function readNodes(array $nodes, string $file = null): Schema
1234 74
    {
1235
        $rootSchema = new Schema();
1236
        $rootSchema->addSchema($this->getGlobalSchema());
1237
1238
        if ($file !== null) {
1239
            $this->setLoadedFile($file, $rootSchema);
1240 2
        }
1241
1242 2
        $all = [];
1243 2
        foreach ($nodes as $k => $node) {
1244
            if (($node instanceof \DOMElement) && $node->namespaceURI === self::XSD_NS && $node->localName == 'schema') {
1245 2
                $holderSchema = new Schema();
1246 2
                $holderSchema->addSchema($this->getGlobalSchema());
1247
1248
                $this->setLoadedSchemaFromElement($node, $holderSchema);
1249 2
1250 2
                $rootSchema->addSchema($holderSchema);
1251 2
1252 2
                $callbacks = $this->schemaNode($holderSchema, $node);
1253 2
                $all = array_merge($callbacks, $all);
1254
            }
1255 2
        }
1256
1257 2
        foreach ($all as $callback) {
1258
            call_user_func($callback);
1259 2
        }
1260 2
1261
        return $rootSchema;
1262
    }
1263
1264 2
    public function readNode(DOMElement $node, string $file = null): Schema
1265 2
    {
1266
        $rootSchema = new Schema();
1267
        $rootSchema->addSchema($this->getGlobalSchema());
1268 2
1269
        if ($file !== null) {
1270
            $this->setLoadedFile($file, $rootSchema);
1271 72
        }
1272
1273 72
        $this->setLoadedSchemaFromElement($node, $rootSchema);
1274 72
1275
        $callbacks = $this->schemaNode($rootSchema, $node);
1276 72
1277 71
        foreach ($callbacks as $callback) {
1278
            call_user_func($callback);
1279
        }
1280 72
1281
        return $rootSchema;
1282 72
    }
1283
1284 72
    /**
1285 66
     * @throws IOException
1286
     */
1287
    public function readString(string $content, string $file = 'schema.xsd'): Schema
1288 72
    {
1289
        $xml = new DOMDocument('1.0', 'UTF-8');
1290
        libxml_use_internal_errors(true);
1291
        if (!@$xml->loadXML($content)) {
1292
            throw new IOException("Can't load the schema", 0, $this->extractErrorMessage());
1293
        }
1294 68
        libxml_use_internal_errors(false);
1295
        $xml->documentURI = $file;
1296 68
1297 68
        return $this->readNode($xml->documentElement, $file);
1298 68
    }
1299 1
1300
    /**
1301 67
     * @throws IOException
1302 67
     */
1303
    public function readFile(string $file): Schema
1304 67
    {
1305
        $xml = $this->getDOM($file);
1306
1307
        return $this->readNode($xml->documentElement, $file);
1308
    }
1309
1310 5
    /**
1311
     * @throws IOException
1312 5
     */
1313
    private function getDOM(string $file): DOMDocument
1314 4
    {
1315
        $xml = new DOMDocument('1.0', 'UTF-8');
1316
        libxml_use_internal_errors(true);
1317
        if (!@$xml->load($file)) {
1318
            libxml_use_internal_errors(false);
1319
            throw new IOException("Can't load the file '$file'", 0, $this->extractErrorMessage());
1320 75
        }
1321
        libxml_use_internal_errors(false);
1322 75
1323 75
        return $xml;
1324 75
    }
1325 1
1326 1
    private static function againstDOMNodeList(DOMElement $node, Closure $againstNodeList): void
1327
    {
1328 74
        $limit = $node->childNodes->length;
1329
        for ($i = 0; $i < $limit; ++$i) {
1330 74
            /**
1331
             * @var DOMNode
1332
             */
1333 74
            $childNode = $node->childNodes->item($i);
1334
1335
            if ($childNode instanceof DOMElement) {
1336
                $againstNodeList($node, $childNode);
1337 74
            }
1338 74
        }
1339
    }
1340
1341
    private function loadTypeWithCallback(Schema $schema, DOMElement $childNode, Closure $callback): void
1342 74
    {
1343
        /**
1344 74
         * @var Closure|null $func
1345 74
         */
1346 74
        $func = null;
1347 74
1348
        switch ($childNode->localName) {
1349
            case 'complexType':
1350
                $func = $this->loadComplexType($schema, $childNode, $callback);
1351 74
                break;
1352
            case 'simpleType':
1353 74
                $func = $this->loadSimpleType($schema, $childNode, $callback);
1354
                break;
1355
        }
1356
1357
        if ($func instanceof Closure) {
1358
            call_user_func($func);
1359
        }
1360
    }
1361 74
1362
    private function createElement(Schema $schema, DOMElement $node): Element
1363 74
    {
1364 74
        $element = new Element($schema, $node->getAttribute('name'));
1365 74
        $element->setDoc($this->getDocumentation($node));
1366 74
        $this->fillItem($element, $node);
1367 74
        $this->fillElement($element, $node);
1368 74
1369 74
        return $element;
1370
    }
1371
1372 74
    private function fillElement(AbstractElementSingle $element, DOMElement $node): void
1373 74
    {
1374
        self::maybeSetMax($element, $node);
1375 74
        self::maybeSetMin($element, $node);
1376
        self::maybeSetFixed($element, $node);
1377 74
        self::maybeSetDefault($element, $node);
1378
1379
        $xp = new \DOMXPath($node->ownerDocument);
0 ignored issues
show
Bug introduced by
It seems like $node->ownerDocument can also be of type null; however, parameter $document of DOMXPath::__construct() does only seem to accept DOMDocument, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1379
        $xp = new \DOMXPath(/** @scrutinizer ignore-type */ $node->ownerDocument);
Loading history...
1380
        $xp->registerNamespace('xs', self::XSD_NS);
1381 74
1382 74
        if ($xp->query('ancestor::xs:choice', $node)->length) {
1383
            $element->setMin(0);
1384 74
        }
1385
1386 74
        if ($node->hasAttribute('nillable')) {
1387 74
            $element->setNil('true' === $node->getAttribute('nillable'));
1388 74
        }
1389
        if ($node->hasAttribute('form')) {
1390 74
            $element->setQualified('qualified' === $node->getAttribute('form'));
1391 74
        }
1392
1393 74
        $parentNode = $node->parentNode;
1394 74
        if ('schema' !== $parentNode->localName || self::XSD_NS !== $parentNode->namespaceURI) {
1395
            $element->setLocal(true);
1396
        }
1397 74
    }
1398 4
1399
    private function addAttributeFromAttributeOrRef(
1400 74
        BaseComplexType $type,
1401 5
        DOMElement $childNode,
1402
        Schema $schema,
1403
        DOMElement $node
1404 74
    ): void {
1405
        $attribute = $this->getAttributeFromAttributeOrRef(
1406 74
            $childNode,
1407 74
            $schema,
1408
            $node
1409
        );
1410 74
1411
        $type->addAttribute($attribute);
1412
    }
1413 74
1414
    private function findSomethingLikeAttributeGroup(
1415
        Schema $schema,
1416
        DOMElement $node,
1417
        DOMElement $childNode,
1418
        AttributeContainer $addToThis
1419 74
    ): void {
1420 74
        $attribute = $this->findAttributeGroup($schema, $node, $childNode->getAttribute('ref'));
1421 74
        $addToThis->addAttribute($attribute);
1422 74
    }
1423
1424
    private function setLoadedFile(string $key, Schema $schema): void
1425 74
    {
1426 74
        $this->loadedFiles[$key] = $schema;
1427
    }
1428 74
1429
    private function setLoadedSchemaFromElement(DOMElement $node, Schema $schema): void
1430
    {
1431
        if ($node->hasAttribute('targetNamespace')) {
1432
            $this->setLoadedSchema($node->getAttribute('targetNamespace'), $schema);
1433
        }
1434 74
    }
1435 74
1436 74
    private function setLoadedSchema(string $namespace, Schema $schema): void
1437
    {
1438 74
        if (!isset($this->loadedSchemas[$namespace])) {
1439
            $this->loadedSchemas[$namespace] = [];
1440 74
        }
1441 74
        if (!in_array($schema, $this->loadedSchemas[$namespace], true)) {
1442
            $this->loadedSchemas[$namespace][] = $schema;
1443 74
        }
1444
    }
1445 74
1446 74
    private function setSchemaThingsFromNode(
1447
        Schema $schema,
1448 74
        DOMElement $node,
1449
        Schema $parent = null
1450 74
    ): void {
1451
        $schema->setDoc($this->getDocumentation($node));
1452 74
1453 74
        if ($node->hasAttribute('targetNamespace')) {
1454
            $schema->setTargetNamespace($node->getAttribute('targetNamespace'));
1455 74
        } elseif ($parent instanceof Schema) {
1456 74
            $schema->setTargetNamespace($parent->getTargetNamespace());
1457
        }
1458 74
        $schema->setElementsQualification($node->getAttribute('elementFormDefault') == 'qualified');
1459
        $schema->setAttributesQualification($node->getAttribute('attributeFormDefault') == 'qualified');
1460 74
        $schema->setDoc($this->getDocumentation($node));
1461
    }
1462
}
1463