Passed
Push — static-analysis ( 717909...67082a )
by SignpostMarv
01:32
created

SchemaReader::loadTypeWithCallback()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 24
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 15
nc 4
nop 3
dl 0
loc 24
rs 8.6845
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
        if ($node->hasAttribute("itemType")) {
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
            $this->loadTypeWithCallbackOnChildNodes(
571
                $type->getSchema(),
572
                $node,
573
                $addCallback
574
            );
575
        }
576
    }
577
578
    private function loadTypeWithCallbackOnChildNodes(
579
        Schema $schema,
580
        DOMNode $node,
581
        Closure $callback
582
    ) {
583
        foreach ($node->childNodes as $childNode) {
584
            $this->loadTypeWithCallback($schema, $childNode, $callback);
585
        }
586
    }
587
588
    private function loadTypeWithCallback(
589
        Schema $schema,
590
        DOMNode $childNode,
591
        Closure $callback
592
    ) {
593
        if (! ($childNode instanceof DOMElement)) {
594
            return;
595
        }
596
        switch ($childNode->localName) {
597
            case 'complexType':
598
                $childNode = $childNode;
599
                call_user_func(
600
                    $this->loadComplexType(
601
                        $schema,
602
                        $childNode,
603
                        $callback
604
                    )
605
                );
606
                break;
607
            case 'simpleType':
608
                call_user_func(
609
                    $this->loadSimpleType($schema, $childNode, $callback)
610
                );
611
                break;
612
        }
613
    }
614
615
    /**
616
    * @return SchemaItem
617
    */
618
    private function findSomeType(
619
        SchemaItem $fromThis,
620
        DOMElement $node,
621
        string $attributeName
622
    ) {
623
        return $this->findSomeTypeFromAttribute(
624
            $fromThis,
625
            $node,
626
            $node->getAttribute($attributeName)
627
        );
628
    }
629
630
    /**
631
    * @return SchemaItem
632
    */
633
    private function findSomeTypeFromAttribute(
634
        SchemaItem $fromThis,
635
        DOMElement $node,
636
        string $attributeName
637
    ) {
638
        /**
639
        * @var SchemaItem $out
640
        */
641
        $out = $this->findSomething(
642
            'findType',
643
            $fromThis->getSchema(),
644
            $node,
645
            $attributeName
646
        );
647
648
        return $out;
649
    }
650
651
    private function loadUnion(SimpleType $type, DOMElement $node)
652
    {
653
        if ($node->hasAttribute("memberTypes")) {
654
            $types = preg_split('/\s+/', $node->getAttribute("memberTypes"));
655
            foreach ($types as $typeName) {
656
                /**
657
                * @var SimpleType $unionType
658
                */
659
                $unionType = $this->findSomeTypeFromAttribute(
660
                    $type,
661
                    $node,
662
                    $typeName
663
                );
664
                $type->addUnion($unionType);
665
            }
666
        }
667
        $addCallback = function (SimpleType $unType) use ($type) {
668
            $type->addUnion($unType);
669
        };
670
671
        $this->loadTypeWithCallbackOnChildNodes(
672
            $type->getSchema(),
673
            $node,
674
            $addCallback
675
        );
676
    }
677
678
    /**
679
    * @param bool $checkAbstract
680
    */
681
    private function fillTypeNode(Type $type, DOMElement $node, $checkAbstract = true)
682
    {
683
684
        if ($checkAbstract) {
685
            $type->setAbstract($node->getAttribute("abstract") === "true" || $node->getAttribute("abstract") === "1");
686
        }
687
688
        foreach ($node->childNodes as $childNode) {
689
            switch ($childNode->localName) {
690
                case 'restriction':
691
                    $this->loadRestriction($type, $childNode);
692
                    break;
693
                case 'extension':
694
                    $this->maybeLoadExtensionFromBaseComplexType(
695
                        $type,
696
                        $childNode
697
                    );
698
                    break;
699
                case 'simpleContent':
700
                case 'complexContent':
701
                    $this->fillTypeNode($type, $childNode, false);
702
                    break;
703
            }
704
        }
705
    }
706
707
    private function loadExtension(BaseComplexType $type, DOMElement $node)
708
    {
709
        $extension = new Extension();
710
        $type->setExtension($extension);
711
712
        if ($node->hasAttribute("base")) {
713
            $this->findAndSetSomeBase(
714
                $type,
715
                $extension,
716
                $node
717
            );
718
        }
719
720
        foreach ($node->childNodes as $childNode) {
721
            switch ($childNode->localName) {
722
                case 'sequence':
723
                case 'choice':
724
                case 'all':
725
                    $this->maybeLoadSequenceFromElementContainer(
726
                        $type,
727
                        $childNode
728
                    );
729
                    break;
730
                case 'attribute':
731
                    $attribute = $this->getAttributeFromAttributeOrRef(
732
                        $childNode,
733
                        $type->getSchema(),
734
                        $node
735
                    );
736
                    $type->addAttribute($attribute);
737
                    break;
738
                case 'attributeGroup':
739
                    AttributeGroup::findSomethingLikeThis(
740
                        $this,
741
                        $type->getSchema(),
742
                        $node,
743
                        $childNode,
744
                        $type
745
                    );
746
                    break;
747
            }
748
        }
749
    }
750
751
    private function findAndSetSomeBase(
752
        Type $type,
753
        Base $setBaseOnThis,
754
        DOMElement $node
755
    ) {
756
        /**
757
        * @var Type $parent
758
        */
759
        $parent = $this->findSomeType($type, $node, 'base');
760
        $setBaseOnThis->setBase($parent);
761
    }
762
763
    private function maybeLoadExtensionFromBaseComplexType(
764
        Type $type,
765
        DOMElement $childNode
766
    ) {
767
        if (! ($type instanceof BaseComplexType)) {
768
            throw new RuntimeException(
769
                'Argument 1 passed to ' .
770
                __METHOD__ .
771
                ' needs to be an instance of ' .
772
                BaseComplexType::class .
773
                ' when passed onto ' .
774
                static::class .
775
                '::loadExtension(), ' .
776
                get_class($type) .
777
                ' given.'
778
            );
779
        }
780
        $this->loadExtension($type, $childNode);
781
    }
782
783
    private function loadRestriction(Type $type, DOMElement $node)
784
    {
785
        $restriction = new Restriction();
786
        $type->setRestriction($restriction);
787
        if ($node->hasAttribute("base")) {
788
            $this->findAndSetSomeBase($type, $restriction, $node);
789
        } else {
790
            $addCallback = function (Type $restType) use ($restriction) {
791
                $restriction->setBase($restType);
792
            };
793
794
            $this->loadTypeWithCallbackOnChildNodes(
795
                $type->getSchema(),
796
                $node,
797
                $addCallback
798
            );
799
        }
800
        foreach ($node->childNodes as $childNode) {
801
            if (in_array($childNode->localName,
802
                [
803
                    'enumeration',
804
                    'pattern',
805
                    'length',
806
                    'minLength',
807
                    'maxLength',
808
                    'minInclusive',
809
                    'maxInclusive',
810
                    'minExclusive',
811
                    'maxExclusive',
812
                    'fractionDigits',
813
                    'totalDigits',
814
                    'whiteSpace'
815
                ], true)) {
816
                $restriction->addCheck($childNode->localName,
817
                    [
818
                        'value' => $childNode->getAttribute("value"),
819
                        'doc' => $this->getDocumentation($childNode)
820
                    ]);
821
            }
822
        }
823
    }
824
825
    /**
826
    * @param string $typeName
827
    *
828
    * @return mixed[]
829
    */
830
    private static function splitParts(DOMElement $node, $typeName)
831
    {
832
        $namespace = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $namespace is dead and can be removed.
Loading history...
833
        $prefix = null;
834
        $name = $typeName;
835
        if (strpos($typeName, ':') !== false) {
836
            list ($prefix, $name) = explode(':', $typeName);
837
        }
838
839
        $namespace = $node->lookupNamespaceUri($prefix ?: '');
840
        return array(
841
            $name,
842
            $namespace,
843
            $prefix
844
        );
845
    }
846
847
    /**
848
     *
849
     * @param string $finder
850
     * @param Schema $schema
851
     * @param DOMElement $node
852
     * @param string $typeName
853
     * @throws TypeException
854
     * @return ElementItem|Group|AttributeItem|AttributeGroup|Type
855
     */
856
    public function findSomething($finder, Schema $schema, DOMElement $node, $typeName)
857
    {
858
        list ($name, $namespace) = self::splitParts($node, $typeName);
859
860
        $namespace = $namespace ?: $schema->getTargetNamespace();
861
862
        try {
863
            return $schema->$finder($name, $namespace);
864
        } catch (TypeNotFoundException $e) {
865
            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);
866
        }
867
    }
868
869
    /**
870
    * @return Closure
871
    */
872 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...
873
    {
874
        $element = new ElementDef($schema, $node->getAttribute("name"));
875
        $schema->addElement($element);
876
877
        return function () use ($element, $node) {
878
            $this->fillItem($element, $node);
879
        };
880
    }
881
882
    private function fillItem(Item $element, DOMElement $node)
883
    {
884
        $localType = null;
885
        foreach ($node->childNodes as $childNode) {
886
            switch ($childNode->localName) {
887
                case 'complexType':
888
                case 'simpleType':
889
                    $localType = $childNode;
890
                    break 2;
891
            }
892
        }
893
894
        if ($localType) {
895
            $addCallback = function (Type $type) use ($element) {
896
                $element->setType($type);
897
            };
898
            $this->loadTypeWithCallback(
899
                $element->getSchema(),
900
                $localType,
901
                $addCallback
902
            );
903
        } else {
904
905
            if ($node->getAttribute("type")) {
906
                /**
907
                * @var Type $type
908
                */
909
                $type = $this->findSomeType($element, $node, 'type');
910
            } else {
911
                /**
912
                * @var Type $type
913
                */
914
                $type = $this->findSomeTypeFromAttribute(
915
                    $element,
916
                    $node,
917
                    ($node->lookupPrefix(self::XSD_NS) . ':anyType')
918
                );
919
            }
920
921
            $element->setType($type);
922
        }
923
    }
924
925
    /**
926
    * @return Closure
927
    */
928
    private function loadImport(Schema $schema, DOMElement $node)
929
    {
930
        $base = urldecode($node->ownerDocument->documentURI);
931
        $file = UrlUtils::resolveRelativeUrl($base, $node->getAttribute("schemaLocation"));
932
        if ($node->hasAttribute("namespace")
933
            && isset(self::$globalSchemaInfo[$node->getAttribute("namespace")])
934
            && isset($this->loadedFiles[self::$globalSchemaInfo[$node->getAttribute("namespace")]])
935
        ) {
936
937
            $schema->addSchema($this->loadedFiles[self::$globalSchemaInfo[$node->getAttribute("namespace")]]);
938
939
            return function () {
940
            };
941
        } elseif ($node->hasAttribute("namespace")
942
            && isset($this->loadedFiles[$this->getNamespaceSpecificFileIndex($file, $node->getAttribute("namespace"))])) {
943
            $schema->addSchema($this->loadedFiles[$this->getNamespaceSpecificFileIndex($file, $node->getAttribute("namespace"))]);
944
            return function () {
945
            };
946
        } elseif (isset($this->loadedFiles[$file])) {
947
            $schema->addSchema($this->loadedFiles[$file]);
948
            return function () {
949
            };
950
        }
951
952
        if (!$node->getAttribute("namespace")) {
953
            $this->loadedFiles[$file] = $newSchema = $schema;
954
        } else {
955
            $this->loadedFiles[$file] = $newSchema = new Schema();
956
            $newSchema->addSchema($this->getGlobalSchema());
957
        }
958
959
        $xml = $this->getDOM(isset($this->knownLocationSchemas[$file]) ? $this->knownLocationSchemas[$file] : $file);
960
961
        $callbacks = $this->schemaNode($newSchema, $xml->documentElement, $schema);
962
963
        if ($node->getAttribute("namespace")) {
964
            $schema->addSchema($newSchema);
965
        }
966
967
968
        return function () use ($callbacks) {
969
            foreach ($callbacks as $callback) {
970
                call_user_func($callback);
971
            }
972
        };
973
    }
974
975
    /**
976
    * @var Schema|null
977
    */
978
    private $globalSchema;
979
980
    /**
981
     *
982
     * @return Schema
983
     */
984
    public function getGlobalSchema()
985
    {
986
        if (!$this->globalSchema) {
987
            $callbacks = array();
988
            $globalSchemas = array();
989
            foreach (self::$globalSchemaInfo as $namespace => $uri) {
990
                $this->loadedFiles[$uri] = $globalSchemas [$namespace] = $schema = new Schema();
991
                if ($namespace === self::XSD_NS) {
992
                    $this->globalSchema = $schema;
993
                }
994
                $xml = $this->getDOM($this->knownLocationSchemas[$uri]);
995
                $callbacks = array_merge($callbacks, $this->schemaNode($schema, $xml->documentElement));
996
            }
997
998
            $globalSchemas[self::XSD_NS]->addType(new SimpleType($globalSchemas[self::XSD_NS], "anySimpleType"));
999
            $globalSchemas[self::XSD_NS]->addType(new SimpleType($globalSchemas[self::XSD_NS], "anyType"));
1000
1001
            $globalSchemas[self::XML_NS]->addSchema($globalSchemas[self::XSD_NS], self::XSD_NS);
1002
            $globalSchemas[self::XSD_NS]->addSchema($globalSchemas[self::XML_NS], self::XML_NS);
1003
1004
            foreach ($callbacks as $callback) {
1005
                $callback();
1006
            }
1007
        }
1008
1009
        /**
1010
        * @var Schema $out
1011
        */
1012
        $out = $this->globalSchema;
1013
1014
        return $out;
1015
    }
1016
1017
    /**
1018
     * @param DOMElement $node
1019
     * @param string  $file
1020
     *
1021
     * @return Schema
1022
     */
1023
    public function readNode(DOMElement $node, $file = 'schema.xsd')
1024
    {
1025
        $fileKey = $node->hasAttribute('targetNamespace') ? $this->getNamespaceSpecificFileIndex($file, $node->getAttribute('targetNamespace')) : $file;
1026
        $this->loadedFiles[$fileKey] = $rootSchema = new Schema();
1027
1028
        $rootSchema->addSchema($this->getGlobalSchema());
1029
        $callbacks = $this->schemaNode($rootSchema, $node);
1030
1031
        foreach ($callbacks as $callback) {
1032
            call_user_func($callback);
1033
        }
1034
1035
        return $rootSchema;
1036
    }
1037
1038
    /**
1039
     * It is possible that a single file contains multiple <xsd:schema/> nodes, for instance in a WSDL file.
1040
     *
1041
     * Each of these  <xsd:schema/> nodes typically target a specific namespace. Append the target namespace to the
1042
     * file to distinguish between multiple schemas in a single file.
1043
     *
1044
     * @param string $file
1045
     * @param string $targetNamespace
1046
     *
1047
     * @return string
1048
     */
1049
    private function getNamespaceSpecificFileIndex($file, $targetNamespace)
1050
    {
1051
        return $file . '#' . $targetNamespace;
1052
    }
1053
1054
    /**
1055
     * @param string $content
1056
     * @param string $file
1057
     *
1058
     * @return Schema
1059
     *
1060
     * @throws IOException
1061
     */
1062
    public function readString($content, $file = 'schema.xsd')
1063
    {
1064
        $xml = new DOMDocument('1.0', 'UTF-8');
1065
        if (!$xml->loadXML($content)) {
1066
            throw new IOException("Can't load the schema");
1067
        }
1068
        $xml->documentURI = $file;
1069
1070
        return $this->readNode($xml->documentElement, $file);
1071
    }
1072
1073
    /**
1074
     * @param string $file
1075
     *
1076
     * @return Schema
1077
     */
1078
    public function readFile($file)
1079
    {
1080
        $xml = $this->getDOM($file);
1081
        return $this->readNode($xml->documentElement, $file);
1082
    }
1083
1084
    /**
1085
     * @param string $file
1086
     *
1087
     * @return DOMDocument
1088
     *
1089
     * @throws IOException
1090
     */
1091
    private function getDOM($file)
1092
    {
1093
        $xml = new DOMDocument('1.0', 'UTF-8');
1094
        if (!$xml->load($file)) {
1095
            throw new IOException("Can't load the file $file");
1096
        }
1097
        return $xml;
1098
    }
1099
}
1100