Passed
Push — static-analysis ( 13ccd4...7ddd7f )
by SignpostMarv
02:50
created

SchemaReader::setupGlobalSchemas()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 3

Importance

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