Passed
Push — static-analysis ( d0f53e...7cdbe5 )
by SignpostMarv
02:41
created

SchemaReader::setSchemaThingsFromNode()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3.072

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 8
cts 10
cp 0.8
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 8
nc 3
nop 3
crap 3.072
1
<?php
2
3
namespace GoetasWebservices\XML\XSDReader;
4
5
use Closure;
6
use DOMDocument;
7
use DOMElement;
8
use DOMNode;
9
use GoetasWebservices\XML\XSDReader\Exception\IOException;
10
use GoetasWebservices\XML\XSDReader\Exception\TypeException;
11
use GoetasWebservices\XML\XSDReader\Schema\Attribute\Attribute;
12
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeContainer;
13
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeDef;
14
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeItem;
15
use GoetasWebservices\XML\XSDReader\Schema\Attribute\Group as AttributeGroup;
16
use GoetasWebservices\XML\XSDReader\Schema\Element\Element;
17
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementContainer;
18
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementDef;
19
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementItem;
20
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementRef;
21
use GoetasWebservices\XML\XSDReader\Schema\Element\Group;
22
use GoetasWebservices\XML\XSDReader\Schema\Element\InterfaceSetMinMax;
23
use GoetasWebservices\XML\XSDReader\Schema\Element\GroupRef;
24
use GoetasWebservices\XML\XSDReader\Schema\Exception\TypeNotFoundException;
25
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Base;
26
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Extension;
27
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Restriction;
28
use GoetasWebservices\XML\XSDReader\Schema\Item;
29
use GoetasWebservices\XML\XSDReader\Schema\Schema;
30
use GoetasWebservices\XML\XSDReader\Schema\SchemaItem;
31
use GoetasWebservices\XML\XSDReader\Schema\Type\BaseComplexType;
32
use GoetasWebservices\XML\XSDReader\Schema\Type\ComplexType;
33
use GoetasWebservices\XML\XSDReader\Schema\Type\ComplexTypeSimpleContent;
34
use GoetasWebservices\XML\XSDReader\Schema\Type\SimpleType;
35
use GoetasWebservices\XML\XSDReader\Schema\Type\Type;
36
use GoetasWebservices\XML\XSDReader\Utils\UrlUtils;
37
use RuntimeException;
38
39
class SchemaReader
40
{
41
    /**
42
     * @param string $typeName
43
     *
44
     * @return mixed[]
45
     */
46 45
    private static function splitParts(DOMElement $node, $typeName)
47
    {
48 45
        $prefix = null;
49 45
        $name = $typeName;
50 45
        if (strpos($typeName, ':') !== false) {
51 45
            list($prefix, $name) = explode(':', $typeName);
52 45
        }
53
54 45
        $namespace = $node->lookupNamespaceUri($prefix ?: '');
55
56
        return array(
57 45
            $name,
58 45
            $namespace,
59 45
            $prefix,
60 45
        );
61
    }
62
63
    /**
64
     * @param bool $attributeDef
65
     *
66
     * @return Closure
67
     */
68 45
    private function loadAttributeOrElementDef(
69
        Schema $schema,
70
        DOMElement $node,
71
        $attributeDef
72
    ) {
73 45
        $name = $node->getAttribute('name');
74 45
        if ($attributeDef) {
75 45
            $attribute = new AttributeDef($schema, $name);
76 45
            $schema->addAttribute($attribute);
77 45
        } else {
78 45
            $attribute = new ElementDef($schema, $name);
79 45
            $schema->addElement($attribute);
80
        }
81
82
        return function () use ($attribute, $node) {
83 45
            $this->fillItem($attribute, $node);
84 45
        };
85
    }
86
87
    /**
88
     * @return Closure
89
     */
90 45
    private function loadAttributeDef(Schema $schema, DOMElement $node)
91
    {
92 45
        return $this->loadAttributeOrElementDef($schema, $node, true);
93
    }
94
95
    /**
96
     * @param int|null $max
97
     *
98
     * @return int|null
99
     */
100 45
    private static function loadSequenceNormaliseMax(DOMElement $node, $max)
101
    {
102
        return
103
        (
104 45
            (is_int($max) && (bool) $max) ||
105 45
            $node->getAttribute('maxOccurs') == 'unbounded' ||
106 45
            $node->getAttribute('maxOccurs') > 1
107 45
        )
108 45
            ? 2
109 45
            : null;
110
    }
111
112
    /**
113
     * @param int|null $max
114
     */
115 45
    private function loadSequence(ElementContainer $elementContainer, DOMElement $node, $max = null)
116
    {
117 45
        $max = static::loadSequenceNormaliseMax($node, $max);
118
119 45
        static::againstDOMNodeList(
120 45
            $node,
121
            function (
122
                DOMElement $node,
123
                DOMElement $childNode
124
            ) use (
125 45
                $elementContainer,
126 45
                $max
127
            ) {
128 45
                $this->loadSequenceChildNode(
129 45
                    $elementContainer,
130 45
                    $node,
131 45
                    $childNode,
132
                    $max
133 45
                );
134 45
            }
135 45
        );
136 45
    }
137
138
    /**
139
     * @param int|null $max
140
     */
141 45
    private function loadSequenceChildNode(
142
        ElementContainer $elementContainer,
143
        DOMElement $node,
144
        DOMElement $childNode,
145
        $max
146
    ) {
147
        $commonMethods = [
148
            [
149 45
                ['sequence', 'choice', 'all'],
150 45
                [$this, 'loadSequenceChildNodeLoadSequence'],
151
                [
152 45
                    $elementContainer,
153 45
                    $childNode,
154 45
                    $max,
155 45
                ],
156 45
            ],
157 45
        ];
158
        $methods = [
159
            'element' => [
160 45
                [$this, 'loadSequenceChildNodeLoadElement'],
161
                [
162 45
                    $elementContainer,
163 45
                    $node,
164 45
                    $childNode,
165 45
                    $max,
166 45
                ],
167 45
            ],
168
            'group' => [
169 45
                [$this, 'loadSequenceChildNodeLoadGroup'],
170
                [
171 45
                    $elementContainer,
172 45
                    $node,
173 45
                    $childNode,
174 45
                ],
175 45
            ],
176 45
        ];
177
178 45
        $this->maybeCallCallableWithArgs($childNode, $commonMethods, $methods);
179 45
    }
180
181
    /**
182
     * @param int|null $max
183
     */
184 45
    private function loadSequenceChildNodeLoadSequence(
185
        ElementContainer $elementContainer,
186
        DOMElement $childNode,
187
        $max
188
    ) {
189 45
        $this->loadSequence($elementContainer, $childNode, $max);
190 45
    }
191
192
    /**
193
     * @param int|null $max
194
     */
195 45
    private function loadSequenceChildNodeLoadElement(
196
        ElementContainer $elementContainer,
197
        DOMElement $node,
198
        DOMElement $childNode,
199
        $max
200
    ) {
201 45
        if ($childNode->hasAttribute('ref')) {
202
            /**
203
             * @var ElementDef $referencedElement
204
             */
205 45
            $referencedElement = $this->findSomething('findElement', $elementContainer->getSchema(), $node, $childNode->getAttribute('ref'));
206 45
            $element = static::loadElementRef(
207 45
                $referencedElement,
208
                $childNode
209 45
            );
210 45
        } else {
211 45
            $element = $this->loadElement(
212 45
                $elementContainer->getSchema(),
213
                $childNode
214 45
            );
215
        }
216 45
        if ($max > 1) {
217
            /*
218
            * although one might think the typecast is not needed with $max being `? int $max` after passing > 1,
219
            * phpstan@a4f89fa still thinks it's possibly null.
220
            * see https://github.com/phpstan/phpstan/issues/577 for related issue
221
            */
222 45
            $element->setMax((int) $max);
223 45
        }
224 45
        $elementContainer->addElement($element);
225 45
    }
226
227 45
    private function loadSequenceChildNodeLoadGroup(
228
        ElementContainer $elementContainer,
229
        DOMElement $node,
230
        DOMElement $childNode
231
    ) {
232 45
        $this->addGroupAsElement(
233 45
            $elementContainer->getSchema(),
234 45
            $node,
235 45
            $childNode,
236
            $elementContainer
237 45
        );
238 45
    }
239
240 45
    private function addGroupAsElement(
241
        Schema $schema,
242
        DOMElement $node,
243
        DOMElement $childNode,
244
        ElementContainer $elementContainer
245
    ) {
246
        /**
247
         * @var Group
248
         */
249 45
        $referencedGroup = $this->findSomething(
250 45
            'findGroup',
251 45
            $schema,
252 45
            $node,
253 45
            $childNode->getAttribute('ref')
254 45
        );
255
256 45
        $group = $this->loadGroupRef($referencedGroup, $childNode);
257 45
        $elementContainer->addElement($group);
258 45
    }
259
260
    /**
261
     * @return Closure
262
     */
263 45
    private function loadGroup(Schema $schema, DOMElement $node)
264
    {
265 45
        $group = static::loadGroupBeforeCheckingChildNodes(
266 45
            $schema,
267
            $node
268 45
        );
269
        static $methods = [
270
            'sequence' => 'loadSequence',
271
            'choice' => 'loadSequence',
272
            'all' => 'loadSequence',
273 45
        ];
274
275
        return function () use ($group, $node, $methods) {
276
            /**
277
             * @var string[]
278
             */
279 45
            $methods = $methods;
280 45
            $this->maybeCallMethodAgainstDOMNodeList(
281 45
                $node,
282 45
                $group,
283
                $methods
284 45
            );
285 45
        };
286
    }
287
288
    /**
289
     * @return Group|GroupRef
290
     */
291 45
    private static function loadGroupBeforeCheckingChildNodes(
292
        Schema $schema,
293
        DOMElement $node
294
    ) {
295 45
        $group = new Group($schema, $node->getAttribute('name'));
296 45
        $group->setDoc(self::getDocumentation($node));
297
298 45
        if ($node->hasAttribute('maxOccurs')) {
299
            /**
300
             * @var GroupRef
301
             */
302
            $group = self::maybeSetMax(new GroupRef($group), $node);
303
        }
304 45
        if ($node->hasAttribute('minOccurs')) {
305
            /**
306
             * @var GroupRef
307
             */
308
            $group = self::maybeSetMin(
309
                $group instanceof GroupRef ? $group : new GroupRef($group),
310
                $node
311
            );
312
        }
313
314 45
        $schema->addGroup($group);
315
316 45
        return $group;
317
    }
318
319
    /**
320
     * @return GroupRef
321
     */
322 45
    private function loadGroupRef(Group $referenced, DOMElement $node)
323
    {
324 45
        $ref = new GroupRef($referenced);
325 45
        $ref->setDoc(self::getDocumentation($node));
326
327 45
        self::maybeSetMax($ref, $node);
328 45
        self::maybeSetMin($ref, $node);
329
330 45
        return $ref;
331
    }
332
333
    /**
334
     * @return BaseComplexType
335
     */
336 45
    private function loadComplexTypeBeforeCallbackCallback(
337
        Schema $schema,
338
        DOMElement $node
339
    ) {
340
        /**
341
         * @var bool
342
         */
343 45
        $isSimple = false;
344
345 45
        static::againstDOMNodeList(
346 45
            $node,
347
            function (
348
                DOMElement $node,
349
                DOMElement $childNode
350
            ) use (
351
                &$isSimple
352
            ) {
353 45
                if ($isSimple) {
354 1
                    return;
355
                }
356 45
                if ($childNode->localName === 'simpleContent') {
357 2
                    $isSimple = true;
358 2
                }
359 45
            }
360 45
        );
361
362 45
        $type = $isSimple ? new ComplexTypeSimpleContent($schema, $node->getAttribute('name')) : new ComplexType($schema, $node->getAttribute('name'));
363
364 45
        $type->setDoc(static::getDocumentation($node));
365 45
        if ($node->getAttribute('name')) {
366 45
            $schema->addType($type);
367 45
        }
368
369 45
        return $type;
370
    }
371
372
    /**
373
     * @param Closure|null $callback
374
     *
375
     * @return Closure
376
     */
377 45
    private function loadComplexType(Schema $schema, DOMElement $node, $callback = null)
378
    {
379 45
        $type = $this->loadComplexTypeBeforeCallbackCallback($schema, $node);
380
381 45
        return $this->makeCallbackCallback(
382 45
            $type,
383 45
            $node,
384
            function (
385
                DOMElement $node,
386
                DOMElement $childNode
387
            ) use (
388 45
                $schema,
389 45
                $type
390
            ) {
391 45
                $this->loadComplexTypeFromChildNode(
392 45
                    $type,
393 45
                    $node,
394 45
                    $childNode,
395
                    $schema
396 45
                );
397 45
            },
398
            $callback
399 45
        );
400
    }
401
402 45
    private function loadComplexTypeFromChildNode(
403
        BaseComplexType $type,
404
        DOMElement $node,
405
        DOMElement $childNode,
406
        Schema $schema
407
    ) {
408
        $commonMethods = [
409
            [
410 45
                ['sequence', 'choice', 'all'],
411 45
                [$this, 'maybeLoadSequenceFromElementContainer'],
412
                [
413 45
                    $type,
414 45
                    $childNode,
415 45
                ],
416 45
            ],
417 45
        ];
418
        $methods = [
419
            'attribute' => [
420 45
                [$this, 'addAttributeFromAttributeOrRef'],
421
                [
422 45
                    $type,
423 45
                    $childNode,
424 45
                    $schema,
425 45
                    $node,
426 45
                ],
427 45
            ],
428
            'attributeGroup' => [
429 45
                [$this, 'findSomethingLikeAttributeGroup'],
430
                [
431 45
                    $schema,
432 45
                    $node,
433 45
                    $childNode,
434 45
                    $type,
435 45
                ],
436 45
            ],
437 45
        ];
438
        if (
439
            $type instanceof ComplexType
440 45
        ) {
441 45
            $methods['group'] = [
442 45
                [$this, 'addGroupAsElement'],
443
                [
444 45
                    $schema,
445 45
                    $node,
446 45
                    $childNode,
447 45
                    $type,
448 45
                ],
449
            ];
450 45
        }
451
452 45
        $this->maybeCallCallableWithArgs($childNode, $commonMethods, $methods);
453 45
    }
454
455
    /**
456
     * @param Closure|null $callback
457
     *
458
     * @return Closure
459
     */
460 45
    private function loadSimpleType(Schema $schema, DOMElement $node, $callback = null)
461
    {
462 45
        $type = new SimpleType($schema, $node->getAttribute('name'));
463 45
        $type->setDoc(static::getDocumentation($node));
464 45
        if ($node->getAttribute('name')) {
465 45
            $schema->addType($type);
466 45
        }
467
468 45
        return $this->makeCallbackCallback(
469 45
            $type,
470 45
            $node,
471 45
            $this->CallbackGeneratorMaybeCallMethodAgainstDOMNodeList(
472 45
                $type,
473
                [
474 45
                    'union' => 'loadUnion',
475 45
                    'list' => 'loadList',
476
                ]
477 45
            ),
478
            $callback
479 45
        );
480
    }
481
482 45
    private function loadList(SimpleType $type, DOMElement $node)
483
    {
484 45
        if ($node->hasAttribute('itemType')) {
485
            /**
486
             * @var SimpleType
487
             */
488 45
            $listType = $this->findSomeType($type, $node, 'itemType');
489 45
            $type->setList($listType);
490 45
        } else {
491
            $addCallback = function (SimpleType $list) use ($type) {
492 45
                $type->setList($list);
493 45
            };
494
495 45
            $this->loadTypeWithCallbackOnChildNodes(
496 45
                $type->getSchema(),
497 45
                $node,
498
                $addCallback
499 45
            );
500
        }
501 45
    }
502
503 45
    private function loadUnion(SimpleType $type, DOMElement $node)
504
    {
505 45
        if ($node->hasAttribute('memberTypes')) {
506 45
            $types = preg_split('/\s+/', $node->getAttribute('memberTypes'));
507 45
            foreach ($types as $typeName) {
508
                /**
509
                 * @var SimpleType
510
                 */
511 45
                $unionType = $this->findSomeTypeFromAttribute(
512 45
                    $type,
513 45
                    $node,
514
                    $typeName
515 45
                );
516 45
                $type->addUnion($unionType);
517 45
            }
518 45
        }
519
        $addCallback = function (SimpleType $unType) use ($type) {
520 45
            $type->addUnion($unType);
521 45
        };
522
523 45
        $this->loadTypeWithCallbackOnChildNodes(
524 45
            $type->getSchema(),
525 45
            $node,
526
            $addCallback
527 45
        );
528 45
    }
529
530 45
    private function loadExtensionChildNodes(
531
        BaseComplexType $type,
532
        DOMElement $node
533
    ) {
534 45
        static::againstDOMNodeList(
535 45
            $node,
536
            function (
537
                DOMElement $node,
538
                DOMElement $childNode
539
            ) use (
540 45
                $type
541
            ) {
542
                $commonMethods = [
543
                    [
544 45
                        ['sequence', 'choice', 'all'],
545 45
                        [$this, 'maybeLoadSequenceFromElementContainer'],
546
                        [
547 45
                            $type,
548 45
                            $childNode,
549 45
                        ],
550 45
                    ],
551 45
                ];
552
                $methods = [
553
                    'attribute' => [
554 45
                        [$this, 'addAttributeFromAttributeOrRef'],
555
                        [
556 45
                            $type,
557 45
                            $childNode,
558 45
                            $type->getSchema(),
559 45
                            $node,
560 45
                        ],
561 45
                    ],
562
                    'attributeGroup' => [
563 45
                        [$this, 'findSomethingLikeAttributeGroup'],
564
                        [
565 45
                            $type->getSchema(),
566 45
                            $node,
567 45
                            $childNode,
568 45
                            $type,
569 45
                        ],
570 45
                    ],
571 45
                ];
572
573 45
                $this->maybeCallCallableWithArgs(
574 45
                    $childNode,
575 45
                    $commonMethods,
576
                    $methods
577 45
                );
578 45
            }
579 45
        );
580 45
    }
581
582 45
    private function loadExtension(BaseComplexType $type, DOMElement $node)
583
    {
584 45
        $extension = new Extension();
585 45
        $type->setExtension($extension);
586
587 45
        if ($node->hasAttribute('base')) {
588 45
            $this->findAndSetSomeBase(
589 45
                $type,
590 45
                $extension,
591
                $node
592 45
            );
593 45
        }
594 45
        $this->loadExtensionChildNodes($type, $node);
595 45
    }
596
597 45
    private function loadRestriction(Type $type, DOMElement $node)
598
    {
599 45
        $restriction = new Restriction();
600 45
        $type->setRestriction($restriction);
601 45
        if ($node->hasAttribute('base')) {
602 45
            $this->findAndSetSomeBase($type, $restriction, $node);
603 45
        } else {
604
            $addCallback = function (Type $restType) use ($restriction) {
605 45
                $restriction->setBase($restType);
606 45
            };
607
608 45
            $this->loadTypeWithCallbackOnChildNodes(
609 45
                $type->getSchema(),
610 45
                $node,
611
                $addCallback
612 45
            );
613
        }
614 45
        self::againstDOMNodeList(
615 45
            $node,
616
            function (
617
                DOMElement $node,
618
                DOMElement $childNode
619
            ) use (
620 45
                $restriction
621
            ) {
622 45
                static::maybeLoadRestrictionOnChildNode(
623 45
                    $restriction,
624
                    $childNode
625 45
                );
626 45
            }
627 45
        );
628 45
    }
629
630 45
    private static function maybeLoadRestrictionOnChildNode(
631
        Restriction $restriction,
632
        DOMElement $childNode
633
    ) {
634
        if (
635 45
            in_array(
636 45
                $childNode->localName,
637
                [
638 45
                    'enumeration',
639 45
                    'pattern',
640 45
                    'length',
641 45
                    'minLength',
642 45
                    'maxLength',
643 45
                    'minInclusive',
644 45
                    'maxInclusive',
645 45
                    'minExclusive',
646 45
                    'maxExclusive',
647 45
                    'fractionDigits',
648 45
                    'totalDigits',
649 45
                    'whiteSpace',
650 45
                ],
651
                true
652 45
            )
653 45
        ) {
654 45
            static::definitelyLoadRestrictionOnChildNode(
655 45
                $restriction,
656
                $childNode
657 45
            );
658 45
        }
659 45
    }
660
661 45
    private static function definitelyLoadRestrictionOnChildNode(
662
        Restriction $restriction,
663
        DOMElement $childNode
664
    ) {
665 45
        $restriction->addCheck(
666 45
            $childNode->localName,
667
            [
668 45
                'value' => $childNode->getAttribute('value'),
669 45
                'doc' => self::getDocumentation($childNode),
670
            ]
671 45
        );
672 45
    }
673
674
    /**
675
     * @return Closure
676
     */
677 45
    private function loadElementDef(Schema $schema, DOMElement $node)
678
    {
679 45
        return $this->loadAttributeOrElementDef($schema, $node, false);
680
    }
681
682
    /**
683
     * @param bool $checkAbstract
684
     */
685 45
    private function fillTypeNode(Type $type, DOMElement $node, $checkAbstract = false)
686
    {
687 45
        if ($checkAbstract) {
688 45
            $type->setAbstract($node->getAttribute('abstract') === 'true' || $node->getAttribute('abstract') === '1');
689 45
        }
690
        static $methods = [
691
            'restriction' => 'loadRestriction',
692
            'extension' => 'maybeLoadExtensionFromBaseComplexType',
693
            'simpleContent' => 'fillTypeNode',
694
            'complexContent' => 'fillTypeNode',
695 45
        ];
696
697
        /**
698
         * @var string[]
699
         */
700 45
        $methods = $methods;
701
702 45
        $this->maybeCallMethodAgainstDOMNodeList($node, $type, $methods);
703 45
    }
704
705 45
    private function fillItemNonLocalType(Item $element, DOMElement $node)
706
    {
707 45
        if ($node->getAttribute('type')) {
708
            /**
709
             * @var Type
710
             */
711 45
            $type = $this->findSomeType($element, $node, 'type');
712 45
        } else {
713
            /**
714
             * @var Type
715
             */
716 45
            $type = $this->findSomeTypeFromAttribute(
717 45
                $element,
718 45
                $node,
719 45
                ($node->lookupPrefix(self::XSD_NS).':anyType')
720 45
            );
721
        }
722
723 45
        $element->setType($type);
724 45
    }
725
726
    /**
727
     * @param string $attributeName
728
     *
729
     * @return SchemaItem
730
     */
731 45
    private function findSomeType(
732
        SchemaItem $fromThis,
733
        DOMElement $node,
734
        $attributeName
735
    ) {
736 45
        return $this->findSomeTypeFromAttribute(
737 45
            $fromThis,
738 45
            $node,
739 45
            $node->getAttribute($attributeName)
740 45
        );
741
    }
742
743
    /**
744
     * @param string $attributeName
745
     *
746
     * @return SchemaItem
747
     */
748 45
    private function findSomeTypeFromAttribute(
749
        SchemaItem $fromThis,
750
        DOMElement $node,
751
        $attributeName
752
    ) {
753
        /**
754
         * @var SchemaItem
755
         */
756 45
        $out = $this->findSomething(
757 45
            'findType',
758 45
            $fromThis->getSchema(),
759 45
            $node,
760
            $attributeName
761 45
        );
762
763 45
        return $out;
764
    }
765
766
    /**
767
     * @param mixed[][] $commonMethods
768
     * @param mixed[][] $methods
769
     * @param mixed[][] $commonArguments
770
     *
771
     * @return mixed
772
     */
773 45
    private function maybeCallCallableWithArgs(
774
        DOMElement $childNode,
775
        array $commonMethods = [],
776
        array $methods = [],
777
        array $commonArguments = []
778
    ) {
779 45
        foreach ($commonMethods as $commonMethodsSpec) {
780 45
            list($localNames, $callable, $args) = $commonMethodsSpec;
781
782
            /**
783
             * @var string[]
784
             */
785 45
            $localNames = $localNames;
786
787
            /**
788
             * @var callable
789
             */
790 45
            $callable = $callable;
791
792
            /**
793
             * @var mixed[]
794
             */
795 45
            $args = $args;
796
797 45
            if (in_array($childNode->localName, $localNames)) {
798 45
                return call_user_func_array($callable, $args);
799
            }
800 45
        }
801 45
        foreach ($commonArguments as $commonArgumentSpec) {
802
            /*
803
            * @var mixed[] $commonArgumentSpec
804
            */
805 45
            list($callables, $args) = $commonArgumentSpec;
806
807
            /**
808
             * @var callable[]
809
             */
810 45
            $callables = $callables;
811
812
            /**
813
             * @var mixed[]
814
             */
815 45
            $args = $args;
816
817 45
            if (isset($callables[$childNode->localName])) {
818 45
                return call_user_func_array(
819 45
                    $callables[$childNode->localName],
820
                    $args
821 45
                );
822
            }
823 45
        }
824 45
        if (isset($methods[$childNode->localName])) {
825 45
            list($callable, $args) = $methods[$childNode->localName];
826
827
            /**
828
             * @var callable
829
             */
830 45
            $callable = $callable;
831
832
            /**
833
             * @var mixed[]
834
             */
835 45
            $args = $args;
836
837 45
            return call_user_func_array($callable, $args);
838
        }
839 45
    }
840
841 45
    private function maybeLoadSequenceFromElementContainer(
842
        BaseComplexType $type,
843
        DOMElement $childNode
844
    ) {
845 45
        $this->maybeLoadThingFromThing(
846 45
            $type,
847 45
            $childNode,
848 45
            ElementContainer::class,
849
            'loadSequence'
850 45
        );
851 45
    }
852
853
    /**
854
     * @param string $instanceof
855
     * @param string $passTo
856
     */
857 45
    private function maybeLoadThingFromThing(
858
        Type $type,
859
        DOMElement $childNode,
860
        $instanceof,
861
        $passTo
862
    ) {
863 45
        if (!is_a($type, $instanceof, true)) {
864
            /**
865
             * @var string
866
             */
867
            $class = static::class;
868
            throw new RuntimeException(
869
                'Argument 1 passed to '.
870
                __METHOD__.
871
                ' needs to be an instance of '.
872
                $instanceof.
873
                ' when passed onto '.
874
                $class.
875
                '::'.
876
                $passTo.
877
                '(), '.
878
                (string) get_class($type).
879
                ' given.'
880
            );
881
        }
882
883 45
        $this->$passTo($type, $childNode);
884 45
    }
885
886
    /**
887
     * @param Closure|null $callback
888
     *
889
     * @return Closure
890
     */
891 45
    private function makeCallbackCallback(
892
        Type $type,
893
        DOMElement $node,
894
        Closure $callbackCallback,
895
        $callback = null
896
    ) {
897
        return function (
898
        ) use (
899 45
            $type,
900 45
            $node,
901 45
            $callbackCallback,
902 45
            $callback
903
        ) {
904 45
            $this->runCallbackAgainstDOMNodeList(
905 45
                $type,
906 45
                $node,
907 45
                $callbackCallback,
908
                $callback
909 45
            );
910 45
        };
911
    }
912
913
    /**
914
     * @param Closure|null $callback
915
     */
916 45
    private function runCallbackAgainstDOMNodeList(
917
        Type $type,
918
        DOMElement $node,
919
        Closure $againstNodeList,
920
        $callback = null
921
    ) {
922 45
        $this->fillTypeNode($type, $node, true);
923
924 45
        static::againstDOMNodeList($node, $againstNodeList);
925
926 45
        if ($callback) {
927 45
            call_user_func($callback, $type);
928 45
        }
929 45
    }
930
931 45
    private function maybeLoadExtensionFromBaseComplexType(
932
        Type $type,
933
        DOMElement $childNode
934
    ) {
935 45
        $this->maybeLoadThingFromThing(
936 45
            $type,
937 45
            $childNode,
938 45
            BaseComplexType::class,
939
            'loadExtension'
940 45
        );
941 45
    }
942
943
    const XSD_NS = 'http://www.w3.org/2001/XMLSchema';
944
945
    const XML_NS = 'http://www.w3.org/XML/1998/namespace';
946
947
    /**
948
     * @var string[]
949
     */
950
    protected $knownLocationSchemas = [
951
        'http://www.w3.org/2001/xml.xsd' => (
952
            __DIR__.'/Resources/xml.xsd'
953
        ),
954
        'http://www.w3.org/2001/XMLSchema.xsd' => (
955
            __DIR__.'/Resources/XMLSchema.xsd'
956
        ),
957
        'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' => (
958
            __DIR__.'/Resources/oasis-200401-wss-wssecurity-secext-1.0.xsd'
959
        ),
960
        'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd' => (
961
            __DIR__.'/Resources/oasis-200401-wss-wssecurity-utility-1.0.xsd'
962
        ),
963
        'https://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd' => (
964
            __DIR__.'/Resources/xmldsig-core-schema.xsd'
965
        ),
966
        'http://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd' => (
967
            __DIR__.'/Resources/xmldsig-core-schema.xsd'
968
        ),
969
    ];
970
971
    /**
972
     * @var string[]
973
     */
974
    protected static $globalSchemaInfo = array(
975
        self::XML_NS => 'http://www.w3.org/2001/xml.xsd',
976
        self::XSD_NS => 'http://www.w3.org/2001/XMLSchema.xsd',
977
    );
978
979
    /**
980
     * @param string $remote
981
     * @param string $local
982
     */
983
    public function addKnownSchemaLocation($remote, $local)
984
    {
985
        $this->knownLocationSchemas[$remote] = $local;
986
    }
987
988
    /**
989
     * @param string $remote
990
     *
991
     * @return bool
992
     */
993 1
    private function hasKnownSchemaLocation($remote)
994
    {
995 1
        return isset($this->knownLocationSchemas[$remote]);
996
    }
997
998
    /**
999
     * @param string $remote
1000
     *
1001
     * @return string
1002
     */
1003
    private function getKnownSchemaLocation($remote)
1004
    {
1005
        return $this->knownLocationSchemas[$remote];
1006
    }
1007
1008
    /**
1009
     * @param DOMElement $node
1010
     *
1011
     * @return string
1012
     */
1013 45
    private static function getDocumentation(DOMElement $node)
1014
    {
1015 45
        $doc = '';
1016 45
        static::againstDOMNodeList(
1017 45
            $node,
1018
            function (
1019
                DOMElement $node,
1020
                DOMElement $childNode
1021
            ) use (
1022
                &$doc
1023
            ) {
1024 45
                if ($childNode->localName == 'annotation') {
1025 45
                    $doc .= static::getDocumentation($childNode);
1026 45
                } elseif ($childNode->localName == 'documentation') {
1027 45
                    $doc .= $childNode->nodeValue;
1028 45
                }
1029 45
            }
1030 45
        );
1031 45
        $doc = preg_replace('/[\t ]+/', ' ', $doc);
1032
1033 45
        return trim($doc);
1034
    }
1035
1036
    /**
1037
     * @param string[] $methods
1038
     * @param string   $key
1039
     *
1040
     * @return Closure|null
1041
     */
1042 45
    private function maybeCallMethod(
1043
        array $methods,
1044
        $key,
1045
        DOMNode $childNode,
1046
        ...$args
1047
    ) {
1048 45
        if ($childNode instanceof DOMElement && isset($methods[$key])) {
1049 45
            $method = $methods[$key];
1050
1051
            /**
1052
             * @var Closure|null
1053
             */
1054 45
            $append = $this->$method(...$args);
1055
1056 45
            if ($append instanceof Closure) {
1057 45
                return $append;
1058
            }
1059 45
        }
1060 45
    }
1061
1062
    /**
1063
     * @param Schema     $schema
1064
     * @param DOMElement $node
1065
     * @param Schema     $parent
1066
     *
1067
     * @return Closure[]
1068
     */
1069 45
    private function schemaNode(Schema $schema, DOMElement $node, Schema $parent = null)
1070
    {
1071 45
        $this->setSchemaThingsFromNode($schema, $node, $parent);
1072 45
        $functions = array();
1073
1074
        $thisMethods = [
1075 45
            'attributeGroup' => [$this, 'loadAttributeGroup'],
1076 45
            'include' => [$this, 'loadImport'],
1077 45
            'import' => [$this, 'loadImport'],
1078 45
            'element' => [$this, 'loadElementDef'],
1079 45
            'attribute' => [$this, 'loadAttributeDef'],
1080 45
            'group' => [$this, 'loadGroup'],
1081 45
            'complexType' => [$this, 'loadComplexType'],
1082 45
            'simpleType' => [$this, 'loadSimpleType'],
1083 45
        ];
1084
1085 45
        static::againstDOMNodeList(
1086 45
            $node,
1087
            function (
1088
                DOMElement $node,
1089
                DOMElement $childNode
1090
            ) use (
1091 45
                $schema,
1092 45
                $thisMethods,
1093
                &$functions
1094
            ) {
1095
                /**
1096
                 * @var Closure|null
1097
                 */
1098 45
                $callback = $this->maybeCallCallableWithArgs(
1099 45
                    $childNode,
1100 45
                    [],
1101 45
                    [],
1102
                    [
1103
                        [
1104 45
                            $thisMethods,
1105
                            [
1106 45
                                $schema,
1107 45
                                $childNode,
1108 45
                            ],
1109 45
                        ],
1110
                    ]
1111 45
                );
1112
1113 45
                if ($callback instanceof Closure) {
1114 45
                    $functions[] = $callback;
1115 45
                }
1116 45
            }
1117 45
        );
1118
1119 45
        return $functions;
1120
    }
1121
1122
    /**
1123
     * @return InterfaceSetMinMax
1124
     */
1125 45
    private static function maybeSetMax(InterfaceSetMinMax $ref, DOMElement $node)
1126
    {
1127
        if (
1128 45
            $node->hasAttribute('maxOccurs')
1129 45
        ) {
1130 45
            $ref->setMax($node->getAttribute('maxOccurs') == 'unbounded' ? -1 : (int) $node->getAttribute('maxOccurs'));
1131 45
        }
1132
1133 45
        return $ref;
1134
    }
1135
1136
    /**
1137
     * @return InterfaceSetMinMax
1138
     */
1139 45
    private static function maybeSetMin(InterfaceSetMinMax $ref, DOMElement $node)
1140
    {
1141 45
        if ($node->hasAttribute('minOccurs')) {
1142 45
            $ref->setMin((int) $node->getAttribute('minOccurs'));
1143 45
        }
1144
1145 45
        return $ref;
1146
    }
1147
1148 45
    private function findAndSetSomeBase(
1149
        Type $type,
1150
        Base $setBaseOnThis,
1151
        DOMElement $node
1152
    ) {
1153
        /**
1154
         * @var Type
1155
         */
1156 45
        $parent = $this->findSomeType($type, $node, 'base');
1157 45
        $setBaseOnThis->setBase($parent);
1158 45
    }
1159
1160
    /**
1161
     * @param string     $finder
1162
     * @param Schema     $schema
1163
     * @param DOMElement $node
1164
     * @param string     $typeName
1165
     *
1166
     * @throws TypeException
1167
     *
1168
     * @return ElementItem|Group|AttributeItem|AttributeGroup|Type
1169
     */
1170 45
    private function findSomething($finder, Schema $schema, DOMElement $node, $typeName)
1171
    {
1172 45
        list($name, $namespace) = static::splitParts($node, $typeName);
1173
1174
        /**
1175
         * @var string|null
1176
         */
1177 45
        $namespace = $namespace ?: $schema->getTargetNamespace();
1178
1179
        try {
1180
            /**
1181
             * @var ElementItem|Group|AttributeItem|AttributeGroup|Type
1182
             */
1183 45
            $out = $schema->$finder($name, $namespace);
1184
1185 45
            return $out;
1186
        } catch (TypeNotFoundException $e) {
1187
            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);
1188
        }
1189
    }
1190
1191 45
    private function fillItem(Item $element, DOMElement $node)
1192
    {
1193
        /**
1194
         * @var bool
1195
         */
1196 45
        $skip = false;
1197 45
        static::againstDOMNodeList(
1198 45
            $node,
1199
            function (
1200
                DOMElement $node,
1201
                DOMElement $childNode
1202
            ) use (
1203 45
                $element,
1204
                &$skip
1205
            ) {
1206
                if (
1207 45
                    !$skip &&
1208 45
                    in_array(
1209 45
                        $childNode->localName,
1210
                        [
1211 45
                            'complexType',
1212 45
                            'simpleType',
1213
                        ]
1214 45
                    )
1215 45
                ) {
1216 45
                    $this->loadTypeWithCallback(
1217 45
                        $element->getSchema(),
1218 45
                        $childNode,
1219
                        function (Type $type) use ($element) {
1220 45
                            $element->setType($type);
1221 45
                        }
1222 45
                    );
1223 45
                    $skip = true;
1224 45
                }
1225 45
            }
1226 45
        );
1227 45
        if ($skip) {
1228 45
            return;
1229
        }
1230 45
        $this->fillItemNonLocalType($element, $node);
1231 45
    }
1232
1233
    /**
1234
     * @var Schema|null
1235
     */
1236
    protected $globalSchema;
1237
1238
    /**
1239
     * @return Schema[]
1240
     */
1241 45
    private function setupGlobalSchemas(array &$callbacks)
1242
    {
1243 45
        $globalSchemas = array();
1244 45
        foreach (self::$globalSchemaInfo as $namespace => $uri) {
1245 45
            self::setLoadedFile(
1246 45
                $uri,
1247 45
                $globalSchemas[$namespace] = $schema = new Schema()
1248 45
            );
1249 45
            if ($namespace === self::XSD_NS) {
1250 45
                $this->globalSchema = $schema;
1251 45
            }
1252 45
            $xml = $this->getDOM($this->knownLocationSchemas[$uri]);
1253 45
            $callbacks = array_merge($callbacks, $this->schemaNode($schema, $xml->documentElement));
1254 45
        }
1255
1256 45
        return $globalSchemas;
1257
    }
1258
1259
    /**
1260
     * @return string[]
1261
     */
1262 45
    public function getGlobalSchemaInfo()
1263
    {
1264 45
        return self::$globalSchemaInfo;
1265
    }
1266
1267
    /**
1268
     * @return Schema
1269
     */
1270 45
    public function getGlobalSchema()
1271
    {
1272 45
        if (!$this->globalSchema) {
1273 45
            $callbacks = array();
1274 45
            $globalSchemas = $this->setupGlobalSchemas($callbacks);
1275
1276 45
            $globalSchemas[static::XSD_NS]->addType(new SimpleType($globalSchemas[static::XSD_NS], 'anySimpleType'));
1277 45
            $globalSchemas[static::XSD_NS]->addType(new SimpleType($globalSchemas[static::XSD_NS], 'anyType'));
1278
1279 45
            $globalSchemas[static::XML_NS]->addSchema(
1280 45
                $globalSchemas[static::XSD_NS],
1281
                (string) static::XSD_NS
1282 45
            );
1283 45
            $globalSchemas[static::XSD_NS]->addSchema(
1284 45
                $globalSchemas[static::XML_NS],
1285
                (string) static::XML_NS
1286 45
            );
1287
1288
            /**
1289
             * @var Closure
1290
             */
1291 45
            foreach ($callbacks as $callback) {
1292 45
                $callback();
1293 45
            }
1294 45
        }
1295
1296
        /**
1297
         * @var Schema
1298
         */
1299 45
        $out = $this->globalSchema;
1300
1301 45
        return $out;
1302
    }
1303
1304
    /**
1305
     * @param DOMElement $node
1306
     * @param string     $file
1307
     *
1308
     * @return Schema
1309
     */
1310 45
    private function readNode(DOMElement $node, $file = 'schema.xsd')
1311
    {
1312 45
        $fileKey = $node->hasAttribute('targetNamespace') ? $this->getNamespaceSpecificFileIndex($file, $node->getAttribute('targetNamespace')) : $file;
1313 45
        self::setLoadedFile($fileKey, $rootSchema = new Schema());
1314
1315 45
        $rootSchema->addSchema($this->getGlobalSchema());
1316 45
        $callbacks = $this->schemaNode($rootSchema, $node);
1317
1318 45
        foreach ($callbacks as $callback) {
1319 39
            call_user_func($callback);
1320 45
        }
1321
1322 45
        return $rootSchema;
1323
    }
1324
1325
    /**
1326
     * It is possible that a single file contains multiple <xsd:schema/> nodes, for instance in a WSDL file.
1327
     *
1328
     * Each of these  <xsd:schema/> nodes typically target a specific namespace. Append the target namespace to the
1329
     * file to distinguish between multiple schemas in a single file.
1330
     *
1331
     * @param string $file
1332
     * @param string $targetNamespace
1333
     *
1334
     * @return string
1335
     */
1336 45
    private function getNamespaceSpecificFileIndex($file, $targetNamespace)
1337
    {
1338 45
        return $file.'#'.$targetNamespace;
1339
    }
1340
1341
    /**
1342
     * @param string $content
1343
     * @param string $file
1344
     *
1345
     * @return Schema
1346
     *
1347
     * @throws IOException
1348
     */
1349 44
    public function readString($content, $file = 'schema.xsd')
1350
    {
1351 44
        $xml = new DOMDocument('1.0', 'UTF-8');
1352 44
        if (!$xml->loadXML($content)) {
1353
            throw new IOException("Can't load the schema");
1354
        }
1355 44
        $xml->documentURI = $file;
1356
1357 44
        return $this->readNode($xml->documentElement, $file);
1358
    }
1359
1360
    /**
1361
     * @param string $file
1362
     *
1363
     * @return Schema
1364
     */
1365 1
    public function readFile($file)
1366
    {
1367 1
        $xml = $this->getDOM($file);
1368
1369 1
        return $this->readNode($xml->documentElement, $file);
1370
    }
1371
1372
    /**
1373
     * @param string $file
1374
     *
1375
     * @return DOMDocument
1376
     *
1377
     * @throws IOException
1378
     */
1379 45
    private function getDOM($file)
1380
    {
1381 45
        $xml = new DOMDocument('1.0', 'UTF-8');
1382 45
        if (!$xml->load($file)) {
1383
            throw new IOException("Can't load the file $file");
1384
        }
1385
1386 45
        return $xml;
1387
    }
1388
1389 45
    private static function againstDOMNodeList(
1390
        DOMElement $node,
1391
        Closure $againstNodeList
1392
    ) {
1393 45
        $limit = $node->childNodes->length;
1394 45
        for ($i = 0; $i < $limit; $i += 1) {
1395
            /**
1396
             * @var DOMNode
1397
             */
1398 45
            $childNode = $node->childNodes->item($i);
1399
1400 45
            if ($childNode instanceof DOMElement) {
1401 45
                $againstNodeList(
1402 45
                    $node,
1403
                    $childNode
1404 45
                );
1405 45
            }
1406 45
        }
1407 45
    }
1408
1409 45
    private function maybeCallMethodAgainstDOMNodeList(
1410
        DOMElement $node,
1411
        SchemaItem $type,
1412
        array $methods
1413
    ) {
1414 45
        static::againstDOMNodeList(
1415 45
            $node,
1416 45
            $this->CallbackGeneratorMaybeCallMethodAgainstDOMNodeList(
1417 45
                $type,
1418
                $methods
1419 45
            )
1420 45
        );
1421 45
    }
1422
1423
    /**
1424
     * @return Closure
1425
     */
1426 45
    private function CallbackGeneratorMaybeCallMethodAgainstDOMNodeList(
1427
        SchemaItem $type,
1428
        array $methods
1429
    ) {
1430
        return function (
1431
            DOMElement $node,
1432
            DOMElement $childNode
1433
        ) use (
1434 45
            $methods,
1435 45
            $type
1436
        ) {
1437
            /**
1438
             * @var string[]
1439
             */
1440 45
            $methods = $methods;
1441
1442 45
            $this->maybeCallMethod(
1443 45
                $methods,
1444 45
                $childNode->localName,
1445 45
                $childNode,
1446 45
                $type,
1447
                $childNode
1448 45
            );
1449 45
        };
1450
    }
1451
1452 45
    private function loadTypeWithCallbackOnChildNodes(
1453
        Schema $schema,
1454
        DOMElement $node,
1455
        Closure $callback
1456
    ) {
1457 45
        self::againstDOMNodeList(
1458 45
            $node,
1459
            function (
1460
                DOMElement $node,
1461
                DOMElement $childNode
1462
            ) use (
1463 45
                $schema,
1464 45
                $callback
1465
            ) {
1466 45
                $this->loadTypeWithCallback(
1467 45
                    $schema,
1468 45
                    $childNode,
1469
                    $callback
1470 45
                );
1471 45
            }
1472 45
        );
1473 45
    }
1474
1475 45
    private function loadTypeWithCallback(
1476
        Schema $schema,
1477
        DOMElement $childNode,
1478
        Closure $callback
1479
    ) {
1480
        $methods = [
1481 45
            'complexType' => 'loadComplexType',
1482 45
            'simpleType' => 'loadSimpleType',
1483 45
        ];
1484
1485
        /**
1486
         * @var Closure|null
1487
         */
1488 45
        $func = $this->maybeCallMethod(
1489 45
            $methods,
1490 45
            $childNode->localName,
1491 45
            $childNode,
1492 45
            $schema,
1493 45
            $childNode,
1494
            $callback
1495 45
        );
1496
1497 45
        if ($func instanceof Closure) {
1498 45
            call_user_func($func);
1499 45
        }
1500 45
    }
1501
1502
    /**
1503
     * @param string $file
1504
     * @param string $namespace
1505
     *
1506
     * @return Closure
1507
     */
1508 45
    private function loadImport(
1509
        Schema $schema,
1510
        DOMElement $node
1511
    ) {
1512 45
        $base = urldecode($node->ownerDocument->documentURI);
1513 45
        $file = UrlUtils::resolveRelativeUrl($base, $node->getAttribute('schemaLocation'));
1514
1515 45
        $namespace = $node->getAttribute('namespace');
1516
1517 45
        $keys = $this->loadImportFreshKeys($namespace, $file);
1518
1519
        if (
1520 45
            self::hasLoadedFile(...$keys)
1521 45
        ) {
1522 45
            $schema->addSchema(self::getLoadedFile(...$keys));
1523
1524
            return function () {
1525 45
            };
1526
        }
1527
1528 1
        return $this->loadImportFresh($namespace, $schema, $file);
1529
    }
1530
1531
    /**
1532
     * @param string $namespace
1533
     * @param string $file
1534
     *
1535
     * @return mixed[]
1536
     */
1537 45
    private function loadImportFreshKeys(
1538
        $namespace,
1539
        $file
1540
    ) {
1541 45
        $globalSchemaInfo = $this->getGlobalSchemaInfo();
1542
1543 45
        $keys = [];
1544
1545 45
        if (isset($globalSchemaInfo[$namespace])) {
1546 45
            $keys[] = $globalSchemaInfo[$namespace];
1547 45
        }
1548
1549 45
        $keys[] = $this->getNamespaceSpecificFileIndex(
1550 45
            $file,
1551
            $namespace
1552 45
        );
1553
1554 45
        $keys[] = $file;
1555
1556 45
        return $keys;
1557
    }
1558
1559
    /**
1560
     * @param string $namespace
1561
     * @param string $file
1562
     *
1563
     * @return Schema
1564
     */
1565 1
    private function loadImportFreshCallbacksNewSchema(
1566
        $namespace,
1567
        Schema $schema,
1568
        $file
1569
    ) {
1570
        /**
1571
         * @var Schema $newSchema
1572
         */
1573 1
        $newSchema = self::setLoadedFile(
1574 1
            $file,
1575 1
            ($namespace ? new Schema() : $schema)
1576 1
        );
1577
1578 1
        if ($namespace) {
1579
            $newSchema->addSchema($this->getGlobalSchema());
1580
            $schema->addSchema($newSchema);
1581
        }
1582
1583 1
        return $newSchema;
1584
    }
1585
1586
    /**
1587
     * @param string $namespace
1588
     * @param string $file
1589
     *
1590
     * @return Closure[]
1591
     */
1592 1
    private function loadImportFreshCallbacks(
1593
        $namespace,
1594
        Schema $schema,
1595
        $file
1596
    ) {
1597
        /**
1598
         * @var string
1599
         */
1600 1
        $file = $file;
1601
1602 1
        return $this->schemaNode(
1603 1
            $this->loadImportFreshCallbacksNewSchema(
1604 1
                $namespace,
1605 1
                $schema,
1606
                $file
1607 1
            ),
1608 1
            $this->getDOM(
1609 1
                $this->hasKnownSchemaLocation($file)
1610 1
                    ? $this->getKnownSchemaLocation($file)
1611
                    : $file
1612 1
            )->documentElement,
1613
            $schema
1614 1
        );
1615
    }
1616
1617
    /**
1618
     * @param string $namespace
1619
     * @param string $file
1620
     *
1621
     * @return Closure
1622
     */
1623 1
    private function loadImportFresh(
1624
        $namespace,
1625
        Schema $schema,
1626
        $file
1627
    ) {
1628
        return function () use ($namespace, $schema, $file) {
1629
            foreach (
1630 1
                $this->loadImportFreshCallbacks(
1631 1
                    $namespace,
1632 1
                    $schema,
1633
                    $file
1634 1
                ) as $callback
1635 1
            ) {
1636 1
                $callback();
1637 1
            }
1638 1
        };
1639
    }
1640
1641
    /**
1642
     * @return Element
1643
     */
1644 45
    private function loadElement(
1645
        Schema $schema,
1646
        DOMElement $node
1647
    ) {
1648 45
        $element = new Element($schema, $node->getAttribute('name'));
1649 45
        $element->setDoc(self::getDocumentation($node));
1650
1651 45
        $this->fillItem($element, $node);
1652
1653 45
        self::maybeSetMax($element, $node);
1654 45
        self::maybeSetMin($element, $node);
1655
1656 45
        $xp = new \DOMXPath($node->ownerDocument);
1657 45
        $xp->registerNamespace('xs', 'http://www.w3.org/2001/XMLSchema');
1658
1659 45
        if ($xp->query('ancestor::xs:choice', $node)->length) {
1660 45
            $element->setMin(0);
1661 45
        }
1662
1663 45
        if ($node->hasAttribute('nillable')) {
1664 3
            $element->setNil($node->getAttribute('nillable') == 'true');
1665 3
        }
1666 45
        if ($node->hasAttribute('form')) {
1667 3
            $element->setQualified($node->getAttribute('form') == 'qualified');
1668 3
        }
1669
1670 45
        return $element;
1671
    }
1672
1673
    /**
1674
     * @return ElementRef
1675
     */
1676 45
    private static function loadElementRef(
1677
        ElementDef $referenced,
1678
        DOMElement $node
1679
    ) {
1680 45
        $ref = new ElementRef($referenced);
1681 45
        $ref->setDoc(self::getDocumentation($node));
1682
1683 45
        self::maybeSetMax($ref, $node);
1684 45
        self::maybeSetMin($ref, $node);
1685 45
        if ($node->hasAttribute('nillable')) {
1686
            $ref->setNil($node->getAttribute('nillable') == 'true');
1687
        }
1688 45
        if ($node->hasAttribute('form')) {
1689
            $ref->setQualified($node->getAttribute('form') == 'qualified');
1690
        }
1691
1692 45
        return $ref;
1693
    }
1694
1695
    /**
1696
     * @return \Closure
1697
     */
1698 45
    private function loadAttributeGroup(
1699
        Schema $schema,
1700
        DOMElement $node
1701
    ) {
1702 45
        $attGroup = new AttributeGroup($schema, $node->getAttribute('name'));
1703 45
        $attGroup->setDoc(self::getDocumentation($node));
1704 45
        $schema->addAttributeGroup($attGroup);
1705
1706
        return function () use ($schema, $node, $attGroup) {
1707 45
            SchemaReader::againstDOMNodeList(
1708 45
                $node,
1709 45
                function (
1710
                    DOMElement $node,
1711
                    DOMElement $childNode
1712
                ) use (
1713 45
                    $schema,
1714 45
                    $attGroup
1715
                ) {
1716 45
                    switch ($childNode->localName) {
1717 45
                        case 'attribute':
1718 45
                            $attribute = $this->getAttributeFromAttributeOrRef(
1719 45
                                $childNode,
1720 45
                                $schema,
1721
                                $node
1722 45
                            );
1723 45
                            $attGroup->addAttribute($attribute);
1724 45
                            break;
1725 45
                        case 'attributeGroup':
1726 1
                            $this->findSomethingLikeAttributeGroup(
1727 1
                                $schema,
1728 1
                                $node,
1729 1
                                $childNode,
1730
                                $attGroup
1731 1
                            );
1732 1
                            break;
1733 45
                    }
1734 45
                }
1735 45
            );
1736 45
        };
1737
    }
1738
1739
    /**
1740
     * @return AttributeItem
1741
     */
1742 45
    private function getAttributeFromAttributeOrRef(
1743
        DOMElement $childNode,
1744
        Schema $schema,
1745
        DOMElement $node
1746
    ) {
1747 45
        if ($childNode->hasAttribute('ref')) {
1748
            /**
1749
             * @var AttributeItem
1750
             */
1751 45
            $attribute = $this->findSomething('findAttribute', $schema, $node, $childNode->getAttribute('ref'));
1752 45
        } else {
1753
            /**
1754
             * @var Attribute
1755
             */
1756 45
            $attribute = $this->loadAttribute($schema, $childNode);
1757
        }
1758
1759 45
        return $attribute;
1760
    }
1761
1762
    /**
1763
     * @return Attribute
1764
     */
1765 45
    private function loadAttribute(
1766
        Schema $schema,
1767
        DOMElement $node
1768
    ) {
1769 45
        $attribute = new Attribute($schema, $node->getAttribute('name'));
1770 45
        $attribute->setDoc(self::getDocumentation($node));
1771 45
        $this->fillItem($attribute, $node);
1772
1773 45
        if ($node->hasAttribute('nillable')) {
1774 1
            $attribute->setNil($node->getAttribute('nillable') == 'true');
1775 1
        }
1776 45
        if ($node->hasAttribute('form')) {
1777 1
            $attribute->setQualified($node->getAttribute('form') == 'qualified');
1778 1
        }
1779 45
        if ($node->hasAttribute('use')) {
1780 45
            $attribute->setUse($node->getAttribute('use'));
1781 45
        }
1782
1783 45
        return $attribute;
1784
    }
1785
1786 45
    private function addAttributeFromAttributeOrRef(
1787
        BaseComplexType $type,
1788
        DOMElement $childNode,
1789
        Schema $schema,
1790
        DOMElement $node
1791
    ) {
1792 45
        $attribute = $this->getAttributeFromAttributeOrRef(
1793 45
            $childNode,
1794 45
            $schema,
1795
            $node
1796 45
        );
1797
1798 45
        $type->addAttribute($attribute);
1799 45
    }
1800
1801 45
    private function findSomethingLikeAttributeGroup(
1802
        Schema $schema,
1803
        DOMElement $node,
1804
        DOMElement $childNode,
1805
        AttributeContainer $addToThis
1806
    ) {
1807
        /**
1808
         * @var AttributeItem
1809
         */
1810 45
        $attribute = $this->findSomething('findAttributeGroup', $schema, $node, $childNode->getAttribute('ref'));
1811 45
        $addToThis->addAttribute($attribute);
1812 45
    }
1813
1814
    /**
1815
     * @var Schema[]
1816
     */
1817
    protected static $loadedFiles = array();
1818
1819
    /**
1820
     * @param string ...$keys
1821
     *
1822
     * @return bool
1823
     */
1824 45
    private static function hasLoadedFile(...$keys)
1825
    {
1826 45
        foreach ($keys as $key) {
1827 45
            if (isset(self::$loadedFiles[$key])) {
1828 45
                return true;
1829
            }
1830 1
        }
1831
1832 1
        return false;
1833
    }
1834
1835
    /**
1836
     * @param string ...$keys
1837
     *
1838
     * @return Schema
1839
     *
1840
     * @throws RuntimeException if loaded file not found
1841
     */
1842 45
    public static function getLoadedFile(...$keys)
1843
    {
1844 45
        foreach ($keys as $key) {
1845 45
            if (isset(self::$loadedFiles[$key])) {
1846 45
                return self::$loadedFiles[$key];
1847
            }
1848
        }
1849
1850
        throw new RuntimeException('Loaded file was not found!');
1851
    }
1852
1853
    /**
1854
     * @param string $key
1855
     *
1856
     * @return Schema
1857
     */
1858 45
    private static function setLoadedFile($key, Schema $schema)
1859
    {
1860 45
        self::$loadedFiles[$key] = $schema;
1861
1862 45
        return $schema;
1863
    }
1864
1865 45
    private function setSchemaThingsFromNode(
1866
        Schema $schema,
1867
        DOMElement $node,
1868
        Schema $parent = null
1869
    ) {
1870 45
        $schema->setDoc(self::getDocumentation($node));
1871
1872 45
        if ($node->hasAttribute('targetNamespace')) {
1873 45
            $schema->setTargetNamespace($node->getAttribute('targetNamespace'));
1874 45
        } elseif ($parent) {
1875
            $schema->setTargetNamespace($parent->getTargetNamespace());
1876
        }
1877 45
        $schema->setElementsQualification($node->getAttribute('elementFormDefault') == 'qualified');
1878 45
        $schema->setAttributesQualification($node->getAttribute('attributeFormDefault') == 'qualified');
1879 45
        $schema->setDoc(self::getDocumentation($node));
1880 45
    }
1881
}
1882