Completed
Pull Request — master (#48)
by Carsten
04:33
created

SchemaReader::loadRedefine()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 24
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 4

Importance

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