Test Failed
Pull Request — master (#18)
by SignpostMarv
03:04
created

SchemaReader::getGlobalSchema()   B

Complexity

Conditions 5
Paths 2

Size

Total Lines 34
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

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