Passed
Pull Request — master (#70)
by Axel
07:43
created

SchemaReader::loadAttributeOrElement()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 13
c 0
b 0
f 0
dl 0
loc 20
ccs 6
cts 6
cp 1
rs 9.8333
cc 2
nc 2
nop 3
crap 2
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\AttributeItem;
18
use GoetasWebservices\XML\XSDReader\Schema\Attribute\Group as AttributeGroup;
19
use GoetasWebservices\XML\XSDReader\Schema\Element\AbstractElementSingle;
20
use GoetasWebservices\XML\XSDReader\Schema\Element\Element;
21
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementContainer;
22
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementRef;
23
use GoetasWebservices\XML\XSDReader\Schema\Element\Group;
24
use GoetasWebservices\XML\XSDReader\Schema\Element\GroupRef;
25
use GoetasWebservices\XML\XSDReader\Schema\Element\InterfaceSetDefault;
26
use GoetasWebservices\XML\XSDReader\Schema\Element\InterfaceSetFixed;
27
use GoetasWebservices\XML\XSDReader\Schema\Element\InterfaceSetMinMax;
28
use GoetasWebservices\XML\XSDReader\Schema\Exception\TypeNotFoundException;
29
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Base;
30
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Extension;
31
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Restriction;
32
use GoetasWebservices\XML\XSDReader\Schema\Item;
33
use GoetasWebservices\XML\XSDReader\Schema\Schema;
34
use GoetasWebservices\XML\XSDReader\Schema\SchemaItem;
35
use GoetasWebservices\XML\XSDReader\Schema\Type\BaseComplexType;
36
use GoetasWebservices\XML\XSDReader\Schema\Type\ComplexType;
37
use GoetasWebservices\XML\XSDReader\Schema\Type\ComplexTypeSimpleContent;
38
use GoetasWebservices\XML\XSDReader\Schema\Type\SimpleType;
39
use GoetasWebservices\XML\XSDReader\Schema\Type\Type;
40
use GoetasWebservices\XML\XSDReader\Utils\UrlUtils;
41
42
class SchemaReader
43
{
44
    public const XSD_NS = 'http://www.w3.org/2001/XMLSchema';
45
46
    public const XML_NS = 'http://www.w3.org/XML/1998/namespace';
47
48
    /**
49
     * @var DocumentationReader
50
     */
51
    private $documentationReader;
52
53
    /**
54
     * @var Schema[]
55
     */
56
    private $loadedFiles = [];
57
58
    /**
59
     * @var Schema[][]
60
     */
61
    private $loadedSchemas = [];
62
63
    /**
64
     * @var string[]
65
     */
66
    protected $knownLocationSchemas = [
67
        'http://www.w3.org/2001/xml.xsd' => (
68
            __DIR__.'/Resources/xml.xsd'
69
        ),
70
        'http://www.w3.org/2001/XMLSchema.xsd' => (
71
            __DIR__.'/Resources/XMLSchema.xsd'
72
        ),
73
        'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' => (
74
            __DIR__.'/Resources/oasis-200401-wss-wssecurity-secext-1.0.xsd'
75
        ),
76
        'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd' => (
77
            __DIR__.'/Resources/oasis-200401-wss-wssecurity-utility-1.0.xsd'
78
        ),
79
        'https://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd' => (
80
            __DIR__.'/Resources/xmldsig-core-schema.xsd'
81
        ),
82
        'http://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd' => (
83
            __DIR__.'/Resources/xmldsig-core-schema.xsd'
84
        ),
85
    ];
86
87
    /**
88
     * @var string[]
89
     */
90
    protected $knownNamespaceSchemaLocations = [
91
        'http://www.w3.org/2000/09/xmldsig#' => (
92
            __DIR__.'/Resources/xmldsig-core-schema.xsd'
93
        ),
94
    ];
95
96
    /**
97
     * @var string[]
98
     */
99
    protected static $globalSchemaInfo = [
100 2
        self::XML_NS => 'http://www.w3.org/2001/xml.xsd',
101
        self::XSD_NS => 'http://www.w3.org/2001/XMLSchema.xsd',
102 2
    ];
103
104 2
    private function extractErrorMessage(): \Exception
105 1
    {
106
        $errors = [];
107 2
108 2
        foreach (libxml_get_errors() as $error) {
109
            $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);
110 2
        }
111
        $e = new \Exception(implode('; ', $errors));
112
        libxml_use_internal_errors(false);
113 85
114
        return $e;
115 85
    }
116 85
117
    public function __construct(DocumentationReader $documentationReader = null)
118 85
    {
119 85
        if (null === $documentationReader) {
120
            $documentationReader = new StandardDocumentationReader();
121
        }
122
        $this->documentationReader = $documentationReader;
123
    }
124
125
    /**
126
     * Override remote location with a local file.
127 1
     *
128
     * @param string $remote remote schema URL
129 1
     * @param string $local  local file path
130 1
     */
131
    public function addKnownSchemaLocation(string $remote, string $local): void
132
    {
133
        $this->knownLocationSchemas[$remote] = $local;
134
    }
135
136
    /**
137
     * Specify schema location by namespace.
138
     * This can be used for schemas which import namespaces but do not specify schemaLocation attributes.
139 1
     *
140
     * @param string $namespace namespace
141 1
     * @param string $location  schema URL
142 1
     */
143
    public function addKnownNamespaceSchemaLocation(string $namespace, string $location): void
144 74
    {
145
        $this->knownNamespaceSchemaLocations[$namespace] = $location;
146
    }
147
148 74
    private function loadAttributeGroup(
149 74
        Schema $schema,
150 74
        DOMElement $node
151
    ): Closure {
152
        $attGroup = new AttributeGroup($schema, $node->getAttribute('name'));
153 74
        $attGroup->setDoc($this->getDocumentation($node));
154 74
        $schema->addAttributeGroup($attGroup);
155
156
        return function () use ($schema, $node, $attGroup): void {
157
            SchemaReader::againstDOMNodeList(
158
                $node,
159 74
                function (
160 74
                    DOMElement $node,
161
                    DOMElement $childNode
162 74
                ) use (
163 74
                    $schema,
164 74
                    $attGroup
165 74
                ): void {
166 74
                    switch ($childNode->localName) {
167 74
                        case 'attribute':
168
                            $attribute = $this->getAttributeFromAttributeOrRef(
169 74
                                $childNode,
170 74
                                $schema,
171 74
                                $node
172 1
                            );
173 1
                            $attGroup->addAttribute($attribute);
174 1
                            break;
175 1
                        case 'attributeGroup':
176 1
                            $this->findSomethingLikeAttributeGroup(
177
                                $schema,
178 1
                                $node,
179
                                $childNode,
180 74
                                $attGroup
181
                            );
182 74
                            break;
183
                    }
184
                }
185 74
            );
186
        };
187
    }
188
189
    private function getAttributeFromAttributeOrRef(
190 74
        DOMElement $childNode,
191 74
        Schema $schema,
192
        DOMElement $node
193
    ): AttributeItem {
194
        if ($childNode->hasAttribute('ref')) {
195
            $attribute = $this->findAttributeItem($schema, $node, $childNode->getAttribute('ref'));
196 74
        } else {
197
            /**
198
             * @var Attribute
199 74
             */
200
            $attribute = $this->loadAttribute($schema, $childNode);
201
        }
202 74
203
        return $attribute;
204
    }
205
206 74
    private function loadAttribute(Schema $schema, DOMElement $node): Attribute
207 74
    {
208 74
        $attribute = new Attribute($schema, $node->getAttribute('name'));
209
        $attribute->setDoc($this->getDocumentation($node));
210 74
        $this->fillItem($attribute, $node);
211 1
        $this->fillAttribute($attribute, $node);
212
213 74
        return $attribute;
214 1
    }
215
216 74
    private function fillAttribute(Attribute $attribute, DOMElement $node): void
217 74
    {
218
        if ($node->hasAttribute('fixed')) {
219
            $attribute->setFixed($node->getAttribute('fixed'));
220 74
        }
221
        if ($node->hasAttribute('default')) {
222
            $attribute->setDefault($node->getAttribute('default'));
223 74
        }
224
225
        if ($node->hasAttribute('nillable')) {
226
            $attribute->setNil($node->getAttribute('nillable') == 'true');
227
        }
228 74
        if ($node->hasAttribute('form')) {
229 74
            $attribute->setQualified($node->getAttribute('form') == 'qualified');
230 74
        }
231 74
        if ($node->hasAttribute('use')) {
232
            $attribute->setUse($node->getAttribute('use'));
233 74
        }
234 74
    }
235 74
236
    private function loadAttributeOrElement(
237
        Schema $schema,
238
        DOMElement $node,
239 74
        bool $isAttribute
240 74
    ): Closure {
241
        $name = $node->getAttribute('name');
242
        if ($isAttribute) {
243 74
            $attribute = new Attribute($schema, $name);
244
            $attribute->setDoc($this->getDocumentation($node));
245 74
            $this->fillAttribute($attribute, $node);
246
            $schema->addAttribute($attribute);
247
        } else {
248 74
            $attribute = new Element($schema, $name);
249
            $attribute->setDoc($this->getDocumentation($node));
250 74
            $this->fillElement($attribute, $node);
251
            $schema->addElement($attribute);
252
        }
253
254
        return function () use ($attribute, $node): void {
255
            $this->fillItem($attribute, $node);
256 74
        };
257
    }
258 74
259 74
    private function getDocumentation(DOMElement $node): string
260
    {
261 74
        return $this->documentationReader->get($node);
262 74
    }
263
264
    /**
265
     * @return Closure[]
266
     */
267 74
    private function schemaNode(Schema $schema, DOMElement $node, Schema $parent = null): array
268 74
    {
269
        $this->setSchemaThingsFromNode($schema, $node, $parent);
270 74
        $functions = [];
271
272 74
        self::againstDOMNodeList(
273 74
            $node,
274 74
            function (
275 74
                DOMElement $node,
276 74
                DOMElement $childNode
277 74
            ) use (
278 74
                $schema,
279 74
                &$functions
280 74
            ): void {
281 74
                $callback = null;
282 74
283 74
                switch ($childNode->localName) {
284 74
                    case 'attributeGroup':
285 74
                        $callback = $this->loadAttributeGroup($schema, $childNode);
286 74
                        break;
287 74
                    case 'include':
288 74
                    case 'import':
289 74
                        $callback = $this->loadImport($schema, $childNode);
290 74
                        break;
291 74
                    case 'element':
292 74
                        $callback = $this->loadAttributeOrElement($schema, $childNode, false);
293 74
                        break;
294 74
                    case 'attribute':
295
                        $callback = $this->loadAttributeOrElement($schema, $childNode, true);
296
                        break;
297 74
                    case 'group':
298 74
                        $callback = $this->loadGroup($schema, $childNode);
299
                        break;
300 74
                    case 'complexType':
301
                        $callback = $this->loadComplexType($schema, $childNode);
302
                        break;
303 74
                    case 'simpleType':
304
                        $callback = $this->loadSimpleType($schema, $childNode);
305
                        break;
306 74
                }
307
308 74
                if ($callback instanceof Closure) {
309 74
                    $functions[] = $callback;
310
                }
311 74
            }
312 74
        );
313
314 74
        return $functions;
315
    }
316
317 74
    private function loadGroupRef(Group $referenced, DOMElement $node): GroupRef
318
    {
319 74
        $ref = new GroupRef($referenced);
320 74
        $ref->setDoc($this->getDocumentation($node));
321
322 74
        self::maybeSetMax($ref, $node);
323
        self::maybeSetMin($ref, $node);
324 74
325
        return $ref;
326 74
    }
327 74
328 74
    private static function maybeSetMax(InterfaceSetMinMax $ref, DOMElement $node): void
329 7
    {
330
        if ($node->hasAttribute('maxOccurs')) {
331
            $ref->setMax($node->getAttribute('maxOccurs') == 'unbounded' ? -1 : (int) $node->getAttribute('maxOccurs'));
332 74
        }
333
    }
334 74
335
    private static function maybeSetMin(InterfaceSetMinMax $ref, DOMElement $node): void
336 74
    {
337 1
        if ($node->hasAttribute('minOccurs')) {
338
            $ref->setMin((int) $node->getAttribute('minOccurs'));
339 74
            if ($ref->getMin() > $ref->getMax() && $ref->getMax() !== -1) {
340
                $ref->setMax($ref->getMin());
341 74
            }
342
        }
343
    }
344
345 74
    private static function maybeSetFixed(InterfaceSetFixed $ref, DOMElement $node): void
346 74
    {
347 74
        if ($node->hasAttribute('fixed')) {
348
            $ref->setFixed($node->getAttribute('fixed'));
349 74
        }
350 74
    }
351
352
    private static function maybeSetDefault(InterfaceSetDefault $ref, DOMElement $node): void
353 74
    {
354 74
        if ($node->hasAttribute('default')) {
355
            $ref->setDefault($node->getAttribute('default'));
356 74
        }
357 74
    }
358
359 74
    private function loadSequence(ElementContainer $elementContainer, DOMElement $node, int $max = null, int $min = null): void
360 74
    {
361
        $max =
362
            (
363
                (is_int($max) && (bool) $max) ||
364
                $node->getAttribute('maxOccurs') == 'unbounded' ||
365 74
                $node->getAttribute('maxOccurs') > 1
366 74
            )
367 74
                ? 2
368
                : null;
369 74
        $min =
370 74
            (
371 74
                $min === null &&
372 74
                !$node->hasAttribute('minOccurs')
373 74
            )
374 74
                ? null
375
                : (int) max((int) $min, $node->getAttribute('minOccurs'));
376 74
377
        self::againstDOMNodeList(
378 74
            $node,
379
            function (
380 74
                DOMElement $node,
381
                DOMElement $childNode
382
            ) use (
383
                $elementContainer,
384
                $max,
385
                $min
386
            ): void {
387 74
                $this->loadSequenceChildNode(
388 74
                    $elementContainer,
389 74
                    $node,
390 74
                    $childNode,
391 74
                    $max,
392 74
                    $min
393 74
                );
394 74
            }
395 74
        );
396
    }
397 74
398 74
    private function loadSequenceChildNode(
399 74
        ElementContainer $elementContainer,
400 74
        DOMElement $node,
401 74
        DOMElement $childNode,
402 74
        ?int $max,
403 74
        ?int $min = null
404 74
    ): void {
405
        switch ($childNode->localName) {
406 74
            case 'sequence':
407 74
            case 'choice':
408 74
            case 'all':
409 74
                $this->loadSequence(
410 74
                    $elementContainer,
411 74
                    $childNode,
412 74
                    $max,
413
                    $min
414 74
                );
415
                break;
416 74
            case 'element':
417
                $this->loadSequenceChildNodeLoadElement(
418 74
                    $elementContainer,
419
                    $node,
420
                    $childNode,
421
                    $max,
422
                    $min
423
                );
424
                break;
425 74
            case 'group':
426 74
                $this->addGroupAsElement(
427 74
                    $elementContainer->getSchema(),
428 74
                    $node,
429
                    $childNode,
430 74
                    $elementContainer
431 74
                );
432
                break;
433 74
        }
434 74
    }
435
436 74
    private function loadSequenceChildNodeLoadElement(
437 74
        ElementContainer $elementContainer,
438
        DOMElement $node,
439
        DOMElement $childNode,
440 74
        ?int $max,
441
        ?int $min
442
    ): void {
443 74
        if ($childNode->hasAttribute('ref')) {
444 74
            $element = $this->findElement($elementContainer->getSchema(), $node, $childNode->getAttribute('ref'));
445
            $element = new ElementRef($element);
446
            $element->setDoc($this->getDocumentation($childNode));
447 74
            $this->fillElement($element, $childNode);
448 74
        } else {
449 74
            $element = $this->loadElement($elementContainer->getSchema(), $childNode);
450
        }
451
452
        if ($min !== null) {
453 74
            $element->setMin($min);
454 74
        }
455
456
        if ($max > 1) {
457 74
            /*
458
            * although one might think the typecast is not needed with $max being `? int $max` after passing > 1,
459
            * phpstan@a4f89fa still thinks it's possibly null.
460
            * see https://github.com/phpstan/phpstan/issues/577 for related issue
461
            */
462
            $element->setMax((int) $max);
463 74
        }
464
        $elementContainer->addElement($element);
465 74
    }
466 74
467
    private function addGroupAsElement(
468 74
        Schema $schema,
469
        DOMElement $node,
470
        DOMElement $childNode,
471
        ElementContainer $elementContainer
472
    ): void {
473
        $referencedGroup = $this->findGroup(
474 74
            $schema,
475 74
            $node,
476 74
            $childNode->getAttribute('ref')
477 74
        );
478
479
        $group = $this->loadGroupRef($referencedGroup, $childNode);
480 74
        $elementContainer->addElement($group);
481 74
    }
482 74
483
    private function loadGroup(Schema $schema, DOMElement $node): Closure
484 74
    {
485
        $group = new Group($schema, $node->getAttribute('name'));
486 74
        $group->setDoc($this->getDocumentation($node));
487 74
        $groupOriginal = $group;
488 74
489
        if ($node->hasAttribute('maxOccurs') || $node->hasAttribute('minOccurs')) {
490 74
            $group = new GroupRef($group);
491 1
492
            if ($node->hasAttribute('maxOccurs')) {
493 1
                self::maybeSetMax($group, $node);
494 1
            }
495
            if ($node->hasAttribute('minOccurs')) {
496 1
                self::maybeSetMin($group, $node);
497 1
            }
498
        }
499
500
        $schema->addGroup($group);
501 74
502
        return function () use ($groupOriginal, $node): void {
503
            static::againstDOMNodeList(
504 74
                $node,
505 74
                function (DOMelement $node, DOMElement $childNode) use ($groupOriginal): void {
506
                    switch ($childNode->localName) {
507 74
                        case 'sequence':
508 74
                        case 'choice':
509 74
                        case 'all':
510 74
                            $this->loadSequence($groupOriginal, $childNode);
511 74
                            break;
512 74
                    }
513
                }
514 74
            );
515
        };
516 74
    }
517
518
    private function loadComplexType(Schema $schema, DOMElement $node, Closure $callback = null): Closure
519 74
    {
520
        /**
521
         * @var bool
522
         */
523
        $isSimple = false;
524 74
525
        self::againstDOMNodeList(
526 74
            $node,
527 74
            function (
528
                DOMElement $node,
529
                DOMElement $childNode
530
            ) use (
531
                &$isSimple
532 74
            ): void {
533
                if ($isSimple) {
534 74
                    return;
535 1
                }
536
                if ($childNode->localName === 'simpleContent') {
537 74
                    $isSimple = true;
538 2
                }
539
            }
540 74
        );
541
542
        $type = $isSimple ? new ComplexTypeSimpleContent($schema, $node->getAttribute('name')) : new ComplexType($schema, $node->getAttribute('name'));
543 74
544
        $type->setDoc($this->getDocumentation($node));
545 74
        if ($node->getAttribute('name')) {
546 74
            $schema->addType($type);
547 74
        }
548
549
        return function () use ($type, $node, $schema, $callback): void {
550
            $this->fillTypeNode($type, $node, true);
551 74
552
            self::againstDOMNodeList(
553 74
                $node,
554 74
                function (
555
                    DOMElement $node,
556
                    DOMElement $childNode
557
                ) use (
558
                    $schema,
559 74
                    $type
560 74
                ): void {
561
                    $this->loadComplexTypeFromChildNode(
562 74
                        $type,
563 74
                        $node,
564 74
                        $childNode,
565 74
                        $schema
566 74
                    );
567
                }
568 74
            );
569
570
            if ($callback instanceof Closure) {
571 74
                call_user_func($callback, $type);
572 74
            }
573
        };
574 74
    }
575
576
    private function loadComplexTypeFromChildNode(
577 74
        BaseComplexType $type,
578
        DOMElement $node,
579
        DOMElement $childNode,
580
        Schema $schema
581
    ): void {
582
        switch ($childNode->localName) {
583 74
            case 'sequence':
584 74
            case 'choice':
585 74
            case 'all':
586 74
                if ($type instanceof ElementContainer) {
587 74
                    $this->loadSequence(
588 74
                        $type,
589 74
                        $childNode
590 74
                    );
591
                }
592
                break;
593 74
            case 'attribute':
594 74
                $this->addAttributeFromAttributeOrRef(
595 74
                    $type,
596 74
                    $childNode,
597 74
                    $schema,
598 74
                    $node
599 74
                );
600
                break;
601 74
            case 'attributeGroup':
602 74
                $this->findSomethingLikeAttributeGroup(
603 2
                    $schema,
604 2
                    $node,
605 2
                    $childNode,
606 2
                    $type
607 2
                );
608
                break;
609 2
            case 'group':
610 74
                if (
611
                    $type instanceof ComplexType
612 1
                ) {
613
                    $this->addGroupAsElement(
614 1
                        $schema,
615 1
                        $node,
616 1
                        $childNode,
617 1
                        $type
618 1
                    );
619
                }
620
                break;
621 1
        }
622
    }
623 74
624
    private function loadSimpleType(Schema $schema, DOMElement $node, Closure $callback = null): Closure
625 74
    {
626
        $type = new SimpleType($schema, $node->getAttribute('name'));
627 74
        $type->setDoc($this->getDocumentation($node));
628 74
        if ($node->getAttribute('name')) {
629 74
            $schema->addType($type);
630 74
        }
631
632
        return function () use ($type, $node, $callback): void {
633
            $this->fillTypeNode($type, $node, true);
634 74
635
            self::againstDOMNodeList(
636 74
                $node,
637 74
                function (DOMElement $node, DOMElement $childNode) use ($type): void {
638
                    switch ($childNode->localName) {
639 74
                        case 'union':
640 74
                            $this->loadUnion($type, $childNode);
641 74
                            break;
642 74
                        case 'list':
643 74
                            $this->loadList($type, $childNode);
644 74
                            break;
645 74
                    }
646
                }
647 74
            );
648
649
            if ($callback instanceof Closure) {
650 74
                call_user_func($callback, $type);
651 74
            }
652
        };
653 74
    }
654
655
    private function loadList(SimpleType $type, DOMElement $node): void
656 74
    {
657
        if ($node->hasAttribute('itemType')) {
658 74
            /**
659
             * @var SimpleType
660
             */
661
            $listType = $this->findSomeType($type, $node, 'itemType');
662 74
            $type->setList($listType);
663 74
        } else {
664
            self::againstDOMNodeList(
665 74
                $node,
666 74
                function (
667
                    DOMElement $node,
668
                    DOMElement $childNode
669
                ) use (
670
                    $type
671 74
                ): void {
672
                    $this->loadTypeWithCallback(
673 74
                        $type->getSchema(),
674 74
                        $childNode,
675 74
                        function (SimpleType $list) use ($type): void {
676
                            $type->setList($list);
677 74
                        }
678 74
                    );
679
                }
680 74
            );
681
        }
682
    }
683 74
684
    private function findSomeType(
685 74
        SchemaItem $fromThis,
686
        DOMElement $node,
687
        string $attributeName
688
    ): SchemaItem {
689
        return $this->findSomeTypeFromAttribute(
690 74
            $fromThis,
691 74
            $node,
692 74
            $node->getAttribute($attributeName)
693 74
        );
694
    }
695
696
    private function findSomeTypeFromAttribute(
697 74
        SchemaItem $fromThis,
698
        DOMElement $node,
699
        string $attributeName
700
    ): SchemaItem {
701
        $out = $this->findType(
702 74
            $fromThis->getSchema(),
703 74
            $node,
704 74
            $attributeName
705 74
        );
706
707
        return $out;
708 74
    }
709
710
    private function loadUnion(SimpleType $type, DOMElement $node): void
711 74
    {
712
        if ($node->hasAttribute('memberTypes')) {
713 74
            $types = preg_split('/\s+/', $node->getAttribute('memberTypes'));
714 74
            foreach ($types as $typeName) {
715 74
                /**
716
                 * @var SimpleType
717
                 */
718
                $unionType = $this->findSomeTypeFromAttribute(
719 74
                    $type,
720 74
                    $node,
721 74
                    $typeName
722 74
                );
723
                $type->addUnion($unionType);
724 74
            }
725
        }
726
        self::againstDOMNodeList(
727 74
            $node,
728 74
            function (
729
                DOMElement $node,
730
                DOMElement $childNode
731
            ) use (
732
                $type
733 74
            ): void {
734
                $this->loadTypeWithCallback(
735 74
                    $type->getSchema(),
736 74
                    $childNode,
737 74
                    function (SimpleType $unType) use ($type): void {
738
                        $type->addUnion($unType);
739 74
                    }
740 74
                );
741
            }
742 74
        );
743
    }
744 74
745
    private function fillTypeNode(Type $type, DOMElement $node, bool $checkAbstract = false): void
746 74
    {
747
        if ($checkAbstract) {
748 74
            $type->setAbstract($node->getAttribute('abstract') === 'true' || $node->getAttribute('abstract') === '1');
749 74
        }
750
751
        self::againstDOMNodeList(
752 74
            $node,
753 74
            function (DOMElement $node, DOMElement $childNode) use ($type): void {
754
                switch ($childNode->localName) {
755 74
                    case 'restriction':
756 74
                        $this->loadRestriction($type, $childNode);
757 74
                        break;
758 74
                    case 'extension':
759 74
                        if ($type instanceof BaseComplexType) {
760 74
                            $this->loadExtension($type, $childNode);
761 74
                        }
762
                        break;
763 74
                    case 'simpleContent':
764 74
                    case 'complexContent':
765 74
                        $this->fillTypeNode($type, $childNode);
766 74
                        break;
767 74
                }
768
            }
769 74
        );
770
    }
771 74
772
    private function loadExtension(BaseComplexType $type, DOMElement $node): void
773 74
    {
774
        $extension = new Extension();
775 74
        $type->setExtension($extension);
776 74
777
        if ($node->hasAttribute('base')) {
778 74
            $this->findAndSetSomeBase($type, $extension, $node);
779 74
        }
780 74
        $this->loadExtensionChildNodes($type, $node);
781 74
    }
782 74
783
    private function findAndSetSomeBase(
784
        Type $type,
785 74
        Base $setBaseOnThis,
786 74
        DOMElement $node
787
    ): void {
788 74
        /**
789
         * @var Type
790
         */
791
        $parent = $this->findSomeType($type, $node, 'base');
792
        $setBaseOnThis->setBase($parent);
793
    }
794
795
    private function loadExtensionChildNodes(
796 74
        BaseComplexType $type,
797 74
        DOMElement $node
798 74
    ): void {
799
        self::againstDOMNodeList(
800 74
            $node,
801
            function (
802
                DOMElement $node,
803
                DOMElement $childNode
804 74
            ) use (
805 74
                $type
806
            ): void {
807
                switch ($childNode->localName) {
808
                    case 'sequence':
809
                    case 'choice':
810 74
                    case 'all':
811
                        if ($type instanceof ElementContainer) {
812 74
                            $this->loadSequence($type, $childNode);
813 74
                        }
814 74
                        break;
815 74
                }
816 74
                $this->loadChildAttributesAndAttributeGroups($type, $node, $childNode);
817 74
            }
818 74
        );
819 74
    }
820
821
    private function loadChildAttributesAndAttributeGroups(
822 74
        BaseComplexType $type,
823 74
        DOMElement $node,
824 74
        DOMElement $childNode
825 74
    ): void {
826 74
        switch ($childNode->localName) {
827 74
            case 'attribute':
828 74
                $this->addAttributeFromAttributeOrRef(
829
                    $type,
830 74
                    $childNode,
831 74
                    $type->getSchema(),
832 74
                    $node
833 74
                );
834 74
                break;
835 74
            case 'attributeGroup':
836 74
                $this->findSomethingLikeAttributeGroup(
837
                    $type->getSchema(),
838 74
                    $node,
839
                    $childNode,
840 74
                    $type
841
                );
842 74
                break;
843
        }
844 74
    }
845
846 74
    private function loadRestriction(Type $type, DOMElement $node): void
847 74
    {
848 74
        $restriction = new Restriction();
849 74
        $type->setRestriction($restriction);
850
        if ($node->hasAttribute('base')) {
851 74
            $this->findAndSetSomeBase($type, $restriction, $node);
852 74
        } else {
853
            self::againstDOMNodeList(
854
                $node,
855
                function (
856
                    DOMElement $node,
857 74
                    DOMElement $childNode
858 74
                ) use (
859
                    $type,
860 74
                    $restriction
861 74
                ): void {
862 74
                    $this->loadTypeWithCallback(
863
                        $type->getSchema(),
864 74
                        $childNode,
865 74
                        function (Type $restType) use ($restriction): void {
866
                            $restriction->setBase($restType);
867 74
                        }
868
                    );
869
                }
870 74
            );
871 74
        }
872
        $this->loadRestrictionChildNodes($type, $restriction, $node);
873
    }
874
875
    private function loadRestrictionChildNodes(
876 74
        Type $type,
877
        Restriction $restriction,
878
        DOMElement $node
879 74
    ): void {
880 74
        self::againstDOMNodeList(
881
            $node,
882 74
            function (
883
                DOMElement $node,
884
                DOMElement $childNode
885
            ) use (
886
                $type,
887
                $restriction
888
            ): void {
889
                if ($type instanceof BaseComplexType) {
890
                    $this->loadChildAttributesAndAttributeGroups($type, $node, $childNode);
891
                }
892
                if (
893
                in_array(
894
                    $childNode->localName,
895 74
                    [
896
                        'enumeration',
897
                        'pattern',
898 74
                        'length',
899 74
                        'minLength',
900
                        'maxLength',
901 74
                        'minInclusive',
902 74
                        'maxInclusive',
903
                        'minExclusive',
904
                        'maxExclusive',
905
                        'fractionDigits',
906 74
                        'totalDigits',
907
                        'whiteSpace',
908 74
                    ],
909
                    true
910
                )
911
                ) {
912
                    $restriction->addCheck(
913 74
                        $childNode->localName,
914
                        [
915 74
                            'value' => $childNode->getAttribute('value'),
916 74
                            'doc' => $this->getDocumentation($childNode),
917 74
                        ]
918 74
                    );
919
                }
920
            }
921
        );
922
    }
923
924 74
    /**
925
     * @return mixed[]
926
     */
927 74
    private static function splitParts(DOMElement $node, string $typeName): array
928 74
    {
929 74
        $prefix = null;
930
        $name = $typeName;
931
        if (strpos($typeName, ':') !== false) {
932
            [$prefix, $name] = explode(':', $typeName);
933 74
        }
934
935 74
        /**
936
         * @psalm-suppress PossiblyNullArgument
937
         */
938
        $namespace = $node->lookupNamespaceUri($prefix);
939
940 74
        return [
941
            $name,
942
            $namespace,
943
            $prefix,
944
        ];
945
    }
946 74
947
    private function findAttributeItem(Schema $schema, DOMElement $node, string $typeName): AttributeItem
948 74
    {
949
        [$name, $namespace] = static::splitParts($node, $typeName);
950
951
        /**
952
         * @var string|null $namespace
953
         */
954 74
        $namespace = $namespace ?: $schema->getTargetNamespace();
955
956 74
        try {
957
            /**
958
             * @var AttributeItem $out
959
             */
960
            $out = $schema->findAttribute((string) $name, $namespace);
961 74
962
            return $out;
963
        } catch (TypeNotFoundException $e) {
964
            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);
965
        }
966
    }
967 74
968
    private function findAttributeGroup(Schema $schema, DOMElement $node, string $typeName): AttributeGroup
969 74
    {
970
        [$name, $namespace] = static::splitParts($node, $typeName);
971
972
        /**
973
         * @var string|null $namespace
974
         */
975 74
        $namespace = $namespace ?: $schema->getTargetNamespace();
976
977 74
        try {
978
            /**
979
             * @var AttributeGroup $out
980
             */
981
            $out = $schema->findAttributeGroup((string) $name, $namespace);
982 74
983
            return $out;
984
        } catch (TypeNotFoundException $e) {
985 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);
986
        }
987
    }
988
989
    private function findElement(Schema $schema, DOMElement $node, string $typeName): Element
990
    {
991 74
        [$name, $namespace] = static::splitParts($node, $typeName);
992
993 74
        /**
994
         * @var string|null $namespace
995
         */
996
        $namespace = $namespace ?: $schema->getTargetNamespace();
997
998 74
        try {
999
            return $schema->findElement((string) $name, $namespace);
1000
        } catch (TypeNotFoundException $e) {
1001
            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);
1002
        }
1003
    }
1004 74
1005
    private function findGroup(Schema $schema, DOMElement $node, string $typeName): Group
1006 74
    {
1007
        [$name, $namespace] = static::splitParts($node, $typeName);
1008
1009
        /**
1010
         * @var string|null $namespace
1011
         */
1012 74
        $namespace = $namespace ?: $schema->getTargetNamespace();
1013
1014 74
        try {
1015
            /**
1016
             * @var Group $out
1017
             */
1018
            $out = $schema->findGroup((string) $name, $namespace);
1019 74
1020
            return $out;
1021
        } catch (TypeNotFoundException $e) {
1022
            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);
1023 74
        }
1024 1
    }
1025 1
1026
    private function findType(Schema $schema, DOMElement $node, string $typeName): SchemaItem
1027 74
    {
1028
        [$name, $namespace] = static::splitParts($node, $typeName);
1029 74
1030 74
        /**
1031 74
         * @var string|null $namespace
1032 74
         */
1033
        $namespace = $namespace ?: $schema->getTargetNamespace();
1034
1035
        $tryFindType = static function (Schema $schema, string $name, ?string $namespace): ?SchemaItem {
1036
            try {
1037
                return $schema->findType($name, $namespace);
1038
            } catch (TypeNotFoundException $e) {
1039 74
                return null;
1040
            }
1041 74
        };
1042
1043
        $interestingSchemas = array_merge([$schema], $this->loadedSchemas[$namespace] ?? []);
1044 74
        foreach ($interestingSchemas as $interestingSchema) {
1045
            if ($result = $tryFindType($interestingSchema, $name, $namespace)) {
1046
                return $result;
1047
            }
1048
        }
1049 74
1050 74
        throw new TypeException(sprintf("Can't find %s named {%s}#%s, at line %d in %s ", 'type', $namespace, $name, $node->getLineNo(), $node->ownerDocument->documentURI));
1051 74
    }
1052
1053
    private function fillItem(Item $element, DOMElement $node): void
1054
    {
1055
        /**
1056 74
         * @var bool
1057 74
         */
1058
        $skip = false;
1059
        self::againstDOMNodeList(
1060 74
            $node,
1061 74
            function (
1062 74
                DOMElement $node,
1063
                DOMElement $childNode
1064 74
            ) use (
1065
                $element,
1066
                &$skip
1067 74
            ): void {
1068
                if (
1069
                    !$skip &&
1070 74
                    in_array(
1071 74
                        $childNode->localName,
1072 74
                        [
1073
                            'complexType',
1074 74
                            'simpleType',
1075 74
                        ],
1076
                        true
1077 74
                    )
1078
                ) {
1079 74
                    $this->loadTypeWithCallback(
1080
                        $element->getSchema(),
1081 74
                        $childNode,
1082 74
                        function (Type $type) use ($element): void {
1083
                            $element->setType($type);
1084 74
                        }
1085 74
                    );
1086
                    $skip = true;
1087 74
                }
1088
            }
1089 74
        );
1090
        if ($skip) {
1091
            return;
1092
        }
1093 74
        $this->fillItemNonLocalType($element, $node);
1094
    }
1095
1096
    private function fillItemNonLocalType(Item $element, DOMElement $node): void
1097
    {
1098 74
        if ($node->getAttribute('type')) {
1099 74
            /**
1100 74
             * @var Type
1101 74
             */
1102
            $type = $this->findSomeType($element, $node, 'type');
1103
        } else {
1104
            /**
1105 74
             * @var Type
1106 74
             */
1107
            $type = $this->findSomeTypeFromAttribute(
1108 74
                $element,
1109
                $node,
1110
                ($node->lookupPrefix(self::XSD_NS).':anyType')
1111
            );
1112 74
        }
1113 74
1114 74
        $element->setType($type);
1115 1
    }
1116
1117
    private function loadImport(
1118
        Schema $schema,
1119 74
        DOMElement $node
1120
    ): Closure {
1121 3
        $namespace = $node->getAttribute('namespace');
1122 3
        $schemaLocation = $node->getAttribute('schemaLocation');
1123 3
        if (!$schemaLocation && isset($this->knownNamespaceSchemaLocations[$namespace])) {
1124
            $schemaLocation = $this->knownNamespaceSchemaLocations[$namespace];
1125
        }
1126 3
1127 74
        // postpone schema loading
1128 1
        if ($namespace && !$schemaLocation && !isset(self::$globalSchemaInfo[$namespace])) {
1129 1
            return function () use ($schema, $namespace): void {
1130
                if (!empty($this->loadedSchemas[$namespace])) {
1131
                    foreach ($this->loadedSchemas[$namespace] as $s) {
1132
                        $schema->addSchema($s, $namespace);
1133 74
                    }
1134 74
                }
1135
            };
1136 74
        } elseif ($namespace && !$schemaLocation && !empty($this->loadedSchemas[$namespace])) {
1137 74
            foreach ($this->loadedSchemas[$namespace] as $s) {
1138
                $schema->addSchema($s, $namespace);
1139
            }
1140 74
        }
1141
1142
        $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

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

1403
        $xp = new \DOMXPath(/** @scrutinizer ignore-type */ $node->ownerDocument);
Loading history...
1404 74
        $xp->registerNamespace('xs', self::XSD_NS);
1405
1406 74
        if ($xp->query('ancestor::xs:choice', $node)->length) {
1407 74
            $element->setMin(0);
1408
        }
1409
1410 74
        if ($node->hasAttribute('nillable')) {
1411
            $element->setNil($node->getAttribute('nillable') == 'true');
1412
        }
1413 74
        if ($node->hasAttribute('form')) {
1414
            $element->setQualified($node->getAttribute('form') == 'qualified');
1415
        }
1416
1417
        $parentNode = $node->parentNode;
1418
        if ($parentNode->localName != 'schema' || $parentNode->namespaceURI != self::XSD_NS) {
1419 74
            $element->setLocal(true);
1420 74
        }
1421 74
    }
1422 74
1423
    private function addAttributeFromAttributeOrRef(
1424
        BaseComplexType $type,
1425 74
        DOMElement $childNode,
1426 74
        Schema $schema,
1427
        DOMElement $node
1428 74
    ): void {
1429
        $attribute = $this->getAttributeFromAttributeOrRef(
1430
            $childNode,
1431
            $schema,
1432
            $node
1433
        );
1434 74
1435 74
        $type->addAttribute($attribute);
1436 74
    }
1437
1438 74
    private function findSomethingLikeAttributeGroup(
1439
        Schema $schema,
1440 74
        DOMElement $node,
1441 74
        DOMElement $childNode,
1442
        AttributeContainer $addToThis
1443 74
    ): void {
1444
        $attribute = $this->findAttributeGroup($schema, $node, $childNode->getAttribute('ref'));
1445 74
        $addToThis->addAttribute($attribute);
1446 74
    }
1447
1448 74
    private function setLoadedFile(string $key, Schema $schema): void
1449
    {
1450 74
        $this->loadedFiles[$key] = $schema;
1451
    }
1452 74
1453 74
    private function setLoadedSchemaFromElement(DOMElement $node, Schema $schema): void
1454
    {
1455 74
        if ($node->hasAttribute('targetNamespace')) {
1456 74
            $this->setLoadedSchema($node->getAttribute('targetNamespace'), $schema);
1457
        }
1458 74
    }
1459
1460 74
    private function setLoadedSchema(string $namespace, Schema $schema): void
1461
    {
1462
        if (!isset($this->loadedSchemas[$namespace])) {
1463
            $this->loadedSchemas[$namespace] = [];
1464
        }
1465 74
        if (!in_array($schema, $this->loadedSchemas[$namespace], true)) {
1466
            $this->loadedSchemas[$namespace][] = $schema;
1467 74
        }
1468 74
    }
1469
1470
    private function setSchemaThingsFromNode(
1471
        Schema $schema,
1472 74
        DOMElement $node,
1473 74
        Schema $parent = null
1474 74
    ): void {
1475 74
        $schema->setDoc($this->getDocumentation($node));
1476
1477
        if ($node->hasAttribute('targetNamespace')) {
1478
            $schema->setTargetNamespace($node->getAttribute('targetNamespace'));
1479
        } elseif ($parent instanceof Schema) {
1480
            $schema->setTargetNamespace($parent->getTargetNamespace());
1481
        }
1482
        $schema->setElementsQualification($node->getAttribute('elementFormDefault') == 'qualified');
1483
        $schema->setAttributesQualification($node->getAttribute('attributeFormDefault') == 'qualified');
1484
        $schema->setDoc($this->getDocumentation($node));
1485
    }
1486
}
1487