Passed
Push — static-analysis ( 6a1bf3...717909 )
by SignpostMarv
01:30
created

SchemaReader::findAndSetSomeBase()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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