Completed
Pull Request — master (#45)
by Carsten
02:34
created

SchemaReader::addKnownNamespaceSchemaLocation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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