Passed
Push — php-7.1 ( 349118...b6da63 )
by SignpostMarv
04:36 queued 02:54
created

SchemaReader::maybeCallMethod()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 17
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4

Importance

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