Completed
Branch psalm (013a1f)
by SignpostMarv
04:38 queued 02:50
created

SchemaReader::loadImport()   D

Complexity

Conditions 10
Paths 5

Size

Total Lines 31
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 10.017

Importance

Changes 0
Metric Value
dl 0
loc 31
ccs 17
cts 18
cp 0.9444
rs 4.8196
c 0
b 0
f 0
cc 10
eloc 15
nc 5
nop 2
crap 10.017

How to fix   Complexity   

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