Passed
Push — static-analysis ( 0b3993...0c4f4f )
by SignpostMarv
03:18
created

SchemaReader::loadComplexTypeFromChildNode()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 67
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 55
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 45
nc 4
nop 4
dl 0
loc 67
ccs 55
cts 55
cp 1
crap 3
rs 9.2815
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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