Passed
Push — php-7.1 ( b6da63...280be0 )
by SignpostMarv
01:58
created

SchemaReader::loadSequenceChildNodeLoadSequence()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1.125

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 3
dl 0
loc 7
ccs 1
cts 2
cp 0.5
crap 1.125
rs 9.4285
1
<?php
2
declare(strict_types = 1);
3
namespace GoetasWebservices\XML\XSDReader;
4
5
use Closure;
6
use DOMDocument;
7
use DOMElement;
8
use DOMNode;
9
use DOMNodeList;
10
use GoetasWebservices\XML\XSDReader\Exception\IOException;
11
use GoetasWebservices\XML\XSDReader\Exception\TypeException;
12
use GoetasWebservices\XML\XSDReader\Schema\Attribute\Attribute;
13
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeDef;
14
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeItem;
15
use GoetasWebservices\XML\XSDReader\Schema\Attribute\Group as AttributeGroup;
16
use GoetasWebservices\XML\XSDReader\Schema\Element\Element;
17
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementContainer;
18
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementDef;
19
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementItem;
20
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementRef;
21
use GoetasWebservices\XML\XSDReader\Schema\Element\Group;
22
use GoetasWebservices\XML\XSDReader\Schema\Element\GroupRef;
23
use GoetasWebservices\XML\XSDReader\Schema\Element\InterfaceSetMinMax;
24
use GoetasWebservices\XML\XSDReader\Schema\Exception\TypeNotFoundException;
25
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Base;
26
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Extension;
27
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Restriction;
28
use GoetasWebservices\XML\XSDReader\Schema\Item;
29
use GoetasWebservices\XML\XSDReader\Schema\Schema;
30
use GoetasWebservices\XML\XSDReader\Schema\SchemaItem;
31
use GoetasWebservices\XML\XSDReader\Schema\Type\BaseComplexType;
32
use GoetasWebservices\XML\XSDReader\Schema\Type\ComplexType;
33
use GoetasWebservices\XML\XSDReader\Schema\Type\ComplexTypeSimpleContent;
34
use GoetasWebservices\XML\XSDReader\Schema\Type\SimpleType;
35
use GoetasWebservices\XML\XSDReader\Schema\Type\Type;
36
use GoetasWebservices\XML\XSDReader\Utils\UrlUtils;
37
use RuntimeException;
38
39
class SchemaReader
40
{
41
42
    const XSD_NS = "http://www.w3.org/2001/XMLSchema";
43
44
    const XML_NS = "http://www.w3.org/XML/1998/namespace";
45
46
    /**
47
    * @var string[]
48
    */
49
    private $knownLocationSchemas = [
50
        'http://www.w3.org/2001/xml.xsd' => (
51
            __DIR__ . '/Resources/xml.xsd'
52
        ),
53
        'http://www.w3.org/2001/XMLSchema.xsd' => (
54
            __DIR__ . '/Resources/XMLSchema.xsd'
55
        ),
56
        'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' => (
57
            __DIR__ . '/Resources/oasis-200401-wss-wssecurity-secext-1.0.xsd'
58
        ),
59
        'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd' => (
60
            __DIR__ . '/Resources/oasis-200401-wss-wssecurity-utility-1.0.xsd'
61
        ),
62
        'https://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd' => (
63
            __DIR__ . '/Resources/xmldsig-core-schema.xsd'
64
        ),
65
        'http://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd' => (
66
            __DIR__ . '/Resources/xmldsig-core-schema.xsd'
67
        ),
68
    ];
69
70
    /**
71
    * @var string[]
72
    */
73
    private static $globalSchemaInfo = array(
74
        self::XML_NS => 'http://www.w3.org/2001/xml.xsd',
75
        self::XSD_NS => 'http://www.w3.org/2001/XMLSchema.xsd'
76
    );
77
78 54
    public function __construct()
79
    {
80 54
    }
81
82
    public function addKnownSchemaLocation(
83
        string $remote,
84
        string $local
85
    ) : void {
86
        $this->knownLocationSchemas[$remote] = $local;
87
    }
88
89 45
    private function loadAttributeGroup(
90
        Schema $schema,
91
        DOMElement $node
92
    ) : Closure {
93 45
        return AttributeGroup::loadAttributeGroup($this, $schema, $node);
94
    }
95
96 45
    private function loadAttributeOrElementDef(
97
        Schema $schema,
98
        DOMElement $node,
99
        bool $attributeDef
100
    ) : Closure {
101 45
        $name = $node->getAttribute('name');
102 45
        if ($attributeDef) {
103 45
            $attribute = new AttributeDef($schema, $name);
104 45
            $schema->addAttribute($attribute);
105
        } else {
106 45
            $attribute = new ElementDef($schema, $name);
107 45
            $schema->addElement($attribute);
108
        }
109
110
111 45
        return function () use ($attribute, $node) : void {
112 45
            $this->fillItem($attribute, $node);
113 45
        };
114
    }
115
116 45
    private function loadAttributeDef(
117
        Schema $schema,
118
        DOMElement $node
119
    ) : Closure {
120 45
        return $this->loadAttributeOrElementDef($schema, $node, true);
121
    }
122
123 45
    public static function getDocumentation(DOMElement $node) : string
124
    {
125 45
        $doc = '';
126 45
        foreach ($node->childNodes as $childNode) {
127 45
            if ($childNode->localName == "annotation") {
128 45
                $doc .= static::getDocumentation($childNode);
129 45
            } elseif ($childNode->localName == 'documentation') {
130 45
                $doc .= ($childNode->nodeValue);
131
            }
132
        }
133 45
        $doc = preg_replace('/[\t ]+/', ' ', $doc);
134 45
        return trim($doc);
135
    }
136
137 45
    private function setSchemaThingsFromNode(
138
        Schema $schema,
139
        DOMElement $node,
140
        ? Schema $parent
141
    ) : void {
142 45
        $schema->setDoc(static::getDocumentation($node));
143
144 45
        if ($node->hasAttribute("targetNamespace")) {
145 45
            $schema->setTargetNamespace($node->getAttribute("targetNamespace"));
146
        } elseif ($parent) {
147
            $schema->setTargetNamespace($parent->getTargetNamespace());
148
        }
149 45
        $schema->setElementsQualification($node->getAttribute("elementFormDefault") == "qualified");
150 45
        $schema->setAttributesQualification($node->getAttribute("attributeFormDefault") == "qualified");
151 45
        $schema->setDoc(static::getDocumentation($node));
152 45
    }
153
154 45
    public function maybeCallMethod(
155
        array $methods,
156
        string $key,
157
        DOMNode $childNode,
158
        ...$args
159
    ) : ? Closure {
160 45
        if ($childNode instanceof DOMElement && isset($methods[$key])) {
161 45
            $method = $methods[$key];
162
163 45
            $append = $this->$method(...$args);
164
165 45
            if ($append instanceof Closure) {
166 45
                return $append;
167
            }
168
        }
169
170 45
        return null;
171
    }
172
173
    /**
174
     * @return Closure[]
175
     */
176 45
    private function schemaNode(
177
        Schema $schema,
178
        DOMElement $node,
179
        Schema $parent = null
180
    ) : array {
181 45
        $this->setSchemaThingsFromNode($schema, $node, $parent);
182 45
        $functions = array();
183
184 45
        static $methods = [
185
            'include' => 'loadImport',
186
            'import' => 'loadImport',
187
            'element' => 'loadElementDef',
188
            'attribute' => 'loadAttributeDef',
189
            'attributeGroup' => 'loadAttributeGroup',
190
            'group' => 'loadGroup',
191
            'complexType' => 'loadComplexType',
192
            'simpleType' => 'loadSimpleType',
193
        ];
194
195 45
        foreach ($node->childNodes as $childNode) {
196 45
            $callback = $this->maybeCallMethod(
197 45
                $methods,
198 45
                (string) $childNode->localName,
199 45
                $childNode,
200 45
                $schema,
201 45
                $childNode
202
            );
203
204 45
            if ($callback instanceof Closure) {
205 45
                $functions[] = $callback;
206
            }
207
        }
208
209 45
        return $functions;
210
    }
211
212 45
    public static function maybeSetMax(
213
        InterfaceSetMinMax $ref,
214
        DOMElement $node
215
    ) : InterfaceSetMinMax {
216
        if (
217 45
            $node->hasAttribute("maxOccurs")
218
        ) {
219 45
            $ref->setMax($node->getAttribute("maxOccurs") == "unbounded" ? -1 : (int)$node->getAttribute("maxOccurs"));
220
        }
221
222 45
        return $ref;
223
    }
224
225 45
    public static function maybeSetMin(
226
        InterfaceSetMinMax $ref,
227
        DOMElement $node
228
    ) : InterfaceSetMinMax {
229 45
        if ($node->hasAttribute("minOccurs")) {
230 45
            $ref->setMin((int) $node->getAttribute("minOccurs"));
231
        }
232
233 45
        return $ref;
234
    }
235
236 45
    private static function loadSequenceNormaliseMax(
237
        DOMElement $node,
238
        ? int $max
239
    ) : ? int {
240
        return
241
        (
242 45
            (is_int($max) && (bool) $max) ||
243 45
            $node->getAttribute("maxOccurs") == "unbounded" ||
244 45
            $node->getAttribute("maxOccurs") > 1
245
        )
246 45
            ? 2
247 45
            : null;
248
    }
249
250 45
    private function loadSequence(
251
        ElementContainer $elementContainer,
252
        DOMElement $node,
253
        int $max = null
254
    ) : void {
255 45
        $max = static::loadSequenceNormaliseMax($node, $max);
256
257 45
        foreach ($node->childNodes as $childNode) {
258 45
            if ($childNode instanceof DOMElement) {
259 45
                $this->loadSequenceChildNode(
260 45
                    $elementContainer,
261 45
                    $node,
262 45
                    $childNode,
263 45
                    $max
264
                );
265
            }
266
        }
267 45
    }
268
269 45
    private function loadSequenceChildNode(
270
        ElementContainer $elementContainer,
271
        DOMElement $node,
272
        DOMElement $childNode,
273
        ? int $max
274
    ) : void {
275
        $commonMethods = [
276
            [
277 45
                ['sequence', 'choice', 'all'],
278 45
                [$this, 'loadSequenceChildNodeLoadSequence'],
279
                [
280 45
                    $elementContainer,
281 45
                    $childNode,
282 45
                    $max,
283
                ],
284
            ],
285
        ];
286
        $methods = [
287
            'element' => [
288 45
                [$this, 'loadSequenceChildNodeLoadElement'],
289
                [
290 45
                    $elementContainer,
291 45
                    $node,
292 45
                    $childNode,
293 45
                    $max
294
                ]
295
            ],
296
            'group' => [
297 45
                [$this, 'loadSequenceChildNodeLoadGroup'],
298
                [
299 45
                    $elementContainer,
300 45
                    $node,
301 45
                    $childNode
302
                ]
303
            ],
304
        ];
305
306 45
        $this->maybeCallCallableWithArgs($childNode, $commonMethods, $methods);
307 45
    }
308
309
    /**
310
    * @param mixed[][] $methods
311
    *
312
    * @return mixed
313
    */
314 45
    private function maybeCallCallableWithArgs(
315
        DOMElement $childNode,
316
        array $commonMethods = [],
317
        array $methods = []
318
    ) {
319 45
        foreach ($commonMethods as $commonMethodsSpec) {
320 45
            list ($localNames, $callable, $args) = $commonMethodsSpec;
321
322 45
            if (in_array($childNode->localName, $localNames)) {
323 45
                return call_user_func_array($callable, $args);
324
            }
325
        }
326 45
        if (isset($methods[$childNode->localName])) {
327 45
            list ($callable, $args) = $methods[$childNode->localName];
328
329 45
            return call_user_func_array($callable, $args);
330
        }
331 45
    }
332
333
    private function loadSequenceChildNodeLoadSequence(
334
        ElementContainer $elementContainer,
335
        DOMElement $childNode,
336
        ? int $max
337
    ) : Closure {
338 45
        return function () use ($elementContainer, $childNode, $max) : void {
339
            $this->loadSequence($elementContainer, $childNode, $max);
340 45
        };
341
    }
342
343 45
    private function loadSequenceChildNodeLoadElement(
344
        ElementContainer $elementContainer,
345
        DOMElement $node,
346
        DOMElement $childNode,
347
        ? int $max
348
    ) : void {
349 45
        if ($childNode->hasAttribute("ref")) {
350
            /**
351
            * @var ElementDef $referencedElement
352
            */
353 45
            $referencedElement = $this->findSomething('findElement', $elementContainer->getSchema(), $node, $childNode->getAttribute("ref"));
354 45
            $element = ElementRef::loadElementRef(
355 45
                $referencedElement,
356 45
                $childNode
357
            );
358
        } else {
359 45
            $element = Element::loadElement(
360 45
                $this,
361 45
                $elementContainer->getSchema(),
362 45
                $childNode
363
            );
364
        }
365 45
        if (is_int($max) && (bool) $max) {
366 45
            $element->setMax($max);
367
        }
368 45
        $elementContainer->addElement($element);
369 45
    }
370
371 45
    private function loadSequenceChildNodeLoadGroup(
372
        ElementContainer $elementContainer,
373
        DOMElement $node,
374
        DOMElement $childNode
375
    ) : void {
376 45
        $this->addGroupAsElement(
377 45
            $elementContainer->getSchema(),
378 45
            $node,
379 45
            $childNode,
380 45
            $elementContainer
381
        );
382 45
    }
383
384 45
    private function addGroupAsElement(
385
        Schema $schema,
386
        DOMElement $node,
387
        DOMElement $childNode,
388
        ElementContainer $elementContainer
389
    ) : void {
390
        /**
391
        * @var Group $referencedGroup
392
        */
393 45
        $referencedGroup = $this->findSomething(
394 45
            'findGroup',
395 45
            $schema,
396 45
            $node,
397 45
            $childNode->getAttribute("ref")
398
        );
399
400 45
        $group = GroupRef::loadGroupRef($referencedGroup, $childNode);
401 45
        $elementContainer->addElement($group);
402 45
    }
403
404 45
    private function maybeLoadSequenceFromElementContainer(
405
        BaseComplexType $type,
406
        DOMElement $childNode
407
    ) : void {
408 45
        if (! ($type instanceof ElementContainer)) {
409
            throw new RuntimeException(
410
                '$type passed to ' .
411
                __FUNCTION__ .
412
                'expected to be an instance of ' .
413
                ElementContainer::class .
414
                ' when child node localName is "group", ' .
415
                get_class($type) .
416
                ' given.'
417
            );
418
        }
419 45
        $this->loadSequence($type, $childNode);
420 45
    }
421
422 45
    private function loadGroup(Schema $schema, DOMElement $node) : Closure
423
    {
424 45
        return Group::loadGroup($this, $schema, $node);
425
    }
426
427
428 45
    private function loadComplexTypeBeforeCallbackCallback(
429
        Schema $schema,
430
        DOMElement $node
431
    ) : BaseComplexType {
432 45
        $isSimple = false;
433
434 45
        foreach ($node->childNodes as $childNode) {
435 45
            if ($childNode->localName === "simpleContent") {
436 2
                $isSimple = true;
437 3
                break;
438
            }
439
        }
440
441 45
        $type = $isSimple ? new ComplexTypeSimpleContent($schema, $node->getAttribute("name")) : new ComplexType($schema, $node->getAttribute("name"));
442
443 45
        $type->setDoc(static::getDocumentation($node));
444 45
        if ($node->getAttribute("name")) {
445 45
            $schema->addType($type);
446
        }
447
448 45
        return $type;
449
    }
450
451
    /**
452
    * @param Closure|null $callback
453
    *
454
    * @return Closure
455
    */
456 45
    private function loadComplexType(Schema $schema, DOMElement $node, $callback = null)
457
    {
458 45
        $type = $this->loadComplexTypeBeforeCallbackCallback($schema, $node);
459
460 45
        return $this->makeCallbackCallback(
461 45
            $type,
462 45
            $node,
463 45
            function (
464
                DOMElement $node,
465
                DOMElement $childNode
466
            ) use(
467 45
                $schema,
468 45
                $type
469
            ) : void {
470 45
                $this->loadComplexTypeFromChildNode(
471 45
                    $type,
472 45
                    $node,
473 45
                    $childNode,
474 45
                    $schema
475
                );
476 45
            },
477 45
            $callback
478
        );
479
    }
480
481
    /**
482
    * @param Closure|null $callback
483
    *
484
    * @return Closure
485
    */
486
    private function makeCallbackCallback(
487
        Type $type,
488
        DOMElement $node,
489
        Closure $callbackCallback,
490
        Closure $callback = null
491
    ) : Closure {
492 45
        return function (
493
        ) use (
494 45
            $type,
495 45
            $node,
496 45
            $callbackCallback,
497 45
            $callback
498
        ) : void {
499 45
            $this->runCallbackAgainstDOMNodeList(
500 45
                $type,
501 45
                $node,
502 45
                $callbackCallback,
503 45
                $callback
504
            );
505 45
        };
506
    }
507
508 45
    private function runCallbackAgainstDOMNodeList(
509
        Type $type,
510
        DOMElement $node,
511
        Closure $againstNodeList,
512
        Closure $callback = null
513
    ) : void {
514 45
        $this->fillTypeNode($type, $node, true);
515
516 45
        foreach ($node->childNodes as $childNode) {
517 45
            if ($childNode instanceof DOMElement) {
518 45
                $againstNodeList(
519 45
                    $node,
520 45
                    $childNode
521
                );
522
            }
523
        }
524
525 45
        if ($callback) {
526 45
            call_user_func($callback, $type);
527
        }
528 45
    }
529
530 45
    private function loadComplexTypeFromChildNode(
531
        BaseComplexType $type,
532
        DOMElement $node,
533
        DOMElement $childNode,
534
        Schema $schema
535
    ) : void {
536
        $commonMethods = [
537
            [
538 45
                ['sequence', 'choice', 'all'],
539 45
                [$this, 'maybeLoadSequenceFromElementContainer'],
540
                [
541 45
                    $type,
542 45
                    $childNode,
543
                ],
544
            ],
545
        ];
546
        $methods = [
547 45
            'attribute' => [
548 45
                [$type, 'addAttributeFromAttributeOrRef'],
549
                [
550 45
                    $this,
551 45
                    $childNode,
552 45
                    $schema,
553 45
                    $node
554
                ]
555
            ],
556
            'attributeGroup' => [
557
                (AttributeGroup::class . '::findSomethingLikeThis'),
558
                [
559 45
                    $this,
560 45
                    $schema,
561 45
                    $node,
562 45
                    $childNode,
563 45
                    $type
564
                ]
565
            ],
566
        ];
567
        if (
568 45
            $type instanceof ComplexType
569
        ) {
570 45
            $methods['group'] = [
571 45
                [$this, 'addGroupAsElement'],
572
                [
573 45
                    $schema,
574 45
                    $node,
575 45
                    $childNode,
576 45
                    $type
577
                ]
578
            ];
579
        }
580
581 45
        $this->maybeCallCallableWithArgs($childNode, $commonMethods, $methods);
582 45
    }
583
584 45
    private function loadSimpleType(
585
        Schema $schema,
586
        DOMElement $node,
587
        Closure $callback = null
588
    ) : Closure {
589 45
        $type = new SimpleType($schema, $node->getAttribute("name"));
590 45
        $type->setDoc(static::getDocumentation($node));
591 45
        if ($node->getAttribute("name")) {
592 45
            $schema->addType($type);
593
        }
594
595 45
        static $methods = [
596
            'union' => 'loadUnion',
597
            'list' => 'loadList',
598
        ];
599
600 45
        return $this->makeCallbackCallback(
601 45
            $type,
602 45
            $node,
603 45
            function (
604
                DOMElement $node,
605
                DOMElement $childNode
606
            ) use (
607 45
                $methods,
608 45
                $type
609
            ) : void {
610 45
                $this->maybeCallMethod(
611 45
                    $methods,
612 45
                    $childNode->localName,
613 45
                    $childNode,
614 45
                    $type,
615 45
                    $childNode
616
                );
617 45
            },
618 45
            $callback
619
        );
620
    }
621
622 45
    private function loadList(SimpleType $type, DOMElement $node) : void
623
    {
624 45
        if ($node->hasAttribute("itemType")) {
625
            /**
626
            * @var SimpleType $listType
627
            */
628 45
            $listType = $this->findSomeType($type, $node, 'itemType');
629 45
            $type->setList($listType);
630
        } else {
631 45
            $addCallback = function (SimpleType $list) use ($type) : void {
632 45
                $type->setList($list);
633 45
            };
634
635 45
            Type::loadTypeWithCallbackOnChildNodes(
636 45
                $this,
637 45
                $type->getSchema(),
638 45
                $node,
639 45
                $addCallback
640
            );
641
        }
642 45
    }
643
644 45
    private function findSomeType(
645
        SchemaItem $fromThis,
646
        DOMElement $node,
647
        string $attributeName
648
    ) : SchemaItem {
649 45
        return $this->findSomeTypeFromAttribute(
650 45
            $fromThis,
651 45
            $node,
652 45
            $node->getAttribute($attributeName)
653
        );
654
    }
655
656
    /**
657
    * @param string $attributeName
658
    *
659
    * @return SchemaItem
660
    */
661 45
    private function findSomeTypeFromAttribute(
662
        SchemaItem $fromThis,
663
        DOMElement $node,
664
        string $attributeName
665
    ) : SchemaItem {
666
        /**
667
        * @var SchemaItem $out
668
        */
669 45
        $out = $this->findSomething(
670 45
            'findType',
671 45
            $fromThis->getSchema(),
672 45
            $node,
673 45
            $attributeName
674
        );
675
676 45
        return $out;
677
    }
678
679 45
    private function loadUnion(SimpleType $type, DOMElement $node) : void
680
    {
681 45
        if ($node->hasAttribute("memberTypes")) {
682 45
            $types = preg_split('/\s+/', $node->getAttribute("memberTypes"));
683 45
            foreach ($types as $typeName) {
684
                /**
685
                * @var SimpleType $unionType
686
                */
687 45
                $unionType = $this->findSomeTypeFromAttribute(
688 45
                    $type,
689 45
                    $node,
690 45
                    $typeName
691
                );
692 45
                $type->addUnion($unionType);
693
            }
694
        }
695 45
        $addCallback = function (SimpleType $unType) use ($type) : void {
696 45
            $type->addUnion($unType);
697 45
        };
698
699 45
        Type::loadTypeWithCallbackOnChildNodes(
700 45
            $this,
701 45
            $type->getSchema(),
702 45
            $node,
703 45
            $addCallback
704
        );
705 45
    }
706
707 45
    private function fillTypeNode(
708
        Type $type,
709
        DOMElement $node,
710
        bool $checkAbstract = false
711
    ) : void {
712
713 45
        if ($checkAbstract) {
714 45
            $type->setAbstract($node->getAttribute("abstract") === "true" || $node->getAttribute("abstract") === "1");
715
        }
716
717 45
        static $methods = [
718
            'restriction' => 'loadRestriction',
719
            'extension' => 'maybeLoadExtensionFromBaseComplexType',
720
            'simpleContent' => 'fillTypeNode',
721
            'complexContent' => 'fillTypeNode',
722
        ];
723
724 45
        foreach ($node->childNodes as $childNode) {
725 45
            $this->maybeCallMethod(
726 45
                $methods,
727 45
                (string) $childNode->localName,
728 45
                $childNode,
729 45
                $type,
730 45
                $childNode
731
            );
732
        }
733 45
    }
734
735 45
    private function loadExtensionChildNode(
736
        BaseComplexType $type,
737
        DOMElement $node,
738
        DOMElement $childNode
739
    ) : void {
740
        $commonMethods = [
741
            [
742 45
                ['sequence', 'choice', 'all'],
743 45
                [$this, 'maybeLoadSequenceFromElementContainer'],
744
                [
745 45
                    $type,
746 45
                    $childNode,
747
                ],
748
            ],
749
        ];
750
        $methods = [
751 45
            'attribute' => [
752 45
                [$type, 'addAttributeFromAttributeOrRef'],
753
                [
754 45
                    $this,
755 45
                    $childNode,
756 45
                    $type->getSchema(),
757 45
                    $node
758
                ]
759
            ],
760
            'attributeGroup' => [
761
                (AttributeGroup::class . '::findSomethingLikeThis'),
762
                [
763 45
                    $this,
764 45
                    $type->getSchema(),
765 45
                    $node,
766 45
                    $childNode,
767 45
                    $type
768
                ]
769
            ],
770
        ];
771
772 45
        $this->maybeCallCallableWithArgs($childNode, $commonMethods, $methods);
773 45
    }
774
775 45
    private function loadExtension(
776
        BaseComplexType $type,
777
        DOMElement $node
778
    ) : void {
779 45
        $extension = new Extension();
780 45
        $type->setExtension($extension);
781
782 45
        if ($node->hasAttribute("base")) {
783 45
            $this->findAndSetSomeBase(
784 45
                $type,
785 45
                $extension,
786 45
                $node
787
            );
788
        }
789
790 45
        $this->loadExtensionChildNodes($type, $node->childNodes, $node);
791 45
    }
792
793 45
    private function loadExtensionChildNodes(
794
        BaseComplexType $type,
795
        DOMNodeList $childNodes,
796
        DOMElement $node
797
    ) : void {
798 45
        foreach ($childNodes as $childNode) {
799 45
            if ($childNode instanceof DOMElement) {
800 45
                $this->loadExtensionChildNode(
801 45
                    $type,
802 45
                    $node,
803 45
                    $childNode
804
                );
805
            }
806
        }
807 45
    }
808
809 45
    public function findAndSetSomeBase(
810
        Type $type,
811
        Base $setBaseOnThis,
812
        DOMElement $node
813
    ) : void {
814
        /**
815
        * @var Type $parent
816
        */
817 45
        $parent = $this->findSomeType($type, $node, 'base');
818 45
        $setBaseOnThis->setBase($parent);
819 45
    }
820
821 45
    private function maybeLoadExtensionFromBaseComplexType(
822
        Type $type,
823
        DOMElement $childNode
824
    ) : void {
825 45
        if (! ($type instanceof BaseComplexType)) {
826
            throw new RuntimeException(
827
                'Argument 1 passed to ' .
828
                __METHOD__ .
829
                ' needs to be an instance of ' .
830
                BaseComplexType::class .
831
                ' when passed onto ' .
832
                static::class .
833
                '::loadExtension(), ' .
834
                get_class($type) .
835
                ' given.'
836
            );
837
        }
838 45
        $this->loadExtension($type, $childNode);
839 45
    }
840
841 45
    private function loadRestriction(Type $type, DOMElement $node) : void
842
    {
843 45
        Restriction::loadRestriction($this, $type, $node);
844 45
    }
845
846
    /**
847
    * @return mixed[]
848
    */
849 45
    private static function splitParts(
850
        DOMElement $node,
851
        string $typeName
852
    ) : array {
853 45
        $prefix = null;
854 45
        $name = $typeName;
855 45
        if (strpos($typeName, ':') !== false) {
856 45
            list ($prefix, $name) = explode(':', $typeName);
857
        }
858
859 45
        $namespace = $node->lookupNamespaceUri($prefix ?: '');
860
        return array(
861 45
            $name,
862 45
            $namespace,
863 45
            $prefix
864
        );
865
    }
866
867
    /**
868
     *
869
     * @param Schema $schema
870
     * @param DOMElement $node
871
     * @throws TypeException
872
     * @return ElementItem|Group|AttributeItem|AttributeGroup|Type
873
     */
874 45
    public function findSomething(
875
        string $finder,
876
        Schema $schema,
877
        DOMElement $node,
878
        string $typeName
879
    ) {
880 45
        list ($name, $namespace) = self::splitParts($node, $typeName);
881
882 45
        $namespace = $namespace ?: $schema->getTargetNamespace();
883
884
        try {
885 45
            return $schema->$finder($name, $namespace);
886
        } catch (TypeNotFoundException $e) {
887
            throw new TypeException(sprintf("Can't find %s named {%s}#%s, at line %d in %s ", strtolower(substr($finder, 4)), $namespace, $name, $node->getLineNo(), $node->ownerDocument->documentURI), 0, $e);
888
        }
889
    }
890
891 45
    private function loadElementDef(Schema $schema, DOMElement $node) : Closure
892
    {
893 45
        return $this->loadAttributeOrElementDef($schema, $node, false);
894
    }
895
896 45
    public function fillItem(Item $element, DOMElement $node) : void
897
    {
898 45
        foreach ($node->childNodes as $childNode) {
899
            if (
900 45
                in_array(
901 45
                    $childNode->localName,
902
                    [
903 45
                        'complexType',
904
                        'simpleType',
905
                    ]
906
                )
907
            ) {
908 45
                Type::loadTypeWithCallback(
909 45
                    $this,
910 45
                    $element->getSchema(),
911 45
                    $childNode,
912 45
                    function (Type $type) use ($element) : void {
913 45
                        $element->setType($type);
914 45
                    }
915
                );
916 45
                return;
917
            }
918
        }
919
920 45
        $this->fillItemNonLocalType($element, $node);
921 45
    }
922
923 45
    private function fillItemNonLocalType(
924
        Item $element,
925
        DOMElement $node
926
    ) : void {
927 45
        if ($node->getAttribute("type")) {
928
            /**
929
            * @var Type $type
930
            */
931 45
            $type = $this->findSomeType($element, $node, 'type');
932
        } else {
933
            /**
934
            * @var Type $type
935
            */
936 45
            $type = $this->findSomeTypeFromAttribute(
937 45
                $element,
938 45
                $node,
939 45
                ($node->lookupPrefix(self::XSD_NS) . ':anyType')
940
            );
941
        }
942
943 45
        $element->setType($type);
944 45
    }
945
946 45
    private function loadImport(Schema $schema, DOMElement $node) : Closure
947
    {
948 45
        $base = urldecode($node->ownerDocument->documentURI);
949 45
        $file = UrlUtils::resolveRelativeUrl($base, $node->getAttribute("schemaLocation"));
950
951 45
        $namespace = $node->getAttribute("namespace");
952
953
        if (
954
            (
955 45
                isset(self::$globalSchemaInfo[$namespace]) &&
956 45
                Schema::hasLoadedFile(
957 45
                    $loadedFilesKey = self::$globalSchemaInfo[$namespace]
958
                )
959
            ) ||
960 3
            Schema::hasLoadedFile(
961 3
                $loadedFilesKey = $this->getNamespaceSpecificFileIndex(
962 3
                    $file,
963 3
                    $namespace
964
                )
965
            ) ||
966 1
            Schema::hasLoadedFile($loadedFilesKey = $file)
967
        ) {
968 45
            $schema->addSchema(Schema::getLoadedFile($loadedFilesKey));
969
970 45
            return function() : void {
971 45
            };
972
        }
973
974 1
        return $this->loadImportFresh($schema, $node, $file, $namespace);
975
    }
976
977 1
    private function loadImportFresh(
978
        Schema $schema,
979
        DOMElement $node,
980
        string $file,
981
        string $namespace
982
    ) : Closure {
983 1
        if (! $namespace) {
984 1
            $newSchema = Schema::setLoadedFile($file, $schema);
985
        } else {
986
            $newSchema = Schema::setLoadedFile($file, new Schema());
987
            $newSchema->addSchema($this->getGlobalSchema());
988
        }
989
990 1
        $xml = $this->getDOM(isset($this->knownLocationSchemas[$file]) ? $this->knownLocationSchemas[$file] : $file);
991
992 1
        $callbacks = $this->schemaNode($newSchema, $xml->documentElement, $schema);
993
994 1
        if ($namespace) {
995
            $schema->addSchema($newSchema);
996
        }
997
998
999 1
        return function () use ($callbacks) : void {
1000 1
            foreach ($callbacks as $callback) {
1001 1
                $callback();
1002
            }
1003 1
        };
1004
    }
1005
1006
    /**
1007
    * @var Schema|null
1008
    */
1009
    private $globalSchema;
1010
1011
    /**
1012
    * @return Schema[]
1013
    */
1014 45
    private function setupGlobalSchemas(array & $callbacks) : array
1015
    {
1016 45
            $globalSchemas = array();
1017 45
            foreach (self::$globalSchemaInfo as $namespace => $uri) {
1018 45
                Schema::setLoadedFile(
1019 45
                    $uri,
1020 45
                    $globalSchemas[$namespace] = $schema = new Schema()
1021
                );
1022 45
                if ($namespace === self::XSD_NS) {
1023 45
                    $this->globalSchema = $schema;
1024
                }
1025 45
                $xml = $this->getDOM($this->knownLocationSchemas[$uri]);
1026 45
                $callbacks = array_merge($callbacks, $this->schemaNode($schema, $xml->documentElement));
1027
            }
1028
1029 45
        return $globalSchemas;
1030
    }
1031
1032
    /**
1033
     *
1034
     * @return Schema
1035
     */
1036 45
    public function getGlobalSchema()
1037
    {
1038 45
        if (!$this->globalSchema) {
1039 45
            $callbacks = array();
1040 45
            $globalSchemas = $this->setupGlobalSchemas($callbacks);
1041
1042 45
            $globalSchemas[self::XSD_NS]->addType(new SimpleType($globalSchemas[self::XSD_NS], "anySimpleType"));
1043 45
            $globalSchemas[self::XSD_NS]->addType(new SimpleType($globalSchemas[self::XSD_NS], "anyType"));
1044
1045 45
            $globalSchemas[self::XML_NS]->addSchema($globalSchemas[self::XSD_NS], self::XSD_NS);
1046 45
            $globalSchemas[self::XSD_NS]->addSchema($globalSchemas[self::XML_NS], self::XML_NS);
1047
1048 45
            foreach ($callbacks as $callback) {
1049 45
                $callback();
1050
            }
1051
        }
1052
1053
        /**
1054
        * @var Schema $out
1055
        */
1056 45
        $out = $this->globalSchema;
1057
1058 45
        return $out;
1059
    }
1060
1061
    /**
1062
     * @param DOMElement $node
1063
     * @param string  $file
1064
     *
1065
     * @return Schema
1066
     */
1067 45
    public function readNode(
1068
        DOMElement $node,
1069
        string $file = 'schema.xsd'
1070
    ) : Schema {
1071 45
        $fileKey = $node->hasAttribute('targetNamespace') ? $this->getNamespaceSpecificFileIndex($file, $node->getAttribute('targetNamespace')) : $file;
1072 45
        Schema::setLoadedFile($fileKey, $rootSchema = new Schema());
1073
1074 45
        $rootSchema->addSchema($this->getGlobalSchema());
1075 45
        $callbacks = $this->schemaNode($rootSchema, $node);
1076
1077 45
        foreach ($callbacks as $callback) {
1078 39
            call_user_func($callback);
1079
        }
1080
1081 45
        return $rootSchema;
1082
    }
1083
1084
    /**
1085
     * It is possible that a single file contains multiple <xsd:schema/> nodes, for instance in a WSDL file.
1086
     *
1087
     * Each of these  <xsd:schema/> nodes typically target a specific namespace. Append the target namespace to the
1088
     * file to distinguish between multiple schemas in a single file.
1089
     */
1090 45
    private function getNamespaceSpecificFileIndex(
1091
        string $file,
1092
        string $targetNamespace
1093
    ) : string {
1094 45
        return $file . '#' . $targetNamespace;
1095
    }
1096
1097
    /**
1098
     * @throws IOException
1099
     */
1100 44
    public function readString(
1101
        string $content,
1102
        string $file = 'schema.xsd'
1103
    ) : Schema {
1104 44
        $xml = new DOMDocument('1.0', 'UTF-8');
1105 44
        if (!$xml->loadXML($content)) {
1106
            throw new IOException("Can't load the schema");
1107
        }
1108 44
        $xml->documentURI = $file;
1109
1110 44
        return $this->readNode($xml->documentElement, $file);
1111
    }
1112
1113 1
    public function readFile(string $file) : Schema
1114
    {
1115 1
        $xml = $this->getDOM($file);
1116 1
        return $this->readNode($xml->documentElement, $file);
1117
    }
1118
1119
    /**
1120
     * @throws IOException
1121
     */
1122 45
    private function getDOM(string $file) : DOMDocument
1123
    {
1124 45
        $xml = new DOMDocument('1.0', 'UTF-8');
1125 45
        if (!$xml->load($file)) {
1126
            throw new IOException("Can't load the file $file");
1127
        }
1128 45
        return $xml;
1129
    }
1130
}
1131