Completed
Push — php-7.1 ( 657403...bab180 )
by SignpostMarv
07:32
created

SchemaReader::loadUnion()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 22
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

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