Passed
Push — static-analysis ( 7c9eae...1fc4bc )
by SignpostMarv
01:28
created

SchemaReader::maybeSetMin()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 2
dl 0
loc 7
rs 9.4285
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
                switch ($childNode->localName) {
526
                    case 'sequence':
527
                    case 'choice':
528
                    case 'all':
529
                        $this->maybeLoadSequenceFromElementContainer(
530
                            $type,
531
                            $childNode
532
                        );
533
                        break;
534
                    case 'attribute':
535
                        $attribute = $this->getAttributeFromAttributeOrRef(
536
                            $childNode,
537
                            $schema,
538
                            $node
539
                        );
540
541
                        $type->addAttribute($attribute);
542
                        break;
543
                    case 'group':
544
                        if (! ($type instanceof ComplexType)) {
545
                            throw new RuntimeException(
546
                                '$type passed to ' .
547
                                __FUNCTION__ .
548
                                'expected to be an instance of ' .
549
                                ComplexType::class .
550
                                ' when child node localName is "group", ' .
551
                                get_class($type) .
552
                                ' given.'
553
                            );
554
                        }
555
556
                        $this->addGroupAsElement(
557
                            $schema,
558
                            $node,
559
                            $childNode,
560
                            $type
561
                        );
562
                        break;
563
                    case 'attributeGroup':
564
                        AttributeGroup::findSomethingLikeThis(
565
                            $this,
566
                            $schema,
567
                            $node,
568
                            $childNode,
569
                            $type
570
                        );
571
                        break;
572
                }
573
            }
574
575
            if ($callback) {
576
                call_user_func($callback, $type);
577
            }
578
        };
579
    }
580
581
    /**
582
    * @param Closure|null $callback
583
    *
584
    * @return Closure
585
    */
586
    private function loadSimpleType(Schema $schema, DOMElement $node, $callback = null)
587
    {
588
        $type = new SimpleType($schema, $node->getAttribute("name"));
589
        $type->setDoc($this->getDocumentation($node));
590
        if ($node->getAttribute("name")) {
591
            $schema->addType($type);
592
        }
593
594
        return function () use ($type, $node, $callback) {
595
            $this->fillTypeNode($type, $node);
596
597
            foreach ($node->childNodes as $childNode) {
598
                switch ($childNode->localName) {
599
                    case 'union':
600
                        $this->loadUnion($type, $childNode);
601
                        break;
602
                    case 'list':
603
                        $this->loadList($type, $childNode);
604
                        break;
605
                }
606
            }
607
608
            if ($callback) {
609
                call_user_func($callback, $type);
610
            }
611
        };
612
    }
613
614
    private function loadList(SimpleType $type, DOMElement $node)
615
    {
616
        if ($node->hasAttribute("itemType")) {
617
            /**
618
            * @var SimpleType $listType
619
            */
620
            $listType = $this->findSomeType($type, $node, 'itemType');
621
            $type->setList($listType);
622
        } else {
623
            $addCallback = function (SimpleType $list) use ($type) {
624
                $type->setList($list);
625
            };
626
627
            $this->loadTypeWithCallbackOnChildNodes(
628
                $type->getSchema(),
629
                $node,
630
                $addCallback
631
            );
632
        }
633
    }
634
635
    private function loadTypeWithCallbackOnChildNodes(
636
        Schema $schema,
637
        DOMNode $node,
638
        Closure $callback
639
    ) {
640
        foreach ($node->childNodes as $childNode) {
641
            $this->loadTypeWithCallback($schema, $childNode, $callback);
642
        }
643
    }
644
645
    private function loadTypeWithCallback(
646
        Schema $schema,
647
        DOMNode $childNode,
648
        Closure $callback
649
    ) {
650
        if (! ($childNode instanceof DOMElement)) {
651
            return;
652
        }
653
        switch ($childNode->localName) {
654
            case 'complexType':
655
                $childNode = $childNode;
656
                call_user_func(
657
                    $this->loadComplexType(
658
                        $schema,
659
                        $childNode,
660
                        $callback
661
                    )
662
                );
663
                break;
664
            case 'simpleType':
665
                call_user_func(
666
                    $this->loadSimpleType($schema, $childNode, $callback)
667
                );
668
                break;
669
        }
670
    }
671
672
    /**
673
    * @return SchemaItem
674
    */
675
    private function findSomeType(
676
        SchemaItem $fromThis,
677
        DOMElement $node,
678
        string $attributeName
679
    ) {
680
        return $this->findSomeTypeFromAttribute(
681
            $fromThis,
682
            $node,
683
            $node->getAttribute($attributeName)
684
        );
685
    }
686
687
    /**
688
    * @return SchemaItem
689
    */
690
    private function findSomeTypeFromAttribute(
691
        SchemaItem $fromThis,
692
        DOMElement $node,
693
        string $attributeName
694
    ) {
695
        /**
696
        * @var SchemaItem $out
697
        */
698
        $out = $this->findSomething(
699
            'findType',
700
            $fromThis->getSchema(),
701
            $node,
702
            $attributeName
703
        );
704
705
        return $out;
706
    }
707
708
    private function loadUnion(SimpleType $type, DOMElement $node)
709
    {
710
        if ($node->hasAttribute("memberTypes")) {
711
            $types = preg_split('/\s+/', $node->getAttribute("memberTypes"));
712
            foreach ($types as $typeName) {
713
                /**
714
                * @var SimpleType $unionType
715
                */
716
                $unionType = $this->findSomeTypeFromAttribute(
717
                    $type,
718
                    $node,
719
                    $typeName
720
                );
721
                $type->addUnion($unionType);
722
            }
723
        }
724
        $addCallback = function (SimpleType $unType) use ($type) {
725
            $type->addUnion($unType);
726
        };
727
728
        $this->loadTypeWithCallbackOnChildNodes(
729
            $type->getSchema(),
730
            $node,
731
            $addCallback
732
        );
733
    }
734
735
    /**
736
    * @param bool $checkAbstract
737
    */
738
    private function fillTypeNode(Type $type, DOMElement $node, $checkAbstract = true)
739
    {
740
741
        if ($checkAbstract) {
742
            $type->setAbstract($node->getAttribute("abstract") === "true" || $node->getAttribute("abstract") === "1");
743
        }
744
745
        foreach ($node->childNodes as $childNode) {
746
            switch ($childNode->localName) {
747
                case 'restriction':
748
                    $this->loadRestriction($type, $childNode);
749
                    break;
750
                case 'extension':
751
                    $this->maybeLoadExtensionFromBaseComplexType(
752
                        $type,
753
                        $childNode
754
                    );
755
                    break;
756
                case 'simpleContent':
757
                case 'complexContent':
758
                    $this->fillTypeNode($type, $childNode, false);
759
                    break;
760
            }
761
        }
762
    }
763
764
    private function loadExtension(BaseComplexType $type, DOMElement $node)
765
    {
766
        $extension = new Extension();
767
        $type->setExtension($extension);
768
769
        if ($node->hasAttribute("base")) {
770
            $this->findAndSetSomeBase(
771
                $type,
772
                $extension,
773
                $node
774
            );
775
        }
776
777
        foreach ($node->childNodes as $childNode) {
778
            switch ($childNode->localName) {
779
                case 'sequence':
780
                case 'choice':
781
                case 'all':
782
                    $this->maybeLoadSequenceFromElementContainer(
783
                        $type,
784
                        $childNode
785
                    );
786
                    break;
787
                case 'attribute':
788
                    $attribute = $this->getAttributeFromAttributeOrRef(
789
                        $childNode,
790
                        $type->getSchema(),
791
                        $node
792
                    );
793
                    $type->addAttribute($attribute);
794
                    break;
795
                case 'attributeGroup':
796
                    AttributeGroup::findSomethingLikeThis(
797
                        $this,
798
                        $type->getSchema(),
799
                        $node,
800
                        $childNode,
801
                        $type
802
                    );
803
                    break;
804
            }
805
        }
806
    }
807
808
    private function findAndSetSomeBase(
809
        Type $type,
810
        Base $setBaseOnThis,
811
        DOMElement $node
812
    ) {
813
        /**
814
        * @var Type $parent
815
        */
816
        $parent = $this->findSomeType($type, $node, 'base');
817
        $setBaseOnThis->setBase($parent);
818
    }
819
820
    private function maybeLoadExtensionFromBaseComplexType(
821
        Type $type,
822
        DOMElement $childNode
823
    ) {
824
        if (! ($type instanceof BaseComplexType)) {
825
            throw new RuntimeException(
826
                'Argument 1 passed to ' .
827
                __METHOD__ .
828
                ' needs to be an instance of ' .
829
                BaseComplexType::class .
830
                ' when passed onto ' .
831
                static::class .
832
                '::loadExtension(), ' .
833
                get_class($type) .
834
                ' given.'
835
            );
836
        }
837
        $this->loadExtension($type, $childNode);
838
    }
839
840
    private function loadRestriction(Type $type, DOMElement $node)
841
    {
842
        $restriction = new Restriction();
843
        $type->setRestriction($restriction);
844
        if ($node->hasAttribute("base")) {
845
            $this->findAndSetSomeBase($type, $restriction, $node);
846
        } else {
847
            $addCallback = function (Type $restType) use ($restriction) {
848
                $restriction->setBase($restType);
849
            };
850
851
            $this->loadTypeWithCallbackOnChildNodes(
852
                $type->getSchema(),
853
                $node,
854
                $addCallback
855
            );
856
        }
857
        foreach ($node->childNodes as $childNode) {
858
            if (in_array($childNode->localName,
859
                [
860
                    'enumeration',
861
                    'pattern',
862
                    'length',
863
                    'minLength',
864
                    'maxLength',
865
                    'minInclusive',
866
                    'maxInclusive',
867
                    'minExclusive',
868
                    'maxExclusive',
869
                    'fractionDigits',
870
                    'totalDigits',
871
                    'whiteSpace'
872
                ], true)) {
873
                $restriction->addCheck($childNode->localName,
874
                    [
875
                        'value' => $childNode->getAttribute("value"),
876
                        'doc' => $this->getDocumentation($childNode)
877
                    ]);
878
            }
879
        }
880
    }
881
882
    /**
883
    * @param string $typeName
884
    *
885
    * @return mixed[]
886
    */
887
    private static function splitParts(DOMElement $node, $typeName)
888
    {
889
        $namespace = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $namespace is dead and can be removed.
Loading history...
890
        $prefix = null;
891
        $name = $typeName;
892
        if (strpos($typeName, ':') !== false) {
893
            list ($prefix, $name) = explode(':', $typeName);
894
        }
895
896
        $namespace = $node->lookupNamespaceUri($prefix ?: '');
897
        return array(
898
            $name,
899
            $namespace,
900
            $prefix
901
        );
902
    }
903
904
    /**
905
     *
906
     * @param string $finder
907
     * @param Schema $schema
908
     * @param DOMElement $node
909
     * @param string $typeName
910
     * @throws TypeException
911
     * @return ElementItem|Group|AttributeItem|AttributeGroup|Type
912
     */
913
    public function findSomething($finder, Schema $schema, DOMElement $node, $typeName)
914
    {
915
        list ($name, $namespace) = self::splitParts($node, $typeName);
916
917
        $namespace = $namespace ?: $schema->getTargetNamespace();
918
919
        try {
920
            return $schema->$finder($name, $namespace);
921
        } catch (TypeNotFoundException $e) {
922
            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);
923
        }
924
    }
925
926
    /**
927
    * @return Closure
928
    */
929 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...
930
    {
931
        $element = new ElementDef($schema, $node->getAttribute("name"));
932
        $schema->addElement($element);
933
934
        return function () use ($element, $node) {
935
            $this->fillItem($element, $node);
936
        };
937
    }
938
939
    private function fillItem(Item $element, DOMElement $node)
940
    {
941
        $localType = null;
942
        foreach ($node->childNodes as $childNode) {
943
            switch ($childNode->localName) {
944
                case 'complexType':
945
                case 'simpleType':
946
                    $localType = $childNode;
947
                    break 2;
948
            }
949
        }
950
951
        if ($localType) {
952
            $addCallback = function (Type $type) use ($element) {
953
                $element->setType($type);
954
            };
955
            $this->loadTypeWithCallback(
956
                $element->getSchema(),
957
                $localType,
958
                $addCallback
959
            );
960
        } else {
961
962
            if ($node->getAttribute("type")) {
963
                /**
964
                * @var Type $type
965
                */
966
                $type = $this->findSomeType($element, $node, 'type');
967
            } else {
968
                /**
969
                * @var Type $type
970
                */
971
                $type = $this->findSomeTypeFromAttribute(
972
                    $element,
973
                    $node,
974
                    ($node->lookupPrefix(self::XSD_NS) . ':anyType')
975
                );
976
            }
977
978
            $element->setType($type);
979
        }
980
    }
981
982
    /**
983
    * @return Closure
984
    */
985
    private function loadImport(Schema $schema, DOMElement $node)
986
    {
987
        $base = urldecode($node->ownerDocument->documentURI);
988
        $file = UrlUtils::resolveRelativeUrl($base, $node->getAttribute("schemaLocation"));
989
        if ($node->hasAttribute("namespace")
990
            && isset(self::$globalSchemaInfo[$node->getAttribute("namespace")])
991
            && isset($this->loadedFiles[self::$globalSchemaInfo[$node->getAttribute("namespace")]])
992
        ) {
993
994
            $schema->addSchema($this->loadedFiles[self::$globalSchemaInfo[$node->getAttribute("namespace")]]);
995
996
            return function () {
997
            };
998
        } elseif ($node->hasAttribute("namespace")
999
            && isset($this->loadedFiles[$this->getNamespaceSpecificFileIndex($file, $node->getAttribute("namespace"))])) {
1000
            $schema->addSchema($this->loadedFiles[$this->getNamespaceSpecificFileIndex($file, $node->getAttribute("namespace"))]);
1001
            return function () {
1002
            };
1003
        } elseif (isset($this->loadedFiles[$file])) {
1004
            $schema->addSchema($this->loadedFiles[$file]);
1005
            return function () {
1006
            };
1007
        }
1008
1009
        if (!$node->getAttribute("namespace")) {
1010
            $this->loadedFiles[$file] = $newSchema = $schema;
1011
        } else {
1012
            $this->loadedFiles[$file] = $newSchema = new Schema();
1013
            $newSchema->addSchema($this->getGlobalSchema());
1014
        }
1015
1016
        $xml = $this->getDOM(isset($this->knownLocationSchemas[$file]) ? $this->knownLocationSchemas[$file] : $file);
1017
1018
        $callbacks = $this->schemaNode($newSchema, $xml->documentElement, $schema);
1019
1020
        if ($node->getAttribute("namespace")) {
1021
            $schema->addSchema($newSchema);
1022
        }
1023
1024
1025
        return function () use ($callbacks) {
1026
            foreach ($callbacks as $callback) {
1027
                call_user_func($callback);
1028
            }
1029
        };
1030
    }
1031
1032
    /**
1033
    * @var Schema|null
1034
    */
1035
    private $globalSchema;
1036
1037
    /**
1038
     *
1039
     * @return Schema
1040
     */
1041
    public function getGlobalSchema()
1042
    {
1043
        if (!$this->globalSchema) {
1044
            $callbacks = array();
1045
            $globalSchemas = array();
1046
            foreach (self::$globalSchemaInfo as $namespace => $uri) {
1047
                $this->loadedFiles[$uri] = $globalSchemas [$namespace] = $schema = new Schema();
1048
                if ($namespace === self::XSD_NS) {
1049
                    $this->globalSchema = $schema;
1050
                }
1051
                $xml = $this->getDOM($this->knownLocationSchemas[$uri]);
1052
                $callbacks = array_merge($callbacks, $this->schemaNode($schema, $xml->documentElement));
1053
            }
1054
1055
            $globalSchemas[self::XSD_NS]->addType(new SimpleType($globalSchemas[self::XSD_NS], "anySimpleType"));
1056
            $globalSchemas[self::XSD_NS]->addType(new SimpleType($globalSchemas[self::XSD_NS], "anyType"));
1057
1058
            $globalSchemas[self::XML_NS]->addSchema($globalSchemas[self::XSD_NS], self::XSD_NS);
1059
            $globalSchemas[self::XSD_NS]->addSchema($globalSchemas[self::XML_NS], self::XML_NS);
1060
1061
            foreach ($callbacks as $callback) {
1062
                $callback();
1063
            }
1064
        }
1065
1066
        /**
1067
        * @var Schema $out
1068
        */
1069
        $out = $this->globalSchema;
1070
1071
        return $out;
1072
    }
1073
1074
    /**
1075
     * @param DOMElement $node
1076
     * @param string  $file
1077
     *
1078
     * @return Schema
1079
     */
1080
    public function readNode(DOMElement $node, $file = 'schema.xsd')
1081
    {
1082
        $fileKey = $node->hasAttribute('targetNamespace') ? $this->getNamespaceSpecificFileIndex($file, $node->getAttribute('targetNamespace')) : $file;
1083
        $this->loadedFiles[$fileKey] = $rootSchema = new Schema();
1084
1085
        $rootSchema->addSchema($this->getGlobalSchema());
1086
        $callbacks = $this->schemaNode($rootSchema, $node);
1087
1088
        foreach ($callbacks as $callback) {
1089
            call_user_func($callback);
1090
        }
1091
1092
        return $rootSchema;
1093
    }
1094
1095
    /**
1096
     * It is possible that a single file contains multiple <xsd:schema/> nodes, for instance in a WSDL file.
1097
     *
1098
     * Each of these  <xsd:schema/> nodes typically target a specific namespace. Append the target namespace to the
1099
     * file to distinguish between multiple schemas in a single file.
1100
     *
1101
     * @param string $file
1102
     * @param string $targetNamespace
1103
     *
1104
     * @return string
1105
     */
1106
    private function getNamespaceSpecificFileIndex($file, $targetNamespace)
1107
    {
1108
        return $file . '#' . $targetNamespace;
1109
    }
1110
1111
    /**
1112
     * @param string $content
1113
     * @param string $file
1114
     *
1115
     * @return Schema
1116
     *
1117
     * @throws IOException
1118
     */
1119
    public function readString($content, $file = 'schema.xsd')
1120
    {
1121
        $xml = new DOMDocument('1.0', 'UTF-8');
1122
        if (!$xml->loadXML($content)) {
1123
            throw new IOException("Can't load the schema");
1124
        }
1125
        $xml->documentURI = $file;
1126
1127
        return $this->readNode($xml->documentElement, $file);
1128
    }
1129
1130
    /**
1131
     * @param string $file
1132
     *
1133
     * @return Schema
1134
     */
1135
    public function readFile($file)
1136
    {
1137
        $xml = $this->getDOM($file);
1138
        return $this->readNode($xml->documentElement, $file);
1139
    }
1140
1141
    /**
1142
     * @param string $file
1143
     *
1144
     * @return DOMDocument
1145
     *
1146
     * @throws IOException
1147
     */
1148
    private function getDOM($file)
1149
    {
1150
        $xml = new DOMDocument('1.0', 'UTF-8');
1151
        if (!$xml->load($file)) {
1152
            throw new IOException("Can't load the file $file");
1153
        }
1154
        return $xml;
1155
    }
1156
}
1157