Passed
Push — php-7.1 ( 51ebd7...657403 )
by SignpostMarv
06:58
created

SchemaReader::addKnownSchemaLocation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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