Passed
Push — static-analysis ( 5a0c71...7ccf34 )
by SignpostMarv
03:20
created

SchemaReader::loadGroup()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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