Passed
Push — php-7.1 ( 52dd08...541704 )
by SignpostMarv
01:57
created

SchemaReader::fillTypeNode()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4

Importance

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