Completed
Push — master ( 6783dc...0b3ddc )
by Asmir
04:37 queued 03:15
created

SchemaReader   F

Complexity

Total Complexity 198

Size/Duplication

Total Lines 1463
Duplicated Lines 0 %

Test Coverage

Coverage 92.24%

Importance

Changes 0
Metric Value
wmc 198
eloc 740
dl 0
loc 1463
ccs 701
cts 760
cp 0.9224
rs 1.86
c 0
b 0
f 0

59 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 2
A extractErrorMessage() 0 11 2
A loadAttributeDef() 0 3 1
A addKnownSchemaLocation() 0 3 1
A getDocumentation() 0 3 1
A maybeSetDefault() 0 4 2
A maybeSetMin() 0 6 4
A loadAttribute() 0 19 4
B loadSequenceChildNode() 0 32 6
A loadGroupRef() 0 9 1
A maybeSetMax() 0 4 3
A getAttributeFromAttributeOrRef() 0 15 2
A loadSequence() 0 25 5
A loadAttributeGroup() 0 35 3
A addKnownNamespaceSchemaLocation() 0 3 1
A loadAttributeOrElementDef() 0 17 2
B schemaNode() 0 48 10
B loadExtensionChildNodes() 0 39 7
A setLoadedFile() 0 3 1
A findSomeTypeFromAttribute() 0 12 1
A readFile() 0 5 1
A addAttributeFromAttributeOrRef() 0 13 1
A findAttributeItem() 0 28 3
A addGroupAsElement() 0 14 1
A setLoadedSchemaFromElement() 0 4 2
B getGlobalSchema() 0 46 6
B loadGroup() 0 29 8
A createOrUseSchemaForNs() 0 13 2
A findGroup() 0 28 3
A loadElement() 0 28 4
A loadUnion() 0 29 3
A findAttributeGroup() 0 28 3
A findSomethingLikeAttributeGroup() 0 8 1
B loadSequenceChildNodeLoadElement() 0 43 6
A findType() 0 28 3
C loadImport() 0 36 13
A loadTypeWithCallback() 0 21 4
B loadComplexType() 0 54 6
B fillTypeNode() 0 22 8
A loadImportFresh() 0 20 3
A findSomeType() 0 9 1
A loadSimpleType() 0 27 5
A loadRestriction() 0 59 3
B loadComplexTypeFromChildNode() 0 45 9
A readNode() 0 18 3
A findElement() 0 23 3
A setLoadedSchema() 0 7 3
B readNodes() 0 29 7
A findAndSetSomeBase() 0 10 1
A fillItem() 0 41 4
A splitParts() 0 17 2
A loadList() 0 22 2
A loadElementDef() 0 3 1
A readString() 0 11 2
A fillItemNonLocalType() 0 19 2
A setSchemaThingsFromNode() 0 15 3
A loadExtension() 0 13 2
A againstDOMNodeList() 0 15 3
A getDOM() 0 11 2

How to fix   Complexity   

Complex Class

Complex classes like SchemaReader often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SchemaReader, and based on these observations, apply Extract Interface, too.

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