Passed
Push — static-analysis ( 62dcd9...a54da0 )
by SignpostMarv
01:30
created

SchemaReader::loadSequenceChildNode()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 37
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

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