Passed
Push — static-analysis ( 5cd72e...89afd5 )
by SignpostMarv
03:00
created

SchemaReader::getNamespaceSpecificFileIndex()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 2
crap 1
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 View Code Duplication
    private function loadSequenceChildNode(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
142
        ElementContainer $elementContainer,
143
        DOMElement $node,
144
        DOMElement $childNode,
145
        $max
146
    ) {
147 45
        switch ($childNode->localName) {
148 45
            case 'sequence':
149 45
            case 'choice':
150 45
            case 'all':
151 45
                $this->loadSequence(
152 45
                    $elementContainer,
153 45
                    $childNode,
154
                    $max
155 45
                );
156 45
                break;
157 45
            case 'element':
158 45
                $this->loadSequenceChildNodeLoadElement(
159 45
                    $elementContainer,
160 45
                    $node,
161 45
                    $childNode,
162
                    $max
163 45
                );
164 45
                break;
165 45
            case 'group':
166 45
                $this->addGroupAsElement(
167 45
                    $elementContainer->getSchema(),
168 45
                    $node,
169 45
                    $childNode,
170
                    $elementContainer
171 45
                );
172 45
                break;
173 45
        }
174 45
    }
175
176
    /**
177
     * @param int|null $max
178
     */
179 45
    private function loadSequenceChildNodeLoadElement(
180
        ElementContainer $elementContainer,
181
        DOMElement $node,
182
        DOMElement $childNode,
183
        $max
184
    ) {
185 45
        if ($childNode->hasAttribute('ref')) {
186
            /**
187
             * @var ElementDef $referencedElement
188
             */
189 45
            $referencedElement = $this->findSomething('findElement', $elementContainer->getSchema(), $node, $childNode->getAttribute('ref'));
190 45
            $element = static::loadElementRef(
191 45
                $referencedElement,
192
                $childNode
193 45
            );
194 45
        } else {
195 45
            $element = $this->loadElement(
196 45
                $elementContainer->getSchema(),
197
                $childNode
198 45
            );
199
        }
200 45
        if ($max > 1) {
201
            /*
202
            * although one might think the typecast is not needed with $max being `? int $max` after passing > 1,
203
            * phpstan@a4f89fa still thinks it's possibly null.
204
            * see https://github.com/phpstan/phpstan/issues/577 for related issue
205
            */
206 45
            $element->setMax((int) $max);
207 45
        }
208 45
        $elementContainer->addElement($element);
209 45
    }
210
211 45
    private function addGroupAsElement(
212
        Schema $schema,
213
        DOMElement $node,
214
        DOMElement $childNode,
215
        ElementContainer $elementContainer
216
    ) {
217
        /**
218
         * @var Group
219
         */
220 45
        $referencedGroup = $this->findSomething(
221 45
            'findGroup',
222 45
            $schema,
223 45
            $node,
224 45
            $childNode->getAttribute('ref')
225 45
        );
226
227 45
        $group = $this->loadGroupRef($referencedGroup, $childNode);
228 45
        $elementContainer->addElement($group);
229 45
    }
230
231
    /**
232
     * @return Closure
233
     */
234 45
    private function loadGroup(Schema $schema, DOMElement $node)
235
    {
236 45
        $group = static::loadGroupBeforeCheckingChildNodes(
237 45
            $schema,
238
            $node
239 45
        );
240
        static $methods = [
241
            'sequence' => 'loadSequence',
242
            'choice' => 'loadSequence',
243
            'all' => 'loadSequence',
244 45
        ];
245
246
        return function () use ($group, $node, $methods) {
247
            /**
248
             * @var string[]
249
             */
250 45
            $methods = $methods;
251 45
            $this->maybeCallMethodAgainstDOMNodeList(
252 45
                $node,
253 45
                $group,
254
                $methods
255 45
            );
256 45
        };
257
    }
258
259
    /**
260
     * @return Group|GroupRef
261
     */
262 45
    private static function loadGroupBeforeCheckingChildNodes(
263
        Schema $schema,
264
        DOMElement $node
265
    ) {
266 45
        $group = new Group($schema, $node->getAttribute('name'));
267 45
        $group->setDoc(self::getDocumentation($node));
268
269 45
        if ($node->hasAttribute('maxOccurs')) {
270
            /**
271
             * @var GroupRef
272
             */
273
            $group = self::maybeSetMax(new GroupRef($group), $node);
274
        }
275 45
        if ($node->hasAttribute('minOccurs')) {
276
            /**
277
             * @var GroupRef
278
             */
279
            $group = self::maybeSetMin(
280
                $group instanceof GroupRef ? $group : new GroupRef($group),
281
                $node
282
            );
283
        }
284
285 45
        $schema->addGroup($group);
286
287 45
        return $group;
288
    }
289
290
    /**
291
     * @return GroupRef
292
     */
293 45
    private function loadGroupRef(Group $referenced, DOMElement $node)
294
    {
295 45
        $ref = new GroupRef($referenced);
296 45
        $ref->setDoc(self::getDocumentation($node));
297
298 45
        self::maybeSetMax($ref, $node);
299 45
        self::maybeSetMin($ref, $node);
300
301 45
        return $ref;
302
    }
303
304
    /**
305
     * @return BaseComplexType
306
     */
307 45
    private function loadComplexTypeBeforeCallbackCallback(
308
        Schema $schema,
309
        DOMElement $node
310
    ) {
311
        /**
312
         * @var bool
313
         */
314 45
        $isSimple = false;
315
316 45
        static::againstDOMNodeList(
317 45
            $node,
318
            function (
319
                DOMElement $node,
320
                DOMElement $childNode
321
            ) use (
322
                &$isSimple
323
            ) {
324 45
                if ($isSimple) {
325 1
                    return;
326
                }
327 45
                if ($childNode->localName === 'simpleContent') {
328 2
                    $isSimple = true;
329 2
                }
330 45
            }
331 45
        );
332
333 45
        $type = $isSimple ? new ComplexTypeSimpleContent($schema, $node->getAttribute('name')) : new ComplexType($schema, $node->getAttribute('name'));
334
335 45
        $type->setDoc(static::getDocumentation($node));
336 45
        if ($node->getAttribute('name')) {
337 45
            $schema->addType($type);
338 45
        }
339
340 45
        return $type;
341
    }
342
343
    /**
344
     * @param Closure|null $callback
345
     *
346
     * @return Closure
347
     */
348 45
    private function loadComplexType(Schema $schema, DOMElement $node, $callback = null)
349
    {
350 45
        $type = $this->loadComplexTypeBeforeCallbackCallback($schema, $node);
351
352 45
        return $this->makeCallbackCallback(
353 45
            $type,
354 45
            $node,
355
            function (
356
                DOMElement $node,
357
                DOMElement $childNode
358
            ) use (
359 45
                $schema,
360 45
                $type
361
            ) {
362 45
                $this->loadComplexTypeFromChildNode(
363 45
                    $type,
364 45
                    $node,
365 45
                    $childNode,
366
                    $schema
367 45
                );
368 45
            },
369
            $callback
370 45
        );
371
    }
372
373 45
    private function loadComplexTypeFromChildNode(
374
        BaseComplexType $type,
375
        DOMElement $node,
376
        DOMElement $childNode,
377
        Schema $schema
378
    ) {
379 45
        switch ($childNode->localName) {
380 45
            case 'sequence':
381 45
            case 'choice':
382 45
            case 'all':
383 45
                if ($type instanceof ElementContainer) {
384 45
                    $this->loadSequence(
385 45
                        $type,
386
                        $childNode
387 45
                    );
388 45
                }
389 45
                break;
390 45
            case 'attribute':
391 45
                $this->addAttributeFromAttributeOrRef(
392 45
                    $type,
393 45
                    $childNode,
394 45
                    $schema,
395
                    $node
396 45
                );
397 45
                break;
398 45
            case 'attributeGroup':
399 2
                $this->findSomethingLikeAttributeGroup(
400 2
                    $schema,
401 2
                    $node,
402 2
                    $childNode,
403
                    $type
404 2
                );
405 2
                break;
406 45
            case 'group':
407
                if (
408
                    $type instanceof ComplexType
409 1
                ) {
410 1
                    $this->addGroupAsElement(
411 1
                        $schema,
412 1
                        $node,
413 1
                        $childNode,
414
                        $type
415 1
                    );
416 1
                }
417 1
                break;
418 45
        }
419 45
    }
420
421
    /**
422
     * @param Closure|null $callback
423
     *
424
     * @return Closure
425
     */
426 45
    private function loadSimpleType(Schema $schema, DOMElement $node, $callback = null)
427
    {
428 45
        $type = new SimpleType($schema, $node->getAttribute('name'));
429 45
        $type->setDoc(static::getDocumentation($node));
430 45
        if ($node->getAttribute('name')) {
431 45
            $schema->addType($type);
432 45
        }
433
434 45
        return $this->makeCallbackCallback(
435 45
            $type,
436 45
            $node,
437 45
            $this->CallbackGeneratorMaybeCallMethodAgainstDOMNodeList(
438 45
                $type,
439
                [
440 45
                    'union' => 'loadUnion',
441 45
                    'list' => 'loadList',
442
                ]
443 45
            ),
444
            $callback
445 45
        );
446
    }
447
448 45
    private function loadList(SimpleType $type, DOMElement $node)
449
    {
450 45
        if ($node->hasAttribute('itemType')) {
451
            /**
452
             * @var SimpleType
453
             */
454 45
            $listType = $this->findSomeType($type, $node, 'itemType');
455 45
            $type->setList($listType);
456 45
        } else {
457
            $addCallback = function (SimpleType $list) use ($type) {
458 45
                $type->setList($list);
459 45
            };
460
461 45
            $this->loadTypeWithCallbackOnChildNodes(
462 45
                $type->getSchema(),
463 45
                $node,
464
                $addCallback
465 45
            );
466
        }
467 45
    }
468
469 45
    private function loadUnion(SimpleType $type, DOMElement $node)
470
    {
471 45
        if ($node->hasAttribute('memberTypes')) {
472 45
            $types = preg_split('/\s+/', $node->getAttribute('memberTypes'));
473 45
            foreach ($types as $typeName) {
474
                /**
475
                 * @var SimpleType
476
                 */
477 45
                $unionType = $this->findSomeTypeFromAttribute(
478 45
                    $type,
479 45
                    $node,
480
                    $typeName
481 45
                );
482 45
                $type->addUnion($unionType);
483 45
            }
484 45
        }
485
        $addCallback = function (SimpleType $unType) use ($type) {
486 45
            $type->addUnion($unType);
487 45
        };
488
489 45
        $this->loadTypeWithCallbackOnChildNodes(
490 45
            $type->getSchema(),
491 45
            $node,
492
            $addCallback
493 45
        );
494 45
    }
495
496 45
    private function loadExtensionChildNodes(
497
        BaseComplexType $type,
498
        DOMElement $node
499
    ) {
500 45
        static::againstDOMNodeList(
501 45
            $node,
502 View Code Duplication
            function (
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
503
                DOMElement $node,
504
                DOMElement $childNode
505
            ) use (
506 45
                $type
507
            ) {
508 45
                switch ($childNode->localName) {
509 45
                    case 'sequence':
510 45
                    case 'choice':
511 45
                    case 'all':
512 45
                        if ($type instanceof ElementContainer) {
513 45
                            $this->loadSequence(
514 45
                                $type,
515
                                $childNode
516 45
                            );
517 45
                        }
518 45
                        break;
519 45
                    case 'attribute':
520 45
                        $this->addAttributeFromAttributeOrRef(
521 45
                            $type,
522 45
                            $childNode,
523 45
                            $type->getSchema(),
524
                            $node
525 45
                        );
526 45
                        break;
527 45
                    case 'attributeGroup':
528 45
                        $this->findSomethingLikeAttributeGroup(
529 45
                            $type->getSchema(),
530 45
                            $node,
531 45
                            $childNode,
532
                            $type
533 45
                        );
534 45
                        break;
535 45
                }
536 45
            }
537 45
        );
538 45
    }
539
540 45
    private function loadExtension(BaseComplexType $type, DOMElement $node)
541
    {
542 45
        $extension = new Extension();
543 45
        $type->setExtension($extension);
544
545 45
        if ($node->hasAttribute('base')) {
546 45
            $this->findAndSetSomeBase(
547 45
                $type,
548 45
                $extension,
549
                $node
550 45
            );
551 45
        }
552 45
        $this->loadExtensionChildNodes($type, $node);
553 45
    }
554
555 45
    private function loadRestriction(Type $type, DOMElement $node)
556
    {
557 45
        $restriction = new Restriction();
558 45
        $type->setRestriction($restriction);
559 45
        if ($node->hasAttribute('base')) {
560 45
            $this->findAndSetSomeBase($type, $restriction, $node);
561 45
        } else {
562
            $addCallback = function (Type $restType) use ($restriction) {
563 45
                $restriction->setBase($restType);
564 45
            };
565
566 45
            $this->loadTypeWithCallbackOnChildNodes(
567 45
                $type->getSchema(),
568 45
                $node,
569
                $addCallback
570 45
            );
571
        }
572 45
        self::againstDOMNodeList(
573 45
            $node,
574
            function (
575
                DOMElement $node,
576
                DOMElement $childNode
577
            ) use (
578 45
                $restriction
579
            ) {
580
                if (
581 45
                    in_array(
582 45
                        $childNode->localName,
583
                        [
584 45
                            'enumeration',
585 45
                            'pattern',
586 45
                            'length',
587 45
                            'minLength',
588 45
                            'maxLength',
589 45
                            'minInclusive',
590 45
                            'maxInclusive',
591 45
                            'minExclusive',
592 45
                            'maxExclusive',
593 45
                            'fractionDigits',
594 45
                            'totalDigits',
595 45
                            'whiteSpace',
596 45
                        ],
597
                        true
598 45
                    )
599 45
                ) {
600 45
                    $restriction->addCheck(
601 45
                        $childNode->localName,
602
                        [
603 45
                            'value' => $childNode->getAttribute('value'),
604 45
                            'doc' => self::getDocumentation($childNode),
605
                        ]
606 45
                    );
607 45
                }
608 45
            }
609 45
        );
610 45
    }
611
612
    /**
613
     * @return Closure
614
     */
615 45
    private function loadElementDef(Schema $schema, DOMElement $node)
616
    {
617 45
        return $this->loadAttributeOrElementDef($schema, $node, false);
618
    }
619
620
    /**
621
     * @param bool $checkAbstract
622
     */
623 45
    private function fillTypeNode(Type $type, DOMElement $node, $checkAbstract = false)
624
    {
625 45
        if ($checkAbstract) {
626 45
            $type->setAbstract($node->getAttribute('abstract') === 'true' || $node->getAttribute('abstract') === '1');
627 45
        }
628
        static $methods = [
629
            'restriction' => 'loadRestriction',
630
            'extension' => 'maybeLoadExtensionFromBaseComplexType',
631
            'simpleContent' => 'fillTypeNode',
632
            'complexContent' => 'fillTypeNode',
633 45
        ];
634
635
        /**
636
         * @var string[]
637
         */
638 45
        $methods = $methods;
639
640 45
        $this->maybeCallMethodAgainstDOMNodeList($node, $type, $methods);
641 45
    }
642
643 45
    private function fillItemNonLocalType(Item $element, DOMElement $node)
644
    {
645 45
        if ($node->getAttribute('type')) {
646
            /**
647
             * @var Type
648
             */
649 45
            $type = $this->findSomeType($element, $node, 'type');
650 45
        } else {
651
            /**
652
             * @var Type
653
             */
654 45
            $type = $this->findSomeTypeFromAttribute(
655 45
                $element,
656 45
                $node,
657 45
                ($node->lookupPrefix(self::XSD_NS).':anyType')
658 45
            );
659
        }
660
661 45
        $element->setType($type);
662 45
    }
663
664
    /**
665
     * @param string $attributeName
666
     *
667
     * @return SchemaItem
668
     */
669 45
    private function findSomeType(
670
        SchemaItem $fromThis,
671
        DOMElement $node,
672
        $attributeName
673
    ) {
674 45
        return $this->findSomeTypeFromAttribute(
675 45
            $fromThis,
676 45
            $node,
677 45
            $node->getAttribute($attributeName)
678 45
        );
679
    }
680
681
    /**
682
     * @param string $attributeName
683
     *
684
     * @return SchemaItem
685
     */
686 45
    private function findSomeTypeFromAttribute(
687
        SchemaItem $fromThis,
688
        DOMElement $node,
689
        $attributeName
690
    ) {
691
        /**
692
         * @var SchemaItem
693
         */
694 45
        $out = $this->findSomething(
695 45
            'findType',
696 45
            $fromThis->getSchema(),
697 45
            $node,
698
            $attributeName
699 45
        );
700
701 45
        return $out;
702
    }
703
704
    /**
705
     * @param Closure|null $callback
706
     *
707
     * @return Closure
708
     */
709 45
    private function makeCallbackCallback(
710
        Type $type,
711
        DOMElement $node,
712
        Closure $callbackCallback,
713
        $callback = null
714
    ) {
715
        return function (
716
        ) use (
717 45
            $type,
718 45
            $node,
719 45
            $callbackCallback,
720 45
            $callback
721
        ) {
722 45
            $this->runCallbackAgainstDOMNodeList(
723 45
                $type,
724 45
                $node,
725 45
                $callbackCallback,
726
                $callback
727 45
            );
728 45
        };
729
    }
730
731
    /**
732
     * @param Closure|null $callback
733
     */
734 45
    private function runCallbackAgainstDOMNodeList(
735
        Type $type,
736
        DOMElement $node,
737
        Closure $againstNodeList,
738
        $callback = null
739
    ) {
740 45
        $this->fillTypeNode($type, $node, true);
741
742 45
        static::againstDOMNodeList($node, $againstNodeList);
743
744 45
        if ($callback) {
745 45
            call_user_func($callback, $type);
746 45
        }
747 45
    }
748
749 45
    private function maybeLoadExtensionFromBaseComplexType(
750
        Type $type,
751
        DOMElement $childNode
752
    ) {
753 45
        if ($type instanceof BaseComplexType) {
754 45
            $this->loadExtension($type, $childNode);
755 45
        }
756 45
    }
757
758
    const XSD_NS = 'http://www.w3.org/2001/XMLSchema';
759
760
    const XML_NS = 'http://www.w3.org/XML/1998/namespace';
761
762
    /**
763
     * @var string[]
764
     */
765
    protected $knownLocationSchemas = [
766
        'http://www.w3.org/2001/xml.xsd' => (
767
            __DIR__.'/Resources/xml.xsd'
768
        ),
769
        'http://www.w3.org/2001/XMLSchema.xsd' => (
770
            __DIR__.'/Resources/XMLSchema.xsd'
771
        ),
772
        'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' => (
773
            __DIR__.'/Resources/oasis-200401-wss-wssecurity-secext-1.0.xsd'
774
        ),
775
        'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd' => (
776
            __DIR__.'/Resources/oasis-200401-wss-wssecurity-utility-1.0.xsd'
777
        ),
778
        'https://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd' => (
779
            __DIR__.'/Resources/xmldsig-core-schema.xsd'
780
        ),
781
        'http://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd' => (
782
            __DIR__.'/Resources/xmldsig-core-schema.xsd'
783
        ),
784
    ];
785
786
    /**
787
     * @var string[]
788
     */
789
    protected static $globalSchemaInfo = array(
790
        self::XML_NS => 'http://www.w3.org/2001/xml.xsd',
791
        self::XSD_NS => 'http://www.w3.org/2001/XMLSchema.xsd',
792
    );
793
794
    /**
795
     * @param string $remote
796
     * @param string $local
797
     */
798
    public function addKnownSchemaLocation($remote, $local)
799
    {
800
        $this->knownLocationSchemas[$remote] = $local;
801
    }
802
803
    /**
804
     * @param string $remote
805
     *
806
     * @return bool
807
     */
808 1
    private function hasKnownSchemaLocation($remote)
809
    {
810 1
        return isset($this->knownLocationSchemas[$remote]);
811
    }
812
813
    /**
814
     * @param string $remote
815
     *
816
     * @return string
817
     */
818
    private function getKnownSchemaLocation($remote)
819
    {
820
        return $this->knownLocationSchemas[$remote];
821
    }
822
823
    /**
824
     * @param DOMElement $node
825
     *
826
     * @return string
827
     */
828 45
    private static function getDocumentation(DOMElement $node)
829
    {
830 45
        $doc = '';
831 45
        static::againstDOMNodeList(
832 45
            $node,
833
            function (
834
                DOMElement $node,
835
                DOMElement $childNode
836
            ) use (
837
                &$doc
838
            ) {
839 45
                if ($childNode->localName == 'annotation') {
840 45
                    $doc .= static::getDocumentation($childNode);
841 45
                } elseif ($childNode->localName == 'documentation') {
842 45
                    $doc .= $childNode->nodeValue;
843 45
                }
844 45
            }
845 45
        );
846 45
        $doc = preg_replace('/[\t ]+/', ' ', $doc);
847
848 45
        return trim($doc);
849
    }
850
851
    /**
852
     * @param string[] $methods
853
     * @param string   $key
854
     *
855
     * @return Closure|null
856
     */
857 45
    private function maybeCallMethod(
858
        array $methods,
859
        $key,
860
        DOMNode $childNode,
861
        ...$args
862
    ) {
863 45
        if ($childNode instanceof DOMElement && isset($methods[$key])) {
864 45
            $method = $methods[$key];
865
866
            /**
867
             * @var Closure|null
868
             */
869 45
            $append = $this->$method(...$args);
870
871 45
            if ($append instanceof Closure) {
872 45
                return $append;
873
            }
874 45
        }
875 45
    }
876
877
    /**
878
     * @param Schema     $schema
879
     * @param DOMElement $node
880
     * @param Schema     $parent
881
     *
882
     * @return Closure[]
883
     */
884 45
    private function schemaNode(Schema $schema, DOMElement $node, Schema $parent = null)
885
    {
886 45
        $this->setSchemaThingsFromNode($schema, $node, $parent);
887 45
        $functions = array();
888
889 45
        static::againstDOMNodeList(
890 45
            $node,
891
            function (
892
                DOMElement $node,
893
                DOMElement $childNode
894
            ) use (
895 45
                $schema,
896
                &$functions
897
            ) {
898 45
                $callback = null;
899
900 45
                switch ($childNode->localName) {
901 45
                    case 'attributeGroup':
902 45
                        $callback = $this->loadAttributeGroup($schema, $childNode);
903 45
                        break;
904 45
                    case 'include':
905 45
                    case 'import':
906 45
                        $callback = $this->loadImport($schema, $childNode);
907 45
                        break;
908 45
                    case 'element':
909 45
                        $callback = $this->loadElementDef($schema, $childNode);
910 45
                        break;
911 45
                    case 'attribute':
912 45
                        $callback = $this->loadAttributeDef($schema, $childNode);
913 45
                        break;
914 45
                    case 'group':
915 45
                        $callback = $this->loadGroup($schema, $childNode);
916 45
                        break;
917 45
                    case 'complexType':
918 45
                        $callback = $this->loadComplexType($schema, $childNode);
919 45
                        break;
920 45
                    case 'simpleType':
921 45
                        $callback = $this->loadSimpleType($schema, $childNode);
922 45
                        break;
923 45
                }
924
925 45
                if ($callback instanceof Closure) {
926 45
                    $functions[] = $callback;
927 45
                }
928 45
            }
929 45
        );
930
931 45
        return $functions;
932
    }
933
934
    /**
935
     * @return InterfaceSetMinMax
936
     */
937 45
    private static function maybeSetMax(InterfaceSetMinMax $ref, DOMElement $node)
938
    {
939
        if (
940 45
            $node->hasAttribute('maxOccurs')
941 45
        ) {
942 45
            $ref->setMax($node->getAttribute('maxOccurs') == 'unbounded' ? -1 : (int) $node->getAttribute('maxOccurs'));
943 45
        }
944
945 45
        return $ref;
946
    }
947
948
    /**
949
     * @return InterfaceSetMinMax
950
     */
951 45
    private static function maybeSetMin(InterfaceSetMinMax $ref, DOMElement $node)
952
    {
953 45
        if ($node->hasAttribute('minOccurs')) {
954 45
            $ref->setMin((int) $node->getAttribute('minOccurs'));
955 45
        }
956
957 45
        return $ref;
958
    }
959
960 45
    private function findAndSetSomeBase(
961
        Type $type,
962
        Base $setBaseOnThis,
963
        DOMElement $node
964
    ) {
965
        /**
966
         * @var Type
967
         */
968 45
        $parent = $this->findSomeType($type, $node, 'base');
969 45
        $setBaseOnThis->setBase($parent);
970 45
    }
971
972
    /**
973
     * @param string     $finder
974
     * @param Schema     $schema
975
     * @param DOMElement $node
976
     * @param string     $typeName
977
     *
978
     * @throws TypeException
979
     *
980
     * @return ElementItem|Group|AttributeItem|AttributeGroup|Type
981
     */
982 45
    private function findSomething($finder, Schema $schema, DOMElement $node, $typeName)
983
    {
984 45
        list($name, $namespace) = static::splitParts($node, $typeName);
985
986
        /**
987
         * @var string|null
988
         */
989 45
        $namespace = $namespace ?: $schema->getTargetNamespace();
990
991
        try {
992
            /**
993
             * @var ElementItem|Group|AttributeItem|AttributeGroup|Type
994
             */
995 45
            $out = $schema->$finder($name, $namespace);
996
997 45
            return $out;
998
        } catch (TypeNotFoundException $e) {
999
            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);
1000
        }
1001
    }
1002
1003 45
    private function fillItem(Item $element, DOMElement $node)
1004
    {
1005
        /**
1006
         * @var bool
1007
         */
1008 45
        $skip = false;
1009 45
        static::againstDOMNodeList(
1010 45
            $node,
1011
            function (
1012
                DOMElement $node,
1013
                DOMElement $childNode
1014
            ) use (
1015 45
                $element,
1016
                &$skip
1017
            ) {
1018
                if (
1019 45
                    !$skip &&
1020 45
                    in_array(
1021 45
                        $childNode->localName,
1022
                        [
1023 45
                            'complexType',
1024 45
                            'simpleType',
1025
                        ]
1026 45
                    )
1027 45
                ) {
1028 45
                    $this->loadTypeWithCallback(
1029 45
                        $element->getSchema(),
1030 45
                        $childNode,
1031
                        function (Type $type) use ($element) {
1032 45
                            $element->setType($type);
1033 45
                        }
1034 45
                    );
1035 45
                    $skip = true;
1036 45
                }
1037 45
            }
1038 45
        );
1039 45
        if ($skip) {
1040 45
            return;
1041
        }
1042 45
        $this->fillItemNonLocalType($element, $node);
1043 45
    }
1044
1045
    /**
1046
     * @var Schema|null
1047
     */
1048
    protected $globalSchema;
1049
1050
    /**
1051
     * @return Schema[]
1052
     */
1053 45
    private function setupGlobalSchemas(array &$callbacks)
1054
    {
1055 45
        $globalSchemas = array();
1056 45
        foreach (self::$globalSchemaInfo as $namespace => $uri) {
1057 45
            self::setLoadedFile(
1058 45
                $uri,
1059 45
                $globalSchemas[$namespace] = $schema = new Schema()
1060 45
            );
1061 45
            if ($namespace === self::XSD_NS) {
1062 45
                $this->globalSchema = $schema;
1063 45
            }
1064 45
            $xml = $this->getDOM($this->knownLocationSchemas[$uri]);
1065 45
            $callbacks = array_merge($callbacks, $this->schemaNode($schema, $xml->documentElement));
1066 45
        }
1067
1068 45
        return $globalSchemas;
1069
    }
1070
1071
    /**
1072
     * @return string[]
1073
     */
1074 45
    public function getGlobalSchemaInfo()
1075
    {
1076 45
        return self::$globalSchemaInfo;
1077
    }
1078
1079
    /**
1080
     * @return Schema
1081
     */
1082 45
    public function getGlobalSchema()
1083
    {
1084 45
        if (!$this->globalSchema) {
1085 45
            $callbacks = array();
1086 45
            $globalSchemas = $this->setupGlobalSchemas($callbacks);
1087
1088 45
            $globalSchemas[static::XSD_NS]->addType(new SimpleType($globalSchemas[static::XSD_NS], 'anySimpleType'));
1089 45
            $globalSchemas[static::XSD_NS]->addType(new SimpleType($globalSchemas[static::XSD_NS], 'anyType'));
1090
1091 45
            $globalSchemas[static::XML_NS]->addSchema(
1092 45
                $globalSchemas[static::XSD_NS],
1093
                (string) static::XSD_NS
1094 45
            );
1095 45
            $globalSchemas[static::XSD_NS]->addSchema(
1096 45
                $globalSchemas[static::XML_NS],
1097
                (string) static::XML_NS
1098 45
            );
1099
1100
            /**
1101
             * @var Closure
1102
             */
1103 45
            foreach ($callbacks as $callback) {
1104 45
                $callback();
1105 45
            }
1106 45
        }
1107
1108
        /**
1109
         * @var Schema
1110
         */
1111 45
        $out = $this->globalSchema;
1112
1113 45
        return $out;
1114
    }
1115
1116
    /**
1117
     * @param DOMElement $node
1118
     * @param string     $file
1119
     *
1120
     * @return Schema
1121
     */
1122 45
    private function readNode(DOMElement $node, $file = 'schema.xsd')
1123
    {
1124 45
        $fileKey = $node->hasAttribute('targetNamespace') ? $this->getNamespaceSpecificFileIndex($file, $node->getAttribute('targetNamespace')) : $file;
1125 45
        self::setLoadedFile($fileKey, $rootSchema = new Schema());
1126
1127 45
        $rootSchema->addSchema($this->getGlobalSchema());
1128 45
        $callbacks = $this->schemaNode($rootSchema, $node);
1129
1130 45
        foreach ($callbacks as $callback) {
1131 39
            call_user_func($callback);
1132 45
        }
1133
1134 45
        return $rootSchema;
1135
    }
1136
1137
    /**
1138
     * It is possible that a single file contains multiple <xsd:schema/> nodes, for instance in a WSDL file.
1139
     *
1140
     * Each of these  <xsd:schema/> nodes typically target a specific namespace. Append the target namespace to the
1141
     * file to distinguish between multiple schemas in a single file.
1142
     *
1143
     * @param string $file
1144
     * @param string $targetNamespace
1145
     *
1146
     * @return string
1147
     */
1148 45
    private function getNamespaceSpecificFileIndex($file, $targetNamespace)
1149
    {
1150 45
        return $file.'#'.$targetNamespace;
1151
    }
1152
1153
    /**
1154
     * @param string $content
1155
     * @param string $file
1156
     *
1157
     * @return Schema
1158
     *
1159
     * @throws IOException
1160
     */
1161 44
    public function readString($content, $file = 'schema.xsd')
1162
    {
1163 44
        $xml = new DOMDocument('1.0', 'UTF-8');
1164 44
        if (!$xml->loadXML($content)) {
1165
            throw new IOException("Can't load the schema");
1166
        }
1167 44
        $xml->documentURI = $file;
1168
1169 44
        return $this->readNode($xml->documentElement, $file);
1170
    }
1171
1172
    /**
1173
     * @param string $file
1174
     *
1175
     * @return Schema
1176
     */
1177 1
    public function readFile($file)
1178
    {
1179 1
        $xml = $this->getDOM($file);
1180
1181 1
        return $this->readNode($xml->documentElement, $file);
1182
    }
1183
1184
    /**
1185
     * @param string $file
1186
     *
1187
     * @return DOMDocument
1188
     *
1189
     * @throws IOException
1190
     */
1191 45
    private function getDOM($file)
1192
    {
1193 45
        $xml = new DOMDocument('1.0', 'UTF-8');
1194 45
        if (!$xml->load($file)) {
1195
            throw new IOException("Can't load the file $file");
1196
        }
1197
1198 45
        return $xml;
1199
    }
1200
1201 45
    private static function againstDOMNodeList(
1202
        DOMElement $node,
1203
        Closure $againstNodeList
1204
    ) {
1205 45
        $limit = $node->childNodes->length;
1206 45
        for ($i = 0; $i < $limit; $i += 1) {
1207
            /**
1208
             * @var DOMNode
1209
             */
1210 45
            $childNode = $node->childNodes->item($i);
1211
1212 45
            if ($childNode instanceof DOMElement) {
1213 45
                $againstNodeList(
1214 45
                    $node,
1215
                    $childNode
1216 45
                );
1217 45
            }
1218 45
        }
1219 45
    }
1220
1221 45
    private function maybeCallMethodAgainstDOMNodeList(
1222
        DOMElement $node,
1223
        SchemaItem $type,
1224
        array $methods
1225
    ) {
1226 45
        static::againstDOMNodeList(
1227 45
            $node,
1228 45
            $this->CallbackGeneratorMaybeCallMethodAgainstDOMNodeList(
1229 45
                $type,
1230
                $methods
1231 45
            )
1232 45
        );
1233 45
    }
1234
1235
    /**
1236
     * @return Closure
1237
     */
1238 45
    private function CallbackGeneratorMaybeCallMethodAgainstDOMNodeList(
1239
        SchemaItem $type,
1240
        array $methods
1241
    ) {
1242
        return function (
1243
            DOMElement $node,
1244
            DOMElement $childNode
1245
        ) use (
1246 45
            $methods,
1247 45
            $type
1248
        ) {
1249
            /**
1250
             * @var string[]
1251
             */
1252 45
            $methods = $methods;
1253
1254 45
            $this->maybeCallMethod(
1255 45
                $methods,
1256 45
                $childNode->localName,
1257 45
                $childNode,
1258 45
                $type,
1259
                $childNode
1260 45
            );
1261 45
        };
1262
    }
1263
1264 45
    private function loadTypeWithCallbackOnChildNodes(
1265
        Schema $schema,
1266
        DOMElement $node,
1267
        Closure $callback
1268
    ) {
1269 45
        self::againstDOMNodeList(
1270 45
            $node,
1271
            function (
1272
                DOMElement $node,
1273
                DOMElement $childNode
1274
            ) use (
1275 45
                $schema,
1276 45
                $callback
1277
            ) {
1278 45
                $this->loadTypeWithCallback(
1279 45
                    $schema,
1280 45
                    $childNode,
1281
                    $callback
1282 45
                );
1283 45
            }
1284 45
        );
1285 45
    }
1286
1287 45
    private function loadTypeWithCallback(
1288
        Schema $schema,
1289
        DOMElement $childNode,
1290
        Closure $callback
1291
    ) {
1292
        $methods = [
1293 45
            'complexType' => 'loadComplexType',
1294 45
            'simpleType' => 'loadSimpleType',
1295 45
        ];
1296
1297
        /**
1298
         * @var Closure|null
1299
         */
1300 45
        $func = $this->maybeCallMethod(
1301 45
            $methods,
1302 45
            $childNode->localName,
1303 45
            $childNode,
1304 45
            $schema,
1305 45
            $childNode,
1306
            $callback
1307 45
        );
1308
1309 45
        if ($func instanceof Closure) {
1310 45
            call_user_func($func);
1311 45
        }
1312 45
    }
1313
1314
    /**
1315
     * @param string $file
1316
     * @param string $namespace
1317
     *
1318
     * @return Closure
1319
     */
1320 45
    private function loadImport(
1321
        Schema $schema,
1322
        DOMElement $node
1323
    ) {
1324 45
        $base = urldecode($node->ownerDocument->documentURI);
1325 45
        $file = UrlUtils::resolveRelativeUrl($base, $node->getAttribute('schemaLocation'));
1326
1327 45
        $namespace = $node->getAttribute('namespace');
1328
1329 45
        $keys = $this->loadImportFreshKeys($namespace, $file);
1330
1331
        if (
1332 45
            self::hasLoadedFile(...$keys)
1333 45
        ) {
1334 45
            $schema->addSchema(self::getLoadedFile(...$keys));
1335
1336
            return function () {
1337 45
            };
1338
        }
1339
1340 1
        return $this->loadImportFresh($namespace, $schema, $file);
1341
    }
1342
1343
    /**
1344
     * @param string $namespace
1345
     * @param string $file
1346
     *
1347
     * @return mixed[]
1348
     */
1349 45
    private function loadImportFreshKeys(
1350
        $namespace,
1351
        $file
1352
    ) {
1353 45
        $globalSchemaInfo = $this->getGlobalSchemaInfo();
1354
1355 45
        $keys = [];
1356
1357 45
        if (isset($globalSchemaInfo[$namespace])) {
1358 45
            $keys[] = $globalSchemaInfo[$namespace];
1359 45
        }
1360
1361 45
        $keys[] = $this->getNamespaceSpecificFileIndex(
1362 45
            $file,
1363
            $namespace
1364 45
        );
1365
1366 45
        $keys[] = $file;
1367
1368 45
        return $keys;
1369
    }
1370
1371
    /**
1372
     * @param string $namespace
1373
     * @param string $file
1374
     *
1375
     * @return Schema
1376
     */
1377 1
    private function loadImportFreshCallbacksNewSchema(
1378
        $namespace,
1379
        Schema $schema,
1380
        $file
1381
    ) {
1382
        /**
1383
         * @var Schema $newSchema
1384
         */
1385 1
        $newSchema = self::setLoadedFile(
1386 1
            $file,
1387 1
            ($namespace ? new Schema() : $schema)
1388 1
        );
1389
1390 1
        if ($namespace) {
1391
            $newSchema->addSchema($this->getGlobalSchema());
1392
            $schema->addSchema($newSchema);
1393
        }
1394
1395 1
        return $newSchema;
1396
    }
1397
1398
    /**
1399
     * @param string $namespace
1400
     * @param string $file
1401
     *
1402
     * @return Closure[]
1403
     */
1404 1
    private function loadImportFreshCallbacks(
1405
        $namespace,
1406
        Schema $schema,
1407
        $file
1408
    ) {
1409
        /**
1410
         * @var string
1411
         */
1412 1
        $file = $file;
1413
1414 1
        return $this->schemaNode(
1415 1
            $this->loadImportFreshCallbacksNewSchema(
1416 1
                $namespace,
1417 1
                $schema,
1418
                $file
1419 1
            ),
1420 1
            $this->getDOM(
1421 1
                $this->hasKnownSchemaLocation($file)
1422 1
                    ? $this->getKnownSchemaLocation($file)
1423
                    : $file
1424 1
            )->documentElement,
1425
            $schema
1426 1
        );
1427
    }
1428
1429
    /**
1430
     * @param string $namespace
1431
     * @param string $file
1432
     *
1433
     * @return Closure
1434
     */
1435 1
    private function loadImportFresh(
1436
        $namespace,
1437
        Schema $schema,
1438
        $file
1439
    ) {
1440
        return function () use ($namespace, $schema, $file) {
1441
            foreach (
1442 1
                $this->loadImportFreshCallbacks(
1443 1
                    $namespace,
1444 1
                    $schema,
1445
                    $file
1446 1
                ) as $callback
1447 1
            ) {
1448 1
                $callback();
1449 1
            }
1450 1
        };
1451
    }
1452
1453
    /**
1454
     * @return Element
1455
     */
1456 45
    private function loadElement(
1457
        Schema $schema,
1458
        DOMElement $node
1459
    ) {
1460 45
        $element = new Element($schema, $node->getAttribute('name'));
1461 45
        $element->setDoc(self::getDocumentation($node));
1462
1463 45
        $this->fillItem($element, $node);
1464
1465 45
        self::maybeSetMax($element, $node);
1466 45
        self::maybeSetMin($element, $node);
1467
1468 45
        $xp = new \DOMXPath($node->ownerDocument);
1469 45
        $xp->registerNamespace('xs', 'http://www.w3.org/2001/XMLSchema');
1470
1471 45
        if ($xp->query('ancestor::xs:choice', $node)->length) {
1472 45
            $element->setMin(0);
1473 45
        }
1474
1475 45
        if ($node->hasAttribute('nillable')) {
1476 3
            $element->setNil($node->getAttribute('nillable') == 'true');
1477 3
        }
1478 45
        if ($node->hasAttribute('form')) {
1479 3
            $element->setQualified($node->getAttribute('form') == 'qualified');
1480 3
        }
1481
1482 45
        return $element;
1483
    }
1484
1485
    /**
1486
     * @return ElementRef
1487
     */
1488 45
    private static function loadElementRef(
1489
        ElementDef $referenced,
1490
        DOMElement $node
1491
    ) {
1492 45
        $ref = new ElementRef($referenced);
1493 45
        $ref->setDoc(self::getDocumentation($node));
1494
1495 45
        self::maybeSetMax($ref, $node);
1496 45
        self::maybeSetMin($ref, $node);
1497 45
        if ($node->hasAttribute('nillable')) {
1498
            $ref->setNil($node->getAttribute('nillable') == 'true');
1499
        }
1500 45
        if ($node->hasAttribute('form')) {
1501
            $ref->setQualified($node->getAttribute('form') == 'qualified');
1502
        }
1503
1504 45
        return $ref;
1505
    }
1506
1507
    /**
1508
     * @return \Closure
1509
     */
1510 45
    private function loadAttributeGroup(
1511
        Schema $schema,
1512
        DOMElement $node
1513
    ) {
1514 45
        $attGroup = new AttributeGroup($schema, $node->getAttribute('name'));
1515 45
        $attGroup->setDoc(self::getDocumentation($node));
1516 45
        $schema->addAttributeGroup($attGroup);
1517
1518
        return function () use ($schema, $node, $attGroup) {
1519 45
            SchemaReader::againstDOMNodeList(
1520 45
                $node,
1521 45
                function (
1522
                    DOMElement $node,
1523
                    DOMElement $childNode
1524
                ) use (
1525 45
                    $schema,
1526 45
                    $attGroup
1527
                ) {
1528 45
                    switch ($childNode->localName) {
1529 45
                        case 'attribute':
1530 45
                            $attribute = $this->getAttributeFromAttributeOrRef(
1531 45
                                $childNode,
1532 45
                                $schema,
1533
                                $node
1534 45
                            );
1535 45
                            $attGroup->addAttribute($attribute);
1536 45
                            break;
1537 45
                        case 'attributeGroup':
1538 1
                            $this->findSomethingLikeAttributeGroup(
1539 1
                                $schema,
1540 1
                                $node,
1541 1
                                $childNode,
1542
                                $attGroup
1543 1
                            );
1544 1
                            break;
1545 45
                    }
1546 45
                }
1547 45
            );
1548 45
        };
1549
    }
1550
1551
    /**
1552
     * @return AttributeItem
1553
     */
1554 45
    private function getAttributeFromAttributeOrRef(
1555
        DOMElement $childNode,
1556
        Schema $schema,
1557
        DOMElement $node
1558
    ) {
1559 45
        if ($childNode->hasAttribute('ref')) {
1560
            /**
1561
             * @var AttributeItem
1562
             */
1563 45
            $attribute = $this->findSomething('findAttribute', $schema, $node, $childNode->getAttribute('ref'));
1564 45
        } else {
1565
            /**
1566
             * @var Attribute
1567
             */
1568 45
            $attribute = $this->loadAttribute($schema, $childNode);
1569
        }
1570
1571 45
        return $attribute;
1572
    }
1573
1574
    /**
1575
     * @return Attribute
1576
     */
1577 45
    private function loadAttribute(
1578
        Schema $schema,
1579
        DOMElement $node
1580
    ) {
1581 45
        $attribute = new Attribute($schema, $node->getAttribute('name'));
1582 45
        $attribute->setDoc(self::getDocumentation($node));
1583 45
        $this->fillItem($attribute, $node);
1584
1585 45
        if ($node->hasAttribute('nillable')) {
1586 1
            $attribute->setNil($node->getAttribute('nillable') == 'true');
1587 1
        }
1588 45
        if ($node->hasAttribute('form')) {
1589 1
            $attribute->setQualified($node->getAttribute('form') == 'qualified');
1590 1
        }
1591 45
        if ($node->hasAttribute('use')) {
1592 45
            $attribute->setUse($node->getAttribute('use'));
1593 45
        }
1594
1595 45
        return $attribute;
1596
    }
1597
1598 45
    private function addAttributeFromAttributeOrRef(
1599
        BaseComplexType $type,
1600
        DOMElement $childNode,
1601
        Schema $schema,
1602
        DOMElement $node
1603
    ) {
1604 45
        $attribute = $this->getAttributeFromAttributeOrRef(
1605 45
            $childNode,
1606 45
            $schema,
1607
            $node
1608 45
        );
1609
1610 45
        $type->addAttribute($attribute);
1611 45
    }
1612
1613 45
    private function findSomethingLikeAttributeGroup(
1614
        Schema $schema,
1615
        DOMElement $node,
1616
        DOMElement $childNode,
1617
        AttributeContainer $addToThis
1618
    ) {
1619
        /**
1620
         * @var AttributeItem
1621
         */
1622 45
        $attribute = $this->findSomething('findAttributeGroup', $schema, $node, $childNode->getAttribute('ref'));
1623 45
        $addToThis->addAttribute($attribute);
1624 45
    }
1625
1626
    /**
1627
     * @var Schema[]
1628
     */
1629
    protected static $loadedFiles = array();
1630
1631
    /**
1632
     * @param string ...$keys
1633
     *
1634
     * @return bool
1635
     */
1636 45
    private static function hasLoadedFile(...$keys)
1637
    {
1638 45
        foreach ($keys as $key) {
1639 45
            if (isset(self::$loadedFiles[$key])) {
1640 45
                return true;
1641
            }
1642 1
        }
1643
1644 1
        return false;
1645
    }
1646
1647
    /**
1648
     * @param string ...$keys
1649
     *
1650
     * @return Schema
1651
     *
1652
     * @throws RuntimeException if loaded file not found
1653
     */
1654 45
    public static function getLoadedFile(...$keys)
1655
    {
1656 45
        foreach ($keys as $key) {
1657 45
            if (isset(self::$loadedFiles[$key])) {
1658 45
                return self::$loadedFiles[$key];
1659
            }
1660
        }
1661
1662
        throw new RuntimeException('Loaded file was not found!');
1663
    }
1664
1665
    /**
1666
     * @param string $key
1667
     *
1668
     * @return Schema
1669
     */
1670 45
    private static function setLoadedFile($key, Schema $schema)
1671
    {
1672 45
        self::$loadedFiles[$key] = $schema;
1673
1674 45
        return $schema;
1675
    }
1676
1677 45
    private function setSchemaThingsFromNode(
1678
        Schema $schema,
1679
        DOMElement $node,
1680
        Schema $parent = null
1681
    ) {
1682 45
        $schema->setDoc(self::getDocumentation($node));
1683
1684 45
        if ($node->hasAttribute('targetNamespace')) {
1685 45
            $schema->setTargetNamespace($node->getAttribute('targetNamespace'));
1686 45
        } elseif ($parent) {
1687
            $schema->setTargetNamespace($parent->getTargetNamespace());
1688
        }
1689 45
        $schema->setElementsQualification($node->getAttribute('elementFormDefault') == 'qualified');
1690 45
        $schema->setAttributesQualification($node->getAttribute('attributeFormDefault') == 'qualified');
1691 45
        $schema->setDoc(self::getDocumentation($node));
1692 45
    }
1693
}
1694