Passed
Push — static-analysis ( 02999b...ebbe0d )
by SignpostMarv
03:03
created

SchemaReader::loadExtensionChildNodes()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 6
c 0
b 0
f 0
nc 3
nop 3
dl 0
loc 11
ccs 7
cts 7
cp 1
crap 3
rs 9.4285
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
        $loadSeq = [
300 135
            [$this, 'loadSequenceChildNodeLoadSequence'],
301
            [
302 135
                $elementContainer,
303 135
                $childNode,
304 90
                $max
305 45
            ]
306 45
        ];
307
        $methods = [
308 135
            'choice' => $loadSeq,
309 135
            'sequence' => $loadSeq,
310 135
            'all' => $loadSeq,
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, $methods);
331 135
    }
332
333
    /**
334
    * @param mixed[][] $methods
335
    *
336
    * @return mixed
337
    */
338 135
    private function maybeCallCallableWithArgs(
339
        DOMElement $childNode,
340
        array $methods
341
    ) {
342 135
        if (isset($methods[$childNode->localName])) {
343 135
            list ($callable, $args) = $methods[$childNode->localName];
344
345 135
            return call_user_func_array($callable, $args);
346
        }
347 135
    }
348
349
    /**
350
    * @param int|null $max
351
    */
352 135
    private function loadSequenceChildNodeLoadSequence(
353
        ElementContainer $elementContainer,
354
        DOMElement $childNode,
355
        $max
356
    ) {
357 135
        $this->loadSequence($elementContainer, $childNode, $max);
358 135
    }
359
360
    /**
361
    * @param int|null $max
362
    */
363 135
    private function loadSequenceChildNodeLoadElement(
364
        ElementContainer $elementContainer,
365
        DOMElement $node,
366
        DOMElement $childNode,
367
        $max
368
    ) {
369 135
        if ($childNode->hasAttribute("ref")) {
370
            /**
371
            * @var ElementDef $referencedElement
372
            */
373 135
            $referencedElement = $this->findSomething('findElement', $elementContainer->getSchema(), $node, $childNode->getAttribute("ref"));
374 135
            $element = ElementRef::loadElementRef(
375 135
                $referencedElement,
376 90
                $childNode
377 45
            );
378 45
        } else {
379 135
            $element = Element::loadElement(
380 135
                $this,
381 135
                $elementContainer->getSchema(),
382 90
                $childNode
383 45
            );
384
        }
385 135
        if (is_int($max) && (bool) $max) {
386 135
            $element->setMax($max);
387 45
        }
388 135
        $elementContainer->addElement($element);
389 135
    }
390
391 135
    private function loadSequenceChildNodeLoadGroup(
392
        ElementContainer $elementContainer,
393
        DOMElement $node,
394
        DOMElement $childNode
395
    ) {
396 135
        $this->addGroupAsElement(
397 135
            $elementContainer->getSchema(),
398 135
            $node,
399 135
            $childNode,
400 90
            $elementContainer
401 45
        );
402 135
    }
403
404 135
    private function addGroupAsElement(
405
        Schema $schema,
406
        DOMElement $node,
407
        DOMElement $childNode,
408
        ElementContainer $elementContainer
409
    ) {
410
        /**
411
        * @var Group $referencedGroup
412
        */
413 135
        $referencedGroup = $this->findSomething(
414 135
            'findGroup',
415 135
            $schema,
416 135
            $node,
417 135
            $childNode->getAttribute("ref")
418 45
        );
419
420 135
        $group = GroupRef::loadGroupRef($referencedGroup, $childNode);
421 135
        $elementContainer->addElement($group);
422 135
    }
423
424 135
    private function maybeLoadSequenceFromElementContainer(
425
        BaseComplexType $type,
426
        DOMElement $childNode
427
    ) {
428 135
        if (! ($type instanceof ElementContainer)) {
429
            throw new RuntimeException(
430
                '$type passed to ' .
431
                __FUNCTION__ .
432
                'expected to be an instance of ' .
433
                ElementContainer::class .
434
                ' when child node localName is "group", ' .
435
                get_class($type) .
436
                ' given.'
437
            );
438
        }
439 135
        $this->loadSequence($type, $childNode);
440 135
    }
441
442
    /**
443
    * @return Closure
444
    */
445 135
    private function loadGroup(Schema $schema, DOMElement $node)
446
    {
447 135
        return Group::loadGroup($this, $schema, $node);
448
    }
449
450
    /**
451
    * @param Closure|null $callback
452
    *
453
    * @return Closure
454
    */
455 135
    private function loadComplexType(Schema $schema, DOMElement $node, $callback = null)
456
    {
457 135
        $isSimple = false;
458
459 135
        foreach ($node->childNodes as $childNode) {
460 135
            if ($childNode->localName === "simpleContent") {
461 6
                $isSimple = true;
462 49
                break;
463
            }
464 45
        }
465
466 135
        $type = $isSimple ? new ComplexTypeSimpleContent($schema, $node->getAttribute("name")) : new ComplexType($schema, $node->getAttribute("name"));
467
468 135
        $type->setDoc(static::getDocumentation($node));
469 135
        if ($node->getAttribute("name")) {
470 135
            $schema->addType($type);
471 45
        }
472
473 135
        return $this->makeCallbackCallback(
474 135
            $type,
475 135
            $node,
476
                function (
477
                    DOMElement $node,
478
                    DOMElement $childNode
479
                ) use(
480 135
                    $schema,
481 135
                    $type
482
                ) {
483 135
                    $this->loadComplexTypeFromChildNode(
484 135
                        $type,
485 135
                        $node,
486 135
                        $childNode,
487 90
                        $schema
488 45
                    );
489 135
                },
490 90
            $callback
491 45
        );
492
    }
493
494
    /**
495
    * @param Closure|null $callback
496
    *
497
    * @return Closure
498
    */
499 135
    private function makeCallbackCallback(
500
        Type $type,
501
        DOMElement $node,
502
        Closure $callbackCallback,
503
        $callback = null
504
    ) {
505
        return function (
506
        ) use (
507 135
            $type,
508 135
            $node,
509 135
            $callbackCallback,
510 135
            $callback
511
        ) {
512 135
            $this->runCallbackAgainstDOMNodeList(
513 135
                $type,
514 135
                $node,
515 135
                $callbackCallback,
516 90
                $callback
517 45
            );
518 135
        };
519
    }
520
521
    /**
522
    * @param Closure|null $callback
523
    */
524 135
    private function runCallbackAgainstDOMNodeList(
525
        Type $type,
526
        DOMElement $node,
527
        Closure $againstNodeList,
528
        $callback = null
529
    ) {
530 135
        $this->fillTypeNode($type, $node, true);
531
532 135
        foreach ($node->childNodes as $childNode) {
533 135
            if ($childNode instanceof DOMElement) {
534 135
                $againstNodeList(
535 135
                    $node,
536 90
                    $childNode
537 45
                );
538 45
            }
539 45
        }
540
541 135
        if ($callback) {
542 135
            call_user_func($callback, $type);
543 45
        }
544 135
    }
545
546 135
    private function loadComplexTypeFromChildNode(
547
        BaseComplexType $type,
548
        DOMElement $node,
549
        DOMElement $childNode,
550
        Schema $schema
551
    ) {
552
        $maybeLoadSeq = [
553 135
            [$this, 'maybeLoadSequenceFromElementContainer'],
554
            [
555 135
                $type,
556 90
                $childNode
557 45
            ]
558 45
        ];
559
        $methods = [
560 135
            'sequence' => $maybeLoadSeq,
561 135
            'choice' => $maybeLoadSeq,
562 135
            'all' => $maybeLoadSeq,
563
            'attribute' => [
564 135
                [$type, 'addAttributeFromAttributeOrRef'],
565
                [
566 135
                    $this,
567 135
                    $childNode,
568 135
                    $schema,
569 90
                    $node
570 45
                ]
571 45
            ],
572
            'attributeGroup' => [
573 45
                (AttributeGroup::class . '::findSomethingLikeThis'),
574
                [
575 135
                    $this,
576 135
                    $schema,
577 135
                    $node,
578 135
                    $childNode,
579 90
                    $type
580 45
                ]
581 45
            ],
582 45
        ];
583
        if (
584 90
            $type instanceof ComplexType
585 45
        ) {
586 135
            $methods['group'] = [
587 135
                [$this, 'addGroupAsElement'],
588
                [
589 135
                    $schema,
590 135
                    $node,
591 135
                    $childNode,
592 90
                    $type
593 45
                ]
594 45
            ];
595 45
        }
596
597 135
        $this->maybeCallCallableWithArgs($childNode, $methods);
598 135
    }
599
600
    /**
601
    * @param Closure|null $callback
602
    *
603
    * @return Closure
604
    */
605 135
    private function loadSimpleType(Schema $schema, DOMElement $node, $callback = null)
606
    {
607 135
        $type = new SimpleType($schema, $node->getAttribute("name"));
608 135
        $type->setDoc(static::getDocumentation($node));
609 135
        if ($node->getAttribute("name")) {
610 135
            $schema->addType($type);
611 45
        }
612
613 90
        static $methods = [
614
            'union' => 'loadUnion',
615
            'list' => 'loadList',
616 45
        ];
617
618 135
        return $this->makeCallbackCallback(
619 135
            $type,
620 135
            $node,
621
            function (
622
                DOMElement $node,
623
                DOMElement $childNode
624
            ) use (
625 135
                $methods,
626 135
                $type
627
            ) {
628 135
                $this->maybeCallMethod(
629 135
                    $methods,
630 135
                    $childNode->localName,
631 135
                    $childNode,
632 135
                    $type,
633 90
                    $childNode
634 45
                );
635 135
            },
636 90
            $callback
637 45
        );
638
    }
639
640 135
    private function loadList(SimpleType $type, DOMElement $node)
641
    {
642 135
        if ($node->hasAttribute("itemType")) {
643
            /**
644
            * @var SimpleType $listType
645
            */
646 135
            $listType = $this->findSomeType($type, $node, 'itemType');
647 135
            $type->setList($listType);
648 45
        } else {
649
            $addCallback = function (SimpleType $list) use ($type) {
650 135
                $type->setList($list);
651 135
            };
652
653 135
            Type::loadTypeWithCallbackOnChildNodes(
654 135
                $this,
655 135
                $type->getSchema(),
656 135
                $node,
657 90
                $addCallback
658 45
            );
659
        }
660 135
    }
661
662
    /**
663
    * @param string $attributeName
664
    *
665
    * @return SchemaItem
666
    */
667 135
    private function findSomeType(
668
        SchemaItem $fromThis,
669
        DOMElement $node,
670
        $attributeName
671
    ) {
672 135
        return $this->findSomeTypeFromAttribute(
673 135
            $fromThis,
674 135
            $node,
675 135
            $node->getAttribute($attributeName)
676 45
        );
677
    }
678
679
    /**
680
    * @param string $attributeName
681
    *
682
    * @return SchemaItem
683
    */
684 135
    private function findSomeTypeFromAttribute(
685
        SchemaItem $fromThis,
686
        DOMElement $node,
687
        $attributeName
688
    ) {
689
        /**
690
        * @var SchemaItem $out
691
        */
692 135
        $out = $this->findSomething(
693 135
            'findType',
694 135
            $fromThis->getSchema(),
695 135
            $node,
696 90
            $attributeName
697 45
        );
698
699 135
        return $out;
700
    }
701
702 135
    private function loadUnion(SimpleType $type, DOMElement $node)
703
    {
704 135
        if ($node->hasAttribute("memberTypes")) {
705 135
            $types = preg_split('/\s+/', $node->getAttribute("memberTypes"));
706 135
            foreach ($types as $typeName) {
707
                /**
708
                * @var SimpleType $unionType
709
                */
710 135
                $unionType = $this->findSomeTypeFromAttribute(
711 135
                    $type,
712 135
                    $node,
713 90
                    $typeName
714 45
                );
715 135
                $type->addUnion($unionType);
716 45
            }
717 45
        }
718
        $addCallback = function (SimpleType $unType) use ($type) {
719 135
            $type->addUnion($unType);
720 135
        };
721
722 135
        Type::loadTypeWithCallbackOnChildNodes(
723 135
            $this,
724 135
            $type->getSchema(),
725 135
            $node,
726 90
            $addCallback
727 45
        );
728 135
    }
729
730
    /**
731
    * @param bool $checkAbstract
732
    */
733 135
    private function fillTypeNode(Type $type, DOMElement $node, $checkAbstract = false)
734
    {
735
736 135
        if ($checkAbstract) {
737 135
            $type->setAbstract($node->getAttribute("abstract") === "true" || $node->getAttribute("abstract") === "1");
738 45
        }
739
740 90
        static $methods = [
741
            'restriction' => 'loadRestriction',
742
            'extension' => 'maybeLoadExtensionFromBaseComplexType',
743
            'simpleContent' => 'fillTypeNode',
744
            'complexContent' => 'fillTypeNode',
745 45
        ];
746
747 135
        foreach ($node->childNodes as $childNode) {
748 135
            $this->maybeCallMethod(
749 135
                $methods,
750 135
                (string) $childNode->localName,
751 135
                $childNode,
752 135
                $type,
753 90
                $childNode
754 45
            );
755 45
        }
756 135
    }
757
758 135
    private function loadExtensionChildNode(
759
        BaseComplexType $type,
760
        DOMElement $node,
761
        DOMElement $childNode
762
    ) {
763
        $seqFromElement = [
764 135
            [$this, 'maybeLoadSequenceFromElementContainer'],
765
            [
766 135
                $type,
767 90
                $childNode
768 45
            ]
769 45
        ];
770
771
        $methods = [
772 135
            'sequence' => $seqFromElement,
773 135
            'choice' => $seqFromElement,
774 135
            'all' => $seqFromElement,
775
            'attribute' => [
776 135
                [$type, 'addAttributeFromAttributeOrRef'],
777
                [
778 135
                    $this,
779 135
                    $childNode,
780 135
                    $type->getSchema(),
781 90
                    $node
782 45
                ]
783 45
            ],
784
            'attributeGroup' => [
785 45
                (AttributeGroup::class . '::findSomethingLikeThis'),
786
                [
787 135
                    $this,
788 135
                    $type->getSchema(),
789 135
                    $node,
790 135
                    $childNode,
791 90
                    $type
792 45
                ]
793 45
            ],
794 45
        ];
795
796 135
        $this->maybeCallCallableWithArgs($childNode, $methods);
797 135
    }
798
799 135
    private function loadExtension(BaseComplexType $type, DOMElement $node)
800
    {
801 135
        $extension = new Extension();
802 135
        $type->setExtension($extension);
803
804 135
        if ($node->hasAttribute("base")) {
805 135
            $this->findAndSetSomeBase(
806 135
                $type,
807 135
                $extension,
808 90
                $node
809 45
            );
810 45
        }
811
812 135
        $this->loadExtensionChildNodes($type, $node->childNodes, $node);
813 135
    }
814
815 135
    private function loadExtensionChildNodes(
816
        BaseComplexType $type,
817
        DOMNodeList $childNodes,
818
        DOMElement $node
819
    ) {
820 135
        foreach ($childNodes as $childNode) {
821 135
            if ($childNode instanceof DOMElement) {
822 135
                $this->loadExtensionChildNode(
823 135
                    $type,
824 135
                    $node,
825 90
                    $childNode
826 45
                );
827 45
            }
828 45
        }
829 135
    }
830
831 135
    public function findAndSetSomeBase(
832
        Type $type,
833
        Base $setBaseOnThis,
834
        DOMElement $node
835
    ) {
836
        /**
837
        * @var Type $parent
838
        */
839 135
        $parent = $this->findSomeType($type, $node, 'base');
840 135
        $setBaseOnThis->setBase($parent);
841 135
    }
842
843 135
    private function maybeLoadExtensionFromBaseComplexType(
844
        Type $type,
845
        DOMElement $childNode
846
    ) {
847 135
        if (! ($type instanceof BaseComplexType)) {
848
            throw new RuntimeException(
849
                'Argument 1 passed to ' .
850
                __METHOD__ .
851
                ' needs to be an instance of ' .
852
                BaseComplexType::class .
853
                ' when passed onto ' .
854
                static::class .
855
                '::loadExtension(), ' .
856
                get_class($type) .
857
                ' given.'
858
            );
859
        }
860 135
        $this->loadExtension($type, $childNode);
861 135
    }
862
863 135
    private function loadRestriction(Type $type, DOMElement $node)
864
    {
865 135
        Restriction::loadRestriction($this, $type, $node);
866 135
    }
867
868
    /**
869
    * @param string $typeName
870
    *
871
    * @return mixed[]
872
    */
873 135
    private static function splitParts(DOMElement $node, $typeName)
874
    {
875 135
        $prefix = null;
876 135
        $name = $typeName;
877 135
        if (strpos($typeName, ':') !== false) {
878 135
            list ($prefix, $name) = explode(':', $typeName);
879 45
        }
880
881 135
        $namespace = $node->lookupNamespaceUri($prefix ?: '');
882
        return array(
883 135
            $name,
884 135
            $namespace,
885 90
            $prefix
886 45
        );
887
    }
888
889
    /**
890
     *
891
     * @param string $finder
892
     * @param Schema $schema
893
     * @param DOMElement $node
894
     * @param string $typeName
895
     * @throws TypeException
896
     * @return ElementItem|Group|AttributeItem|AttributeGroup|Type
897
     */
898 135
    public function findSomething($finder, Schema $schema, DOMElement $node, $typeName)
899
    {
900 135
        list ($name, $namespace) = self::splitParts($node, $typeName);
901
902 135
        $namespace = $namespace ?: $schema->getTargetNamespace();
903
904
        try {
905 135
            return $schema->$finder($name, $namespace);
906
        } catch (TypeNotFoundException $e) {
907
            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);
908
        }
909
    }
910
911
    /**
912
    * @return Closure
913
    */
914 135
    private function loadElementDef(Schema $schema, DOMElement $node)
915
    {
916 135
        return $this->loadAttributeOrElementDef($schema, $node, false);
917
    }
918
919 135
    public function fillItem(Item $element, DOMElement $node)
920
    {
921 135
        foreach ($node->childNodes as $childNode) {
922
            if (
923 135
                in_array(
924 135
                    $childNode->localName,
925
                    [
926 135
                        'complexType',
927 45
                        'simpleType',
928
                    ]
929 45
                )
930 45
            ) {
931 135
                Type::loadTypeWithCallback(
932 135
                    $this,
933 135
                    $element->getSchema(),
934 135
                    $childNode,
935
                    function (Type $type) use ($element) {
936 135
                        $element->setType($type);
937 135
                    }
938 45
                );
939 135
                return;
940
            }
941 45
        }
942
943 135
        $this->fillItemNonLocalType($element, $node);
944 135
    }
945
946 135
    private function fillItemNonLocalType(Item $element, DOMElement $node)
947
    {
948 135
        if ($node->getAttribute("type")) {
949
            /**
950
            * @var Type $type
951
            */
952 135
            $type = $this->findSomeType($element, $node, 'type');
953 45
        } else {
954
            /**
955
            * @var Type $type
956
            */
957 135
            $type = $this->findSomeTypeFromAttribute(
958 135
                $element,
959 135
                $node,
960 135
                ($node->lookupPrefix(self::XSD_NS) . ':anyType')
961 45
            );
962
        }
963
964 135
        $element->setType($type);
965 135
    }
966
967
    /**
968
    * @return Closure
969
    */
970 135
    private function loadImport(Schema $schema, DOMElement $node)
971
    {
972 135
        $base = urldecode($node->ownerDocument->documentURI);
973 135
        $file = UrlUtils::resolveRelativeUrl($base, $node->getAttribute("schemaLocation"));
974
975 135
        $namespace = $node->getAttribute("namespace");
976
977
        if (
978
            (
979 135
                isset(self::$globalSchemaInfo[$namespace]) &&
980 135
                Schema::hasLoadedFile(
981 135
                    $loadedFilesKey = self::$globalSchemaInfo[$namespace]
982 45
                )
983 45
            ) ||
984 9
            Schema::hasLoadedFile(
985 9
                $loadedFilesKey = $this->getNamespaceSpecificFileIndex(
986 9
                    $file,
987 6
                    $namespace
988 3
                )
989 3
            ) ||
990 47
            Schema::hasLoadedFile($loadedFilesKey = $file)
991 45
        ) {
992 135
            $schema->addSchema(Schema::getLoadedFile($loadedFilesKey));
993
994
            return function() {
995 135
            };
996
        }
997
998 3
        return $this->loadImportFresh($schema, $node, $file, $namespace);
999
    }
1000
1001
    /**
1002
    * @param string $file
1003
    * @param string $namespace
1004
    *
1005
    * @return Closure
1006
    */
1007 3
    private function loadImportFresh(
1008
        Schema $schema,
1009
        DOMElement $node,
1010
        $file,
1011
        $namespace
1012
    ) {
1013 3
        if (! $namespace) {
1014 3
            $newSchema = Schema::setLoadedFile($file, $schema);
1015 1
        } else {
1016
            $newSchema = Schema::setLoadedFile($file, new Schema());
1017
            $newSchema->addSchema($this->getGlobalSchema());
1018
        }
1019
1020 3
        $xml = $this->getDOM(isset($this->knownLocationSchemas[$file]) ? $this->knownLocationSchemas[$file] : $file);
1021
1022 3
        $callbacks = $this->schemaNode($newSchema, $xml->documentElement, $schema);
1023
1024 3
        if ($namespace) {
1025
            $schema->addSchema($newSchema);
1026
        }
1027
1028
1029 3
        return function () use ($callbacks) {
1030 3
            foreach ($callbacks as $callback) {
1031 3
                $callback();
1032 1
            }
1033 3
        };
1034
    }
1035
1036
    /**
1037
    * @var Schema|null
1038
    */
1039
    private $globalSchema;
1040
1041
    /**
1042
     *
1043
     * @return Schema
1044
     */
1045 135
    public function getGlobalSchema()
1046
    {
1047 135
        if (!$this->globalSchema) {
1048 135
            $callbacks = array();
1049 135
            $globalSchemas = array();
1050 135
            foreach (self::$globalSchemaInfo as $namespace => $uri) {
1051 135
                Schema::setLoadedFile(
1052 135
                    $uri,
1053 135
                    $globalSchemas[$namespace] = $schema = new Schema()
1054 45
                );
1055 135
                if ($namespace === self::XSD_NS) {
1056 135
                    $this->globalSchema = $schema;
1057 45
                }
1058 135
                $xml = $this->getDOM($this->knownLocationSchemas[$uri]);
1059 135
                $callbacks = array_merge($callbacks, $this->schemaNode($schema, $xml->documentElement));
1060 45
            }
1061
1062 135
            $globalSchemas[self::XSD_NS]->addType(new SimpleType($globalSchemas[self::XSD_NS], "anySimpleType"));
1063 135
            $globalSchemas[self::XSD_NS]->addType(new SimpleType($globalSchemas[self::XSD_NS], "anyType"));
1064
1065 135
            $globalSchemas[self::XML_NS]->addSchema($globalSchemas[self::XSD_NS], self::XSD_NS);
1066 135
            $globalSchemas[self::XSD_NS]->addSchema($globalSchemas[self::XML_NS], self::XML_NS);
1067
1068 135
            foreach ($callbacks as $callback) {
1069 135
                $callback();
1070 45
            }
1071 45
        }
1072
1073
        /**
1074
        * @var Schema $out
1075
        */
1076 135
        $out = $this->globalSchema;
1077
1078 135
        return $out;
1079
    }
1080
1081
    /**
1082
     * @param DOMElement $node
1083
     * @param string  $file
1084
     *
1085
     * @return Schema
1086
     */
1087 135
    public function readNode(DOMElement $node, $file = 'schema.xsd')
1088
    {
1089 135
        $fileKey = $node->hasAttribute('targetNamespace') ? $this->getNamespaceSpecificFileIndex($file, $node->getAttribute('targetNamespace')) : $file;
1090 135
        Schema::setLoadedFile($fileKey, $rootSchema = new Schema());
1091
1092 135
        $rootSchema->addSchema($this->getGlobalSchema());
1093 135
        $callbacks = $this->schemaNode($rootSchema, $node);
1094
1095 135
        foreach ($callbacks as $callback) {
1096 117
            call_user_func($callback);
1097 45
        }
1098
1099 135
        return $rootSchema;
1100
    }
1101
1102
    /**
1103
     * It is possible that a single file contains multiple <xsd:schema/> nodes, for instance in a WSDL file.
1104
     *
1105
     * Each of these  <xsd:schema/> nodes typically target a specific namespace. Append the target namespace to the
1106
     * file to distinguish between multiple schemas in a single file.
1107
     *
1108
     * @param string $file
1109
     * @param string $targetNamespace
1110
     *
1111
     * @return string
1112
     */
1113 135
    private function getNamespaceSpecificFileIndex($file, $targetNamespace)
1114
    {
1115 135
        return $file . '#' . $targetNamespace;
1116
    }
1117
1118
    /**
1119
     * @param string $content
1120
     * @param string $file
1121
     *
1122
     * @return Schema
1123
     *
1124
     * @throws IOException
1125
     */
1126 132
    public function readString($content, $file = 'schema.xsd')
1127
    {
1128 132
        $xml = new DOMDocument('1.0', 'UTF-8');
1129 132
        if (!$xml->loadXML($content)) {
1130
            throw new IOException("Can't load the schema");
1131
        }
1132 132
        $xml->documentURI = $file;
1133
1134 132
        return $this->readNode($xml->documentElement, $file);
1135
    }
1136
1137
    /**
1138
     * @param string $file
1139
     *
1140
     * @return Schema
1141
     */
1142 3
    public function readFile($file)
1143
    {
1144 3
        $xml = $this->getDOM($file);
1145 3
        return $this->readNode($xml->documentElement, $file);
1146
    }
1147
1148
    /**
1149
     * @param string $file
1150
     *
1151
     * @return DOMDocument
1152
     *
1153
     * @throws IOException
1154
     */
1155 135
    private function getDOM($file)
1156
    {
1157 135
        $xml = new DOMDocument('1.0', 'UTF-8');
1158 135
        if (!$xml->load($file)) {
1159
            throw new IOException("Can't load the file $file");
1160
        }
1161 135
        return $xml;
1162
    }
1163
}
1164