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