Passed
Push — static-analysis ( ef580b...e12b81 )
by SignpostMarv
01:29
created

SchemaReader::loadComplexTypeFromChildNode()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 45
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 30
nc 5
nop 4
dl 0
loc 45
rs 8.439
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 View Code Duplication
    private function loadAttributeDef(Schema $schema, DOMElement $node)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
164
    {
165
        $attribute = new AttributeDef($schema, $node->getAttribute("name"));
166
167
        $schema->addAttribute($attribute);
168
169
        return function () use ($attribute, $node) {
170
            $this->fillItem($attribute, $node);
171
        };
172
    }
173
174
    /**
175
     * @param DOMElement $node
176
     * @return string
177
     */
178
    private function getDocumentation(DOMElement $node)
179
    {
180
        $doc = '';
181
        foreach ($node->childNodes as $childNode) {
182
            if ($childNode->localName == "annotation") {
183
                foreach ($childNode->childNodes as $subChildNode) {
184
                    if ($subChildNode->localName == "documentation") {
185
                        $doc .= ($subChildNode->nodeValue);
186
                    }
187
                }
188
            }
189
        }
190
        $doc = preg_replace('/[\t ]+/', ' ', $doc);
191
        return trim($doc);
192
    }
193
194
    private function setSchemaThingsFromNode(
195
        Schema $schema,
196
        DOMElement $node,
197
        Schema $parent = null
198
    ) {
199
        $schema->setDoc($this->getDocumentation($node));
200
201
        if ($node->hasAttribute("targetNamespace")) {
202
            $schema->setTargetNamespace($node->getAttribute("targetNamespace"));
203
        } elseif ($parent) {
204
            $schema->setTargetNamespace($parent->getTargetNamespace());
205
        }
206
        $schema->setElementsQualification($node->getAttribute("elementFormDefault") == "qualified");
207
        $schema->setAttributesQualification($node->getAttribute("attributeFormDefault") == "qualified");
208
        $schema->setDoc($this->getDocumentation($node));
209
    }
210
211
    private function getClosuresFromChildNode(
212
        Schema $schema,
213
        DOMElement $childNode,
214
        array & $functions
215
    ) {
216
        static $methods = [
217
            'include' => 'loadImport',
218
            'import' => 'loadImport',
219
            'element' => 'loadElementDef',
220
            'attribute' => 'loadAttributeDef',
221
            'attributeGroup' => 'loadAttributeGroup',
222
            'group' => 'loadGroup',
223
            'complexType' => 'loadComplexType',
224
            'simpleType' => 'loadSimpleType',
225
        ];
226
227
        if (isset($methods[$childNode->localName])) {
228
            $method = $methods[$childNode->localName];
229
230
            $functions[] = $this->$method($schema, $childNode);
231
        }
232
    }
233
234
    /**
235
     *
236
     * @param Schema $schema
237
     * @param DOMElement $node
238
     * @param Schema $parent
239
     * @return array
240
     */
241
    private function schemaNode(Schema $schema, DOMElement $node, Schema $parent = null)
242
    {
243
        $this->setSchemaThingsFromNode($schema, $node, $parent);
244
        $functions = array();
245
246
        foreach ($node->childNodes as $childNode) {
247
            if ($childNode instanceof DOMElement) {
248
                $this->getClosuresFromChildNode(
249
                    $schema,
250
                    $childNode,
251
                    $functions
252
                );
253
            }
254
        }
255
256
        return $functions;
257
    }
258
259
    /**
260
    * @return Element
261
    */
262
    private function loadElement(Schema $schema, DOMElement $node)
263
    {
264
        $element = new Element($schema, $node->getAttribute("name"));
265
        $element->setDoc($this->getDocumentation($node));
266
267
        $this->fillItem($element, $node);
268
269
        static::maybeSetMax($element, $node);
270
        static::maybeSetMin($element, $node);
271
272
        $xp = new \DOMXPath($node->ownerDocument);
273
        $xp->registerNamespace('xs', 'http://www.w3.org/2001/XMLSchema');
274
275
        if ($xp->query('ancestor::xs:choice', $node)->length) {
276
            $element->setMin(0);
277
        }
278
279
        if ($node->hasAttribute("nillable")) {
280
            $element->setNil($node->getAttribute("nillable") == "true");
281
        }
282
        if ($node->hasAttribute("form")) {
283
            $element->setQualified($node->getAttribute("form") == "qualified");
284
        }
285
        return $element;
286
    }
287
288
    /**
289
    * @return GroupRef
290
    */
291
    private function loadGroupRef(Group $referenced, DOMElement $node)
292
    {
293
        $ref = new GroupRef($referenced);
294
        $ref->setDoc($this->getDocumentation($node));
295
296
        static::maybeSetMax($ref, $node);
297
        static::maybeSetMin($ref, $node);
298
299
        return $ref;
300
    }
301
302
    /**
303
    * @return ElementRef
304
    */
305
    private function loadElementRef(ElementDef $referenced, DOMElement $node)
306
    {
307
        $ref = new ElementRef($referenced);
308
        $this->setDoc($ref, $node);
309
310
        static::maybeSetMax($ref, $node);
311
        static::maybeSetMin($ref, $node);
312
        if ($node->hasAttribute("nillable")) {
313
            $ref->setNil($node->getAttribute("nillable") == "true");
314
        }
315
        if ($node->hasAttribute("form")) {
316
            $ref->setQualified($node->getAttribute("form") == "qualified");
317
        }
318
319
        return $ref;
320
    }
321
322
    private function setDoc(Item $ref, DOMElement $node)
323
    {
324
        $ref->setDoc($this->getDocumentation($node));
325
    }
326
327
    /**
328
    * @return InterfaceSetMinMax
329
    */
330
    private static function maybeSetMax(InterfaceSetMinMax $ref, DOMElement $node)
331
    {
332
        if (
333
            $node->hasAttribute("maxOccurs")
334
        ) {
335
            $ref->setMax($node->getAttribute("maxOccurs") == "unbounded" ? -1 : (int)$node->getAttribute("maxOccurs"));
336
        }
337
338
        return $ref;
339
    }
340
341
    /**
342
    * @return InterfaceSetMinMax
343
    */
344
    private static function maybeSetMin(InterfaceSetMinMax $ref, DOMElement $node)
345
    {
346
        if ($node->hasAttribute("minOccurs")) {
347
            $ref->setMin((int) $node->getAttribute("minOccurs"));
348
        }
349
350
        return $ref;
351
    }
352
353
    /**
354
    * @return AttributeRef
355
    */
356
    private function loadAttributeRef(AttributeDef $referencedAttribiute, DOMElement $node)
357
    {
358
        $attribute = new AttributeRef($referencedAttribiute);
359
        $this->setDoc($attribute, $node);
360
361
        if ($node->hasAttribute("nillable")) {
362
            $attribute->setNil($node->getAttribute("nillable") == "true");
363
        }
364
        if ($node->hasAttribute("form")) {
365
            $attribute->setQualified($node->getAttribute("form") == "qualified");
366
        }
367
        if ($node->hasAttribute("use")) {
368
            $attribute->setUse($node->getAttribute("use"));
369
        }
370
        return $attribute;
371
    }
372
373
    /**
374
    * @param int|null $max
375
    */
376
    private function loadSequence(ElementContainer $elementContainer, DOMElement $node, $max = null)
377
    {
378
        $max = $max || $node->getAttribute("maxOccurs") == "unbounded" || $node->getAttribute("maxOccurs") > 1 ? 2 : null;
0 ignored issues
show
Bug Best Practice introduced by
The expression $max of type null|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
379
380
        foreach ($node->childNodes as $childNode) {
381
382
            switch ($childNode->localName) {
383
                case 'choice':
384
                case 'sequence':
385
                case 'all':
386
                    $this->loadSequence($elementContainer, $childNode, $max);
387
                    break;
388
                case 'element':
389
                    if ($childNode->hasAttribute("ref")) {
390
                        /**
391
                        * @var ElementDef $referencedElement
392
                        */
393
                        $referencedElement = $this->findSomething('findElement', $elementContainer->getSchema(), $node, $childNode->getAttribute("ref"));
394
                        $element = $this->loadElementRef($referencedElement, $childNode);
395
                    } else {
396
                        $element = $this->loadElement($elementContainer->getSchema(), $childNode);
397
                    }
398
                    if ($max) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $max of type null|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
399
                        $element->setMax($max);
400
                    }
401
                    $elementContainer->addElement($element);
402
                    break;
403
                case 'group':
404
                    $this->addGroupAsElement(
405
                        $elementContainer->getSchema(),
406
                        $node,
407
                        $childNode,
408
                        $elementContainer
409
                    );
410
                    break;
411
            }
412
        }
413
    }
414
415
    private function addGroupAsElement(
416
        Schema $schema,
417
        DOMElement $node,
418
        DOMElement $childNode,
419
        ElementContainer $elementContainer
420
    ) {
421
        /**
422
        * @var Group $referencedGroup
423
        */
424
        $referencedGroup = $this->findSomething(
425
            'findGroup',
426
            $schema,
427
            $node,
428
            $childNode->getAttribute("ref")
429
        );
430
431
        $group = $this->loadGroupRef($referencedGroup, $childNode);
432
        $elementContainer->addElement($group);
433
    }
434
435
    private function maybeLoadSequenceFromElementContainer(
436
        BaseComplexType $type,
437
        DOMElement $childNode
438
    ) {
439
        if (! ($type instanceof ElementContainer)) {
440
            throw new RuntimeException(
441
                '$type passed to ' .
442
                __FUNCTION__ .
443
                'expected to be an instance of ' .
444
                ElementContainer::class .
445
                ' when child node localName is "group", ' .
446
                get_class($type) .
447
                ' given.'
448
            );
449
        }
450
        $this->loadSequence($type, $childNode);
451
    }
452
453
    /**
454
    * @return Closure
455
    */
456
    private function loadGroup(Schema $schema, DOMElement $node)
457
    {
458
        $group = new Group($schema, $node->getAttribute("name"));
459
        $group->setDoc($this->getDocumentation($node));
460
461
        if ($node->hasAttribute("maxOccurs")) {
462
            /**
463
            * @var GroupRef $group
464
            */
465
            $group = static::maybeSetMax(new GroupRef($group), $node);
466
        }
467
        if ($node->hasAttribute("minOccurs")) {
468
            /**
469
            * @var GroupRef $group
470
            */
471
            $group = static::maybeSetMin(
472
                $group instanceof GroupRef ? $group : new GroupRef($group),
473
                $node
474
            );
475
        }
476
477
        $schema->addGroup($group);
478
479
        return function () use ($group, $node) {
480
            foreach ($node->childNodes as $childNode) {
481
                if (
482
                    in_array(
483
                        $childNode->localName,
484
                        [
485
                            'sequence',
486
                            'choice',
487
                            'all',
488
                        ]
489
                    )
490
                ) {
491
                        $this->loadSequence($group, $childNode);
492
                }
493
            }
494
        };
495
    }
496
497
    /**
498
    * @param Closure|null $callback
499
    *
500
    * @return Closure
501
    */
502
    private function loadComplexType(Schema $schema, DOMElement $node, $callback = null)
503
    {
504
        $isSimple = false;
505
506
        foreach ($node->childNodes as $childNode) {
507
            if ($childNode->localName === "simpleContent") {
508
                $isSimple = true;
509
                break;
510
            }
511
        }
512
513
        $type = $isSimple ? new ComplexTypeSimpleContent($schema, $node->getAttribute("name")) : new ComplexType($schema, $node->getAttribute("name"));
514
515
        $type->setDoc($this->getDocumentation($node));
516
        if ($node->getAttribute("name")) {
517
            $schema->addType($type);
518
        }
519
520
        return function () use ($type, $node, $schema, $callback) {
521
522
            $this->fillTypeNode($type, $node);
523
524
            foreach ($node->childNodes as $childNode) {
525
                if ($childNode instanceof DOMElement) {
526
                    $this->loadComplexTypeFromChildNode(
527
                        $type,
528
                        $node,
529
                        $childNode,
530
                        $schema
531
                    );
532
                }
533
            }
534
535
            if ($callback) {
536
                call_user_func($callback, $type);
537
            }
538
        };
539
    }
540
541
    private function loadComplexTypeFromChildNode(
542
        BaseComplexType $type,
543
        DOMElement $node,
544
        DOMElement $childNode,
545
        Schema $schema
546
    ) {
547
        if (
548
            in_array(
549
                $childNode->localName,
550
                [
551
                    'sequence',
552
                    'choice',
553
                    'all',
554
                ]
555
            )
556
        ) {
557
            $this->maybeLoadSequenceFromElementContainer(
558
                $type,
559
                $childNode
560
            );
561
        } elseif ($childNode->localName === 'attribute') {
562
            $attribute = $this->getAttributeFromAttributeOrRef(
563
                $childNode,
564
                $schema,
565
                $node
566
            );
567
568
            $type->addAttribute($attribute);
569
        } elseif (
570
            $childNode->localName === 'group' &&
571
            $type instanceof ComplexType
572
        ) {
573
            $this->addGroupAsElement(
574
                $schema,
575
                $node,
576
                $childNode,
577
                $type
578
            );
579
        } elseif ($childNode->localName === 'attributeGroup') {
580
            AttributeGroup::findSomethingLikeThis(
581
                $this,
582
                $schema,
583
                $node,
584
                $childNode,
585
                $type
586
            );
587
        }
588
    }
589
590
    /**
591
    * @param Closure|null $callback
592
    *
593
    * @return Closure
594
    */
595
    private function loadSimpleType(Schema $schema, DOMElement $node, $callback = null)
596
    {
597
        $type = new SimpleType($schema, $node->getAttribute("name"));
598
        $type->setDoc($this->getDocumentation($node));
599
        if ($node->getAttribute("name")) {
600
            $schema->addType($type);
601
        }
602
603
        return function () use ($type, $node, $callback) {
604
            $this->fillTypeNode($type, $node);
605
606
            foreach ($node->childNodes as $childNode) {
607
                switch ($childNode->localName) {
608
                    case 'union':
609
                        $this->loadUnion($type, $childNode);
610
                        break;
611
                    case 'list':
612
                        $this->loadList($type, $childNode);
613
                        break;
614
                }
615
            }
616
617
            if ($callback) {
618
                call_user_func($callback, $type);
619
            }
620
        };
621
    }
622
623
    private function loadList(SimpleType $type, DOMElement $node)
624
    {
625
        if ($node->hasAttribute("itemType")) {
626
            /**
627
            * @var SimpleType $listType
628
            */
629
            $listType = $this->findSomeType($type, $node, 'itemType');
630
            $type->setList($listType);
631
        } else {
632
            $addCallback = function (SimpleType $list) use ($type) {
633
                $type->setList($list);
634
            };
635
636
            $this->loadTypeWithCallbackOnChildNodes(
637
                $type->getSchema(),
638
                $node,
639
                $addCallback
640
            );
641
        }
642
    }
643
644
    private function loadTypeWithCallbackOnChildNodes(
645
        Schema $schema,
646
        DOMNode $node,
647
        Closure $callback
648
    ) {
649
        foreach ($node->childNodes as $childNode) {
650
            $this->loadTypeWithCallback($schema, $childNode, $callback);
651
        }
652
    }
653
654
    private function loadTypeWithCallback(
655
        Schema $schema,
656
        DOMNode $childNode,
657
        Closure $callback
658
    ) {
659
        if (! ($childNode instanceof DOMElement)) {
660
            return;
661
        }
662
        switch ($childNode->localName) {
663
            case 'complexType':
664
                $childNode = $childNode;
665
                call_user_func(
666
                    $this->loadComplexType(
667
                        $schema,
668
                        $childNode,
669
                        $callback
670
                    )
671
                );
672
                break;
673
            case 'simpleType':
674
                call_user_func(
675
                    $this->loadSimpleType($schema, $childNode, $callback)
676
                );
677
                break;
678
        }
679
    }
680
681
    /**
682
    * @return SchemaItem
683
    */
684
    private function findSomeType(
685
        SchemaItem $fromThis,
686
        DOMElement $node,
687
        string $attributeName
688
    ) {
689
        return $this->findSomeTypeFromAttribute(
690
            $fromThis,
691
            $node,
692
            $node->getAttribute($attributeName)
693
        );
694
    }
695
696
    /**
697
    * @return SchemaItem
698
    */
699
    private function findSomeTypeFromAttribute(
700
        SchemaItem $fromThis,
701
        DOMElement $node,
702
        string $attributeName
703
    ) {
704
        /**
705
        * @var SchemaItem $out
706
        */
707
        $out = $this->findSomething(
708
            'findType',
709
            $fromThis->getSchema(),
710
            $node,
711
            $attributeName
712
        );
713
714
        return $out;
715
    }
716
717
    private function loadUnion(SimpleType $type, DOMElement $node)
718
    {
719
        if ($node->hasAttribute("memberTypes")) {
720
            $types = preg_split('/\s+/', $node->getAttribute("memberTypes"));
721
            foreach ($types as $typeName) {
722
                /**
723
                * @var SimpleType $unionType
724
                */
725
                $unionType = $this->findSomeTypeFromAttribute(
726
                    $type,
727
                    $node,
728
                    $typeName
729
                );
730
                $type->addUnion($unionType);
731
            }
732
        }
733
        $addCallback = function (SimpleType $unType) use ($type) {
734
            $type->addUnion($unType);
735
        };
736
737
        $this->loadTypeWithCallbackOnChildNodes(
738
            $type->getSchema(),
739
            $node,
740
            $addCallback
741
        );
742
    }
743
744
    /**
745
    * @param bool $checkAbstract
746
    */
747
    private function fillTypeNode(Type $type, DOMElement $node, $checkAbstract = true)
748
    {
749
750
        if ($checkAbstract) {
751
            $type->setAbstract($node->getAttribute("abstract") === "true" || $node->getAttribute("abstract") === "1");
752
        }
753
754
        foreach ($node->childNodes as $childNode) {
755
            switch ($childNode->localName) {
756
                case 'restriction':
757
                    $this->loadRestriction($type, $childNode);
758
                    break;
759
                case 'extension':
760
                    $this->maybeLoadExtensionFromBaseComplexType(
761
                        $type,
762
                        $childNode
763
                    );
764
                    break;
765
                case 'simpleContent':
766
                case 'complexContent':
767
                    $this->fillTypeNode($type, $childNode, false);
768
                    break;
769
            }
770
        }
771
    }
772
773
    private function loadExtension(BaseComplexType $type, DOMElement $node)
774
    {
775
        $extension = new Extension();
776
        $type->setExtension($extension);
777
778
        if ($node->hasAttribute("base")) {
779
            $this->findAndSetSomeBase(
780
                $type,
781
                $extension,
782
                $node
783
            );
784
        }
785
786
        foreach ($node->childNodes as $childNode) {
787
            switch ($childNode->localName) {
788
                case 'sequence':
789
                case 'choice':
790
                case 'all':
791
                    $this->maybeLoadSequenceFromElementContainer(
792
                        $type,
793
                        $childNode
794
                    );
795
                    break;
796
                case 'attribute':
797
                    $attribute = $this->getAttributeFromAttributeOrRef(
798
                        $childNode,
799
                        $type->getSchema(),
800
                        $node
801
                    );
802
                    $type->addAttribute($attribute);
803
                    break;
804
                case 'attributeGroup':
805
                    AttributeGroup::findSomethingLikeThis(
806
                        $this,
807
                        $type->getSchema(),
808
                        $node,
809
                        $childNode,
810
                        $type
811
                    );
812
                    break;
813
            }
814
        }
815
    }
816
817
    private function findAndSetSomeBase(
818
        Type $type,
819
        Base $setBaseOnThis,
820
        DOMElement $node
821
    ) {
822
        /**
823
        * @var Type $parent
824
        */
825
        $parent = $this->findSomeType($type, $node, 'base');
826
        $setBaseOnThis->setBase($parent);
827
    }
828
829
    private function maybeLoadExtensionFromBaseComplexType(
830
        Type $type,
831
        DOMElement $childNode
832
    ) {
833
        if (! ($type instanceof BaseComplexType)) {
834
            throw new RuntimeException(
835
                'Argument 1 passed to ' .
836
                __METHOD__ .
837
                ' needs to be an instance of ' .
838
                BaseComplexType::class .
839
                ' when passed onto ' .
840
                static::class .
841
                '::loadExtension(), ' .
842
                get_class($type) .
843
                ' given.'
844
            );
845
        }
846
        $this->loadExtension($type, $childNode);
847
    }
848
849
    private function loadRestriction(Type $type, DOMElement $node)
850
    {
851
        $restriction = new Restriction();
852
        $type->setRestriction($restriction);
853
        if ($node->hasAttribute("base")) {
854
            $this->findAndSetSomeBase($type, $restriction, $node);
855
        } else {
856
            $addCallback = function (Type $restType) use ($restriction) {
857
                $restriction->setBase($restType);
858
            };
859
860
            $this->loadTypeWithCallbackOnChildNodes(
861
                $type->getSchema(),
862
                $node,
863
                $addCallback
864
            );
865
        }
866
        foreach ($node->childNodes as $childNode) {
867
            if (in_array($childNode->localName,
868
                [
869
                    'enumeration',
870
                    'pattern',
871
                    'length',
872
                    'minLength',
873
                    'maxLength',
874
                    'minInclusive',
875
                    'maxInclusive',
876
                    'minExclusive',
877
                    'maxExclusive',
878
                    'fractionDigits',
879
                    'totalDigits',
880
                    'whiteSpace'
881
                ], true)) {
882
                $restriction->addCheck($childNode->localName,
883
                    [
884
                        'value' => $childNode->getAttribute("value"),
885
                        'doc' => $this->getDocumentation($childNode)
886
                    ]);
887
            }
888
        }
889
    }
890
891
    /**
892
    * @param string $typeName
893
    *
894
    * @return mixed[]
895
    */
896
    private static function splitParts(DOMElement $node, $typeName)
897
    {
898
        $namespace = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $namespace is dead and can be removed.
Loading history...
899
        $prefix = null;
900
        $name = $typeName;
901
        if (strpos($typeName, ':') !== false) {
902
            list ($prefix, $name) = explode(':', $typeName);
903
        }
904
905
        $namespace = $node->lookupNamespaceUri($prefix ?: '');
906
        return array(
907
            $name,
908
            $namespace,
909
            $prefix
910
        );
911
    }
912
913
    /**
914
     *
915
     * @param string $finder
916
     * @param Schema $schema
917
     * @param DOMElement $node
918
     * @param string $typeName
919
     * @throws TypeException
920
     * @return ElementItem|Group|AttributeItem|AttributeGroup|Type
921
     */
922
    public function findSomething($finder, Schema $schema, DOMElement $node, $typeName)
923
    {
924
        list ($name, $namespace) = self::splitParts($node, $typeName);
925
926
        $namespace = $namespace ?: $schema->getTargetNamespace();
927
928
        try {
929
            return $schema->$finder($name, $namespace);
930
        } catch (TypeNotFoundException $e) {
931
            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);
932
        }
933
    }
934
935
    /**
936
    * @return Closure
937
    */
938 View Code Duplication
    private function loadElementDef(Schema $schema, DOMElement $node)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
939
    {
940
        $element = new ElementDef($schema, $node->getAttribute("name"));
941
        $schema->addElement($element);
942
943
        return function () use ($element, $node) {
944
            $this->fillItem($element, $node);
945
        };
946
    }
947
948
    private function fillItem(Item $element, DOMElement $node)
949
    {
950
        $localType = null;
951
        foreach ($node->childNodes as $childNode) {
952
            switch ($childNode->localName) {
953
                case 'complexType':
954
                case 'simpleType':
955
                    $localType = $childNode;
956
                    break 2;
957
            }
958
        }
959
960
        if ($localType) {
961
            $addCallback = function (Type $type) use ($element) {
962
                $element->setType($type);
963
            };
964
            $this->loadTypeWithCallback(
965
                $element->getSchema(),
966
                $localType,
967
                $addCallback
968
            );
969
        } else {
970
971
            if ($node->getAttribute("type")) {
972
                /**
973
                * @var Type $type
974
                */
975
                $type = $this->findSomeType($element, $node, 'type');
976
            } else {
977
                /**
978
                * @var Type $type
979
                */
980
                $type = $this->findSomeTypeFromAttribute(
981
                    $element,
982
                    $node,
983
                    ($node->lookupPrefix(self::XSD_NS) . ':anyType')
984
                );
985
            }
986
987
            $element->setType($type);
988
        }
989
    }
990
991
    /**
992
    * @return Closure
993
    */
994
    private function loadImport(Schema $schema, DOMElement $node)
995
    {
996
        $base = urldecode($node->ownerDocument->documentURI);
997
        $file = UrlUtils::resolveRelativeUrl($base, $node->getAttribute("schemaLocation"));
998
        if ($node->hasAttribute("namespace")
999
            && isset(self::$globalSchemaInfo[$node->getAttribute("namespace")])
1000
            && isset($this->loadedFiles[self::$globalSchemaInfo[$node->getAttribute("namespace")]])
1001
        ) {
1002
1003
            $schema->addSchema($this->loadedFiles[self::$globalSchemaInfo[$node->getAttribute("namespace")]]);
1004
1005
            return function () {
1006
            };
1007
        } elseif ($node->hasAttribute("namespace")
1008
            && isset($this->loadedFiles[$this->getNamespaceSpecificFileIndex($file, $node->getAttribute("namespace"))])) {
1009
            $schema->addSchema($this->loadedFiles[$this->getNamespaceSpecificFileIndex($file, $node->getAttribute("namespace"))]);
1010
            return function () {
1011
            };
1012
        } elseif (isset($this->loadedFiles[$file])) {
1013
            $schema->addSchema($this->loadedFiles[$file]);
1014
            return function () {
1015
            };
1016
        }
1017
1018
        if (!$node->getAttribute("namespace")) {
1019
            $this->loadedFiles[$file] = $newSchema = $schema;
1020
        } else {
1021
            $this->loadedFiles[$file] = $newSchema = new Schema();
1022
            $newSchema->addSchema($this->getGlobalSchema());
1023
        }
1024
1025
        $xml = $this->getDOM(isset($this->knownLocationSchemas[$file]) ? $this->knownLocationSchemas[$file] : $file);
1026
1027
        $callbacks = $this->schemaNode($newSchema, $xml->documentElement, $schema);
1028
1029
        if ($node->getAttribute("namespace")) {
1030
            $schema->addSchema($newSchema);
1031
        }
1032
1033
1034
        return function () use ($callbacks) {
1035
            foreach ($callbacks as $callback) {
1036
                call_user_func($callback);
1037
            }
1038
        };
1039
    }
1040
1041
    /**
1042
    * @var Schema|null
1043
    */
1044
    private $globalSchema;
1045
1046
    /**
1047
     *
1048
     * @return Schema
1049
     */
1050
    public function getGlobalSchema()
1051
    {
1052
        if (!$this->globalSchema) {
1053
            $callbacks = array();
1054
            $globalSchemas = array();
1055
            foreach (self::$globalSchemaInfo as $namespace => $uri) {
1056
                $this->loadedFiles[$uri] = $globalSchemas [$namespace] = $schema = new Schema();
1057
                if ($namespace === self::XSD_NS) {
1058
                    $this->globalSchema = $schema;
1059
                }
1060
                $xml = $this->getDOM($this->knownLocationSchemas[$uri]);
1061
                $callbacks = array_merge($callbacks, $this->schemaNode($schema, $xml->documentElement));
1062
            }
1063
1064
            $globalSchemas[self::XSD_NS]->addType(new SimpleType($globalSchemas[self::XSD_NS], "anySimpleType"));
1065
            $globalSchemas[self::XSD_NS]->addType(new SimpleType($globalSchemas[self::XSD_NS], "anyType"));
1066
1067
            $globalSchemas[self::XML_NS]->addSchema($globalSchemas[self::XSD_NS], self::XSD_NS);
1068
            $globalSchemas[self::XSD_NS]->addSchema($globalSchemas[self::XML_NS], self::XML_NS);
1069
1070
            foreach ($callbacks as $callback) {
1071
                $callback();
1072
            }
1073
        }
1074
1075
        /**
1076
        * @var Schema $out
1077
        */
1078
        $out = $this->globalSchema;
1079
1080
        return $out;
1081
    }
1082
1083
    /**
1084
     * @param DOMElement $node
1085
     * @param string  $file
1086
     *
1087
     * @return Schema
1088
     */
1089
    public function readNode(DOMElement $node, $file = 'schema.xsd')
1090
    {
1091
        $fileKey = $node->hasAttribute('targetNamespace') ? $this->getNamespaceSpecificFileIndex($file, $node->getAttribute('targetNamespace')) : $file;
1092
        $this->loadedFiles[$fileKey] = $rootSchema = new Schema();
1093
1094
        $rootSchema->addSchema($this->getGlobalSchema());
1095
        $callbacks = $this->schemaNode($rootSchema, $node);
1096
1097
        foreach ($callbacks as $callback) {
1098
            call_user_func($callback);
1099
        }
1100
1101
        return $rootSchema;
1102
    }
1103
1104
    /**
1105
     * It is possible that a single file contains multiple <xsd:schema/> nodes, for instance in a WSDL file.
1106
     *
1107
     * Each of these  <xsd:schema/> nodes typically target a specific namespace. Append the target namespace to the
1108
     * file to distinguish between multiple schemas in a single file.
1109
     *
1110
     * @param string $file
1111
     * @param string $targetNamespace
1112
     *
1113
     * @return string
1114
     */
1115
    private function getNamespaceSpecificFileIndex($file, $targetNamespace)
1116
    {
1117
        return $file . '#' . $targetNamespace;
1118
    }
1119
1120
    /**
1121
     * @param string $content
1122
     * @param string $file
1123
     *
1124
     * @return Schema
1125
     *
1126
     * @throws IOException
1127
     */
1128
    public function readString($content, $file = 'schema.xsd')
1129
    {
1130
        $xml = new DOMDocument('1.0', 'UTF-8');
1131
        if (!$xml->loadXML($content)) {
1132
            throw new IOException("Can't load the schema");
1133
        }
1134
        $xml->documentURI = $file;
1135
1136
        return $this->readNode($xml->documentElement, $file);
1137
    }
1138
1139
    /**
1140
     * @param string $file
1141
     *
1142
     * @return Schema
1143
     */
1144
    public function readFile($file)
1145
    {
1146
        $xml = $this->getDOM($file);
1147
        return $this->readNode($xml->documentElement, $file);
1148
    }
1149
1150
    /**
1151
     * @param string $file
1152
     *
1153
     * @return DOMDocument
1154
     *
1155
     * @throws IOException
1156
     */
1157
    private function getDOM($file)
1158
    {
1159
        $xml = new DOMDocument('1.0', 'UTF-8');
1160
        if (!$xml->load($file)) {
1161
            throw new IOException("Can't load the file $file");
1162
        }
1163
        return $xml;
1164
    }
1165
}
1166