Completed
Push — master ( 5235c9...9b3e12 )
by Asmir
18s queued 10s
created

SchemaReader::loadTypeWithCallback()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 21
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 10
c 1
b 0
f 0
dl 0
loc 21
ccs 11
cts 11
cp 1
rs 9.9332
cc 4
nc 6
nop 3
crap 4
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\Group as AttributeGroup;
20
use GoetasWebservices\XML\XSDReader\Schema\Element\Element;
21
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementContainer;
22
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementDef;
23
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementRef;
24
use GoetasWebservices\XML\XSDReader\Schema\Element\Group;
25
use GoetasWebservices\XML\XSDReader\Schema\Element\GroupRef;
26
use GoetasWebservices\XML\XSDReader\Schema\Element\InterfaceSetDefault;
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
92
    /**
93
     * @var string[]
94
     */
95
    protected static $globalSchemaInfo = [
96
        self::XML_NS => 'http://www.w3.org/2001/xml.xsd',
97
        self::XSD_NS => 'http://www.w3.org/2001/XMLSchema.xsd',
98
    ];
99
100 2
    private function extractErrorMessage(): \Exception
101
    {
102 2
        $errors = [];
103
104 2
        foreach (libxml_get_errors() as $error) {
105 1
            $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);
106
        }
107 2
        $e = new \Exception(implode('; ', $errors));
108 2
        libxml_use_internal_errors(false);
109
110 2
        return $e;
111
    }
112
113 78
    public function __construct(DocumentationReader $documentationReader = null)
114
    {
115 78
        if (null === $documentationReader) {
116 78
            $documentationReader = new StandardDocumentationReader();
117
        }
118 78
        $this->documentationReader = $documentationReader;
119 78
    }
120
121
    /**
122
     * Override remote location with a local file.
123
     *
124
     * @param string $remote remote schema URL
125
     * @param string $local  local file path
126
     */
127 1
    public function addKnownSchemaLocation(string $remote, string $local): void
128
    {
129 1
        $this->knownLocationSchemas[$remote] = $local;
130 1
    }
131
132
    /**
133
     * Specify schema location by namespace.
134
     * This can be used for schemas which import namespaces but do not specify schemaLocation attributes.
135
     *
136
     * @param string $namespace namespace
137
     * @param string $location  schema URL
138
     */
139 1
    public function addKnownNamespaceSchemaLocation(string $namespace, string $location): void
140
    {
141 1
        $this->knownNamespaceSchemaLocations[$namespace] = $location;
142 1
    }
143
144 67
    private function loadAttributeGroup(
145
        Schema $schema,
146
        DOMElement $node
147
    ): Closure {
148 67
        $attGroup = new AttributeGroup($schema, $node->getAttribute('name'));
149 67
        $attGroup->setDoc($this->getDocumentation($node));
150 67
        $schema->addAttributeGroup($attGroup);
151
152
        return function () use ($schema, $node, $attGroup): void {
153 67
            SchemaReader::againstDOMNodeList(
154 67
                $node,
155
                function (
156
                    DOMElement $node,
157
                    DOMElement $childNode
158
                ) use (
159 67
                    $schema,
160 67
                    $attGroup
161
                ): void {
162 67
                    switch ($childNode->localName) {
163 67
                        case 'attribute':
164 67
                            $attribute = $this->getAttributeFromAttributeOrRef(
165 67
                                $childNode,
166 67
                                $schema,
167 67
                                $node
168
                            );
169 67
                            $attGroup->addAttribute($attribute);
170 67
                            break;
171 67
                        case 'attributeGroup':
172 1
                            $this->findSomethingLikeAttributeGroup(
173 1
                                $schema,
174 1
                                $node,
175 1
                                $childNode,
176 1
                                $attGroup
177
                            );
178 1
                            break;
179
                    }
180 67
                }
181
            );
182 67
        };
183
    }
184
185 67
    private function getAttributeFromAttributeOrRef(
186
        DOMElement $childNode,
187
        Schema $schema,
188
        DOMElement $node
189
    ): AttributeItem {
190 67
        if ($childNode->hasAttribute('ref')) {
191 67
            $attribute = $this->findAttributeItem($schema, $node, $childNode->getAttribute('ref'));
192
        } else {
193
            /**
194
             * @var Attribute
195
             */
196 67
            $attribute = $this->loadAttribute($schema, $childNode);
197
        }
198
199 67
        return $attribute;
200
    }
201
202 67
    private function loadAttribute(
203
        Schema $schema,
204
        DOMElement $node
205
    ): Attribute {
206 67
        $attribute = new Attribute($schema, $node->getAttribute('name'));
207 67
        $attribute->setDoc($this->getDocumentation($node));
208 67
        $this->fillItem($attribute, $node);
209
210 67
        if ($node->hasAttribute('nillable')) {
211 1
            $attribute->setNil($node->getAttribute('nillable') == 'true');
212
        }
213 67
        if ($node->hasAttribute('form')) {
214 1
            $attribute->setQualified($node->getAttribute('form') == 'qualified');
215
        }
216 67
        if ($node->hasAttribute('use')) {
217 67
            $attribute->setUse($node->getAttribute('use'));
218
        }
219
220 67
        return $attribute;
221
    }
222
223 67
    private function loadAttributeOrElementDef(
224
        Schema $schema,
225
        DOMElement $node,
226
        bool $attributeDef
227
    ): Closure {
228 67
        $name = $node->getAttribute('name');
229 67
        if ($attributeDef) {
230 67
            $attribute = new AttributeDef($schema, $name);
231 67
            $schema->addAttribute($attribute);
232
        } else {
233 67
            $attribute = new ElementDef($schema, $name);
234 67
            $attribute->setDoc($this->getDocumentation($node));
235 67
            $schema->addElement($attribute);
236
        }
237
238
        return function () use ($attribute, $node): void {
239 67
            $this->fillItem($attribute, $node);
240 67
        };
241
    }
242
243 67
    private function loadAttributeDef(Schema $schema, DOMElement $node): Closure
244
    {
245 67
        return $this->loadAttributeOrElementDef($schema, $node, true);
246
    }
247
248 67
    private function getDocumentation(DOMElement $node): string
249
    {
250 67
        return $this->documentationReader->get($node);
251
    }
252
253
    /**
254
     * @return Closure[]
255
     */
256 67
    private function schemaNode(Schema $schema, DOMElement $node, Schema $parent = null): array
257
    {
258 67
        $this->setSchemaThingsFromNode($schema, $node, $parent);
259 67
        $functions = [];
260
261 67
        self::againstDOMNodeList(
262 67
            $node,
263
            function (
264
                DOMElement $node,
265
                DOMElement $childNode
266
            ) use (
267 67
                $schema,
268 67
                &$functions
269
            ): void {
270 67
                $callback = null;
271
272 67
                switch ($childNode->localName) {
273 67
                    case 'attributeGroup':
274 67
                        $callback = $this->loadAttributeGroup($schema, $childNode);
275 67
                        break;
276 67
                    case 'include':
277 67
                    case 'import':
278 67
                        $callback = $this->loadImport($schema, $childNode);
279 67
                        break;
280 67
                    case 'element':
281 67
                        $callback = $this->loadElementDef($schema, $childNode);
282 67
                        break;
283 67
                    case 'attribute':
284 67
                        $callback = $this->loadAttributeDef($schema, $childNode);
285 67
                        break;
286 67
                    case 'group':
287 67
                        $callback = $this->loadGroup($schema, $childNode);
288 67
                        break;
289 67
                    case 'complexType':
290 67
                        $callback = $this->loadComplexType($schema, $childNode);
291 67
                        break;
292 67
                    case 'simpleType':
293 67
                        $callback = $this->loadSimpleType($schema, $childNode);
294 67
                        break;
295
                }
296
297 67
                if ($callback instanceof Closure) {
298 67
                    $functions[] = $callback;
299
                }
300 67
            }
301
        );
302
303 67
        return $functions;
304
    }
305
306 67
    private function loadGroupRef(Group $referenced, DOMElement $node): GroupRef
307
    {
308 67
        $ref = new GroupRef($referenced);
309 67
        $ref->setDoc($this->getDocumentation($node));
310
311 67
        self::maybeSetMax($ref, $node);
312 67
        self::maybeSetMin($ref, $node);
313
314 67
        return $ref;
315
    }
316
317 67
    private static function maybeSetMax(InterfaceSetMinMax $ref, DOMElement $node): void
318
    {
319 67
        if ($node->hasAttribute('maxOccurs')) {
320 67
            $ref->setMax($node->getAttribute('maxOccurs') == 'unbounded' ? -1 : (int) $node->getAttribute('maxOccurs'));
321
        }
322 67
    }
323
324 67
    private static function maybeSetMin(InterfaceSetMinMax $ref, DOMElement $node): void
325
    {
326 67
        if ($node->hasAttribute('minOccurs')) {
327 67
            $ref->setMin((int) $node->getAttribute('minOccurs'));
328 67
            if ($ref->getMin() > $ref->getMax() && $ref->getMax() !== -1) {
329 7
                $ref->setMax($ref->getMin());
330
            }
331
        }
332 67
    }
333
334 67
    private static function maybeSetDefault(InterfaceSetDefault $ref, DOMElement $node): void
335
    {
336 67
        if ($node->hasAttribute('default')) {
337 1
            $ref->setDefault($node->getAttribute('default'));
338
        }
339 67
    }
340
341 67
    private function loadSequence(ElementContainer $elementContainer, DOMElement $node, int $max = null): void
342
    {
343
        $max =
344
            (
345 67
                (is_int($max) && (bool) $max) ||
346 67
                $node->getAttribute('maxOccurs') == 'unbounded' ||
347 67
                $node->getAttribute('maxOccurs') > 1
348
            )
349 67
                ? 2
350 67
                : null;
351
352 67
        self::againstDOMNodeList(
353 67
            $node,
354
            function (
355
                DOMElement $node,
356
                DOMElement $childNode
357
            ) use (
358 67
                $elementContainer,
359 67
                $max
360
            ): void {
361 67
                $this->loadSequenceChildNode(
362 67
                    $elementContainer,
363 67
                    $node,
364 67
                    $childNode,
365 67
                    $max
366
                );
367 67
            }
368
        );
369 67
    }
370
371 67
    private function loadSequenceChildNode(
372
        ElementContainer $elementContainer,
373
        DOMElement $node,
374
        DOMElement $childNode,
375
        ? int $max
376
    ): void {
377 67
        switch ($childNode->localName) {
378 67
            case 'sequence':
379 67
            case 'choice':
380 67
            case 'all':
381 67
                $this->loadSequence(
382 67
                    $elementContainer,
383 67
                    $childNode,
384 67
                    $max
385
                );
386 67
                break;
387 67
            case 'element':
388 67
                $this->loadSequenceChildNodeLoadElement(
389 67
                    $elementContainer,
390 67
                    $node,
391 67
                    $childNode,
392 67
                    $max
393
                );
394 67
                break;
395 67
            case 'group':
396 67
                $this->addGroupAsElement(
397 67
                    $elementContainer->getSchema(),
398 67
                    $node,
399 67
                    $childNode,
400 67
                    $elementContainer
401
                );
402 67
                break;
403
        }
404 67
    }
405
406 67
    private function loadSequenceChildNodeLoadElement(
407
        ElementContainer $elementContainer,
408
        DOMElement $node,
409
        DOMElement $childNode,
410
        ? int $max
411
    ): void {
412 67
        if ($childNode->hasAttribute('ref')) {
413 67
            $elementDef = $this->findElement($elementContainer->getSchema(), $node, $childNode->getAttribute('ref'));
414 67
            $element = new ElementRef($elementDef);
415 67
            $element->setDoc($this->getDocumentation($childNode));
416
417 67
            self::maybeSetMax($element, $childNode);
418 67
            self::maybeSetMin($element, $childNode);
419
420 67
            $xp = new \DOMXPath($node->ownerDocument);
421 67
            $xp->registerNamespace('xs', 'http://www.w3.org/2001/XMLSchema');
422
423 67
            if ($xp->query('ancestor::xs:choice', $childNode)->length) {
424 67
                $element->setMin(0);
425
            }
426
427 67
            if ($childNode->hasAttribute('nillable')) {
428
                $element->setNil($childNode->getAttribute('nillable') == 'true');
429
            }
430 67
            if ($childNode->hasAttribute('form')) {
431 67
                $element->setQualified($childNode->getAttribute('form') == 'qualified');
432
            }
433
        } else {
434 67
            $element = $this->loadElement(
435 67
                $elementContainer->getSchema(),
436 67
                $childNode
437
            );
438
        }
439 67
        if ($max > 1) {
440
            /*
441
            * although one might think the typecast is not needed with $max being `? int $max` after passing > 1,
442
            * phpstan@a4f89fa still thinks it's possibly null.
443
            * see https://github.com/phpstan/phpstan/issues/577 for related issue
444
            */
445 67
            $element->setMax((int) $max);
446
        }
447 67
        $elementContainer->addElement($element);
448 67
    }
449
450 67
    private function addGroupAsElement(
451
        Schema $schema,
452
        DOMElement $node,
453
        DOMElement $childNode,
454
        ElementContainer $elementContainer
455
    ): void {
456 67
        $referencedGroup = $this->findGroup(
457 67
            $schema,
458 67
            $node,
459 67
            $childNode->getAttribute('ref')
460
        );
461
462 67
        $group = $this->loadGroupRef($referencedGroup, $childNode);
463 67
        $elementContainer->addElement($group);
464 67
    }
465
466 67
    private function loadGroup(Schema $schema, DOMElement $node): Closure
467
    {
468 67
        $group = new Group($schema, $node->getAttribute('name'));
469 67
        $group->setDoc($this->getDocumentation($node));
470 67
        $groupOriginal = $group;
471
472 67
        if ($node->hasAttribute('maxOccurs') || $node->hasAttribute('maxOccurs')) {
473 1
            $group = new GroupRef($group);
474
475 1
            if ($node->hasAttribute('maxOccurs')) {
476 1
                self::maybeSetMax($group, $node);
477
            }
478 1
            if ($node->hasAttribute('minOccurs')) {
479 1
                self::maybeSetMin($group, $node);
480
            }
481
        }
482
483 67
        $schema->addGroup($group);
484
485
        return function () use ($groupOriginal, $node): void {
486 67
            static::againstDOMNodeList(
487 67
                $node,
488
                function (DOMelement $node, DOMElement $childNode) use ($groupOriginal): void {
489 67
                    switch ($childNode->localName) {
490 67
                        case 'sequence':
491 67
                        case 'choice':
492 67
                        case 'all':
493 67
                            $this->loadSequence($groupOriginal, $childNode);
494 67
                            break;
495
                    }
496 67
                }
497
            );
498 67
        };
499
    }
500
501 67
    private function loadComplexType(Schema $schema, DOMElement $node, Closure $callback = null): Closure
502
    {
503
        /**
504
         * @var bool
505
         */
506 67
        $isSimple = false;
507
508 67
        self::againstDOMNodeList(
509 67
            $node,
510
            function (
511
                DOMElement $node,
512
                DOMElement $childNode
513
            ) use (
514 67
                &$isSimple
515
            ): void {
516 67
                if ($isSimple) {
517 1
                    return;
518
                }
519 67
                if ($childNode->localName === 'simpleContent') {
520 2
                    $isSimple = true;
521
                }
522 67
            }
523
        );
524
525 67
        $type = $isSimple ? new ComplexTypeSimpleContent($schema, $node->getAttribute('name')) : new ComplexType($schema, $node->getAttribute('name'));
526
527 67
        $type->setDoc($this->getDocumentation($node));
528 67
        if ($node->getAttribute('name')) {
529 67
            $schema->addType($type);
530
        }
531
532
        return function () use ($type, $node, $schema, $callback): void {
533 67
            $this->fillTypeNode($type, $node, true);
534
535 67
            self::againstDOMNodeList(
536 67
                $node,
537
                function (
538
                    DOMElement $node,
539
                    DOMElement $childNode
540
                ) use (
541 67
                    $schema,
542 67
                    $type
543
                ): void {
544 67
                    $this->loadComplexTypeFromChildNode(
545 67
                        $type,
546 67
                        $node,
547 67
                        $childNode,
548 67
                        $schema
549
                    );
550 67
                }
551
            );
552
553 67
            if ($callback instanceof Closure) {
554 67
                call_user_func($callback, $type);
555
            }
556 67
        };
557
    }
558
559 67
    private function loadComplexTypeFromChildNode(
560
        BaseComplexType $type,
561
        DOMElement $node,
562
        DOMElement $childNode,
563
        Schema $schema
564
    ): void {
565 67
        switch ($childNode->localName) {
566 67
            case 'sequence':
567 67
            case 'choice':
568 67
            case 'all':
569 67
                if ($type instanceof ElementContainer) {
570 67
                    $this->loadSequence(
571 67
                        $type,
572 67
                        $childNode
573
                    );
574
                }
575 67
                break;
576 67
            case 'attribute':
577 67
                $this->addAttributeFromAttributeOrRef(
578 67
                    $type,
579 67
                    $childNode,
580 67
                    $schema,
581 67
                    $node
582
                );
583 67
                break;
584 67
            case 'attributeGroup':
585 2
                $this->findSomethingLikeAttributeGroup(
586 2
                    $schema,
587 2
                    $node,
588 2
                    $childNode,
589 2
                    $type
590
                );
591 2
                break;
592 67
            case 'group':
593
                if (
594 1
                    $type instanceof ComplexType
595
                ) {
596 1
                    $this->addGroupAsElement(
597 1
                        $schema,
598 1
                        $node,
599 1
                        $childNode,
600 1
                        $type
601
                    );
602
                }
603 1
                break;
604
        }
605 67
    }
606
607 67
    private function loadSimpleType(Schema $schema, DOMElement $node, Closure $callback = null): Closure
608
    {
609 67
        $type = new SimpleType($schema, $node->getAttribute('name'));
610 67
        $type->setDoc($this->getDocumentation($node));
611 67
        if ($node->getAttribute('name')) {
612 67
            $schema->addType($type);
613
        }
614
615
        return function () use ($type, $node, $callback): void {
616 67
            $this->fillTypeNode($type, $node, true);
617
618 67
            self::againstDOMNodeList(
619 67
                $node,
620
                function (DOMElement $node, DOMElement $childNode) use ($type): void {
621 67
                    switch ($childNode->localName) {
622 67
                        case 'union':
623 67
                            $this->loadUnion($type, $childNode);
624 67
                            break;
625 67
                        case 'list':
626 67
                            $this->loadList($type, $childNode);
627 67
                            break;
628
                    }
629 67
                }
630
            );
631
632 67
            if ($callback instanceof Closure) {
633 67
                call_user_func($callback, $type);
634
            }
635 67
        };
636
    }
637
638 67
    private function loadList(SimpleType $type, DOMElement $node): void
639
    {
640 67
        if ($node->hasAttribute('itemType')) {
641
            /**
642
             * @var SimpleType
643
             */
644 67
            $listType = $this->findSomeType($type, $node, 'itemType');
645 67
            $type->setList($listType);
646
        } else {
647 67
            self::againstDOMNodeList(
648 67
                $node,
649
                function (
650
                    DOMElement $node,
651
                    DOMElement $childNode
652
                ) use (
653 67
                    $type
654
                ): void {
655 67
                    $this->loadTypeWithCallback(
656 67
                        $type->getSchema(),
657 67
                        $childNode,
658
                        function (SimpleType $list) use ($type): void {
659 67
                            $type->setList($list);
660 67
                        }
661
                    );
662 67
                }
663
            );
664
        }
665 67
    }
666
667 67
    private function findSomeType(
668
        SchemaItem $fromThis,
669
        DOMElement $node,
670
        string $attributeName
671
    ): SchemaItem {
672 67
        return $this->findSomeTypeFromAttribute(
673 67
            $fromThis,
674 67
            $node,
675 67
            $node->getAttribute($attributeName)
676
        );
677
    }
678
679 67
    private function findSomeTypeFromAttribute(
680
        SchemaItem $fromThis,
681
        DOMElement $node,
682
        string $attributeName
683
    ): SchemaItem {
684 67
        $out = $this->findType(
685 67
            $fromThis->getSchema(),
686 67
            $node,
687 67
            $attributeName
688
        );
689
690 67
        return $out;
691
    }
692
693 67
    private function loadUnion(SimpleType $type, DOMElement $node): void
694
    {
695 67
        if ($node->hasAttribute('memberTypes')) {
696 67
            $types = preg_split('/\s+/', $node->getAttribute('memberTypes'));
697 67
            foreach ($types as $typeName) {
698
                /**
699
                 * @var SimpleType
700
                 */
701 67
                $unionType = $this->findSomeTypeFromAttribute(
702 67
                    $type,
703 67
                    $node,
704 67
                    $typeName
705
                );
706 67
                $type->addUnion($unionType);
707
            }
708
        }
709 67
        self::againstDOMNodeList(
710 67
            $node,
711
            function (
712
                DOMElement $node,
713
                DOMElement $childNode
714
            ) use (
715 67
                $type
716
            ): void {
717 67
                $this->loadTypeWithCallback(
718 67
                    $type->getSchema(),
719 67
                    $childNode,
720
                    function (SimpleType $unType) use ($type): void {
721 67
                        $type->addUnion($unType);
722 67
                    }
723
                );
724 67
            }
725
        );
726 67
    }
727
728 67
    private function fillTypeNode(Type $type, DOMElement $node, bool $checkAbstract = false): void
729
    {
730 67
        if ($checkAbstract) {
731 67
            $type->setAbstract($node->getAttribute('abstract') === 'true' || $node->getAttribute('abstract') === '1');
732
        }
733
734 67
        self::againstDOMNodeList(
735 67
            $node,
736
            function (DOMElement $node, DOMElement $childNode) use ($type): void {
737 67
                switch ($childNode->localName) {
738 67
                    case 'restriction':
739 67
                        $this->loadRestriction($type, $childNode);
740 67
                        break;
741 67
                    case 'extension':
742 67
                        if ($type instanceof BaseComplexType) {
743 67
                            $this->loadExtension($type, $childNode);
744
                        }
745 67
                        break;
746 67
                    case 'simpleContent':
747 67
                    case 'complexContent':
748 67
                        $this->fillTypeNode($type, $childNode);
749 67
                        break;
750
                }
751 67
            }
752
        );
753 67
    }
754
755 67
    private function loadExtension(BaseComplexType $type, DOMElement $node): void
756
    {
757 67
        $extension = new Extension();
758 67
        $type->setExtension($extension);
759
760 67
        if ($node->hasAttribute('base')) {
761 67
            $this->findAndSetSomeBase(
762 67
                $type,
763 67
                $extension,
764 67
                $node
765
            );
766
        }
767 67
        $this->loadExtensionChildNodes($type, $node);
768 67
    }
769
770 67
    private function findAndSetSomeBase(
771
        Type $type,
772
        Base $setBaseOnThis,
773
        DOMElement $node
774
    ): void {
775
        /**
776
         * @var Type
777
         */
778 67
        $parent = $this->findSomeType($type, $node, 'base');
779 67
        $setBaseOnThis->setBase($parent);
780 67
    }
781
782 67
    private function loadExtensionChildNodes(
783
        BaseComplexType $type,
784
        DOMElement $node
785
    ): void {
786 67
        self::againstDOMNodeList(
787 67
            $node,
788
            function (
789
                DOMElement $node,
790
                DOMElement $childNode
791
            ) use (
792 67
                $type
793
            ): void {
794 67
                switch ($childNode->localName) {
795 67
                    case 'sequence':
796 67
                    case 'choice':
797 67
                    case 'all':
798 67
                        if ($type instanceof ElementContainer) {
799 67
                            $this->loadSequence(
800 67
                                $type,
801 67
                                $childNode
802
                            );
803
                        }
804 67
                        break;
805 67
                    case 'attribute':
806 67
                        $this->addAttributeFromAttributeOrRef(
807 67
                            $type,
808 67
                            $childNode,
809 67
                            $type->getSchema(),
810 67
                            $node
811
                        );
812 67
                        break;
813 67
                    case 'attributeGroup':
814 67
                        $this->findSomethingLikeAttributeGroup(
815 67
                            $type->getSchema(),
816 67
                            $node,
817 67
                            $childNode,
818 67
                            $type
819
                        );
820 67
                        break;
821
                }
822 67
            }
823
        );
824 67
    }
825
826 67
    private function loadRestriction(Type $type, DOMElement $node): void
827
    {
828 67
        $restriction = new Restriction();
829 67
        $type->setRestriction($restriction);
830 67
        if ($node->hasAttribute('base')) {
831 67
            $this->findAndSetSomeBase($type, $restriction, $node);
832
        } else {
833 67
            self::againstDOMNodeList(
834 67
                $node,
835
                function (
836
                    DOMElement $node,
837
                    DOMElement $childNode
838
                ) use (
839 67
                    $type,
840 67
                    $restriction
841
                ): void {
842 67
                    $this->loadTypeWithCallback(
843 67
                        $type->getSchema(),
844 67
                        $childNode,
845
                        function (Type $restType) use ($restriction): void {
846 67
                            $restriction->setBase($restType);
847 67
                        }
848
                    );
849 67
                }
850
            );
851
        }
852 67
        self::againstDOMNodeList(
853 67
            $node,
854
            function (
855
                DOMElement $node,
856
                DOMElement $childNode
857
            ) use (
858 67
                $restriction
859
            ): void {
860
                if (
861 67
                in_array(
862 67
                    $childNode->localName,
863
                    [
864 67
                        'enumeration',
865
                        'pattern',
866
                        'length',
867
                        'minLength',
868
                        'maxLength',
869
                        'minInclusive',
870
                        'maxInclusive',
871
                        'minExclusive',
872
                        'maxExclusive',
873
                        'fractionDigits',
874
                        'totalDigits',
875
                        'whiteSpace',
876
                    ],
877 67
                    true
878
                )
879
                ) {
880 67
                    $restriction->addCheck(
881 67
                        $childNode->localName,
882
                        [
883 67
                            'value' => $childNode->getAttribute('value'),
884 67
                            'doc' => $this->getDocumentation($childNode),
885
                        ]
886
                    );
887
                }
888 67
            }
889
        );
890 67
    }
891
892
    /**
893
     * @return mixed[]
894
     */
895 67
    private static function splitParts(DOMElement $node, string $typeName): array
896
    {
897 67
        $prefix = null;
898 67
        $name = $typeName;
899 67
        if (strpos($typeName, ':') !== false) {
900 67
            list($prefix, $name) = explode(':', $typeName);
901
        }
902
903
        /**
904
         * @psalm-suppress PossiblyNullArgument
905
         */
906 67
        $namespace = $node->lookupNamespaceUri($prefix);
907
908
        return [
909 67
            $name,
910 67
            $namespace,
911 67
            $prefix,
912
        ];
913
    }
914
915 67
    private function findAttributeItem(Schema $schema, DOMElement $node, string $typeName): AttributeItem
916
    {
917 67
        list($name, $namespace) = static::splitParts($node, $typeName);
918
919
        /**
920
         * @var string|null $namespace
921
         */
922 67
        $namespace = $namespace ?: $schema->getTargetNamespace();
923
924
        try {
925
            /**
926
             * @var AttributeItem $out
927
             */
928 67
            $out = $schema->findAttribute((string) $name, $namespace);
929
930 67
            return $out;
931
        } catch (TypeNotFoundException $e) {
932
            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);
933
        }
934
    }
935
936 67
    private function findAttributeGroup(Schema $schema, DOMElement $node, string $typeName): AttributeGroup
937
    {
938 67
        list($name, $namespace) = static::splitParts($node, $typeName);
939
940
        /**
941
         * @var string|null $namespace
942
         */
943 67
        $namespace = $namespace ?: $schema->getTargetNamespace();
944
945
        try {
946
            /**
947
             * @var AttributeGroup $out
948
             */
949 67
            $out = $schema->findAttributeGroup((string) $name, $namespace);
950
951 67
            return $out;
952
        } catch (TypeNotFoundException $e) {
953
            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);
954
        }
955
    }
956
957 67
    private function findElement(Schema $schema, DOMElement $node, string $typeName): ElementDef
958
    {
959 67
        list($name, $namespace) = static::splitParts($node, $typeName);
960
961
        /**
962
         * @var string|null $namespace
963
         */
964 67
        $namespace = $namespace ?: $schema->getTargetNamespace();
965
966
        try {
967 67
            return $schema->findElement((string) $name, $namespace);
968
        } catch (TypeNotFoundException $e) {
969
            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);
970
        }
971
    }
972
973 67
    private function findGroup(Schema $schema, DOMElement $node, string $typeName): Group
974
    {
975 67
        list($name, $namespace) = static::splitParts($node, $typeName);
976
977
        /**
978
         * @var string|null $namespace
979
         */
980 67
        $namespace = $namespace ?: $schema->getTargetNamespace();
981
982
        try {
983
            /**
984
             * @var Group $out
985
             */
986 67
            $out = $schema->findGroup((string) $name, $namespace);
987
988 67
            return $out;
989
        } catch (TypeNotFoundException $e) {
990
            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);
991
        }
992
    }
993
994 67
    private function findType(Schema $schema, DOMElement $node, string $typeName): SchemaItem
995
    {
996 67
        list($name, $namespace) = static::splitParts($node, $typeName);
997
998
        /**
999
         * @var string|null $namespace
1000
         */
1001 67
        $namespace = $namespace ?: $schema->getTargetNamespace();
1002
1003
        $tryFindType = static function (Schema $schema, string $name, ?string $namespace): ?SchemaItem {
1004
            try {
1005 67
                return $schema->findType($name, $namespace);
1006 1
            } catch (TypeNotFoundException $e) {
1007 1
                return null;
1008
            }
1009 67
        };
1010
1011 67
        $interestingSchemas = array_merge([$schema], $this->loadedSchemas[$namespace] ?? []);
1012 67
        foreach ($interestingSchemas as $interestingSchema) {
1013 67
            if ($result = $tryFindType($interestingSchema, $name, $namespace)) {
1014 67
                return $result;
1015
            }
1016
        }
1017
1018
        throw new TypeException(sprintf("Can't find %s named {%s}#%s, at line %d in %s ", 'type', $namespace, $name, $node->getLineNo(), $node->ownerDocument->documentURI));
1019
    }
1020
1021 67
    private function loadElementDef(Schema $schema, DOMElement $node): Closure
1022
    {
1023 67
        return $this->loadAttributeOrElementDef($schema, $node, false);
1024
    }
1025
1026 67
    private function fillItem(Item $element, DOMElement $node): void
1027
    {
1028
        /**
1029
         * @var bool
1030
         */
1031 67
        $skip = false;
1032 67
        self::againstDOMNodeList(
1033 67
            $node,
1034
            function (
1035
                DOMElement $node,
1036
                DOMElement $childNode
1037
            ) use (
1038 67
                $element,
1039 67
                &$skip
1040
            ): void {
1041
                if (
1042 67
                    !$skip &&
1043 67
                    in_array(
1044 67
                        $childNode->localName,
1045
                        [
1046 67
                            'complexType',
1047
                            'simpleType',
1048
                        ],
1049 67
                        true
1050
                    )
1051
                ) {
1052 67
                    $this->loadTypeWithCallback(
1053 67
                        $element->getSchema(),
1054 67
                        $childNode,
1055
                        function (Type $type) use ($element): void {
1056 67
                            $element->setType($type);
1057 67
                        }
1058
                    );
1059 67
                    $skip = true;
1060
                }
1061 67
            }
1062
        );
1063 67
        if ($skip) {
1064 67
            return;
1065
        }
1066 67
        $this->fillItemNonLocalType($element, $node);
1067 67
    }
1068
1069 67
    private function fillItemNonLocalType(Item $element, DOMElement $node): void
1070
    {
1071 67
        if ($node->getAttribute('type')) {
1072
            /**
1073
             * @var Type
1074
             */
1075 67
            $type = $this->findSomeType($element, $node, 'type');
1076
        } else {
1077
            /**
1078
             * @var Type
1079
             */
1080 67
            $type = $this->findSomeTypeFromAttribute(
1081 67
                $element,
1082 67
                $node,
1083 67
                ($node->lookupPrefix(self::XSD_NS).':anyType')
1084
            );
1085
        }
1086
1087 67
        $element->setType($type);
1088 67
    }
1089
1090 67
    private function loadImport(
1091
        Schema $schema,
1092
        DOMElement $node
1093
    ): Closure {
1094 67
        $namespace = $node->getAttribute('namespace');
1095 67
        $schemaLocation = $node->getAttribute('schemaLocation');
1096 67
        if (!$schemaLocation && isset($this->knownNamespaceSchemaLocations[$namespace])) {
1097 1
            $schemaLocation = $this->knownNamespaceSchemaLocations[$namespace];
1098
        }
1099
1100
        // postpone schema loading
1101 67
        if ($namespace && !$schemaLocation && !isset(self::$globalSchemaInfo[$namespace])) {
1102
            return function () use ($schema, $namespace): void {
1103 3
                if (!empty($this->loadedSchemas[$namespace])) {
1104 3
                    foreach ($this->loadedSchemas[$namespace] as $s) {
1105 3
                        $schema->addSchema($s, $namespace);
1106
                    }
1107
                }
1108 3
            };
1109 67
        } elseif ($namespace && !$schemaLocation && !empty($this->loadedSchemas[$namespace])) {
1110 1
            foreach ($this->loadedSchemas[$namespace] as $s) {
1111 1
                $schema->addSchema($s, $namespace);
1112
            }
1113
        }
1114
1115 67
        $base = urldecode($node->ownerDocument->documentURI);
1116 67
        $file = UrlUtils::resolveRelativeUrl($base, $schemaLocation);
1117
1118 67
        if (isset($this->loadedFiles[$file])) {
1119 67
            $schema->addSchema($this->loadedFiles[$file]);
1120
1121
            return function (): void {
1122 67
            };
1123
        }
1124
1125 3
        return $this->loadImportFresh($namespace, $schema, $file);
1126
    }
1127
1128 3
    private function createOrUseSchemaForNs(
1129
        Schema $schema,
1130
        string $namespace
1131
    ): Schema {
1132 3
        if (('' !== trim($namespace))) {
1133 2
            $newSchema = new Schema();
1134 2
            $newSchema->addSchema($this->getGlobalSchema());
1135 2
            $schema->addSchema($newSchema);
1136
        } else {
1137 1
            $newSchema = $schema;
1138
        }
1139
1140 3
        return $newSchema;
1141
    }
1142
1143 3
    private function loadImportFresh(
1144
        string $namespace,
1145
        Schema $schema,
1146
        string $file
1147
    ): Closure {
1148
        return function () use ($namespace, $schema, $file): void {
1149 3
            $dom = $this->getDOM(
1150 3
                isset($this->knownLocationSchemas[$file])
1151 1
                    ? $this->knownLocationSchemas[$file]
1152 3
                    : $file
1153
            );
1154
1155 3
            $schemaNew = $this->createOrUseSchemaForNs($schema, $namespace);
1156
1157 3
            $this->setLoadedFile($file, $schemaNew);
1158
1159 3
            $callbacks = $this->schemaNode($schemaNew, $dom->documentElement, $schema);
1160
1161 3
            foreach ($callbacks as $callback) {
1162 3
                $callback();
1163
            }
1164 3
        };
1165
    }
1166
1167
    /**
1168
     * @var Schema|null
1169
     */
1170
    protected $globalSchema;
1171
1172 67
    public function getGlobalSchema(): Schema
1173
    {
1174 67
        if (!($this->globalSchema instanceof Schema)) {
1175 67
            $callbacks = [];
1176 67
            $globalSchemas = [];
1177
            /**
1178
             * @var string $namespace
1179
             */
1180 67
            foreach (self::$globalSchemaInfo as $namespace => $uri) {
1181 67
                $this->setLoadedFile(
1182 67
                    $uri,
1183 67
                    $globalSchemas[$namespace] = $schema = new Schema()
1184
                );
1185 67
                $this->setLoadedSchema($namespace, $schema);
1186 67
                if ($namespace === self::XSD_NS) {
1187 67
                    $this->globalSchema = $schema;
1188
                }
1189 67
                $xml = $this->getDOM($this->knownLocationSchemas[$uri]);
1190 67
                $callbacks = array_merge($callbacks, $this->schemaNode($schema, $xml->documentElement));
1191
            }
1192
1193 67
            $globalSchemas[(string) static::XSD_NS]->addType(new SimpleType($globalSchemas[(string) static::XSD_NS], 'anySimpleType'));
1194 67
            $globalSchemas[(string) static::XSD_NS]->addType(new SimpleType($globalSchemas[(string) static::XSD_NS], 'anyType'));
1195
1196 67
            $globalSchemas[(string) static::XML_NS]->addSchema(
1197 67
                $globalSchemas[(string) static::XSD_NS],
1198 67
                (string) static::XSD_NS
1199
            );
1200 67
            $globalSchemas[(string) static::XSD_NS]->addSchema(
1201 67
                $globalSchemas[(string) static::XML_NS],
1202 67
                (string) static::XML_NS
1203
            );
1204
1205
            /**
1206
             * @var Closure $callback
1207
             */
1208 67
            foreach ($callbacks as $callback) {
1209 67
                $callback();
1210
            }
1211
        }
1212
1213 67
        if (!($this->globalSchema instanceof Schema)) {
1214
            throw new TypeException('Global schema not discovered');
1215
        }
1216
1217 67
        return $this->globalSchema;
1218
    }
1219
1220
    /**
1221
     * @param DOMNode[] $nodes
1222
     */
1223 2
    public function readNodes(array $nodes, string $file = null): Schema
1224
    {
1225 2
        $rootSchema = new Schema();
1226 2
        $rootSchema->addSchema($this->getGlobalSchema());
1227
1228 2
        if ($file !== null) {
1229 2
            $this->setLoadedFile($file, $rootSchema);
1230
        }
1231
1232 2
        $all = [];
1233 2
        foreach ($nodes as $k => $node) {
1234 2
            if (($node instanceof \DOMElement) && $node->namespaceURI === self::XSD_NS && $node->localName == 'schema') {
1235 2
                $holderSchema = new Schema();
1236 2
                $holderSchema->addSchema($this->getGlobalSchema());
1237
1238 2
                $this->setLoadedSchemaFromElement($node, $holderSchema);
1239
1240 2
                $rootSchema->addSchema($holderSchema);
1241
1242 2
                $callbacks = $this->schemaNode($holderSchema, $node);
1243 2
                $all = array_merge($callbacks, $all);
1244
            }
1245
        }
1246
1247 2
        foreach ($all as $callback) {
1248 2
            call_user_func($callback);
1249
        }
1250
1251 2
        return $rootSchema;
1252
    }
1253
1254 65
    public function readNode(DOMElement $node, string $file = null): Schema
1255
    {
1256 65
        $rootSchema = new Schema();
1257 65
        $rootSchema->addSchema($this->getGlobalSchema());
1258
1259 65
        if ($file !== null) {
1260 64
            $this->setLoadedFile($file, $rootSchema);
1261
        }
1262
1263 65
        $this->setLoadedSchemaFromElement($node, $rootSchema);
1264
1265 65
        $callbacks = $this->schemaNode($rootSchema, $node);
1266
1267 65
        foreach ($callbacks as $callback) {
1268 59
            call_user_func($callback);
1269
        }
1270
1271 65
        return $rootSchema;
1272
    }
1273
1274
    /**
1275
     * @throws IOException
1276
     */
1277 61
    public function readString(string $content, string $file = 'schema.xsd'): Schema
1278
    {
1279 61
        $xml = new DOMDocument('1.0', 'UTF-8');
1280 61
        libxml_use_internal_errors(true);
1281 61
        if (!@$xml->loadXML($content)) {
1282 1
            throw new IOException("Can't load the schema", 0, $this->extractErrorMessage());
1283
        }
1284 60
        libxml_use_internal_errors(false);
1285 60
        $xml->documentURI = $file;
1286
1287 60
        return $this->readNode($xml->documentElement, $file);
1288
    }
1289
1290
    /**
1291
     * @throws IOException
1292
     */
1293 5
    public function readFile(string $file): Schema
1294
    {
1295 5
        $xml = $this->getDOM($file);
1296
1297 4
        return $this->readNode($xml->documentElement, $file);
1298
    }
1299
1300
    /**
1301
     * @throws IOException
1302
     */
1303 68
    private function getDOM(string $file): DOMDocument
1304
    {
1305 68
        $xml = new DOMDocument('1.0', 'UTF-8');
1306 68
        libxml_use_internal_errors(true);
1307 68
        if (!@$xml->load($file)) {
1308 1
            libxml_use_internal_errors(false);
1309 1
            throw new IOException("Can't load the file '$file'", 0, $this->extractErrorMessage());
1310
        }
1311 67
        libxml_use_internal_errors(false);
1312
1313 67
        return $xml;
1314
    }
1315
1316 67
    private static function againstDOMNodeList(
1317
        DOMElement $node,
1318
        Closure $againstNodeList
1319
    ): void {
1320 67
        $limit = $node->childNodes->length;
1321 67
        for ($i = 0; $i < $limit; ++$i) {
1322
            /**
1323
             * @var DOMNode
1324
             */
1325 67
            $childNode = $node->childNodes->item($i);
1326
1327 67
            if ($childNode instanceof DOMElement) {
1328 67
                $againstNodeList(
1329 67
                    $node,
1330 67
                    $childNode
1331
                );
1332
            }
1333
        }
1334 67
    }
1335
1336 67
    private function loadTypeWithCallback(
1337
        Schema $schema,
1338
        DOMElement $childNode,
1339
        Closure $callback
1340
    ): void {
1341
        /**
1342
         * @var Closure|null $func
1343
         */
1344 67
        $func = null;
1345
1346 67
        switch ($childNode->localName) {
1347 67
            case 'complexType':
1348 67
                $func = $this->loadComplexType($schema, $childNode, $callback);
1349 67
                break;
1350 67
            case 'simpleType':
1351 67
                $func = $this->loadSimpleType($schema, $childNode, $callback);
1352 67
                break;
1353
        }
1354
1355 67
        if ($func instanceof Closure) {
1356 67
            call_user_func($func);
1357
        }
1358 67
    }
1359
1360 67
    private function loadElement(
1361
        Schema $schema,
1362
        DOMElement $node
1363
    ): Element {
1364 67
        $element = new Element($schema, $node->getAttribute('name'));
1365 67
        $element->setDoc($this->getDocumentation($node));
1366
1367 67
        $this->fillItem($element, $node);
1368
1369 67
        self::maybeSetMax($element, $node);
1370 67
        self::maybeSetMin($element, $node);
1371 67
        self::maybeSetDefault($element, $node);
1372
1373 67
        $xp = new \DOMXPath($node->ownerDocument);
1374 67
        $xp->registerNamespace('xs', 'http://www.w3.org/2001/XMLSchema');
1375
1376 67
        if ($xp->query('ancestor::xs:choice', $node)->length) {
1377 67
            $element->setMin(0);
1378
        }
1379
1380 67
        if ($node->hasAttribute('nillable')) {
1381 4
            $element->setNil($node->getAttribute('nillable') == 'true');
1382
        }
1383 67
        if ($node->hasAttribute('form')) {
1384 5
            $element->setQualified($node->getAttribute('form') == 'qualified');
1385
        }
1386
1387 67
        $parentNode = $node->parentNode;
1388
1389 67
        if ($parentNode->localName != 'schema' || $parentNode->namespaceURI != 'http://www.w3.org/2001/XMLSchema') {
1390 67
            $element->setLocal(true);
1391
        }
1392
1393 67
        return $element;
1394
    }
1395
1396 67
    private function addAttributeFromAttributeOrRef(
1397
        BaseComplexType $type,
1398
        DOMElement $childNode,
1399
        Schema $schema,
1400
        DOMElement $node
1401
    ): void {
1402 67
        $attribute = $this->getAttributeFromAttributeOrRef(
1403 67
            $childNode,
1404 67
            $schema,
1405 67
            $node
1406
        );
1407
1408 67
        $type->addAttribute($attribute);
1409 67
    }
1410
1411 67
    private function findSomethingLikeAttributeGroup(
1412
        Schema $schema,
1413
        DOMElement $node,
1414
        DOMElement $childNode,
1415
        AttributeContainer $addToThis
1416
    ): void {
1417 67
        $attribute = $this->findAttributeGroup($schema, $node, $childNode->getAttribute('ref'));
1418 67
        $addToThis->addAttribute($attribute);
1419 67
    }
1420
1421 67
    private function setLoadedFile(string $key, Schema $schema): void
1422
    {
1423 67
        $this->loadedFiles[$key] = $schema;
1424 67
    }
1425
1426 67
    private function setLoadedSchemaFromElement(DOMElement $node, Schema $schema): void
1427
    {
1428 67
        if ($node->hasAttribute('targetNamespace')) {
1429 67
            $this->setLoadedSchema($node->getAttribute('targetNamespace'), $schema);
1430
        }
1431 67
    }
1432
1433 67
    private function setLoadedSchema(string $namespace, Schema $schema): void
1434
    {
1435 67
        if (!isset($this->loadedSchemas[$namespace])) {
1436 67
            $this->loadedSchemas[$namespace] = [];
1437
        }
1438 67
        if (!in_array($schema, $this->loadedSchemas[$namespace], true)) {
1439 67
            $this->loadedSchemas[$namespace][] = $schema;
1440
        }
1441 67
    }
1442
1443 67
    private function setSchemaThingsFromNode(
1444
        Schema $schema,
1445
        DOMElement $node,
1446
        Schema $parent = null
1447
    ): void {
1448 67
        $schema->setDoc($this->getDocumentation($node));
1449
1450 67
        if ($node->hasAttribute('targetNamespace')) {
1451 67
            $schema->setTargetNamespace($node->getAttribute('targetNamespace'));
1452
        } elseif ($parent instanceof Schema) {
1453
            $schema->setTargetNamespace($parent->getTargetNamespace());
1454
        }
1455 67
        $schema->setElementsQualification($node->getAttribute('elementFormDefault') == 'qualified');
1456 67
        $schema->setAttributesQualification($node->getAttribute('attributeFormDefault') == 'qualified');
1457 67
        $schema->setDoc($this->getDocumentation($node));
1458 67
    }
1459
}
1460