Passed
Push — static-analysis ( fa0f38...7c9eae )
by SignpostMarv
01:29
created

SchemaReader::addGroupAsElement()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 7
nc 1
nop 4
dl 0
loc 18
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
        if ($node->hasAttribute("minOccurs")) {
271
            $element->setMin((int)$node->getAttribute("minOccurs"));
272
        }
273
274
        $xp = new \DOMXPath($node->ownerDocument);
275
        $xp->registerNamespace('xs', 'http://www.w3.org/2001/XMLSchema');
276
277
        if ($xp->query('ancestor::xs:choice', $node)->length) {
278
            $element->setMin(0);
279
        }
280
281
        if ($node->hasAttribute("nillable")) {
282
            $element->setNil($node->getAttribute("nillable") == "true");
283
        }
284
        if ($node->hasAttribute("form")) {
285
            $element->setQualified($node->getAttribute("form") == "qualified");
286
        }
287
        return $element;
288
    }
289
290
    /**
291
    * @return GroupRef
292
    */
293
    private function loadGroupRef(Group $referenced, DOMElement $node)
294
    {
295
        $ref = new GroupRef($referenced);
296
        $ref->setDoc($this->getDocumentation($node));
297
298
        static::maybeSetMax($ref, $node);
299
        if ($node->hasAttribute("minOccurs")) {
300
            $ref->setMin((int)$node->getAttribute("minOccurs"));
301
        }
302
303
        return $ref;
304
    }
305
306
    /**
307
    * @return ElementRef
308
    */
309 View Code Duplication
    private function loadElementRef(ElementDef $referenced, 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...
310
    {
311
        $ref = new ElementRef($referenced);
312
        $this->setDoc($ref, $node);
313
314
        static::maybeSetMax($ref, $node);
315
        if ($node->hasAttribute("minOccurs")) {
316
            $ref->setMin((int)$node->getAttribute("minOccurs"));
317
        }
318
        if ($node->hasAttribute("nillable")) {
319
            $ref->setNil($node->getAttribute("nillable") == "true");
320
        }
321
        if ($node->hasAttribute("form")) {
322
            $ref->setQualified($node->getAttribute("form") == "qualified");
323
        }
324
325
        return $ref;
326
    }
327
328
    private function setDoc(Item $ref, DOMElement $node)
329
    {
330
        $ref->setDoc($this->getDocumentation($node));
331
    }
332
333
    private static function maybeSetMax(InterfaceSetMinMax $ref, DOMElement $node)
334
    {
335
        if (
336
            $node->hasAttribute("maxOccurs")
337
        ) {
338
            $ref->setMax($node->getAttribute("maxOccurs") == "unbounded" ? -1 : (int)$node->getAttribute("maxOccurs"));
339
        }
340
    }
341
342
    /**
343
    * @return AttributeRef
344
    */
345 View Code Duplication
    private function loadAttributeRef(AttributeDef $referencedAttribiute, 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...
346
    {
347
        $attribute = new AttributeRef($referencedAttribiute);
348
        $this->setDoc($attribute, $node);
349
350
        if ($node->hasAttribute("nillable")) {
351
            $attribute->setNil($node->getAttribute("nillable") == "true");
352
        }
353
        if ($node->hasAttribute("form")) {
354
            $attribute->setQualified($node->getAttribute("form") == "qualified");
355
        }
356
        if ($node->hasAttribute("use")) {
357
            $attribute->setUse($node->getAttribute("use"));
358
        }
359
        return $attribute;
360
    }
361
362
    /**
363
    * @param int|null $max
364
    */
365
    private function loadSequence(ElementContainer $elementContainer, DOMElement $node, $max = null)
366
    {
367
        $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...
368
369
        foreach ($node->childNodes as $childNode) {
370
371
            switch ($childNode->localName) {
372
                case 'choice':
373
                case 'sequence':
374
                case 'all':
375
                    $this->loadSequence($elementContainer, $childNode, $max);
376
                    break;
377
                case 'element':
378
                    if ($childNode->hasAttribute("ref")) {
379
                        /**
380
                        * @var ElementDef $referencedElement
381
                        */
382
                        $referencedElement = $this->findSomething('findElement', $elementContainer->getSchema(), $node, $childNode->getAttribute("ref"));
383
                        $element = $this->loadElementRef($referencedElement, $childNode);
384
                    } else {
385
                        $element = $this->loadElement($elementContainer->getSchema(), $childNode);
386
                    }
387
                    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...
388
                        $element->setMax($max);
389
                    }
390
                    $elementContainer->addElement($element);
391
                    break;
392
                case 'group':
393
                    $this->addGroupAsElement(
394
                        $elementContainer->getSchema(),
395
                        $node,
396
                        $childNode,
397
                        $elementContainer
398
                    );
399
                    break;
400
            }
401
        }
402
    }
403
404
    private function addGroupAsElement(
405
        Schema $schema,
406
        DOMElement $node,
407
        DOMElement $childNode,
408
        ElementContainer $elementContainer
409
    ) {
410
        /**
411
        * @var Group $referencedGroup
412
        */
413
        $referencedGroup = $this->findSomething(
414
            'findGroup',
415
            $schema,
416
            $node,
417
            $childNode->getAttribute("ref")
418
        );
419
420
        $group = $this->loadGroupRef($referencedGroup, $childNode);
421
        $elementContainer->addElement($group);
422
    }
423
424
    private function maybeLoadSequenceFromElementContainer(
425
        BaseComplexType $type,
426
        DOMElement $childNode
427
    ) {
428
        if (! ($type instanceof ElementContainer)) {
429
            throw new RuntimeException(
430
                '$type passed to ' .
431
                __FUNCTION__ .
432
                'expected to be an instance of ' .
433
                ElementContainer::class .
434
                ' when child node localName is "group", ' .
435
                get_class($type) .
436
                ' given.'
437
            );
438
        }
439
        $this->loadSequence($type, $childNode);
440
    }
441
442
    /**
443
    * @return Closure
444
    */
445
    private function loadGroup(Schema $schema, DOMElement $node)
446
    {
447
        $group = new Group($schema, $node->getAttribute("name"));
448
        $group->setDoc($this->getDocumentation($node));
449
450
        if ($node->hasAttribute("maxOccurs")) {
451
            static::maybeSetMax(new GroupRef($group), $node);
452
        }
453
        if ($node->hasAttribute("minOccurs")) {
454
            $group = new GroupRef($group);
455
            $group->setMin((int)$node->getAttribute("minOccurs"));
456
        }
457
458
        $schema->addGroup($group);
459
460
        return function () use ($group, $node) {
461
            foreach ($node->childNodes as $childNode) {
462
                switch ($childNode->localName) {
463
                    case 'sequence':
464
                    case 'choice':
465
                    case 'all':
466
                        $this->loadSequence($group, $childNode);
467
                        break;
468
                }
469
            }
470
        };
471
    }
472
473
    /**
474
    * @param Closure|null $callback
475
    *
476
    * @return Closure
477
    */
478
    private function loadComplexType(Schema $schema, DOMElement $node, $callback = null)
479
    {
480
        $isSimple = false;
481
482
        foreach ($node->childNodes as $childNode) {
483
            if ($childNode->localName === "simpleContent") {
484
                $isSimple = true;
485
                break;
486
            }
487
        }
488
489
        $type = $isSimple ? new ComplexTypeSimpleContent($schema, $node->getAttribute("name")) : new ComplexType($schema, $node->getAttribute("name"));
490
491
        $type->setDoc($this->getDocumentation($node));
492
        if ($node->getAttribute("name")) {
493
            $schema->addType($type);
494
        }
495
496
        return function () use ($type, $node, $schema, $callback) {
497
498
            $this->fillTypeNode($type, $node);
499
500
            foreach ($node->childNodes as $childNode) {
501
                switch ($childNode->localName) {
502
                    case 'sequence':
503
                    case 'choice':
504
                    case 'all':
505
                        $this->maybeLoadSequenceFromElementContainer(
506
                            $type,
507
                            $childNode
508
                        );
509
                        break;
510
                    case 'attribute':
511
                        $attribute = $this->getAttributeFromAttributeOrRef(
512
                            $childNode,
513
                            $schema,
514
                            $node
515
                        );
516
517
                        $type->addAttribute($attribute);
518
                        break;
519
                    case 'group':
520
                        if (! ($type instanceof ComplexType)) {
521
                            throw new RuntimeException(
522
                                '$type passed to ' .
523
                                __FUNCTION__ .
524
                                'expected to be an instance of ' .
525
                                ComplexType::class .
526
                                ' when child node localName is "group", ' .
527
                                get_class($type) .
528
                                ' given.'
529
                            );
530
                        }
531
532
                        $this->addGroupAsElement(
533
                            $schema,
534
                            $node,
535
                            $childNode,
536
                            $type
537
                        );
538
                        break;
539
                    case 'attributeGroup':
540
                        AttributeGroup::findSomethingLikeThis(
541
                            $this,
542
                            $schema,
543
                            $node,
544
                            $childNode,
545
                            $type
546
                        );
547
                        break;
548
                }
549
            }
550
551
            if ($callback) {
552
                call_user_func($callback, $type);
553
            }
554
        };
555
    }
556
557
    /**
558
    * @param Closure|null $callback
559
    *
560
    * @return Closure
561
    */
562
    private function loadSimpleType(Schema $schema, DOMElement $node, $callback = null)
563
    {
564
        $type = new SimpleType($schema, $node->getAttribute("name"));
565
        $type->setDoc($this->getDocumentation($node));
566
        if ($node->getAttribute("name")) {
567
            $schema->addType($type);
568
        }
569
570
        return function () use ($type, $node, $callback) {
571
            $this->fillTypeNode($type, $node);
572
573
            foreach ($node->childNodes as $childNode) {
574
                switch ($childNode->localName) {
575
                    case 'union':
576
                        $this->loadUnion($type, $childNode);
577
                        break;
578
                    case 'list':
579
                        $this->loadList($type, $childNode);
580
                        break;
581
                }
582
            }
583
584
            if ($callback) {
585
                call_user_func($callback, $type);
586
            }
587
        };
588
    }
589
590
    private function loadList(SimpleType $type, DOMElement $node)
591
    {
592
        if ($node->hasAttribute("itemType")) {
593
            /**
594
            * @var SimpleType $listType
595
            */
596
            $listType = $this->findSomeType($type, $node, 'itemType');
597
            $type->setList($listType);
598
        } else {
599
            $addCallback = function (SimpleType $list) use ($type) {
600
                $type->setList($list);
601
            };
602
603
            $this->loadTypeWithCallbackOnChildNodes(
604
                $type->getSchema(),
605
                $node,
606
                $addCallback
607
            );
608
        }
609
    }
610
611
    private function loadTypeWithCallbackOnChildNodes(
612
        Schema $schema,
613
        DOMNode $node,
614
        Closure $callback
615
    ) {
616
        foreach ($node->childNodes as $childNode) {
617
            $this->loadTypeWithCallback($schema, $childNode, $callback);
618
        }
619
    }
620
621
    private function loadTypeWithCallback(
622
        Schema $schema,
623
        DOMNode $childNode,
624
        Closure $callback
625
    ) {
626
        if (! ($childNode instanceof DOMElement)) {
627
            return;
628
        }
629
        switch ($childNode->localName) {
630
            case 'complexType':
631
                $childNode = $childNode;
632
                call_user_func(
633
                    $this->loadComplexType(
634
                        $schema,
635
                        $childNode,
636
                        $callback
637
                    )
638
                );
639
                break;
640
            case 'simpleType':
641
                call_user_func(
642
                    $this->loadSimpleType($schema, $childNode, $callback)
643
                );
644
                break;
645
        }
646
    }
647
648
    /**
649
    * @return SchemaItem
650
    */
651
    private function findSomeType(
652
        SchemaItem $fromThis,
653
        DOMElement $node,
654
        string $attributeName
655
    ) {
656
        return $this->findSomeTypeFromAttribute(
657
            $fromThis,
658
            $node,
659
            $node->getAttribute($attributeName)
660
        );
661
    }
662
663
    /**
664
    * @return SchemaItem
665
    */
666
    private function findSomeTypeFromAttribute(
667
        SchemaItem $fromThis,
668
        DOMElement $node,
669
        string $attributeName
670
    ) {
671
        /**
672
        * @var SchemaItem $out
673
        */
674
        $out = $this->findSomething(
675
            'findType',
676
            $fromThis->getSchema(),
677
            $node,
678
            $attributeName
679
        );
680
681
        return $out;
682
    }
683
684
    private function loadUnion(SimpleType $type, DOMElement $node)
685
    {
686
        if ($node->hasAttribute("memberTypes")) {
687
            $types = preg_split('/\s+/', $node->getAttribute("memberTypes"));
688
            foreach ($types as $typeName) {
689
                /**
690
                * @var SimpleType $unionType
691
                */
692
                $unionType = $this->findSomeTypeFromAttribute(
693
                    $type,
694
                    $node,
695
                    $typeName
696
                );
697
                $type->addUnion($unionType);
698
            }
699
        }
700
        $addCallback = function (SimpleType $unType) use ($type) {
701
            $type->addUnion($unType);
702
        };
703
704
        $this->loadTypeWithCallbackOnChildNodes(
705
            $type->getSchema(),
706
            $node,
707
            $addCallback
708
        );
709
    }
710
711
    /**
712
    * @param bool $checkAbstract
713
    */
714
    private function fillTypeNode(Type $type, DOMElement $node, $checkAbstract = true)
715
    {
716
717
        if ($checkAbstract) {
718
            $type->setAbstract($node->getAttribute("abstract") === "true" || $node->getAttribute("abstract") === "1");
719
        }
720
721
        foreach ($node->childNodes as $childNode) {
722
            switch ($childNode->localName) {
723
                case 'restriction':
724
                    $this->loadRestriction($type, $childNode);
725
                    break;
726
                case 'extension':
727
                    $this->maybeLoadExtensionFromBaseComplexType(
728
                        $type,
729
                        $childNode
730
                    );
731
                    break;
732
                case 'simpleContent':
733
                case 'complexContent':
734
                    $this->fillTypeNode($type, $childNode, false);
735
                    break;
736
            }
737
        }
738
    }
739
740
    private function loadExtension(BaseComplexType $type, DOMElement $node)
741
    {
742
        $extension = new Extension();
743
        $type->setExtension($extension);
744
745
        if ($node->hasAttribute("base")) {
746
            $this->findAndSetSomeBase(
747
                $type,
748
                $extension,
749
                $node
750
            );
751
        }
752
753
        foreach ($node->childNodes as $childNode) {
754
            switch ($childNode->localName) {
755
                case 'sequence':
756
                case 'choice':
757
                case 'all':
758
                    $this->maybeLoadSequenceFromElementContainer(
759
                        $type,
760
                        $childNode
761
                    );
762
                    break;
763
                case 'attribute':
764
                    $attribute = $this->getAttributeFromAttributeOrRef(
765
                        $childNode,
766
                        $type->getSchema(),
767
                        $node
768
                    );
769
                    $type->addAttribute($attribute);
770
                    break;
771
                case 'attributeGroup':
772
                    AttributeGroup::findSomethingLikeThis(
773
                        $this,
774
                        $type->getSchema(),
775
                        $node,
776
                        $childNode,
777
                        $type
778
                    );
779
                    break;
780
            }
781
        }
782
    }
783
784
    private function findAndSetSomeBase(
785
        Type $type,
786
        Base $setBaseOnThis,
787
        DOMElement $node
788
    ) {
789
        /**
790
        * @var Type $parent
791
        */
792
        $parent = $this->findSomeType($type, $node, 'base');
793
        $setBaseOnThis->setBase($parent);
794
    }
795
796
    private function maybeLoadExtensionFromBaseComplexType(
797
        Type $type,
798
        DOMElement $childNode
799
    ) {
800
        if (! ($type instanceof BaseComplexType)) {
801
            throw new RuntimeException(
802
                'Argument 1 passed to ' .
803
                __METHOD__ .
804
                ' needs to be an instance of ' .
805
                BaseComplexType::class .
806
                ' when passed onto ' .
807
                static::class .
808
                '::loadExtension(), ' .
809
                get_class($type) .
810
                ' given.'
811
            );
812
        }
813
        $this->loadExtension($type, $childNode);
814
    }
815
816
    private function loadRestriction(Type $type, DOMElement $node)
817
    {
818
        $restriction = new Restriction();
819
        $type->setRestriction($restriction);
820
        if ($node->hasAttribute("base")) {
821
            $this->findAndSetSomeBase($type, $restriction, $node);
822
        } else {
823
            $addCallback = function (Type $restType) use ($restriction) {
824
                $restriction->setBase($restType);
825
            };
826
827
            $this->loadTypeWithCallbackOnChildNodes(
828
                $type->getSchema(),
829
                $node,
830
                $addCallback
831
            );
832
        }
833
        foreach ($node->childNodes as $childNode) {
834
            if (in_array($childNode->localName,
835
                [
836
                    'enumeration',
837
                    'pattern',
838
                    'length',
839
                    'minLength',
840
                    'maxLength',
841
                    'minInclusive',
842
                    'maxInclusive',
843
                    'minExclusive',
844
                    'maxExclusive',
845
                    'fractionDigits',
846
                    'totalDigits',
847
                    'whiteSpace'
848
                ], true)) {
849
                $restriction->addCheck($childNode->localName,
850
                    [
851
                        'value' => $childNode->getAttribute("value"),
852
                        'doc' => $this->getDocumentation($childNode)
853
                    ]);
854
            }
855
        }
856
    }
857
858
    /**
859
    * @param string $typeName
860
    *
861
    * @return mixed[]
862
    */
863
    private static function splitParts(DOMElement $node, $typeName)
864
    {
865
        $namespace = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $namespace is dead and can be removed.
Loading history...
866
        $prefix = null;
867
        $name = $typeName;
868
        if (strpos($typeName, ':') !== false) {
869
            list ($prefix, $name) = explode(':', $typeName);
870
        }
871
872
        $namespace = $node->lookupNamespaceUri($prefix ?: '');
873
        return array(
874
            $name,
875
            $namespace,
876
            $prefix
877
        );
878
    }
879
880
    /**
881
     *
882
     * @param string $finder
883
     * @param Schema $schema
884
     * @param DOMElement $node
885
     * @param string $typeName
886
     * @throws TypeException
887
     * @return ElementItem|Group|AttributeItem|AttributeGroup|Type
888
     */
889
    public function findSomething($finder, Schema $schema, DOMElement $node, $typeName)
890
    {
891
        list ($name, $namespace) = self::splitParts($node, $typeName);
892
893
        $namespace = $namespace ?: $schema->getTargetNamespace();
894
895
        try {
896
            return $schema->$finder($name, $namespace);
897
        } catch (TypeNotFoundException $e) {
898
            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);
899
        }
900
    }
901
902
    /**
903
    * @return Closure
904
    */
905 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...
906
    {
907
        $element = new ElementDef($schema, $node->getAttribute("name"));
908
        $schema->addElement($element);
909
910
        return function () use ($element, $node) {
911
            $this->fillItem($element, $node);
912
        };
913
    }
914
915
    private function fillItem(Item $element, DOMElement $node)
916
    {
917
        $localType = null;
918
        foreach ($node->childNodes as $childNode) {
919
            switch ($childNode->localName) {
920
                case 'complexType':
921
                case 'simpleType':
922
                    $localType = $childNode;
923
                    break 2;
924
            }
925
        }
926
927
        if ($localType) {
928
            $addCallback = function (Type $type) use ($element) {
929
                $element->setType($type);
930
            };
931
            $this->loadTypeWithCallback(
932
                $element->getSchema(),
933
                $localType,
934
                $addCallback
935
            );
936
        } else {
937
938
            if ($node->getAttribute("type")) {
939
                /**
940
                * @var Type $type
941
                */
942
                $type = $this->findSomeType($element, $node, 'type');
943
            } else {
944
                /**
945
                * @var Type $type
946
                */
947
                $type = $this->findSomeTypeFromAttribute(
948
                    $element,
949
                    $node,
950
                    ($node->lookupPrefix(self::XSD_NS) . ':anyType')
951
                );
952
            }
953
954
            $element->setType($type);
955
        }
956
    }
957
958
    /**
959
    * @return Closure
960
    */
961
    private function loadImport(Schema $schema, DOMElement $node)
962
    {
963
        $base = urldecode($node->ownerDocument->documentURI);
964
        $file = UrlUtils::resolveRelativeUrl($base, $node->getAttribute("schemaLocation"));
965
        if ($node->hasAttribute("namespace")
966
            && isset(self::$globalSchemaInfo[$node->getAttribute("namespace")])
967
            && isset($this->loadedFiles[self::$globalSchemaInfo[$node->getAttribute("namespace")]])
968
        ) {
969
970
            $schema->addSchema($this->loadedFiles[self::$globalSchemaInfo[$node->getAttribute("namespace")]]);
971
972
            return function () {
973
            };
974
        } elseif ($node->hasAttribute("namespace")
975
            && isset($this->loadedFiles[$this->getNamespaceSpecificFileIndex($file, $node->getAttribute("namespace"))])) {
976
            $schema->addSchema($this->loadedFiles[$this->getNamespaceSpecificFileIndex($file, $node->getAttribute("namespace"))]);
977
            return function () {
978
            };
979
        } elseif (isset($this->loadedFiles[$file])) {
980
            $schema->addSchema($this->loadedFiles[$file]);
981
            return function () {
982
            };
983
        }
984
985
        if (!$node->getAttribute("namespace")) {
986
            $this->loadedFiles[$file] = $newSchema = $schema;
987
        } else {
988
            $this->loadedFiles[$file] = $newSchema = new Schema();
989
            $newSchema->addSchema($this->getGlobalSchema());
990
        }
991
992
        $xml = $this->getDOM(isset($this->knownLocationSchemas[$file]) ? $this->knownLocationSchemas[$file] : $file);
993
994
        $callbacks = $this->schemaNode($newSchema, $xml->documentElement, $schema);
995
996
        if ($node->getAttribute("namespace")) {
997
            $schema->addSchema($newSchema);
998
        }
999
1000
1001
        return function () use ($callbacks) {
1002
            foreach ($callbacks as $callback) {
1003
                call_user_func($callback);
1004
            }
1005
        };
1006
    }
1007
1008
    /**
1009
    * @var Schema|null
1010
    */
1011
    private $globalSchema;
1012
1013
    /**
1014
     *
1015
     * @return Schema
1016
     */
1017
    public function getGlobalSchema()
1018
    {
1019
        if (!$this->globalSchema) {
1020
            $callbacks = array();
1021
            $globalSchemas = array();
1022
            foreach (self::$globalSchemaInfo as $namespace => $uri) {
1023
                $this->loadedFiles[$uri] = $globalSchemas [$namespace] = $schema = new Schema();
1024
                if ($namespace === self::XSD_NS) {
1025
                    $this->globalSchema = $schema;
1026
                }
1027
                $xml = $this->getDOM($this->knownLocationSchemas[$uri]);
1028
                $callbacks = array_merge($callbacks, $this->schemaNode($schema, $xml->documentElement));
1029
            }
1030
1031
            $globalSchemas[self::XSD_NS]->addType(new SimpleType($globalSchemas[self::XSD_NS], "anySimpleType"));
1032
            $globalSchemas[self::XSD_NS]->addType(new SimpleType($globalSchemas[self::XSD_NS], "anyType"));
1033
1034
            $globalSchemas[self::XML_NS]->addSchema($globalSchemas[self::XSD_NS], self::XSD_NS);
1035
            $globalSchemas[self::XSD_NS]->addSchema($globalSchemas[self::XML_NS], self::XML_NS);
1036
1037
            foreach ($callbacks as $callback) {
1038
                $callback();
1039
            }
1040
        }
1041
1042
        /**
1043
        * @var Schema $out
1044
        */
1045
        $out = $this->globalSchema;
1046
1047
        return $out;
1048
    }
1049
1050
    /**
1051
     * @param DOMElement $node
1052
     * @param string  $file
1053
     *
1054
     * @return Schema
1055
     */
1056
    public function readNode(DOMElement $node, $file = 'schema.xsd')
1057
    {
1058
        $fileKey = $node->hasAttribute('targetNamespace') ? $this->getNamespaceSpecificFileIndex($file, $node->getAttribute('targetNamespace')) : $file;
1059
        $this->loadedFiles[$fileKey] = $rootSchema = new Schema();
1060
1061
        $rootSchema->addSchema($this->getGlobalSchema());
1062
        $callbacks = $this->schemaNode($rootSchema, $node);
1063
1064
        foreach ($callbacks as $callback) {
1065
            call_user_func($callback);
1066
        }
1067
1068
        return $rootSchema;
1069
    }
1070
1071
    /**
1072
     * It is possible that a single file contains multiple <xsd:schema/> nodes, for instance in a WSDL file.
1073
     *
1074
     * Each of these  <xsd:schema/> nodes typically target a specific namespace. Append the target namespace to the
1075
     * file to distinguish between multiple schemas in a single file.
1076
     *
1077
     * @param string $file
1078
     * @param string $targetNamespace
1079
     *
1080
     * @return string
1081
     */
1082
    private function getNamespaceSpecificFileIndex($file, $targetNamespace)
1083
    {
1084
        return $file . '#' . $targetNamespace;
1085
    }
1086
1087
    /**
1088
     * @param string $content
1089
     * @param string $file
1090
     *
1091
     * @return Schema
1092
     *
1093
     * @throws IOException
1094
     */
1095
    public function readString($content, $file = 'schema.xsd')
1096
    {
1097
        $xml = new DOMDocument('1.0', 'UTF-8');
1098
        if (!$xml->loadXML($content)) {
1099
            throw new IOException("Can't load the schema");
1100
        }
1101
        $xml->documentURI = $file;
1102
1103
        return $this->readNode($xml->documentElement, $file);
1104
    }
1105
1106
    /**
1107
     * @param string $file
1108
     *
1109
     * @return Schema
1110
     */
1111
    public function readFile($file)
1112
    {
1113
        $xml = $this->getDOM($file);
1114
        return $this->readNode($xml->documentElement, $file);
1115
    }
1116
1117
    /**
1118
     * @param string $file
1119
     *
1120
     * @return DOMDocument
1121
     *
1122
     * @throws IOException
1123
     */
1124
    private function getDOM($file)
1125
    {
1126
        $xml = new DOMDocument('1.0', 'UTF-8');
1127
        if (!$xml->load($file)) {
1128
            throw new IOException("Can't load the file $file");
1129
        }
1130
        return $xml;
1131
    }
1132
}
1133