Passed
Push — static-analysis ( 97202d...6532ba )
by SignpostMarv
01:32
created

SchemaReader::getAttributeFromAttributeOrRef()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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