Passed
Push — static-analysis ( 6532ba...6a1bf3 )
by SignpostMarv
01:42
created

SchemaReader::findSomeType()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 3
dl 0
loc 9
rs 9.6666
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\Extension;
25
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Restriction;
26
use GoetasWebservices\XML\XSDReader\Schema\Item;
27
use GoetasWebservices\XML\XSDReader\Schema\Schema;
28
use GoetasWebservices\XML\XSDReader\Schema\SchemaItem;
29
use GoetasWebservices\XML\XSDReader\Schema\Type\BaseComplexType;
30
use GoetasWebservices\XML\XSDReader\Schema\Type\ComplexType;
31
use GoetasWebservices\XML\XSDReader\Schema\Type\ComplexTypeSimpleContent;
32
use GoetasWebservices\XML\XSDReader\Schema\Type\SimpleType;
33
use GoetasWebservices\XML\XSDReader\Schema\Type\Type;
34
use GoetasWebservices\XML\XSDReader\Utils\UrlUtils;
35
use RuntimeException;
36
37
class SchemaReader
38
{
39
40
    const XSD_NS = "http://www.w3.org/2001/XMLSchema";
41
42
    const XML_NS = "http://www.w3.org/XML/1998/namespace";
43
44
    /**
45
    * @var Schema[]
46
    */
47
    private $loadedFiles = array();
48
49
    /**
50
    * @var string[]
51
    */
52
    private $knownLocationSchemas = array();
53
54
    /**
55
    * @var string[]
56
    */
57
    private static $globalSchemaInfo = array(
58
        self::XML_NS => 'http://www.w3.org/2001/xml.xsd',
59
        self::XSD_NS => 'http://www.w3.org/2001/XMLSchema.xsd'
60
    );
61
62
    public function __construct()
63
    {
64
        $this->addKnownSchemaLocation('http://www.w3.org/2001/xml.xsd', __DIR__ . '/Resources/xml.xsd');
65
        $this->addKnownSchemaLocation('http://www.w3.org/2001/XMLSchema.xsd', __DIR__ . '/Resources/XMLSchema.xsd');
66
        $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');
67
        $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');
68
        $this->addKnownSchemaLocation('https://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd', __DIR__ . '/Resources/xmldsig-core-schema.xsd');
69
        $this->addKnownSchemaLocation('http://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd', __DIR__ . '/Resources/xmldsig-core-schema.xsd');
70
    }
71
72
    /**
73
    * @param string $remote
74
    * @param string $local
75
    */
76
    public function addKnownSchemaLocation($remote, $local)
77
    {
78
        $this->knownLocationSchemas[$remote] = $local;
79
    }
80
81
    /**
82
    * @return Closure
83
    */
84
    private function loadAttributeGroup(Schema $schema, DOMElement $node)
85
    {
86
        $attGroup = new AttributeGroup($schema, $node->getAttribute("name"));
87
        $attGroup->setDoc($this->getDocumentation($node));
88
        $schema->addAttributeGroup($attGroup);
89
90
        return function () use ($schema, $node, $attGroup) {
91
            foreach ($node->childNodes as $childNode) {
92
                switch ($childNode->localName) {
93
                    case 'attribute':
94
                        $attribute = $this->getAttributeFromAttributeOrRef(
95
                            $childNode,
96
                            $schema,
97
                            $node
98
                        );
99
                        $attGroup->addAttribute($attribute);
100
                        break;
101
                    case 'attributeGroup':
102
                        AttributeGroup::findSomethingLikeThis(
103
                            $this,
104
                            $schema,
105
                            $node,
106
                            $childNode,
107
                            $attGroup
108
                        );
109
                        break;
110
                }
111
            }
112
        };
113
    }
114
115
    /**
116
    * @return AttributeItem
117
    */
118
    private function getAttributeFromAttributeOrRef(
119
        DOMElement $childNode,
120
        Schema $schema,
121
        DOMElement $node
122
    ) {
123
        if ($childNode->hasAttribute("ref")) {
124
            /**
125
            * @var AttributeItem $attribute
126
            */
127
            $attribute = $this->findSomething('findAttribute', $schema, $node, $childNode->getAttribute("ref"));
128
        } else {
129
            /**
130
            * @var Attribute $attribute
131
            */
132
            $attribute = $this->loadAttribute($schema, $childNode);
133
        }
134
135
        return $attribute;
136
    }
137
138
    /**
139
    * @return Attribute
140
    */
141
    private function loadAttribute(Schema $schema, DOMElement $node)
142
    {
143
        $attribute = new Attribute($schema, $node->getAttribute("name"));
144
        $attribute->setDoc($this->getDocumentation($node));
145
        $this->fillItem($attribute, $node);
146
147
        if ($node->hasAttribute("nillable")) {
148
            $attribute->setNil($node->getAttribute("nillable") == "true");
149
        }
150
        if ($node->hasAttribute("form")) {
151
            $attribute->setQualified($node->getAttribute("form") == "qualified");
152
        }
153
        if ($node->hasAttribute("use")) {
154
            $attribute->setUse($node->getAttribute("use"));
155
        }
156
        return $attribute;
157
    }
158
159
    /**
160
    * @return Closure
161
    */
162 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...
163
    {
164
        $attribute = new AttributeDef($schema, $node->getAttribute("name"));
165
166
        $schema->addAttribute($attribute);
167
168
        return function () use ($attribute, $node) {
169
            $this->fillItem($attribute, $node);
170
        };
171
    }
172
173
    /**
174
     * @param DOMElement $node
175
     * @return string
176
     */
177
    private function getDocumentation(DOMElement $node)
178
    {
179
        $doc = '';
180
        foreach ($node->childNodes as $childNode) {
181
            if ($childNode->localName == "annotation") {
182
                foreach ($childNode->childNodes as $subChildNode) {
183
                    if ($subChildNode->localName == "documentation") {
184
                        $doc .= ($subChildNode->nodeValue);
185
                    }
186
                }
187
            }
188
        }
189
        $doc = preg_replace('/[\t ]+/', ' ', $doc);
190
        return trim($doc);
191
    }
192
193
    /**
194
     *
195
     * @param Schema $schema
196
     * @param DOMElement $node
197
     * @param Schema $parent
198
     * @return array
199
     */
200
    private function schemaNode(Schema $schema, DOMElement $node, Schema $parent = null)
201
    {
202
        $schema->setDoc($this->getDocumentation($node));
203
204
        if ($node->hasAttribute("targetNamespace")) {
205
            $schema->setTargetNamespace($node->getAttribute("targetNamespace"));
206
        } elseif ($parent) {
207
            $schema->setTargetNamespace($parent->getTargetNamespace());
208
        }
209
        $schema->setElementsQualification($node->getAttribute("elementFormDefault") == "qualified");
210
        $schema->setAttributesQualification($node->getAttribute("attributeFormDefault") == "qualified");
211
        $schema->setDoc($this->getDocumentation($node));
212
        $functions = array();
213
214
        foreach ($node->childNodes as $childNode) {
215
            switch ($childNode->localName) {
216
                case 'include':
217
                case 'import':
218
                    $functions[] = $this->loadImport($schema, $childNode);
219
                    break;
220
                case 'element':
221
                    $functions[] = $this->loadElementDef($schema, $childNode);
222
                    break;
223
                case 'attribute':
224
                    $functions[] = $this->loadAttributeDef($schema, $childNode);
225
                    break;
226
                case 'attributeGroup':
227
                    $functions[] = $this->loadAttributeGroup($schema, $childNode);
228
                    break;
229
                case 'group':
230
                    $functions[] = $this->loadGroup($schema, $childNode);
231
                    break;
232
                case 'complexType':
233
                    $functions[] = $this->loadComplexType($schema, $childNode);
234
                    break;
235
                case 'simpleType':
236
                    $functions[] = $this->loadSimpleType($schema, $childNode);
237
                    break;
238
            }
239
        }
240
241
        return $functions;
242
    }
243
244
    /**
245
    * @return Element
246
    */
247
    private function loadElement(Schema $schema, DOMElement $node)
248
    {
249
        $element = new Element($schema, $node->getAttribute("name"));
250
        $element->setDoc($this->getDocumentation($node));
251
252
        $this->fillItem($element, $node);
253
254
        static::maybeSetMax($element, $node);
255
        if ($node->hasAttribute("minOccurs")) {
256
            $element->setMin((int)$node->getAttribute("minOccurs"));
257
        }
258
259
        $xp = new \DOMXPath($node->ownerDocument);
260
        $xp->registerNamespace('xs', 'http://www.w3.org/2001/XMLSchema');
261
262
        if ($xp->query('ancestor::xs:choice', $node)->length) {
263
            $element->setMin(0);
264
        }
265
266
        if ($node->hasAttribute("nillable")) {
267
            $element->setNil($node->getAttribute("nillable") == "true");
268
        }
269
        if ($node->hasAttribute("form")) {
270
            $element->setQualified($node->getAttribute("form") == "qualified");
271
        }
272
        return $element;
273
    }
274
275
    /**
276
    * @return GroupRef
277
    */
278
    private function loadGroupRef(Group $referenced, DOMElement $node)
279
    {
280
        $ref = new GroupRef($referenced);
281
        $ref->setDoc($this->getDocumentation($node));
282
283
        static::maybeSetMax($ref, $node);
284
        if ($node->hasAttribute("minOccurs")) {
285
            $ref->setMin((int)$node->getAttribute("minOccurs"));
286
        }
287
288
        return $ref;
289
    }
290
291
    /**
292
    * @return ElementRef
293
    */
294 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...
295
    {
296
        $ref = new ElementRef($referenced);
297
        $this->setDoc($ref, $node);
298
299
        static::maybeSetMax($ref, $node);
300
        if ($node->hasAttribute("minOccurs")) {
301
            $ref->setMin((int)$node->getAttribute("minOccurs"));
302
        }
303
        if ($node->hasAttribute("nillable")) {
304
            $ref->setNil($node->getAttribute("nillable") == "true");
305
        }
306
        if ($node->hasAttribute("form")) {
307
            $ref->setQualified($node->getAttribute("form") == "qualified");
308
        }
309
310
        return $ref;
311
    }
312
313
    private function setDoc(Item $ref, DOMElement $node)
314
    {
315
        $ref->setDoc($this->getDocumentation($node));
316
    }
317
318
    private static function maybeSetMax(InterfaceSetMinMax $ref, DOMElement $node)
319
    {
320
        if (
321
            $node->hasAttribute("maxOccurs")
322
        ) {
323
            $ref->setMax($node->getAttribute("maxOccurs") == "unbounded" ? -1 : (int)$node->getAttribute("maxOccurs"));
324
        }
325
    }
326
327
    /**
328
    * @return AttributeRef
329
    */
330 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...
331
    {
332
        $attribute = new AttributeRef($referencedAttribiute);
333
        $this->setDoc($attribute, $node);
334
335
        if ($node->hasAttribute("nillable")) {
336
            $attribute->setNil($node->getAttribute("nillable") == "true");
337
        }
338
        if ($node->hasAttribute("form")) {
339
            $attribute->setQualified($node->getAttribute("form") == "qualified");
340
        }
341
        if ($node->hasAttribute("use")) {
342
            $attribute->setUse($node->getAttribute("use"));
343
        }
344
        return $attribute;
345
    }
346
347
    /**
348
    * @param int|null $max
349
    */
350
    private function loadSequence(ElementContainer $elementContainer, DOMElement $node, $max = null)
351
    {
352
        $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...
353
354
        foreach ($node->childNodes as $childNode) {
355
356
            switch ($childNode->localName) {
357
                case 'choice':
358
                case 'sequence':
359
                case 'all':
360
                    $this->loadSequence($elementContainer, $childNode, $max);
361
                    break;
362
                case 'element':
363
                    if ($childNode->hasAttribute("ref")) {
364
                        /**
365
                        * @var ElementDef $referencedElement
366
                        */
367
                        $referencedElement = $this->findSomething('findElement', $elementContainer->getSchema(), $node, $childNode->getAttribute("ref"));
368
                        $element = $this->loadElementRef($referencedElement, $childNode);
369
                    } else {
370
                        $element = $this->loadElement($elementContainer->getSchema(), $childNode);
371
                    }
372
                    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...
373
                        $element->setMax($max);
374
                    }
375
                    $elementContainer->addElement($element);
376
                    break;
377
                case 'group':
378
                    /**
379
                    * @var Group $referencedGroup
380
                    */
381
                    $referencedGroup = $this->findSomething('findGroup', $elementContainer->getSchema(), $node, $childNode->getAttribute("ref"));
382
383
                    $group = $this->loadGroupRef($referencedGroup, $childNode);
384
                    $elementContainer->addElement($group);
385
                    break;
386
            }
387
        }
388
    }
389
390
    private function maybeLoadSequenceFromElementContainer(
391
        BaseComplexType $type,
392
        DOMElement $childNode
393
    ) {
394
        if (! ($type instanceof ElementContainer)) {
395
            throw new RuntimeException(
396
                '$type passed to ' .
397
                __FUNCTION__ .
398
                'expected to be an instance of ' .
399
                ElementContainer::class .
400
                ' when child node localName is "group", ' .
401
                get_class($type) .
402
                ' given.'
403
            );
404
        }
405
        $this->loadSequence($type, $childNode);
406
    }
407
408
    /**
409
    * @return Closure
410
    */
411
    private function loadGroup(Schema $schema, DOMElement $node)
412
    {
413
        $group = new Group($schema, $node->getAttribute("name"));
414
        $group->setDoc($this->getDocumentation($node));
415
416
        if ($node->hasAttribute("maxOccurs")) {
417
            static::maybeSetMax(new GroupRef($group), $node);
418
        }
419
        if ($node->hasAttribute("minOccurs")) {
420
            $group = new GroupRef($group);
421
            $group->setMin((int)$node->getAttribute("minOccurs"));
422
        }
423
424
        $schema->addGroup($group);
425
426
        return function () use ($group, $node) {
427
            foreach ($node->childNodes as $childNode) {
428
                switch ($childNode->localName) {
429
                    case 'sequence':
430
                    case 'choice':
431
                    case 'all':
432
                        $this->loadSequence($group, $childNode);
433
                        break;
434
                }
435
            }
436
        };
437
    }
438
439
    /**
440
    * @param Closure|null $callback
441
    *
442
    * @return Closure
443
    */
444
    private function loadComplexType(Schema $schema, DOMElement $node, $callback = null)
445
    {
446
        $isSimple = false;
447
448
        foreach ($node->childNodes as $childNode) {
449
            if ($childNode->localName === "simpleContent") {
450
                $isSimple = true;
451
                break;
452
            }
453
        }
454
455
        $type = $isSimple ? new ComplexTypeSimpleContent($schema, $node->getAttribute("name")) : new ComplexType($schema, $node->getAttribute("name"));
456
457
        $type->setDoc($this->getDocumentation($node));
458
        if ($node->getAttribute("name")) {
459
            $schema->addType($type);
460
        }
461
462
        return function () use ($type, $node, $schema, $callback) {
463
464
            $this->fillTypeNode($type, $node);
465
466
            foreach ($node->childNodes as $childNode) {
467
                switch ($childNode->localName) {
468
                    case 'sequence':
469
                    case 'choice':
470
                    case 'all':
471
                        $this->maybeLoadSequenceFromElementContainer(
472
                            $type,
473
                            $childNode
474
                        );
475
                        break;
476
                    case 'attribute':
477
                        $attribute = $this->getAttributeFromAttributeOrRef(
478
                            $childNode,
479
                            $schema,
480
                            $node
481
                        );
482
483
                        $type->addAttribute($attribute);
484
                        break;
485
                    case 'group':
486
                        if (! ($type instanceof ComplexType)) {
487
                            throw new RuntimeException(
488
                                '$type passed to ' .
489
                                __FUNCTION__ .
490
                                'expected to be an instance of ' .
491
                                ComplexType::class .
492
                                ' when child node localName is "group", ' .
493
                                get_class($type) .
494
                                ' given.'
495
                            );
496
                        }
497
498
                        /**
499
                        * @var Group $referencedGroup
500
                        */
501
                        $referencedGroup = $this->findSomething('findGroup', $schema, $node, $childNode->getAttribute("ref"));
502
                        $group = $this->loadGroupRef($referencedGroup, $childNode);
503
                        $type->addElement($group);
504
                        break;
505
                    case 'attributeGroup':
506
                        AttributeGroup::findSomethingLikeThis(
507
                            $this,
508
                            $schema,
509
                            $node,
510
                            $childNode,
511
                            $type
512
                        );
513
                        break;
514
                }
515
            }
516
517
            if ($callback) {
518
                call_user_func($callback, $type);
519
            }
520
        };
521
    }
522
523
    /**
524
    * @param Closure|null $callback
525
    *
526
    * @return Closure
527
    */
528
    private function loadSimpleType(Schema $schema, DOMElement $node, $callback = null)
529
    {
530
        $type = new SimpleType($schema, $node->getAttribute("name"));
531
        $type->setDoc($this->getDocumentation($node));
532
        if ($node->getAttribute("name")) {
533
            $schema->addType($type);
534
        }
535
536
        return function () use ($type, $node, $callback) {
537
            $this->fillTypeNode($type, $node);
538
539
            foreach ($node->childNodes as $childNode) {
540
                switch ($childNode->localName) {
541
                    case 'union':
542
                        $this->loadUnion($type, $childNode);
543
                        break;
544
                    case 'list':
545
                        $this->loadList($type, $childNode);
546
                        break;
547
                }
548
            }
549
550
            if ($callback) {
551
                call_user_func($callback, $type);
552
            }
553
        };
554
    }
555
556
    private function loadList(SimpleType $type, DOMElement $node)
557
    {
558 View Code Duplication
        if ($node->hasAttribute("itemType")) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
559
            /**
560
            * @var SimpleType $listType
561
            */
562
            $listType = $this->findSomeType($type, $node, 'itemType');
563
            $type->setList($listType);
564
        } else {
565
            $addCallback = function (SimpleType $list) use ($type) {
566
                $type->setList($list);
567
            };
568
569
            foreach ($node->childNodes as $childNode) {
570
                switch ($childNode->localName) {
571
                    case 'simpleType':
572
                        call_user_func($this->loadSimpleType($type->getSchema(), $childNode, $addCallback));
573
                        break;
574
                }
575
            }
576
        }
577
    }
578
579
    /**
580
    * @return SchemaItem
581
    */
582
    private function findSomeType(
583
        SchemaItem $fromThis,
584
        DOMElement $node,
585
        string $attributeName
586
    ) {
587
        return $this->findSomeTypeFromAttribute(
588
            $fromThis,
589
            $node,
590
            $node->getAttribute($attributeName)
591
        );
592
    }
593
594
    /**
595
    * @return SchemaItem
596
    */
597
    private function findSomeTypeFromAttribute(
598
        SchemaItem $fromThis,
599
        DOMElement $node,
600
        string $attributeName
601
    ) {
602
        /**
603
        * @var SchemaItem $out
604
        */
605
        $out = $this->findSomething(
606
            'findType',
607
            $fromThis->getSchema(),
608
            $node,
609
            $attributeName
610
        );
611
612
        return $out;
613
    }
614
615
    private function loadUnion(SimpleType $type, DOMElement $node)
616
    {
617
        if ($node->hasAttribute("memberTypes")) {
618
            $types = preg_split('/\s+/', $node->getAttribute("memberTypes"));
619
            foreach ($types as $typeName) {
620
                /**
621
                * @var SimpleType $unionType
622
                */
623
                $unionType = $this->findSomeTypeFromAttribute(
624
                    $type,
625
                    $node,
626
                    $typeName
627
                );
628
                $type->addUnion($unionType);
629
            }
630
        }
631
        $addCallback = function (SimpleType $unType) use ($type) {
632
            $type->addUnion($unType);
633
        };
634
635
        foreach ($node->childNodes as $childNode) {
636
            switch ($childNode->localName) {
637
                case 'simpleType':
638
                    call_user_func($this->loadSimpleType($type->getSchema(), $childNode, $addCallback));
639
                    break;
640
            }
641
        }
642
    }
643
644
    /**
645
    * @param bool $checkAbstract
646
    */
647
    private function fillTypeNode(Type $type, DOMElement $node, $checkAbstract = true)
648
    {
649
650
        if ($checkAbstract) {
651
            $type->setAbstract($node->getAttribute("abstract") === "true" || $node->getAttribute("abstract") === "1");
652
        }
653
654
        foreach ($node->childNodes as $childNode) {
655
            switch ($childNode->localName) {
656
                case 'restriction':
657
                    $this->loadRestriction($type, $childNode);
658
                    break;
659
                case 'extension':
660
                    $this->maybeLoadExtensionFromBaseComplexType(
661
                        $type,
662
                        $childNode
663
                    );
664
                    break;
665
                case 'simpleContent':
666
                case 'complexContent':
667
                    $this->fillTypeNode($type, $childNode, false);
668
                    break;
669
            }
670
        }
671
    }
672
673
    private function loadExtension(BaseComplexType $type, DOMElement $node)
674
    {
675
        $extension = new Extension();
676
        $type->setExtension($extension);
677
678
        if ($node->hasAttribute("base")) {
679
            /**
680
            * @var Type $parent
681
            */
682
            $parent = $this->findSomeType($type, $node, 'base');
683
            $extension->setBase($parent);
684
        }
685
686
        foreach ($node->childNodes as $childNode) {
687
            switch ($childNode->localName) {
688
                case 'sequence':
689
                case 'choice':
690
                case 'all':
691
                    $this->maybeLoadSequenceFromElementContainer(
692
                        $type,
693
                        $childNode
694
                    );
695
                    break;
696
                case 'attribute':
697
                    $attribute = $this->getAttributeFromAttributeOrRef(
698
                        $childNode,
699
                        $type->getSchema(),
700
                        $node
701
                    );
702
                    $type->addAttribute($attribute);
703
                    break;
704
                case 'attributeGroup':
705
                    AttributeGroup::findSomethingLikeThis(
706
                        $this,
707
                        $type->getSchema(),
708
                        $node,
709
                        $childNode,
710
                        $type
711
                    );
712
                    break;
713
            }
714
        }
715
    }
716
717
    private function maybeLoadExtensionFromBaseComplexType(
718
        Type $type,
719
        DOMElement $childNode
720
    ) {
721
        if (! ($type instanceof BaseComplexType)) {
722
            throw new RuntimeException(
723
                'Argument 1 passed to ' .
724
                __METHOD__ .
725
                ' needs to be an instance of ' .
726
                BaseComplexType::class .
727
                ' when passed onto ' .
728
                static::class .
729
                '::loadExtension(), ' .
730
                get_class($type) .
731
                ' given.'
732
            );
733
        }
734
        $this->loadExtension($type, $childNode);
735
    }
736
737
    private function loadRestriction(Type $type, DOMElement $node)
738
    {
739
        $restriction = new Restriction();
740
        $type->setRestriction($restriction);
741 View Code Duplication
        if ($node->hasAttribute("base")) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
742
            /**
743
            * @var Type $restrictedType
744
            */
745
            $restrictedType = $this->findSomeType($type, $node, 'base');
746
            $restriction->setBase($restrictedType);
747
        } else {
748
            $addCallback = function (Type $restType) use ($restriction) {
749
                $restriction->setBase($restType);
750
            };
751
752
            foreach ($node->childNodes as $childNode) {
753
                switch ($childNode->localName) {
754
                    case 'simpleType':
755
                        call_user_func($this->loadSimpleType($type->getSchema(), $childNode, $addCallback));
756
                        break;
757
                }
758
            }
759
        }
760
        foreach ($node->childNodes as $childNode) {
761
            if (in_array($childNode->localName,
762
                [
763
                    'enumeration',
764
                    'pattern',
765
                    'length',
766
                    'minLength',
767
                    'maxLength',
768
                    'minInclusive',
769
                    'maxInclusive',
770
                    'minExclusive',
771
                    'maxExclusive',
772
                    'fractionDigits',
773
                    'totalDigits',
774
                    'whiteSpace'
775
                ], true)) {
776
                $restriction->addCheck($childNode->localName,
777
                    [
778
                        'value' => $childNode->getAttribute("value"),
779
                        'doc' => $this->getDocumentation($childNode)
780
                    ]);
781
            }
782
        }
783
    }
784
785
    /**
786
    * @param string $typeName
787
    *
788
    * @return mixed[]
789
    */
790
    private static function splitParts(DOMElement $node, $typeName)
791
    {
792
        $namespace = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $namespace is dead and can be removed.
Loading history...
793
        $prefix = null;
794
        $name = $typeName;
795
        if (strpos($typeName, ':') !== false) {
796
            list ($prefix, $name) = explode(':', $typeName);
797
        }
798
799
        $namespace = $node->lookupNamespaceUri($prefix ?: '');
800
        return array(
801
            $name,
802
            $namespace,
803
            $prefix
804
        );
805
    }
806
807
    /**
808
     *
809
     * @param string $finder
810
     * @param Schema $schema
811
     * @param DOMElement $node
812
     * @param string $typeName
813
     * @throws TypeException
814
     * @return ElementItem|Group|AttributeItem|AttributeGroup|Type
815
     */
816
    public function findSomething($finder, Schema $schema, DOMElement $node, $typeName)
817
    {
818
        list ($name, $namespace) = self::splitParts($node, $typeName);
819
820
        $namespace = $namespace ?: $schema->getTargetNamespace();
821
822
        try {
823
            return $schema->$finder($name, $namespace);
824
        } catch (TypeNotFoundException $e) {
825
            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);
826
        }
827
    }
828
829
    /**
830
    * @return Closure
831
    */
832 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...
833
    {
834
        $element = new ElementDef($schema, $node->getAttribute("name"));
835
        $schema->addElement($element);
836
837
        return function () use ($element, $node) {
838
            $this->fillItem($element, $node);
839
        };
840
    }
841
842
    private function fillItem(Item $element, DOMElement $node)
843
    {
844
        $localType = null;
845
        foreach ($node->childNodes as $childNode) {
846
            switch ($childNode->localName) {
847
                case 'complexType':
848
                case 'simpleType':
849
                    $localType = $childNode;
850
                    break 2;
851
            }
852
        }
853
854
        if ($localType) {
855
            $addCallback = function (Type $type) use ($element) {
856
                $element->setType($type);
857
            };
858
            switch ($localType->localName) {
859
                case 'complexType':
860
                    call_user_func($this->loadComplexType($element->getSchema(), $localType, $addCallback));
861
                    break;
862
                case 'simpleType':
863
                    call_user_func($this->loadSimpleType($element->getSchema(), $localType, $addCallback));
864
                    break;
865
            }
866
        } else {
867
868
            if ($node->getAttribute("type")) {
869
                /**
870
                * @var Type $type
871
                */
872
                $type = $this->findSomeType($element, $node, 'type');
873
            } else {
874
                /**
875
                * @var Type $type
876
                */
877
                $type = $this->findSomeTypeFromAttribute(
878
                    $element,
879
                    $node,
880
                    ($node->lookupPrefix(self::XSD_NS) . ':anyType')
881
                );
882
            }
883
884
            $element->setType($type);
885
        }
886
    }
887
888
    /**
889
    * @return Closure
890
    */
891
    private function loadImport(Schema $schema, DOMElement $node)
892
    {
893
        $base = urldecode($node->ownerDocument->documentURI);
894
        $file = UrlUtils::resolveRelativeUrl($base, $node->getAttribute("schemaLocation"));
895
        if ($node->hasAttribute("namespace")
896
            && isset(self::$globalSchemaInfo[$node->getAttribute("namespace")])
897
            && isset($this->loadedFiles[self::$globalSchemaInfo[$node->getAttribute("namespace")]])
898
        ) {
899
900
            $schema->addSchema($this->loadedFiles[self::$globalSchemaInfo[$node->getAttribute("namespace")]]);
901
902
            return function () {
903
            };
904
        } elseif ($node->hasAttribute("namespace")
905
            && isset($this->loadedFiles[$this->getNamespaceSpecificFileIndex($file, $node->getAttribute("namespace"))])) {
906
            $schema->addSchema($this->loadedFiles[$this->getNamespaceSpecificFileIndex($file, $node->getAttribute("namespace"))]);
907
            return function () {
908
            };
909
        } elseif (isset($this->loadedFiles[$file])) {
910
            $schema->addSchema($this->loadedFiles[$file]);
911
            return function () {
912
            };
913
        }
914
915
        if (!$node->getAttribute("namespace")) {
916
            $this->loadedFiles[$file] = $newSchema = $schema;
917
        } else {
918
            $this->loadedFiles[$file] = $newSchema = new Schema();
919
            $newSchema->addSchema($this->getGlobalSchema());
920
        }
921
922
        $xml = $this->getDOM(isset($this->knownLocationSchemas[$file]) ? $this->knownLocationSchemas[$file] : $file);
923
924
        $callbacks = $this->schemaNode($newSchema, $xml->documentElement, $schema);
925
926
        if ($node->getAttribute("namespace")) {
927
            $schema->addSchema($newSchema);
928
        }
929
930
931
        return function () use ($callbacks) {
932
            foreach ($callbacks as $callback) {
933
                call_user_func($callback);
934
            }
935
        };
936
    }
937
938
    /**
939
    * @var Schema|null
940
    */
941
    private $globalSchema;
942
943
    /**
944
     *
945
     * @return Schema
946
     */
947
    public function getGlobalSchema()
948
    {
949
        if (!$this->globalSchema) {
950
            $callbacks = array();
951
            $globalSchemas = array();
952
            foreach (self::$globalSchemaInfo as $namespace => $uri) {
953
                $this->loadedFiles[$uri] = $globalSchemas [$namespace] = $schema = new Schema();
954
                if ($namespace === self::XSD_NS) {
955
                    $this->globalSchema = $schema;
956
                }
957
                $xml = $this->getDOM($this->knownLocationSchemas[$uri]);
958
                $callbacks = array_merge($callbacks, $this->schemaNode($schema, $xml->documentElement));
959
            }
960
961
            $globalSchemas[self::XSD_NS]->addType(new SimpleType($globalSchemas[self::XSD_NS], "anySimpleType"));
962
            $globalSchemas[self::XSD_NS]->addType(new SimpleType($globalSchemas[self::XSD_NS], "anyType"));
963
964
            $globalSchemas[self::XML_NS]->addSchema($globalSchemas[self::XSD_NS], self::XSD_NS);
965
            $globalSchemas[self::XSD_NS]->addSchema($globalSchemas[self::XML_NS], self::XML_NS);
966
967
            foreach ($callbacks as $callback) {
968
                $callback();
969
            }
970
        }
971
972
        /**
973
        * @var Schema $out
974
        */
975
        $out = $this->globalSchema;
976
977
        return $out;
978
    }
979
980
    /**
981
     * @param DOMElement $node
982
     * @param string  $file
983
     *
984
     * @return Schema
985
     */
986
    public function readNode(DOMElement $node, $file = 'schema.xsd')
987
    {
988
        $fileKey = $node->hasAttribute('targetNamespace') ? $this->getNamespaceSpecificFileIndex($file, $node->getAttribute('targetNamespace')) : $file;
989
        $this->loadedFiles[$fileKey] = $rootSchema = new Schema();
990
991
        $rootSchema->addSchema($this->getGlobalSchema());
992
        $callbacks = $this->schemaNode($rootSchema, $node);
993
994
        foreach ($callbacks as $callback) {
995
            call_user_func($callback);
996
        }
997
998
        return $rootSchema;
999
    }
1000
1001
    /**
1002
     * It is possible that a single file contains multiple <xsd:schema/> nodes, for instance in a WSDL file.
1003
     *
1004
     * Each of these  <xsd:schema/> nodes typically target a specific namespace. Append the target namespace to the
1005
     * file to distinguish between multiple schemas in a single file.
1006
     *
1007
     * @param string $file
1008
     * @param string $targetNamespace
1009
     *
1010
     * @return string
1011
     */
1012
    private function getNamespaceSpecificFileIndex($file, $targetNamespace)
1013
    {
1014
        return $file . '#' . $targetNamespace;
1015
    }
1016
1017
    /**
1018
     * @param string $content
1019
     * @param string $file
1020
     *
1021
     * @return Schema
1022
     *
1023
     * @throws IOException
1024
     */
1025
    public function readString($content, $file = 'schema.xsd')
1026
    {
1027
        $xml = new DOMDocument('1.0', 'UTF-8');
1028
        if (!$xml->loadXML($content)) {
1029
            throw new IOException("Can't load the schema");
1030
        }
1031
        $xml->documentURI = $file;
1032
1033
        return $this->readNode($xml->documentElement, $file);
1034
    }
1035
1036
    /**
1037
     * @param string $file
1038
     *
1039
     * @return Schema
1040
     */
1041
    public function readFile($file)
1042
    {
1043
        $xml = $this->getDOM($file);
1044
        return $this->readNode($xml->documentElement, $file);
1045
    }
1046
1047
    /**
1048
     * @param string $file
1049
     *
1050
     * @return DOMDocument
1051
     *
1052
     * @throws IOException
1053
     */
1054
    private function getDOM($file)
1055
    {
1056
        $xml = new DOMDocument('1.0', 'UTF-8');
1057
        if (!$xml->load($file)) {
1058
            throw new IOException("Can't load the file $file");
1059
        }
1060
        return $xml;
1061
    }
1062
}
1063