Passed
Push — static-analysis ( c40947...0c0b57 )
by SignpostMarv
03:08
created

SchemaReader::loadImportFresh()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 25
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 5.583

Importance

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