Completed
Push — master ( f82b26...e61848 )
by Asmir
13s queued 12s
created

SchemaReader::maybeSetDefault()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

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