Passed
Push — static-analysis ( 28a59f...6ec81d )
by SignpostMarv
01:24
created

SchemaReader::findSomething()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 1
nop 4
dl 0
loc 10
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
    /**
212
     *
213
     * @param Schema $schema
214
     * @param DOMElement $node
215
     * @param Schema $parent
216
     * @return array
217
     */
218
    private function schemaNode(Schema $schema, DOMElement $node, Schema $parent = null)
219
    {
220
        $this->setSchemaPropertiesFromNode($schema, $node, $parent);
0 ignored issues
show
Bug introduced by
The method setSchemaPropertiesFromNode() does not exist on GoetasWebservices\XML\XSDReader\SchemaReader. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

220
        $this->/** @scrutinizer ignore-call */ setSchemaPropertiesFromNode($schema, $node, $parent);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

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