Passed
Push — php-7.1 ( a96878...0e63f5 )
by SignpostMarv
10:37 queued 02:26
created

SchemaReader::getLoadedFile()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

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