Passed
Push — static-analysis ( 6063e6...97202d )
by SignpostMarv
01:33
created

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