Completed
Pull Request — master (#49)
by Asmir
03:19
created

SchemaReader::loadComplexType()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 54
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 33
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 35
dl 0
loc 54
ccs 33
cts 33
cp 1
rs 8.7377
c 0
b 0
f 0
cc 6
nc 4
nop 3
crap 6

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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