Completed
Pull Request — master (#45)
by Carsten
03:52
created

SchemaReader::addKnownNamespaceSchemaLocation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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