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

SchemaReader::getClosuresFromChildNode()   D

Complexity

Conditions 9
Paths 9

Size

Total Lines 28
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 23
c 0
b 0
f 0
nc 9
nop 3
dl 0
loc 28
rs 4.909
1
<?php
2
namespace GoetasWebservices\XML\XSDReader;
3
4
use Closure;
5
use DOMDocument;
6
use DOMElement;
7
use DOMNode;
8
use GoetasWebservices\XML\XSDReader\Exception\IOException;
9
use GoetasWebservices\XML\XSDReader\Exception\TypeException;
10
use GoetasWebservices\XML\XSDReader\Schema\Attribute\Attribute;
11
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeDef;
12
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeItem;
13
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeRef;
14
use GoetasWebservices\XML\XSDReader\Schema\Attribute\Group as AttributeGroup;
15
use GoetasWebservices\XML\XSDReader\Schema\Element\Element;
16
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementContainer;
17
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementDef;
18
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementItem;
19
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementRef;
20
use GoetasWebservices\XML\XSDReader\Schema\Element\Group;
21
use GoetasWebservices\XML\XSDReader\Schema\Element\GroupRef;
22
use GoetasWebservices\XML\XSDReader\Schema\Element\InterfaceSetMinMax;
23
use GoetasWebservices\XML\XSDReader\Schema\Exception\TypeNotFoundException;
24
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Base;
25
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Extension;
26
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Restriction;
27
use GoetasWebservices\XML\XSDReader\Schema\Item;
28
use GoetasWebservices\XML\XSDReader\Schema\Schema;
29
use GoetasWebservices\XML\XSDReader\Schema\SchemaItem;
30
use GoetasWebservices\XML\XSDReader\Schema\Type\BaseComplexType;
31
use GoetasWebservices\XML\XSDReader\Schema\Type\ComplexType;
32
use GoetasWebservices\XML\XSDReader\Schema\Type\ComplexTypeSimpleContent;
33
use GoetasWebservices\XML\XSDReader\Schema\Type\SimpleType;
34
use GoetasWebservices\XML\XSDReader\Schema\Type\Type;
35
use GoetasWebservices\XML\XSDReader\Utils\UrlUtils;
36
use RuntimeException;
37
38
class SchemaReader
39
{
40
41
    const XSD_NS = "http://www.w3.org/2001/XMLSchema";
42
43
    const XML_NS = "http://www.w3.org/XML/1998/namespace";
44
45
    /**
46
    * @var Schema[]
47
    */
48
    private $loadedFiles = array();
49
50
    /**
51
    * @var string[]
52
    */
53
    private $knownLocationSchemas = array();
54
55
    /**
56
    * @var string[]
57
    */
58
    private static $globalSchemaInfo = array(
59
        self::XML_NS => 'http://www.w3.org/2001/xml.xsd',
60
        self::XSD_NS => 'http://www.w3.org/2001/XMLSchema.xsd'
61
    );
62
63
    public function __construct()
64
    {
65
        $this->addKnownSchemaLocation('http://www.w3.org/2001/xml.xsd', __DIR__ . '/Resources/xml.xsd');
66
        $this->addKnownSchemaLocation('http://www.w3.org/2001/XMLSchema.xsd', __DIR__ . '/Resources/XMLSchema.xsd');
67
        $this->addKnownSchemaLocation('http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd', __DIR__ . '/Resources/oasis-200401-wss-wssecurity-secext-1.0.xsd');
68
        $this->addKnownSchemaLocation('http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd', __DIR__ . '/Resources/oasis-200401-wss-wssecurity-utility-1.0.xsd');
69
        $this->addKnownSchemaLocation('https://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd', __DIR__ . '/Resources/xmldsig-core-schema.xsd');
70
        $this->addKnownSchemaLocation('http://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd', __DIR__ . '/Resources/xmldsig-core-schema.xsd');
71
    }
72
73
    /**
74
    * @param string $remote
75
    * @param string $local
76
    */
77
    public function addKnownSchemaLocation($remote, $local)
78
    {
79
        $this->knownLocationSchemas[$remote] = $local;
80
    }
81
82
    /**
83
    * @return Closure
84
    */
85
    private function loadAttributeGroup(Schema $schema, DOMElement $node)
86
    {
87
        $attGroup = new AttributeGroup($schema, $node->getAttribute("name"));
88
        $attGroup->setDoc($this->getDocumentation($node));
89
        $schema->addAttributeGroup($attGroup);
90
91
        return function () use ($schema, $node, $attGroup) {
92
            foreach ($node->childNodes as $childNode) {
93
                switch ($childNode->localName) {
94
                    case 'attribute':
95
                        $attribute = $this->getAttributeFromAttributeOrRef(
96
                            $childNode,
97
                            $schema,
98
                            $node
99
                        );
100
                        $attGroup->addAttribute($attribute);
101
                        break;
102
                    case 'attributeGroup':
103
                        AttributeGroup::findSomethingLikeThis(
104
                            $this,
105
                            $schema,
106
                            $node,
107
                            $childNode,
108
                            $attGroup
109
                        );
110
                        break;
111
                }
112
            }
113
        };
114
    }
115
116
    /**
117
    * @return AttributeItem
118
    */
119
    private function getAttributeFromAttributeOrRef(
120
        DOMElement $childNode,
121
        Schema $schema,
122
        DOMElement $node
123
    ) {
124
        if ($childNode->hasAttribute("ref")) {
125
            /**
126
            * @var AttributeItem $attribute
127
            */
128
            $attribute = $this->findSomething('findAttribute', $schema, $node, $childNode->getAttribute("ref"));
129
        } else {
130
            /**
131
            * @var Attribute $attribute
132
            */
133
            $attribute = $this->loadAttribute($schema, $childNode);
134
        }
135
136
        return $attribute;
137
    }
138
139
    /**
140
    * @return Attribute
141
    */
142
    private function loadAttribute(Schema $schema, DOMElement $node)
143
    {
144
        $attribute = new Attribute($schema, $node->getAttribute("name"));
145
        $attribute->setDoc($this->getDocumentation($node));
146
        $this->fillItem($attribute, $node);
147
148
        if ($node->hasAttribute("nillable")) {
149
            $attribute->setNil($node->getAttribute("nillable") == "true");
150
        }
151
        if ($node->hasAttribute("form")) {
152
            $attribute->setQualified($node->getAttribute("form") == "qualified");
153
        }
154
        if ($node->hasAttribute("use")) {
155
            $attribute->setUse($node->getAttribute("use"));
156
        }
157
        return $attribute;
158
    }
159
160
    /**
161
    * @return Closure
162
    */
163 View Code Duplication
    private function loadAttributeDef(Schema $schema, DOMElement $node)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

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