Passed
Push — ref-optional ( 430b94 )
by Asmir
03:01
created

SchemaReader::findGroup()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 28
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 5.9245

Importance

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