Passed
Branch static-analysis (81e4d1)
by SignpostMarv
01:32
created

SchemaReader::loadElement()   B

Complexity

Conditions 5
Paths 16

Size

Total Lines 26
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 15
nc 16
nop 2
dl 0
loc 26
rs 8.439
c 0
b 0
f 0
1
<?php
2
namespace GoetasWebservices\XML\XSDReader;
3
4
use Closure;
5
use DOMDocument;
6
use DOMElement;
7
use DOMNode;
8
use GoetasWebservices\XML\XSDReader\Exception\IOException;
9
use GoetasWebservices\XML\XSDReader\Exception\TypeException;
10
use GoetasWebservices\XML\XSDReader\Schema\Attribute\Attribute;
11
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeDef;
12
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeItem;
13
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeRef;
14
use GoetasWebservices\XML\XSDReader\Schema\Attribute\Group as AttributeGroup;
15
use GoetasWebservices\XML\XSDReader\Schema\Element\Element;
16
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementContainer;
17
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementDef;
18
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementItem;
19
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementRef;
20
use GoetasWebservices\XML\XSDReader\Schema\Element\Group;
21
use GoetasWebservices\XML\XSDReader\Schema\Element\GroupRef;
22
use GoetasWebservices\XML\XSDReader\Schema\Element\InterfaceSetMinMax;
23
use GoetasWebservices\XML\XSDReader\Schema\Exception\TypeNotFoundException;
24
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Extension;
25
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Restriction;
26
use GoetasWebservices\XML\XSDReader\Schema\Item;
27
use GoetasWebservices\XML\XSDReader\Schema\Schema;
28
use GoetasWebservices\XML\XSDReader\Schema\Type\BaseComplexType;
29
use GoetasWebservices\XML\XSDReader\Schema\Type\ComplexType;
30
use GoetasWebservices\XML\XSDReader\Schema\Type\ComplexTypeSimpleContent;
31
use GoetasWebservices\XML\XSDReader\Schema\Type\SimpleType;
32
use GoetasWebservices\XML\XSDReader\Schema\Type\Type;
33
use GoetasWebservices\XML\XSDReader\Utils\UrlUtils;
34
use RuntimeException;
35
36
class SchemaReader
37
{
38
39
    const XSD_NS = "http://www.w3.org/2001/XMLSchema";
40
41
    const XML_NS = "http://www.w3.org/XML/1998/namespace";
42
43
    /**
44
    * @var Schema[]
45
    */
46
    private $loadedFiles = array();
47
48
    /**
49
    * @var string[]
50
    */
51
    private $knownLocationSchemas = array();
52
53
    /**
54
    * @var string[]
55
    */
56
    private static $globalSchemaInfo = array(
57
        self::XML_NS => 'http://www.w3.org/2001/xml.xsd',
58
        self::XSD_NS => 'http://www.w3.org/2001/XMLSchema.xsd'
59
    );
60
61
    public function __construct()
62
    {
63
        $this->addKnownSchemaLocation('http://www.w3.org/2001/xml.xsd', __DIR__ . '/Resources/xml.xsd');
64
        $this->addKnownSchemaLocation('http://www.w3.org/2001/XMLSchema.xsd', __DIR__ . '/Resources/XMLSchema.xsd');
65
        $this->addKnownSchemaLocation('http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd', __DIR__ . '/Resources/oasis-200401-wss-wssecurity-secext-1.0.xsd');
66
        $this->addKnownSchemaLocation('http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd', __DIR__ . '/Resources/oasis-200401-wss-wssecurity-utility-1.0.xsd');
67
        $this->addKnownSchemaLocation('https://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd', __DIR__ . '/Resources/xmldsig-core-schema.xsd');
68
        $this->addKnownSchemaLocation('http://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd', __DIR__ . '/Resources/xmldsig-core-schema.xsd');
69
    }
70
71
    /**
72
    * @param string $remote
73
    * @param string $local
74
    */
75
    public function addKnownSchemaLocation($remote, $local)
76
    {
77
        $this->knownLocationSchemas[$remote] = $local;
78
    }
79
80
    /**
81
    * @return Closure
82
    */
83
    private function loadAttributeGroup(Schema $schema, DOMElement $node)
84
    {
85
        $attGroup = new AttributeGroup($schema, $node->getAttribute("name"));
86
        $attGroup->setDoc($this->getDocumentation($node));
87
        $schema->addAttributeGroup($attGroup);
88
89
        return function () use ($schema, $node, $attGroup) {
90
            foreach ($node->childNodes as $childNode) {
91
                switch ($childNode->localName) {
92 View Code Duplication
                    case 'attribute':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
93
                        if ($childNode->hasAttribute("ref")) {
94
                            /**
95
                            * @var AttributeItem $attribute
96
                            */
97
                            $attribute = $this->findSomething('findAttribute', $schema, $node, $childNode->getAttribute("ref"));
98
                        } else {
99
                            /**
100
                            * @var Attribute $attribute
101
                            */
102
                            $attribute = $this->loadAttribute($schema, $childNode);
103
                        }
104
                        $attGroup->addAttribute($attribute);
105
                        break;
106
                    case 'attributeGroup':
107
                        AttributeGroup::findSomethingLikeThis(
108
                            $this,
109
                            $schema,
110
                            $node,
111
                            $childNode,
112
                            $attGroup
113
                        );
114
                        break;
115
                }
116
            }
117
        };
118
    }
119
120
    /**
121
    * @return Attribute
122
    */
123
    private function loadAttribute(Schema $schema, DOMElement $node)
124
    {
125
        $attribute = new Attribute($schema, $node->getAttribute("name"));
126
        $attribute->setDoc($this->getDocumentation($node));
127
        $this->fillItem($attribute, $node);
128
129
        if ($node->hasAttribute("nillable")) {
130
            $attribute->setNil($node->getAttribute("nillable") == "true");
131
        }
132
        if ($node->hasAttribute("form")) {
133
            $attribute->setQualified($node->getAttribute("form") == "qualified");
134
        }
135
        if ($node->hasAttribute("use")) {
136
            $attribute->setUse($node->getAttribute("use"));
137
        }
138
        return $attribute;
139
    }
140
141
    /**
142
    * @return Closure
143
    */
144 View Code Duplication
    private function loadAttributeDef(Schema $schema, DOMElement $node)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
145
    {
146
        $attribute = new AttributeDef($schema, $node->getAttribute("name"));
147
148
        $schema->addAttribute($attribute);
149
150
        return function () use ($attribute, $node) {
151
            $this->fillItem($attribute, $node);
152
        };
153
    }
154
155
    /**
156
     * @param DOMElement $node
157
     * @return string
158
     */
159
    private function getDocumentation(DOMElement $node)
160
    {
161
        $doc = '';
162
        foreach ($node->childNodes as $childNode) {
163
            if ($childNode->localName == "annotation") {
164
                foreach ($childNode->childNodes as $subChildNode) {
165
                    if ($subChildNode->localName == "documentation") {
166
                        $doc .= ($subChildNode->nodeValue);
167
                    }
168
                }
169
            }
170
        }
171
        $doc = preg_replace('/[\t ]+/', ' ', $doc);
172
        return trim($doc);
173
    }
174
175
    /**
176
     *
177
     * @param Schema $schema
178
     * @param DOMElement $node
179
     * @param Schema $parent
180
     * @return array
181
     */
182
    private function schemaNode(Schema $schema, DOMElement $node, Schema $parent = null)
183
    {
184
        $schema->setDoc($this->getDocumentation($node));
185
186
        if ($node->hasAttribute("targetNamespace")) {
187
            $schema->setTargetNamespace($node->getAttribute("targetNamespace"));
188
        } elseif ($parent) {
189
            $schema->setTargetNamespace($parent->getTargetNamespace());
190
        }
191
        $schema->setElementsQualification($node->getAttribute("elementFormDefault") == "qualified");
192
        $schema->setAttributesQualification($node->getAttribute("attributeFormDefault") == "qualified");
193
        $schema->setDoc($this->getDocumentation($node));
194
        $functions = array();
195
196
        foreach ($node->childNodes as $childNode) {
197
            switch ($childNode->localName) {
198
                case 'include':
199
                case 'import':
200
                    $functions[] = $this->loadImport($schema, $childNode);
201
                    break;
202
                case 'element':
203
                    $functions[] = $this->loadElementDef($schema, $childNode);
204
                    break;
205
                case 'attribute':
206
                    $functions[] = $this->loadAttributeDef($schema, $childNode);
207
                    break;
208
                case 'attributeGroup':
209
                    $functions[] = $this->loadAttributeGroup($schema, $childNode);
210
                    break;
211
                case 'group':
212
                    $functions[] = $this->loadGroup($schema, $childNode);
213
                    break;
214
                case 'complexType':
215
                    $functions[] = $this->loadComplexType($schema, $childNode);
216
                    break;
217
                case 'simpleType':
218
                    $functions[] = $this->loadSimpleType($schema, $childNode);
219
                    break;
220
            }
221
        }
222
223
        return $functions;
224
    }
225
226
    /**
227
    * @return Element
228
    */
229
    private function loadElement(Schema $schema, DOMElement $node)
230
    {
231
        $element = new Element($schema, $node->getAttribute("name"));
232
        $element->setDoc($this->getDocumentation($node));
233
234
        $this->fillItem($element, $node);
235
236
        static::maybeSetMax($element, $node);
237
        if ($node->hasAttribute("minOccurs")) {
238
            $element->setMin((int)$node->getAttribute("minOccurs"));
239
        }
240
241
        $xp = new \DOMXPath($node->ownerDocument);
242
        $xp->registerNamespace('xs', 'http://www.w3.org/2001/XMLSchema');
243
244
        if ($xp->query('ancestor::xs:choice', $node)->length) {
245
            $element->setMin(0);
246
        }
247
248
        if ($node->hasAttribute("nillable")) {
249
            $element->setNil($node->getAttribute("nillable") == "true");
250
        }
251
        if ($node->hasAttribute("form")) {
252
            $element->setQualified($node->getAttribute("form") == "qualified");
253
        }
254
        return $element;
255
    }
256
257
    /**
258
    * @return GroupRef
259
    */
260
    private function loadGroupRef(Group $referenced, DOMElement $node)
261
    {
262
        $ref = new GroupRef($referenced);
263
        $ref->setDoc($this->getDocumentation($node));
264
265
        static::maybeSetMax($ref, $node);
266
        if ($node->hasAttribute("minOccurs")) {
267
            $ref->setMin((int)$node->getAttribute("minOccurs"));
268
        }
269
270
        return $ref;
271
    }
272
273
    /**
274
    * @return ElementRef
275
    */
276 View Code Duplication
    private function loadElementRef(ElementDef $referenced, DOMElement $node)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
277
    {
278
        $ref = new ElementRef($referenced);
279
        $ref->setDoc($this->getDocumentation($node));
280
281
        static::maybeSetMax($ref, $node);
282
        if ($node->hasAttribute("minOccurs")) {
283
            $ref->setMin((int)$node->getAttribute("minOccurs"));
284
        }
285
        if ($node->hasAttribute("nillable")) {
286
            $ref->setNil($node->getAttribute("nillable") == "true");
287
        }
288
        if ($node->hasAttribute("form")) {
289
            $ref->setQualified($node->getAttribute("form") == "qualified");
290
        }
291
292
        return $ref;
293
    }
294
295
    private static function maybeSetMax(InterfaceSetMinMax $ref, DOMElement $node)
296
    {
297
        if (
298
            $node->hasAttribute("maxOccurs")
299
        ) {
300
            $ref->setMax($node->getAttribute("maxOccurs") == "unbounded" ? -1 : (int)$node->getAttribute("maxOccurs"));
301
        }
302
    }
303
304
    /**
305
    * @return AttributeRef
306
    */
307 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...
308
    {
309
        $attribute = new AttributeRef($referencedAttribiute);
310
        $attribute->setDoc($this->getDocumentation($node));
311
312
        if ($node->hasAttribute("nillable")) {
313
            $attribute->setNil($node->getAttribute("nillable") == "true");
314
        }
315
        if ($node->hasAttribute("form")) {
316
            $attribute->setQualified($node->getAttribute("form") == "qualified");
317
        }
318
        if ($node->hasAttribute("use")) {
319
            $attribute->setUse($node->getAttribute("use"));
320
        }
321
        return $attribute;
322
    }
323
324
    /**
325
    * @param int|null $max
326
    */
327
    private function loadSequence(ElementContainer $elementContainer, DOMElement $node, $max = null)
328
    {
329
        $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...
330
331
        foreach ($node->childNodes as $childNode) {
332
333
            switch ($childNode->localName) {
334
                case 'choice':
335
                case 'sequence':
336
                case 'all':
337
                    $this->loadSequence($elementContainer, $childNode, $max);
338
                    break;
339
                case 'element':
340
                    if ($childNode->hasAttribute("ref")) {
341
                        /**
342
                        * @var ElementDef $referencedElement
343
                        */
344
                        $referencedElement = $this->findSomething('findElement', $elementContainer->getSchema(), $node, $childNode->getAttribute("ref"));
345
                        $element = $this->loadElementRef($referencedElement, $childNode);
346
                    } else {
347
                        $element = $this->loadElement($elementContainer->getSchema(), $childNode);
348
                    }
349
                    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...
350
                        $element->setMax($max);
351
                    }
352
                    $elementContainer->addElement($element);
353
                    break;
354
                case 'group':
355
                    /**
356
                    * @var Group $referencedGroup
357
                    */
358
                    $referencedGroup = $this->findSomething('findGroup', $elementContainer->getSchema(), $node, $childNode->getAttribute("ref"));
359
360
                    $group = $this->loadGroupRef($referencedGroup, $childNode);
361
                    $elementContainer->addElement($group);
362
                    break;
363
            }
364
        }
365
    }
366
367
    /**
368
    * @return Closure
369
    */
370
    private function loadGroup(Schema $schema, DOMElement $node)
371
    {
372
        $group = new Group($schema, $node->getAttribute("name"));
373
        $group->setDoc($this->getDocumentation($node));
374
375
        if ($node->hasAttribute("maxOccurs")) {
376
            static::maybeSetMax(new GroupRef($group), $node);
377
        }
378
        if ($node->hasAttribute("minOccurs")) {
379
            $group = new GroupRef($group);
380
            $group->setMin((int)$node->getAttribute("minOccurs"));
381
        }
382
383
        $schema->addGroup($group);
384
385
        return function () use ($group, $node) {
386
            foreach ($node->childNodes as $childNode) {
387
                switch ($childNode->localName) {
388
                    case 'sequence':
389
                    case 'choice':
390
                    case 'all':
391
                        $this->loadSequence($group, $childNode);
392
                        break;
393
                }
394
            }
395
        };
396
    }
397
398
    /**
399
    * @param Closure|null $callback
400
    *
401
    * @return Closure
402
    */
403
    private function loadComplexType(Schema $schema, DOMElement $node, $callback = null)
404
    {
405
        $isSimple = false;
406
407
        foreach ($node->childNodes as $childNode) {
408
            if ($childNode->localName === "simpleContent") {
409
                $isSimple = true;
410
                break;
411
            }
412
        }
413
414
        $type = $isSimple ? new ComplexTypeSimpleContent($schema, $node->getAttribute("name")) : new ComplexType($schema, $node->getAttribute("name"));
415
416
        $type->setDoc($this->getDocumentation($node));
417
        if ($node->getAttribute("name")) {
418
            $schema->addType($type);
419
        }
420
421
        return function () use ($type, $node, $schema, $callback) {
422
423
            $this->fillTypeNode($type, $node);
424
425
            foreach ($node->childNodes as $childNode) {
426
                switch ($childNode->localName) {
427
                    case 'sequence':
428
                    case 'choice':
429
                    case 'all':
430
                        if (! ($type instanceof ElementContainer)) {
431
                            throw new RuntimeException(
432
                                '$type passed to ' .
433
                                __FUNCTION__ .
434
                                'expected to be an instance of ' .
435
                                ElementContainer::class .
436
                                ' when child node localName is "group", ' .
437
                                get_class($type) .
438
                                ' given.'
439
                            );
440
                        }
441
                        $this->loadSequence($type, $childNode);
442
                        break;
443
                    case 'attribute':
444
                        if ($childNode->hasAttribute("ref")) {
445
                            /**
446
                            * @var AttributeDef $referencedAttribute
447
                            */
448
                            $referencedAttribute = $this->findSomething('findAttribute', $schema, $node, $childNode->getAttribute("ref"));
449
                            $attribute = $this->loadAttributeRef($referencedAttribute, $childNode);
450
                        } else {
451
                            $attribute = $this->loadAttribute($schema, $childNode);
452
                        }
453
454
                        $type->addAttribute($attribute);
455
                        break;
456
                    case 'group':
457
                        if (! ($type instanceof ComplexType)) {
458
                            throw new RuntimeException(
459
                                '$type passed to ' .
460
                                __FUNCTION__ .
461
                                'expected to be an instance of ' .
462
                                ComplexType::class .
463
                                ' when child node localName is "group", ' .
464
                                get_class($type) .
465
                                ' given.'
466
                            );
467
                        }
468
469
                        /**
470
                        * @var Group $referencedGroup
471
                        */
472
                        $referencedGroup = $this->findSomething('findGroup', $schema, $node, $childNode->getAttribute("ref"));
473
                        $group = $this->loadGroupRef($referencedGroup, $childNode);
474
                        $type->addElement($group);
475
                        break;
476
                    case 'attributeGroup':
477
                        AttributeGroup::findSomethingLikeThis(
478
                            $this,
479
                            $schema,
480
                            $node,
481
                            $childNode,
482
                            $type
483
                        );
484
                        break;
485
                }
486
            }
487
488
            if ($callback) {
489
                call_user_func($callback, $type);
490
            }
491
        };
492
    }
493
494
    /**
495
    * @param Closure|null $callback
496
    *
497
    * @return Closure
498
    */
499
    private function loadSimpleType(Schema $schema, DOMElement $node, $callback = null)
500
    {
501
        $type = new SimpleType($schema, $node->getAttribute("name"));
502
        $type->setDoc($this->getDocumentation($node));
503
        if ($node->getAttribute("name")) {
504
            $schema->addType($type);
505
        }
506
507
        return function () use ($type, $node, $callback) {
508
            $this->fillTypeNode($type, $node);
509
510
            foreach ($node->childNodes as $childNode) {
511
                switch ($childNode->localName) {
512
                    case 'union':
513
                        $this->loadUnion($type, $childNode);
514
                        break;
515
                    case 'list':
516
                        $this->loadList($type, $childNode);
517
                        break;
518
                }
519
            }
520
521
            if ($callback) {
522
                call_user_func($callback, $type);
523
            }
524
        };
525
    }
526
527
    private function loadList(SimpleType $type, DOMElement $node)
528
    {
529 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...
530
            /**
531
            * @var SimpleType $listType
532
            */
533
            $listType = $this->findSomething('findType', $type->getSchema(), $node, $node->getAttribute("itemType"));
534
            $type->setList($listType);
535
        } else {
536
            $addCallback = function (SimpleType $list) use ($type) {
537
                $type->setList($list);
538
            };
539
540
            foreach ($node->childNodes as $childNode) {
541
                switch ($childNode->localName) {
542
                    case 'simpleType':
543
                        call_user_func($this->loadSimpleType($type->getSchema(), $childNode, $addCallback));
544
                        break;
545
                }
546
            }
547
        }
548
    }
549
550
    private function loadUnion(SimpleType $type, DOMElement $node)
551
    {
552
        if ($node->hasAttribute("memberTypes")) {
553
            $types = preg_split('/\s+/', $node->getAttribute("memberTypes"));
554
            foreach ($types as $typeName) {
555
                /**
556
                * @var SimpleType $unionType
557
                */
558
                $unionType = $this->findSomething('findType', $type->getSchema(), $node, $typeName);
559
                $type->addUnion($unionType);
560
            }
561
        }
562
        $addCallback = function (SimpleType $unType) use ($type) {
563
            $type->addUnion($unType);
564
        };
565
566
        foreach ($node->childNodes as $childNode) {
567
            switch ($childNode->localName) {
568
                case 'simpleType':
569
                    call_user_func($this->loadSimpleType($type->getSchema(), $childNode, $addCallback));
570
                    break;
571
            }
572
        }
573
    }
574
575
    /**
576
    * @param bool $checkAbstract
577
    */
578
    private function fillTypeNode(Type $type, DOMElement $node, $checkAbstract = true)
579
    {
580
581
        if ($checkAbstract) {
582
            $type->setAbstract($node->getAttribute("abstract") === "true" || $node->getAttribute("abstract") === "1");
583
        }
584
585
        foreach ($node->childNodes as $childNode) {
586
            switch ($childNode->localName) {
587
                case 'restriction':
588
                    $this->loadRestriction($type, $childNode);
589
                    break;
590 View Code Duplication
                case 'extension':
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...
591
                    if (! ($type instanceof BaseComplexType)) {
592
                        throw new RuntimeException(
593
                            'Argument 1 passed to ' .
594
                            __METHOD__ .
595
                            ' needs to be an instance of ' .
596
                            BaseComplexType::class .
597
                            ' when passed onto ' .
598
                            static::class .
599
                            '::loadExtension(), ' .
600
                            get_class($type) .
601
                            ' given.'
602
                        );
603
                    }
604
                    $this->loadExtension($type, $childNode);
605
                    break;
606
                case 'simpleContent':
607
                case 'complexContent':
608
                    $this->fillTypeNode($type, $childNode, false);
609
                    break;
610
            }
611
        }
612
    }
613
614
    private function loadExtension(BaseComplexType $type, DOMElement $node)
615
    {
616
        $extension = new Extension();
617
        $type->setExtension($extension);
618
619
        if ($node->hasAttribute("base")) {
620
            /**
621
            * @var Type $parent
622
            */
623
            $parent = $this->findSomething('findType', $type->getSchema(), $node, $node->getAttribute("base"));
624
            $extension->setBase($parent);
625
        }
626
627
        foreach ($node->childNodes as $childNode) {
628
            switch ($childNode->localName) {
629
                case 'sequence':
630
                case 'choice':
631 View Code Duplication
                case 'all':
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...
632
                    if (! ($type instanceof ElementContainer)) {
633
                        throw new RuntimeException(
634
                            'Argument 1 passed to ' .
635
                            __METHOD__ .
636
                            ' needs to be an instance of ' .
637
                            ElementContainer::class .
638
                            ' when passed onto ' .
639
                            static::class .
640
                            '::loadSequence(), ' .
641
                            get_class($type) .
642
                            ' given.'
643
                        );
644
                    }
645
                    $this->loadSequence($type, $childNode);
646
                    break;
647 View Code Duplication
                case 'attribute':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
648
                    if ($childNode->hasAttribute("ref")) {
649
                        /**
650
                        * @var AttributeItem $attribute
651
                        */
652
                        $attribute = $this->findSomething('findAttribute', $type->getSchema(), $node, $childNode->getAttribute("ref"));
653
                    } else {
654
                        /**
655
                        * @var Attribute $attribute
656
                        */
657
                        $attribute = $this->loadAttribute($type->getSchema(), $childNode);
658
                    }
659
                    $type->addAttribute($attribute);
660
                    break;
661
                case 'attributeGroup':
662
                    AttributeGroup::findSomethingLikeThis(
663
                        $this,
664
                        $type->getSchema(),
665
                        $node,
666
                        $childNode,
667
                        $type
668
                    );
669
                    break;
670
            }
671
        }
672
    }
673
674
    private function loadRestriction(Type $type, DOMElement $node)
675
    {
676
        $restriction = new Restriction();
677
        $type->setRestriction($restriction);
678 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...
679
            /**
680
            * @var Type $restrictedType
681
            */
682
            $restrictedType = $this->findSomething('findType', $type->getSchema(), $node, $node->getAttribute("base"));
683
            $restriction->setBase($restrictedType);
684
        } else {
685
            $addCallback = function (Type $restType) use ($restriction) {
686
                $restriction->setBase($restType);
687
            };
688
689
            foreach ($node->childNodes as $childNode) {
690
                switch ($childNode->localName) {
691
                    case 'simpleType':
692
                        call_user_func($this->loadSimpleType($type->getSchema(), $childNode, $addCallback));
693
                        break;
694
                }
695
            }
696
        }
697
        foreach ($node->childNodes as $childNode) {
698
            if (in_array($childNode->localName,
699
                [
700
                    'enumeration',
701
                    'pattern',
702
                    'length',
703
                    'minLength',
704
                    'maxLength',
705
                    'minInclusive',
706
                    'maxInclusive',
707
                    'minExclusive',
708
                    'maxExclusive',
709
                    'fractionDigits',
710
                    'totalDigits',
711
                    'whiteSpace'
712
                ], true)) {
713
                $restriction->addCheck($childNode->localName,
714
                    [
715
                        'value' => $childNode->getAttribute("value"),
716
                        'doc' => $this->getDocumentation($childNode)
717
                    ]);
718
            }
719
        }
720
    }
721
722
    /**
723
    * @param string $typeName
724
    *
725
    * @return mixed[]
726
    */
727
    private static function splitParts(DOMElement $node, $typeName)
728
    {
729
        $namespace = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $namespace is dead and can be removed.
Loading history...
730
        $prefix = null;
731
        $name = $typeName;
732
        if (strpos($typeName, ':') !== false) {
733
            list ($prefix, $name) = explode(':', $typeName);
734
        }
735
736
        $namespace = $node->lookupNamespaceUri($prefix ?: '');
737
        return array(
738
            $name,
739
            $namespace,
740
            $prefix
741
        );
742
    }
743
744
    /**
745
     *
746
     * @param string $finder
747
     * @param Schema $schema
748
     * @param DOMElement $node
749
     * @param string $typeName
750
     * @throws TypeException
751
     * @return ElementItem|Group|AttributeItem|AttributeGroup|Type
752
     */
753
    public function findSomething($finder, Schema $schema, DOMElement $node, $typeName)
754
    {
755
        list ($name, $namespace) = self::splitParts($node, $typeName);
756
757
        $namespace = $namespace ?: $schema->getTargetNamespace();
758
759
        try {
760
            return $schema->$finder($name, $namespace);
761
        } catch (TypeNotFoundException $e) {
762
            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);
763
        }
764
    }
765
766
    /**
767
    * @return Closure
768
    */
769 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...
770
    {
771
        $element = new ElementDef($schema, $node->getAttribute("name"));
772
        $schema->addElement($element);
773
774
        return function () use ($element, $node) {
775
            $this->fillItem($element, $node);
776
        };
777
    }
778
779
    private function fillItem(Item $element, DOMElement $node)
780
    {
781
        $localType = null;
782
        foreach ($node->childNodes as $childNode) {
783
            switch ($childNode->localName) {
784
                case 'complexType':
785
                case 'simpleType':
786
                    $localType = $childNode;
787
                    break 2;
788
            }
789
        }
790
791
        if ($localType) {
792
            $addCallback = function (Type $type) use ($element) {
793
                $element->setType($type);
794
            };
795
            switch ($localType->localName) {
796
                case 'complexType':
797
                    call_user_func($this->loadComplexType($element->getSchema(), $localType, $addCallback));
798
                    break;
799
                case 'simpleType':
800
                    call_user_func($this->loadSimpleType($element->getSchema(), $localType, $addCallback));
801
                    break;
802
            }
803
        } else {
804
805
            if ($node->getAttribute("type")) {
806
                /**
807
                * @var Type $type
808
                */
809
                $type = $this->findSomething('findType', $element->getSchema(), $node, $node->getAttribute("type"));
810
            } else {
811
                /**
812
                * @var Type $type
813
                */
814
                $type = $this->findSomething('findType', $element->getSchema(), $node, ($node->lookupPrefix(self::XSD_NS) . ":anyType"));
815
            }
816
817
            $element->setType($type);
818
        }
819
    }
820
821
    /**
822
    * @return Closure
823
    */
824
    private function loadImport(Schema $schema, DOMElement $node)
825
    {
826
        $base = urldecode($node->ownerDocument->documentURI);
827
        $file = UrlUtils::resolveRelativeUrl($base, $node->getAttribute("schemaLocation"));
828
        if ($node->hasAttribute("namespace")
829
            && isset(self::$globalSchemaInfo[$node->getAttribute("namespace")])
830
            && isset($this->loadedFiles[self::$globalSchemaInfo[$node->getAttribute("namespace")]])
831
        ) {
832
833
            $schema->addSchema($this->loadedFiles[self::$globalSchemaInfo[$node->getAttribute("namespace")]]);
834
835
            return function () {
836
            };
837
        } elseif ($node->hasAttribute("namespace")
838
            && isset($this->loadedFiles[$this->getNamespaceSpecificFileIndex($file, $node->getAttribute("namespace"))])) {
839
            $schema->addSchema($this->loadedFiles[$this->getNamespaceSpecificFileIndex($file, $node->getAttribute("namespace"))]);
840
            return function () {
841
            };
842
        } elseif (isset($this->loadedFiles[$file])) {
843
            $schema->addSchema($this->loadedFiles[$file]);
844
            return function () {
845
            };
846
        }
847
848
        if (!$node->getAttribute("namespace")) {
849
            $this->loadedFiles[$file] = $newSchema = $schema;
850
        } else {
851
            $this->loadedFiles[$file] = $newSchema = new Schema();
852
            $newSchema->addSchema($this->getGlobalSchema());
853
        }
854
855
        $xml = $this->getDOM(isset($this->knownLocationSchemas[$file]) ? $this->knownLocationSchemas[$file] : $file);
856
857
        $callbacks = $this->schemaNode($newSchema, $xml->documentElement, $schema);
858
859
        if ($node->getAttribute("namespace")) {
860
            $schema->addSchema($newSchema);
861
        }
862
863
864
        return function () use ($callbacks) {
865
            foreach ($callbacks as $callback) {
866
                call_user_func($callback);
867
            }
868
        };
869
    }
870
871
    /**
872
    * @var Schema|null
873
    */
874
    private $globalSchema;
875
876
    /**
877
     *
878
     * @return Schema
879
     */
880
    public function getGlobalSchema()
881
    {
882
        if (!$this->globalSchema) {
883
            $callbacks = array();
884
            $globalSchemas = array();
885
            foreach (self::$globalSchemaInfo as $namespace => $uri) {
886
                $this->loadedFiles[$uri] = $globalSchemas [$namespace] = $schema = new Schema();
887
                if ($namespace === self::XSD_NS) {
888
                    $this->globalSchema = $schema;
889
                }
890
                $xml = $this->getDOM($this->knownLocationSchemas[$uri]);
891
                $callbacks = array_merge($callbacks, $this->schemaNode($schema, $xml->documentElement));
892
            }
893
894
            $globalSchemas[self::XSD_NS]->addType(new SimpleType($globalSchemas[self::XSD_NS], "anySimpleType"));
895
            $globalSchemas[self::XSD_NS]->addType(new SimpleType($globalSchemas[self::XSD_NS], "anyType"));
896
897
            $globalSchemas[self::XML_NS]->addSchema($globalSchemas[self::XSD_NS], self::XSD_NS);
898
            $globalSchemas[self::XSD_NS]->addSchema($globalSchemas[self::XML_NS], self::XML_NS);
899
900
            foreach ($callbacks as $callback) {
901
                $callback();
902
            }
903
        }
904
905
        /**
906
        * @var Schema $out
907
        */
908
        $out = $this->globalSchema;
909
910
        return $out;
911
    }
912
913
    /**
914
     * @param DOMElement $node
915
     * @param string  $file
916
     *
917
     * @return Schema
918
     */
919
    public function readNode(DOMElement $node, $file = 'schema.xsd')
920
    {
921
        $fileKey = $node->hasAttribute('targetNamespace') ? $this->getNamespaceSpecificFileIndex($file, $node->getAttribute('targetNamespace')) : $file;
922
        $this->loadedFiles[$fileKey] = $rootSchema = new Schema();
923
924
        $rootSchema->addSchema($this->getGlobalSchema());
925
        $callbacks = $this->schemaNode($rootSchema, $node);
926
927
        foreach ($callbacks as $callback) {
928
            call_user_func($callback);
929
        }
930
931
        return $rootSchema;
932
    }
933
934
    /**
935
     * It is possible that a single file contains multiple <xsd:schema/> nodes, for instance in a WSDL file.
936
     *
937
     * Each of these  <xsd:schema/> nodes typically target a specific namespace. Append the target namespace to the
938
     * file to distinguish between multiple schemas in a single file.
939
     *
940
     * @param string $file
941
     * @param string $targetNamespace
942
     *
943
     * @return string
944
     */
945
    private function getNamespaceSpecificFileIndex($file, $targetNamespace)
946
    {
947
        return $file . '#' . $targetNamespace;
948
    }
949
950
    /**
951
     * @param string $content
952
     * @param string $file
953
     *
954
     * @return Schema
955
     *
956
     * @throws IOException
957
     */
958
    public function readString($content, $file = 'schema.xsd')
959
    {
960
        $xml = new DOMDocument('1.0', 'UTF-8');
961
        if (!$xml->loadXML($content)) {
962
            throw new IOException("Can't load the schema");
963
        }
964
        $xml->documentURI = $file;
965
966
        return $this->readNode($xml->documentElement, $file);
967
    }
968
969
    /**
970
     * @param string $file
971
     *
972
     * @return Schema
973
     */
974
    public function readFile($file)
975
    {
976
        $xml = $this->getDOM($file);
977
        return $this->readNode($xml->documentElement, $file);
978
    }
979
980
    /**
981
     * @param string $file
982
     *
983
     * @return DOMDocument
984
     *
985
     * @throws IOException
986
     */
987
    private function getDOM($file)
988
    {
989
        $xml = new DOMDocument('1.0', 'UTF-8');
990
        if (!$xml->load($file)) {
991
            throw new IOException("Can't load the file $file");
992
        }
993
        return $xml;
994
    }
995
}
996