Passed
Pull Request — master (#18)
by SignpostMarv
03:02
created

SchemaReader::fillTypeNode()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 21
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 4

Importance

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