Completed
Push — static-analysis ( 0c0b57...0aa186 )
by SignpostMarv
02:53
created

makeLoadSequenceChildNodeLoadSequence()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 3
dl 0
loc 7
ccs 2
cts 2
cp 1
crap 1
rs 9.4285
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 GoetasWebservices\XML\XSDReader\Exception\IOException;
9
use GoetasWebservices\XML\XSDReader\Exception\TypeException;
10
use GoetasWebservices\XML\XSDReader\Schema\Attribute\Attribute;
11
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeDef;
12
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeItem;
13
use GoetasWebservices\XML\XSDReader\Schema\Attribute\Group as AttributeGroup;
14
use GoetasWebservices\XML\XSDReader\Schema\Element\Element;
15
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementContainer;
16
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementDef;
17
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementItem;
18
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementRef;
19
use GoetasWebservices\XML\XSDReader\Schema\Element\Group;
20
use GoetasWebservices\XML\XSDReader\Schema\Element\GroupRef;
21
use GoetasWebservices\XML\XSDReader\Schema\Element\InterfaceSetMinMax;
22
use GoetasWebservices\XML\XSDReader\Schema\Exception\TypeNotFoundException;
23
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Base;
24
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Extension;
25
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Restriction;
26
use GoetasWebservices\XML\XSDReader\Schema\Item;
27
use GoetasWebservices\XML\XSDReader\Schema\Schema;
28
use GoetasWebservices\XML\XSDReader\Schema\SchemaItem;
29
use GoetasWebservices\XML\XSDReader\Schema\Type\BaseComplexType;
30
use GoetasWebservices\XML\XSDReader\Schema\Type\ComplexType;
31
use GoetasWebservices\XML\XSDReader\Schema\Type\ComplexTypeSimpleContent;
32
use GoetasWebservices\XML\XSDReader\Schema\Type\SimpleType;
33
use GoetasWebservices\XML\XSDReader\Schema\Type\Type;
34
use GoetasWebservices\XML\XSDReader\Utils\UrlUtils;
35
use RuntimeException;
36
37
class SchemaReader
38
{
39
40
    const XSD_NS = "http://www.w3.org/2001/XMLSchema";
41
42
    const XML_NS = "http://www.w3.org/XML/1998/namespace";
43
44
    /**
45
    * @var string[]
46
    */
47
    private $knownLocationSchemas = [
48
        'http://www.w3.org/2001/xml.xsd' => (
49
            __DIR__ . '/Resources/xml.xsd'
50
        ),
51
        'http://www.w3.org/2001/XMLSchema.xsd' => (
52
            __DIR__ . '/Resources/XMLSchema.xsd'
53
        ),
54
        'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' => (
55
            __DIR__ . '/Resources/oasis-200401-wss-wssecurity-secext-1.0.xsd'
56
        ),
57
        'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd' => (
58
            __DIR__ . '/Resources/oasis-200401-wss-wssecurity-utility-1.0.xsd'
59
        ),
60
        'https://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd' => (
61
            __DIR__ . '/Resources/xmldsig-core-schema.xsd'
62
        ),
63
        'http://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd' => (
64
            __DIR__ . '/Resources/xmldsig-core-schema.xsd'
65
        ),
66
    ];
67
68
    /**
69
    * @var string[]
70
    */
71
    private static $globalSchemaInfo = array(
72
        self::XML_NS => 'http://www.w3.org/2001/xml.xsd',
73
        self::XSD_NS => 'http://www.w3.org/2001/XMLSchema.xsd'
74
    );
75
76 162
    public function __construct()
77
    {
78 162
    }
79
80
    /**
81
    * @param string $remote
82
    * @param string $local
83
    */
84
    public function addKnownSchemaLocation($remote, $local)
85
    {
86
        $this->knownLocationSchemas[$remote] = $local;
87
    }
88
89
    /**
90
    * @return Closure
91
    */
92 135
    private function loadAttributeGroup(Schema $schema, DOMElement $node)
93 1
    {
94 135
        return AttributeGroup::loadAttributeGroup($this, $schema, $node);
95
    }
96
97
    /**
98
    * @param bool $attributeDef
99
    *
100
    * @return Closure
101
    */
102 135
    private function loadAttributeOrElementDef(
103
        Schema $schema,
104
        DOMElement $node,
105
        $attributeDef
106
    ) {
107 135
        $name = $node->getAttribute('name');
108 135
        if ($attributeDef) {
109 135
            $attribute = new AttributeDef($schema, $name);
110 135
            $schema->addAttribute($attribute);
111 45
        } else {
112 135
            $attribute = new ElementDef($schema, $name);
113 135
            $schema->addElement($attribute);
114
        }
115
116
117
        return function () use ($attribute, $node) {
118 135
            $this->fillItem($attribute, $node);
119 135
        };
120
    }
121
122
    /**
123
    * @return Closure
124
    */
125 135
    private function loadAttributeDef(Schema $schema, DOMElement $node)
126
    {
127 135
        return $this->loadAttributeOrElementDef($schema, $node, true);
128
    }
129
130
    /**
131
     * @param DOMElement $node
132
     * @return string
133
     */
134 135
    public static function getDocumentation(DOMElement $node)
135
    {
136 135
        $doc = '';
137 135
        foreach ($node->childNodes as $childNode) {
138 135
            if ($childNode->localName == "annotation") {
139 135
                $doc .= static::getDocumentation($childNode);
140 135
            } elseif ($childNode->localName == 'documentation') {
141 135
                $doc .= ($childNode->nodeValue);
142 45
            }
143 45
        }
144 135
        $doc = preg_replace('/[\t ]+/', ' ', $doc);
145 135
        return trim($doc);
146
    }
147
148 135
    private function setSchemaThingsFromNode(
149
        Schema $schema,
150
        DOMElement $node,
151
        Schema $parent = null
152
    ) {
153 135
        $schema->setDoc(static::getDocumentation($node));
154
155 135
        if ($node->hasAttribute("targetNamespace")) {
156 135
            $schema->setTargetNamespace($node->getAttribute("targetNamespace"));
157 45
        } elseif ($parent) {
158
            $schema->setTargetNamespace($parent->getTargetNamespace());
159
        }
160 135
        $schema->setElementsQualification($node->getAttribute("elementFormDefault") == "qualified");
161 135
        $schema->setAttributesQualification($node->getAttribute("attributeFormDefault") == "qualified");
162 135
        $schema->setDoc(static::getDocumentation($node));
163 135
    }
164
165
    /**
166
    * @param string $key
167
    *
168
    * @return Closure|null
169
    */
170 135
    public function maybeCallMethod(
171
        array $methods,
172
        $key,
173
        DOMNode $childNode,
174
        ...$args
175
    ) {
176 135
        if ($childNode instanceof DOMElement && isset($methods[$key])) {
177 135
            $method = $methods[$key];
178
179 135
            $append = $this->$method(...$args);
180
181 135
            if ($append instanceof Closure) {
182 135
                return $append;
183
            }
184 45
        }
185 135
    }
186
187
    /**
188
     *
189
     * @param Schema $schema
190
     * @param DOMElement $node
191
     * @param Schema $parent
192
     * @return Closure[]
193
     */
194 135
    private function schemaNode(Schema $schema, DOMElement $node, Schema $parent = null)
195
    {
196 135
        $this->setSchemaThingsFromNode($schema, $node, $parent);
197 135
        $functions = array();
198
199 90
        static $methods = [
200
            'include' => 'loadImport',
201
            'import' => 'loadImport',
202
            'element' => 'loadElementDef',
203
            'attribute' => 'loadAttributeDef',
204
            'attributeGroup' => 'loadAttributeGroup',
205
            'group' => 'loadGroup',
206
            'complexType' => 'loadComplexType',
207
            'simpleType' => 'loadSimpleType',
208 45
        ];
209
210 135
        foreach ($node->childNodes as $childNode) {
211 135
            $callback = $this->maybeCallMethod(
212 135
                $methods,
213 135
                (string) $childNode->localName,
214 135
                $childNode,
215 135
                $schema,
216 90
                $childNode
217 45
            );
218
219 135
            if ($callback instanceof Closure) {
220 135
                $functions[] = $callback;
221 45
            }
222 45
        }
223
224 135
        return $functions;
225
    }
226
227
    /**
228
    * @return InterfaceSetMinMax
229
    */
230 135
    public static function maybeSetMax(InterfaceSetMinMax $ref, DOMElement $node)
231
    {
232
        if (
233 135
            $node->hasAttribute("maxOccurs")
234 45
        ) {
235 135
            $ref->setMax($node->getAttribute("maxOccurs") == "unbounded" ? -1 : (int)$node->getAttribute("maxOccurs"));
236 45
        }
237
238 135
        return $ref;
239
    }
240
241
    /**
242
    * @return InterfaceSetMinMax
243
    */
244 135
    public static function maybeSetMin(InterfaceSetMinMax $ref, DOMElement $node)
245
    {
246 135
        if ($node->hasAttribute("minOccurs")) {
247 135
            $ref->setMin((int) $node->getAttribute("minOccurs"));
248 45
        }
249
250 135
        return $ref;
251
    }
252
253
    /**
254
    * @param int|null $max
255
    *
256
    * @return int|null
257
    */
258 135
    private static function loadSequenceNormaliseMax(DOMElement $node, $max)
259
    {
260
        return
261
        (
262 135
            (is_int($max) && (bool) $max) ||
263 135
            $node->getAttribute("maxOccurs") == "unbounded" ||
264 135
            $node->getAttribute("maxOccurs") > 1
265 45
        )
266 90
            ? 2
267 135
            : null;
268
    }
269
270
    /**
271
    * @param int|null $max
272
    */
273 135
    private function loadSequence(ElementContainer $elementContainer, DOMElement $node, $max = null)
274
    {
275 135
        $max = static::loadSequenceNormaliseMax($node, $max);
276
277 135
        foreach ($node->childNodes as $childNode) {
278 135
            if ($childNode instanceof DOMElement) {
279 135
                $this->loadSequenceChildNode(
280 135
                    $elementContainer,
281 135
                    $node,
282 135
                    $childNode,
283 90
                    $max
284 45
                );
285 45
            }
286 45
        }
287 135
    }
288
289
    /**
290
    * @param int|null $max
291
    */
292 135
    private function loadSequenceChildNode(
293
        ElementContainer $elementContainer,
294
        DOMElement $node,
295
        DOMElement $childNode,
296
        $max
297
    ) {
298
        $loadSeq = [
299 135
            'loadSequenceChildNodeLoadSequence',
300
            [
301 135
                $elementContainer,
302 135
                $childNode,
303 90
                $max
304 45
            ]
305 45
        ];
306
        $methods = [
307 135
            'choice' => $loadSeq,
308 135
            'sequence' => $loadSeq,
309 135
            'all' => $loadSeq,
310
            'element' => [
311 135
                'loadSequenceChildNodeLoadElement',
312
                [
313 135
                    $elementContainer,
314 135
                    $node,
315 135
                    $childNode,
316 90
                    $max
317 45
                ]
318 45
            ],
319
            'group' => [
320 135
                'loadSequenceChildNodeLoadGroup',
321
                [
322 135
                    $elementContainer,
323 135
                    $node,
324 90
                    $childNode
325 45
                ]
326 45
            ],
327 45
        ];
328
329 135
        $method = $this->maybeCallMethodWithArgs($childNode, $methods);
330 135
        if ($method instanceof Closure) {
331
            $method();
332
        }
333 135
    }
334
335
    /**
336
    * @param mixed[][] $methods
337
    *
338
    * @return mixed
339
    */
340 135
    private function maybeCallMethodWithArgs(
341
        DOMElement $childNode,
342
        array $methods
343
    ) {
344 135
        if (isset($methods[$childNode->localName])) {
345 135
            list ($method, $args) = $methods[$childNode->localName];
346
347 135
            return $this->$method(...$args);
348
        }
349 135
    }
350
351
    /**
352
    * @param int|null $max
353
    */
354 135
    private function loadSequenceChildNodeLoadSequence(
355
        ElementContainer $elementContainer,
356
        DOMElement $childNode,
357
        $max
358
    ) {
359 135
            $this->loadSequence($elementContainer, $childNode, $max);
360 135
    }
361
362
    /**
363
    * @param int|null $max
364
    */
365 135
    private function loadSequenceChildNodeLoadElement(
366
        ElementContainer $elementContainer,
367
        DOMElement $node,
368
        DOMElement $childNode,
369
        $max
370
    ) {
371 135
            if ($childNode->hasAttribute("ref")) {
372
                /**
373
                * @var ElementDef $referencedElement
374
                */
375 135
                $referencedElement = $this->findSomething('findElement', $elementContainer->getSchema(), $node, $childNode->getAttribute("ref"));
376 135
                $element = ElementRef::loadElementRef(
377 135
                    $referencedElement,
378 90
                    $childNode
379 45
                );
380 45
            } else {
381 135
                $element = Element::loadElement(
382 135
                    $this,
383 135
                    $elementContainer->getSchema(),
384 90
                    $childNode
385 45
                );
386
            }
387 135
            if (is_int($max) && (bool) $max) {
388 135
                $element->setMax($max);
389 45
            }
390 135
            $elementContainer->addElement($element);
391 135
    }
392
393 135
    private function loadSequenceChildNodeLoadGroup(
394
        ElementContainer $elementContainer,
395
        DOMElement $node,
396
        DOMElement $childNode
397
    ) {
398 135
            $this->addGroupAsElement(
399 135
                $elementContainer->getSchema(),
400 135
                $node,
401 135
                $childNode,
402 90
                $elementContainer
403 45
            );
404 135
    }
405
406 135
    private function addGroupAsElement(
407
        Schema $schema,
408
        DOMElement $node,
409
        DOMElement $childNode,
410
        ElementContainer $elementContainer
411
    ) {
412
        /**
413
        * @var Group $referencedGroup
414
        */
415 135
        $referencedGroup = $this->findSomething(
416 135
            'findGroup',
417 135
            $schema,
418 135
            $node,
419 135
            $childNode->getAttribute("ref")
420 45
        );
421
422 135
        $group = GroupRef::loadGroupRef($referencedGroup, $childNode);
423 135
        $elementContainer->addElement($group);
424 135
    }
425
426 135
    private function maybeLoadSequenceFromElementContainer(
427
        BaseComplexType $type,
428
        DOMElement $childNode
429
    ) {
430 135
        if (! ($type instanceof ElementContainer)) {
431
            throw new RuntimeException(
432
                '$type passed to ' .
433
                __FUNCTION__ .
434
                'expected to be an instance of ' .
435
                ElementContainer::class .
436
                ' when child node localName is "group", ' .
437
                get_class($type) .
438
                ' given.'
439
            );
440
        }
441 135
        $this->loadSequence($type, $childNode);
442 135
    }
443
444
    /**
445
    * @return Closure
446
    */
447 135
    private function loadGroup(Schema $schema, DOMElement $node)
448
    {
449 135
        return Group::loadGroup($this, $schema, $node);
450
    }
451
452
    /**
453
    * @param Closure|null $callback
454
    *
455
    * @return Closure
456
    */
457 135
    private function loadComplexType(Schema $schema, DOMElement $node, $callback = null)
458
    {
459 135
        $isSimple = false;
460
461 135
        foreach ($node->childNodes as $childNode) {
462 135
            if ($childNode->localName === "simpleContent") {
463 6
                $isSimple = true;
464 49
                break;
465
            }
466 45
        }
467
468 135
        $type = $isSimple ? new ComplexTypeSimpleContent($schema, $node->getAttribute("name")) : new ComplexType($schema, $node->getAttribute("name"));
469
470 135
        $type->setDoc(static::getDocumentation($node));
471 135
        if ($node->getAttribute("name")) {
472 135
            $schema->addType($type);
473 45
        }
474
475 135
        return $this->makeCallbackCallback(
476 135
            $type,
477 135
            $node,
478
                function (
479
                    DOMElement $node,
480
                    DOMElement $childNode
481
                ) use(
482 135
                    $schema,
483 135
                    $type
484
                ) {
485 135
                    $this->loadComplexTypeFromChildNode(
486 135
                        $type,
487 135
                        $node,
488 135
                        $childNode,
489 90
                        $schema
490 45
                    );
491 135
                },
492 90
            $callback
493 45
        );
494
    }
495
496
    /**
497
    * @param Closure|null $callback
498
    *
499
    * @return Closure
500
    */
501 135
    private function makeCallbackCallback(
502
        Type $type,
503
        DOMElement $node,
504
        Closure $callbackCallback,
505
        $callback = null
506
    ) {
507
        return function (
508
        ) use (
509 135
            $type,
510 135
            $node,
511 135
            $callbackCallback,
512 135
            $callback
513
        ) {
514 135
            $this->runCallbackAgainstDOMNodeList(
515 135
                $type,
516 135
                $node,
517 135
                $callbackCallback,
518 90
                $callback
519 45
            );
520 135
        };
521
    }
522
523
    /**
524
    * @param Closure|null $callback
525
    */
526 135
    private function runCallbackAgainstDOMNodeList(
527
        Type $type,
528
        DOMElement $node,
529
        Closure $againstNodeList,
530
        $callback = null
531
    ) {
532 135
        $this->fillTypeNode($type, $node, true);
533
534 135
        foreach ($node->childNodes as $childNode) {
535 135
            if ($childNode instanceof DOMElement) {
536 135
                $againstNodeList(
537 135
                    $node,
538 90
                    $childNode
539 45
                );
540 45
            }
541 45
        }
542
543 135
        if ($callback) {
544 135
            call_user_func($callback, $type);
545 45
        }
546 135
    }
547
548 135
    private function loadComplexTypeFromChildNode(
549
        BaseComplexType $type,
550
        DOMElement $node,
551
        DOMElement $childNode,
552
        Schema $schema
553
    ) {
554
        $maybeLoadSeq = function () use ($type, $childNode) {
555 135
            $this->maybeLoadSequenceFromElementContainer(
556 135
                $type,
557 90
                $childNode
558 45
            );
559 135
        };
560
        $methods = [
561 135
            'sequence' => $maybeLoadSeq,
562 135
            'choice' => $maybeLoadSeq,
563 135
            'all' => $maybeLoadSeq,
564
            'attribute' => function () use (
565 135
                $childNode,
566 135
                $schema,
567 135
                $node,
568 135
                $type
569
            ) {
570 135
                $attribute = Attribute::getAttributeFromAttributeOrRef(
571 135
                    $this,
572 135
                    $childNode,
573 135
                    $schema,
574 90
                    $node
575 45
                );
576
577 135
                $type->addAttribute($attribute);
578 135
            },
579
            'attributeGroup' => function() use (
580 6
                $schema,
581 6
                $node,
582 6
                $childNode,
583 6
                $type
584
            ) {
585 6
                AttributeGroup::findSomethingLikeThis(
586 6
                    $this,
587 6
                    $schema,
588 6
                    $node,
589 6
                    $childNode,
590 4
                    $type
591 2
                );
592 135
            },
593 45
        ];
594
        if (
595 90
            $type instanceof ComplexType
596 45
        ) {
597
            $methods['group'] = function() use (
598 3
                $schema,
599 3
                $node,
600 3
                $childNode,
601 3
                $type
602
            ) {
603 3
                $this->addGroupAsElement(
604 3
                    $schema,
605 3
                    $node,
606 3
                    $childNode,
607 2
                    $type
608 1
                );
609 3
            };
610 45
        }
611
612 135
        if (isset($methods[$childNode->localName])) {
613 135
            $method = $methods[$childNode->localName];
614 135
            $method();
615 45
        }
616 135
    }
617
618
    /**
619
    * @param Closure|null $callback
620
    *
621
    * @return Closure
622
    */
623 135
    private function loadSimpleType(Schema $schema, DOMElement $node, $callback = null)
624
    {
625 135
        $type = new SimpleType($schema, $node->getAttribute("name"));
626 135
        $type->setDoc(static::getDocumentation($node));
627 135
        if ($node->getAttribute("name")) {
628 135
            $schema->addType($type);
629 45
        }
630
631 90
        static $methods = [
632
            'union' => 'loadUnion',
633
            'list' => 'loadList',
634 45
        ];
635
636 135
        return $this->makeCallbackCallback(
637 135
            $type,
638 135
            $node,
639
            function (
640
                DOMElement $node,
641
                DOMElement $childNode
642
            ) use (
643 135
                $methods,
644 135
                $type
645
            ) {
646 135
                $this->maybeCallMethod(
647 135
                    $methods,
648 135
                    $childNode->localName,
649 135
                    $childNode,
650 135
                    $type,
651 90
                    $childNode
652 45
                );
653 135
            },
654 90
            $callback
655 45
        );
656
    }
657
658 135
    private function loadList(SimpleType $type, DOMElement $node)
659
    {
660 135
        if ($node->hasAttribute("itemType")) {
661
            /**
662
            * @var SimpleType $listType
663
            */
664 135
            $listType = $this->findSomeType($type, $node, 'itemType');
665 135
            $type->setList($listType);
666 45
        } else {
667
            $addCallback = function (SimpleType $list) use ($type) {
668 135
                $type->setList($list);
669 135
            };
670
671 135
            Type::loadTypeWithCallbackOnChildNodes(
672 135
                $this,
673 135
                $type->getSchema(),
674 135
                $node,
675 90
                $addCallback
676 45
            );
677
        }
678 135
    }
679
680
    /**
681
    * @param string $attributeName
682
    *
683
    * @return SchemaItem
684
    */
685 135
    private function findSomeType(
686
        SchemaItem $fromThis,
687
        DOMElement $node,
688
        $attributeName
689
    ) {
690 135
        return $this->findSomeTypeFromAttribute(
691 135
            $fromThis,
692 135
            $node,
693 135
            $node->getAttribute($attributeName)
694 45
        );
695
    }
696
697
    /**
698
    * @param string $attributeName
699
    *
700
    * @return SchemaItem
701
    */
702 135
    private function findSomeTypeFromAttribute(
703
        SchemaItem $fromThis,
704
        DOMElement $node,
705
        $attributeName
706
    ) {
707
        /**
708
        * @var SchemaItem $out
709
        */
710 135
        $out = $this->findSomething(
711 135
            'findType',
712 135
            $fromThis->getSchema(),
713 135
            $node,
714 90
            $attributeName
715 45
        );
716
717 135
        return $out;
718
    }
719
720 135
    private function loadUnion(SimpleType $type, DOMElement $node)
721
    {
722 135
        if ($node->hasAttribute("memberTypes")) {
723 135
            $types = preg_split('/\s+/', $node->getAttribute("memberTypes"));
724 135
            foreach ($types as $typeName) {
725
                /**
726
                * @var SimpleType $unionType
727
                */
728 135
                $unionType = $this->findSomeTypeFromAttribute(
729 135
                    $type,
730 135
                    $node,
731 90
                    $typeName
732 45
                );
733 135
                $type->addUnion($unionType);
734 45
            }
735 45
        }
736
        $addCallback = function (SimpleType $unType) use ($type) {
737 135
            $type->addUnion($unType);
738 135
        };
739
740 135
        Type::loadTypeWithCallbackOnChildNodes(
741 135
            $this,
742 135
            $type->getSchema(),
743 135
            $node,
744 90
            $addCallback
745 45
        );
746 135
    }
747
748
    /**
749
    * @param bool $checkAbstract
750
    */
751 135
    private function fillTypeNode(Type $type, DOMElement $node, $checkAbstract = false)
752
    {
753
754 135
        if ($checkAbstract) {
755 135
            $type->setAbstract($node->getAttribute("abstract") === "true" || $node->getAttribute("abstract") === "1");
756 45
        }
757
758 90
        static $methods = [
759
            'restriction' => 'loadRestriction',
760
            'extension' => 'maybeLoadExtensionFromBaseComplexType',
761
            'simpleContent' => 'fillTypeNode',
762
            'complexContent' => 'fillTypeNode',
763 45
        ];
764
765 135
        foreach ($node->childNodes as $childNode) {
766 135
            $this->maybeCallMethod(
767 135
                $methods,
768 135
                (string) $childNode->localName,
769 135
                $childNode,
770 135
                $type,
771 90
                $childNode
772 45
            );
773 45
        }
774 135
    }
775
776 135
    private function loadExtension(BaseComplexType $type, DOMElement $node)
777
    {
778 135
        $extension = new Extension();
779 135
        $type->setExtension($extension);
780
781 135
        if ($node->hasAttribute("base")) {
782 135
            $this->findAndSetSomeBase(
783 135
                $type,
784 135
                $extension,
785 90
                $node
786 45
            );
787 45
        }
788
789
        $seqFromElement = function (DOMElement $childNode) use ($type) {
790 135
            $this->maybeLoadSequenceFromElementContainer(
791 135
                $type,
792 90
                $childNode
793 45
            );
794 135
        };
795
796
        $methods = [
797 135
            'sequence' => $seqFromElement,
798 135
            'choice' => $seqFromElement,
799 135
            'all' => $seqFromElement,
800
            'attribute' => function (
801
                DOMElement $childNode
802
            ) use (
803 135
                $node,
804 135
                $type
805
            ) {
806 135
                $attribute = Attribute::getAttributeFromAttributeOrRef(
807 135
                    $this,
808 135
                    $childNode,
809 135
                    $type->getSchema(),
810 90
                    $node
811 45
                );
812 135
                $type->addAttribute($attribute);
813 135
            },
814
            'attributeGroup' => function (
815
                DOMElement $childNode
816
            ) use (
817 135
                $node,
818 135
                $type
819
            ) {
820 135
                AttributeGroup::findSomethingLikeThis(
821 135
                    $this,
822 135
                    $type->getSchema(),
823 135
                    $node,
824 135
                    $childNode,
825 90
                    $type
826 45
                );
827 135
            },
828 45
        ];
829
830 135
        foreach ($node->childNodes as $childNode) {
831 135
            if (isset($methods[$childNode->localName])) {
832 135
                $method = $methods[$childNode->localName];
833 135
                $method($childNode);
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