Passed
Push — static-analysis ( e5e07d...fa0f38 )
by SignpostMarv
01:28
created

SchemaReader::getNamespaceSpecificFileIndex()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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