Passed
Push — static-analysis ( c32a1e...74f346 )
by SignpostMarv
03:18
created

SchemaReader   F

Complexity

Total Complexity 112

Size/Duplication

Total Lines 1078
Duplicated Lines 0 %

Test Coverage

Coverage 94.47%

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 1078
ccs 530
cts 561
cp 0.9447
rs 0.7705
wmc 112

44 Methods

Rating   Name   Duplication   Size   Complexity  
B loadSequenceNormaliseMax() 0 10 5
A maybeSetMin() 0 7 2
A maybeSetMax() 0 9 3
A loadSequence() 0 11 3
A readFile() 0 4 1
A runCallbackAgainstDOMNodeList() 0 19 4
A addGroupAsElement() 0 18 1
A maybeCallMethod() 0 13 4
B getGlobalSchema() 0 34 5
A loadGroup() 0 3 1
A loadAttributeDef() 0 3 1
A addKnownSchemaLocation() 0 3 1
A __construct() 0 2 1
A getDocumentation() 0 12 4
A fillTypeNode() 0 21 4
B loadImportFresh() 0 25 5
A findSomeType() 0 9 1
A loadRestriction() 0 3 1
A readNode() 0 13 3
A getNamespaceSpecificFileIndex() 0 3 1
A findAndSetSomeBase() 0 10 1
A splitParts() 0 13 3
A loadElementDef() 0 3 1
A findSomeTypeFromAttribute() 0 16 1
A readString() 0 9 2
A findSomething() 0 10 3
A fillItemNonLocalType() 0 19 2
A setSchemaThingsFromNode() 0 15 3
A loadAttributeGroup() 0 3 1
A maybeLoadSequenceFromElementContainer() 0 16 2
A getDOM() 0 7 2
B schemaNode() 0 31 3
A maybeLoadExtensionFromBaseComplexType() 0 18 2
B loadUnion() 0 25 3
A makeCallbackCallback() 0 18 1
B loadImport() 0 29 5
B loadComplexType() 0 36 5
B loadSimpleType() 0 32 2
A loadComplexTypeFromChildNode() 0 67 3
B loadSequenceChildNode() 0 57 5
B fillItem() 0 25 3
A loadList() 0 18 2
A loadExtension() 0 58 4
A loadAttributeOrElementDef() 0 17 2

How to fix   Complexity   

Complex Class

Complex classes like SchemaReader often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SchemaReader, and based on these observations, apply Extract Interface, too.

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