Passed
Pull Request — master (#35)
by
unknown
01:47
created

SchemaReader::getGlobalSchema()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 46
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 6.002

Importance

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