Passed
Push — static-analysis ( c1a154...1fd97e )
by SignpostMarv
04:06
created

SchemaReader   F

Complexity

Total Complexity 169

Size/Duplication

Total Lines 1603
Duplicated Lines 4.3 %

Test Coverage

Coverage 95.93%

Importance

Changes 0
Metric Value
wmc 169
dl 69
loc 1603
ccs 731
cts 762
cp 0.9593
rs 0.5217
c 0
b 0
f 0

65 Methods

Rating   Name   Duplication   Size   Complexity  
A readNode() 0 13 3
A getNamespaceSpecificFileIndex() 0 3 1
A readString() 0 9 2
A loadAttributeDef() 0 3 1
B loadSequenceNormaliseMax() 0 10 5
A loadSequence() 0 18 1
A splitParts() 0 14 3
A loadAttributeOrElementDef() 0 16 2
A addGroupAsElement() 0 18 1
B loadSequenceChildNodeLoadElement() 0 30 3
B loadSequenceChildNode() 32 32 6
A loadGroup() 0 22 1
A addKnownSchemaLocation() 0 3 1
B loadUnion() 0 24 3
A getDocumentation() 0 21 3
A makeCallbackCallback() 0 19 2
C loadExtensionChildNodes() 33 39 7
A loadComplexType() 0 22 1
A fillTypeNode() 0 22 3
A findSomeType() 0 9 1
A loadSimpleType() 0 19 2
A loadRestriction() 0 50 3
D loadComplexTypeFromChildNode() 0 45 9
A hasKnownSchemaLocation() 0 3 1
A loadGroupRef() 0 9 1
A loadList() 0 17 2
A findSomeTypeFromAttribute() 0 16 1
A loadElementDef() 0 3 1
A fillItemNonLocalType() 0 19 2
B loadComplexTypeBeforeCallbackCallback() 0 34 5
A loadExtension() 0 13 2
A getKnownSchemaLocation() 0 3 1
B loadGroupBeforeCheckingChildNodes() 0 26 4
A maybeLoadExtensionFromBaseComplexType() 0 6 2
A readFile() 0 5 1
A addAttributeFromAttributeOrRef() 0 13 1
B getGlobalSchema() 0 32 3
B loadElement() 0 27 4
A findSomethingLikeAttributeGroup() 0 11 1
A loadImport() 0 21 2
B loadTypeWithCallback() 0 26 4
A hasLoadedFile() 0 9 3
A loadImportFresh() 0 14 2
A setupGlobalSchemas() 0 16 3
A maybeSetMin() 0 7 2
A loadAttribute() 0 19 4
A loadImportFreshKeys() 0 20 2
A loadElementRef() 0 17 3
A setLoadedFile() 0 5 1
A loadImportFreshCallbacks() 0 22 2
A findAndSetSomeBase() 0 10 1
B fillItem() 0 40 4
A CallbackGeneratorMaybeCallMethodAgainstDOMNodeList() 0 19 3
A maybeSetMax() 0 9 3
A getAttributeFromAttributeOrRef() 0 18 2
A loadTypeWithCallbackOnChildNodes() 0 18 1
A getLoadedFile() 0 9 3
A findSomething() 0 18 3
A setSchemaThingsFromNode() 0 15 3
B loadAttributeGroup() 0 35 3
A againstDOMNodeList() 0 15 3
A loadImportFreshCallbacksNewSchema() 0 19 3
C schemaNode() 0 48 10
A getDOM() 0 8 2
A getGlobalSchemaInfo() 0 3 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like SchemaReader often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SchemaReader, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace GoetasWebservices\XML\XSDReader;
4
5
use Closure;
6
use DOMDocument;
7
use DOMElement;
8
use DOMNode;
9
use GoetasWebservices\XML\XSDReader\Exception\IOException;
10
use GoetasWebservices\XML\XSDReader\Exception\TypeException;
11
use GoetasWebservices\XML\XSDReader\Schema\Attribute\Attribute;
12
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeContainer;
13
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeDef;
14
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeItem;
15
use GoetasWebservices\XML\XSDReader\Schema\Attribute\Group as AttributeGroup;
16
use GoetasWebservices\XML\XSDReader\Schema\Element\Element;
17
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementContainer;
18
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementDef;
19
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementItem;
20
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementRef;
21
use GoetasWebservices\XML\XSDReader\Schema\Element\Group;
22
use GoetasWebservices\XML\XSDReader\Schema\Element\InterfaceSetMinMax;
23
use GoetasWebservices\XML\XSDReader\Schema\Element\GroupRef;
24
use GoetasWebservices\XML\XSDReader\Schema\Exception\TypeNotFoundException;
25
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Base;
26
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Extension;
27
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Restriction;
28
use GoetasWebservices\XML\XSDReader\Schema\Item;
29
use GoetasWebservices\XML\XSDReader\Schema\Schema;
30
use GoetasWebservices\XML\XSDReader\Schema\SchemaItem;
31
use GoetasWebservices\XML\XSDReader\Schema\Type\BaseComplexType;
32
use GoetasWebservices\XML\XSDReader\Schema\Type\ComplexType;
33
use GoetasWebservices\XML\XSDReader\Schema\Type\ComplexTypeSimpleContent;
34
use GoetasWebservices\XML\XSDReader\Schema\Type\SimpleType;
35
use GoetasWebservices\XML\XSDReader\Schema\Type\Type;
36
use GoetasWebservices\XML\XSDReader\Utils\UrlUtils;
37
use RuntimeException;
38
39
class SchemaReader
40
{
41
    /**
42
     * @param string $typeName
43
     *
44
     * @return mixed[]
45
     */
46 45
    private static function splitParts(DOMElement $node, $typeName)
47
    {
48 45
        $prefix = null;
49 45
        $name = $typeName;
50 45
        if (strpos($typeName, ':') !== false) {
51 45
            list($prefix, $name) = explode(':', $typeName);
52 45
        }
53
54 45
        $namespace = $node->lookupNamespaceUri($prefix ?: '');
55
56
        return array(
57 45
            $name,
58 45
            $namespace,
59 45
            $prefix,
60 45
        );
61
    }
62
63
    /**
64
     * @param bool $attributeDef
65
     *
66
     * @return Closure
67
     */
68 45
    private function loadAttributeOrElementDef(
69
        Schema $schema,
70
        DOMElement $node,
71
        $attributeDef
72
    ) {
73 45
        $name = $node->getAttribute('name');
74 45
        if ($attributeDef) {
75 45
            $attribute = new AttributeDef($schema, $name);
76 45
            $schema->addAttribute($attribute);
77 45
        } else {
78 45
            $attribute = new ElementDef($schema, $name);
79 45
            $schema->addElement($attribute);
80
        }
81
82
        return function () use ($attribute, $node) {
83 45
            $this->fillItem($attribute, $node);
84 45
        };
85
    }
86
87
    /**
88
     * @return Closure
89
     */
90 45
    private function loadAttributeDef(Schema $schema, DOMElement $node)
91
    {
92 45
        return $this->loadAttributeOrElementDef($schema, $node, true);
93
    }
94
95
    /**
96
     * @param int|null $max
97
     *
98
     * @return int|null
99
     */
100 45
    private static function loadSequenceNormaliseMax(DOMElement $node, $max)
101
    {
102
        return
103
        (
104 45
            (is_int($max) && (bool) $max) ||
105 45
            $node->getAttribute('maxOccurs') == 'unbounded' ||
106 45
            $node->getAttribute('maxOccurs') > 1
107 45
        )
108 45
            ? 2
109 45
            : null;
110
    }
111
112
    /**
113
     * @param int|null $max
114
     */
115 45
    private function loadSequence(ElementContainer $elementContainer, DOMElement $node, $max = null)
116
    {
117 45
        $max = static::loadSequenceNormaliseMax($node, $max);
118
119 45
        static::againstDOMNodeList(
120 45
            $node,
121
            function (
122
                DOMElement $node,
123
                DOMElement $childNode
124
            ) use (
125 45
                $elementContainer,
126 45
                $max
127
            ) {
128 45
                $this->loadSequenceChildNode(
129 45
                    $elementContainer,
130 45
                    $node,
131 45
                    $childNode,
132
                    $max
133 45
                );
134 45
            }
135 45
        );
136 45
    }
137
138
    /**
139
     * @param int|null $max
140
     */
141 45 View Code Duplication
    private function loadSequenceChildNode(
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...
142
        ElementContainer $elementContainer,
143
        DOMElement $node,
144
        DOMElement $childNode,
145
        $max
146
    ) {
147 45
        switch ($childNode->localName) {
148 45
            case 'sequence':
149 45
            case 'choice':
150 45
            case 'all':
151 45
                $this->loadSequence(
152 45
                    $elementContainer,
153 45
                    $childNode,
154
                    $max
155 45
                );
156 45
                break;
157 45
            case 'element':
158 45
                $this->loadSequenceChildNodeLoadElement(
159 45
                    $elementContainer,
160 45
                    $node,
161 45
                    $childNode,
162
                    $max
163 45
                );
164 45
                break;
165 45
            case 'group':
166 45
                $this->addGroupAsElement(
167 45
                    $elementContainer->getSchema(),
168 45
                    $node,
169 45
                    $childNode,
170
                    $elementContainer
171 45
                );
172 45
                break;
173 45
        }
174 45
    }
175
176
    /**
177
     * @param int|null $max
178
     */
179 45
    private function loadSequenceChildNodeLoadElement(
180
        ElementContainer $elementContainer,
181
        DOMElement $node,
182
        DOMElement $childNode,
183
        $max
184
    ) {
185 45
        if ($childNode->hasAttribute('ref')) {
186
            /**
187
             * @var ElementDef $referencedElement
188
             */
189 45
            $referencedElement = $this->findSomething('findElement', $elementContainer->getSchema(), $node, $childNode->getAttribute('ref'));
190 45
            $element = static::loadElementRef(
191 45
                $referencedElement,
192
                $childNode
193 45
            );
194 45
        } else {
195 45
            $element = $this->loadElement(
196 45
                $elementContainer->getSchema(),
197
                $childNode
198 45
            );
199
        }
200 45
        if ($max > 1) {
201
            /*
202
            * although one might think the typecast is not needed with $max being `? int $max` after passing > 1,
203
            * phpstan@a4f89fa still thinks it's possibly null.
204
            * see https://github.com/phpstan/phpstan/issues/577 for related issue
205
            */
206 45
            $element->setMax((int) $max);
207 45
        }
208 45
        $elementContainer->addElement($element);
209 45
    }
210
211 45
    private function addGroupAsElement(
212
        Schema $schema,
213
        DOMElement $node,
214
        DOMElement $childNode,
215
        ElementContainer $elementContainer
216
    ) {
217
        /**
218
         * @var Group
219
         */
220 45
        $referencedGroup = $this->findSomething(
221 45
            'findGroup',
222 45
            $schema,
223 45
            $node,
224 45
            $childNode->getAttribute('ref')
225 45
        );
226
227 45
        $group = $this->loadGroupRef($referencedGroup, $childNode);
228 45
        $elementContainer->addElement($group);
229 45
    }
230
231
    /**
232
     * @return Closure
233
     */
234 45
    private function loadGroup(Schema $schema, DOMElement $node)
235
    {
236 45
        $group = static::loadGroupBeforeCheckingChildNodes(
237 45
            $schema,
238
            $node
239 45
        );
240
        static $methods = [
241
            'sequence' => 'loadSequence',
242
            'choice' => 'loadSequence',
243
            'all' => 'loadSequence',
244 45
        ];
245
246
        return function () use ($group, $node, $methods) {
247
            /**
248
             * @var string[]
249
             */
250 45
            $methods = $methods;
251 45
            static::againstDOMNodeList(
252 45
                $node,
253 45
                $this->CallbackGeneratorMaybeCallMethodAgainstDOMNodeList(
254 45
                    $group,
255
                    $methods
256 45
                )
257 45
            );
258 45
        };
259
    }
260
261
    /**
262
     * @return Group|GroupRef
263
     */
264 45
    private static function loadGroupBeforeCheckingChildNodes(
265
        Schema $schema,
266
        DOMElement $node
267
    ) {
268 45
        $group = new Group($schema, $node->getAttribute('name'));
269 45
        $group->setDoc(self::getDocumentation($node));
270
271 45
        if ($node->hasAttribute('maxOccurs')) {
272
            /**
273
             * @var GroupRef
274
             */
275
            $group = self::maybeSetMax(new GroupRef($group), $node);
276
        }
277 45
        if ($node->hasAttribute('minOccurs')) {
278
            /**
279
             * @var GroupRef
280
             */
281
            $group = self::maybeSetMin(
282
                $group instanceof GroupRef ? $group : new GroupRef($group),
283
                $node
284
            );
285
        }
286
287 45
        $schema->addGroup($group);
288
289 45
        return $group;
290
    }
291
292
    /**
293
     * @return GroupRef
294
     */
295 45
    private function loadGroupRef(Group $referenced, DOMElement $node)
296
    {
297 45
        $ref = new GroupRef($referenced);
298 45
        $ref->setDoc(self::getDocumentation($node));
299
300 45
        self::maybeSetMax($ref, $node);
301 45
        self::maybeSetMin($ref, $node);
302
303 45
        return $ref;
304
    }
305
306
    /**
307
     * @return BaseComplexType
308
     */
309 45
    private function loadComplexTypeBeforeCallbackCallback(
310
        Schema $schema,
311
        DOMElement $node
312
    ) {
313
        /**
314
         * @var bool
315
         */
316 45
        $isSimple = false;
317
318 45
        static::againstDOMNodeList(
319 45
            $node,
320
            function (
321
                DOMElement $node,
322
                DOMElement $childNode
323
            ) use (
324
                &$isSimple
325
            ) {
326 45
                if ($isSimple) {
327 1
                    return;
328
                }
329 45
                if ($childNode->localName === 'simpleContent') {
330 2
                    $isSimple = true;
331 2
                }
332 45
            }
333 45
        );
334
335 45
        $type = $isSimple ? new ComplexTypeSimpleContent($schema, $node->getAttribute('name')) : new ComplexType($schema, $node->getAttribute('name'));
336
337 45
        $type->setDoc(static::getDocumentation($node));
338 45
        if ($node->getAttribute('name')) {
339 45
            $schema->addType($type);
340 45
        }
341
342 45
        return $type;
343
    }
344
345
    /**
346
     * @param Closure|null $callback
347
     *
348
     * @return Closure
349
     */
350 45
    private function loadComplexType(Schema $schema, DOMElement $node, $callback = null)
351
    {
352 45
        $type = $this->loadComplexTypeBeforeCallbackCallback($schema, $node);
353
354 45
        return $this->makeCallbackCallback(
355 45
            $type,
356 45
            $node,
357
            function (
358
                DOMElement $node,
359
                DOMElement $childNode
360
            ) use (
361 45
                $schema,
362 45
                $type
363
            ) {
364 45
                $this->loadComplexTypeFromChildNode(
365 45
                    $type,
366 45
                    $node,
367 45
                    $childNode,
368
                    $schema
369 45
                );
370 45
            },
371
            $callback
372 45
        );
373
    }
374
375 45
    private function loadComplexTypeFromChildNode(
376
        BaseComplexType $type,
377
        DOMElement $node,
378
        DOMElement $childNode,
379
        Schema $schema
380
    ) {
381 45
        switch ($childNode->localName) {
382 45
            case 'sequence':
383 45
            case 'choice':
384 45
            case 'all':
385 45
                if ($type instanceof ElementContainer) {
386 45
                    $this->loadSequence(
387 45
                        $type,
388
                        $childNode
389 45
                    );
390 45
                }
391 45
                break;
392 45
            case 'attribute':
393 45
                $this->addAttributeFromAttributeOrRef(
394 45
                    $type,
395 45
                    $childNode,
396 45
                    $schema,
397
                    $node
398 45
                );
399 45
                break;
400 45
            case 'attributeGroup':
401 2
                $this->findSomethingLikeAttributeGroup(
402 2
                    $schema,
403 2
                    $node,
404 2
                    $childNode,
405
                    $type
406 2
                );
407 2
                break;
408 45
            case 'group':
409
                if (
410
                    $type instanceof ComplexType
411 1
                ) {
412 1
                    $this->addGroupAsElement(
413 1
                        $schema,
414 1
                        $node,
415 1
                        $childNode,
416
                        $type
417 1
                    );
418 1
                }
419 1
                break;
420 45
        }
421 45
    }
422
423
    /**
424
     * @param Closure|null $callback
425
     *
426
     * @return Closure
427
     */
428 45
    private function loadSimpleType(Schema $schema, DOMElement $node, $callback = null)
429
    {
430 45
        $type = new SimpleType($schema, $node->getAttribute('name'));
431 45
        $type->setDoc(static::getDocumentation($node));
432 45
        if ($node->getAttribute('name')) {
433 45
            $schema->addType($type);
434 45
        }
435
436 45
        return $this->makeCallbackCallback(
437 45
            $type,
438 45
            $node,
439 45
            $this->CallbackGeneratorMaybeCallMethodAgainstDOMNodeList(
440 45
                $type,
441
                [
442 45
                    'union' => 'loadUnion',
443 45
                    'list' => 'loadList',
444
                ]
445 45
            ),
446
            $callback
447 45
        );
448
    }
449
450 45
    private function loadList(SimpleType $type, DOMElement $node)
451
    {
452 45
        if ($node->hasAttribute('itemType')) {
453
            /**
454
             * @var SimpleType
455
             */
456 45
            $listType = $this->findSomeType($type, $node, 'itemType');
457 45
            $type->setList($listType);
458 45
        } else {
459
            $addCallback = function (SimpleType $list) use ($type) {
460 45
                $type->setList($list);
461 45
            };
462
463 45
            $this->loadTypeWithCallbackOnChildNodes(
464 45
                $type->getSchema(),
465 45
                $node,
466
                $addCallback
467 45
            );
468
        }
469 45
    }
470
471 45
    private function loadUnion(SimpleType $type, DOMElement $node)
472
    {
473 45
        if ($node->hasAttribute('memberTypes')) {
474 45
            $types = preg_split('/\s+/', $node->getAttribute('memberTypes'));
475 45
            foreach ($types as $typeName) {
476
                /**
477
                 * @var SimpleType
478
                 */
479 45
                $unionType = $this->findSomeTypeFromAttribute(
480 45
                    $type,
481 45
                    $node,
482
                    $typeName
483 45
                );
484 45
                $type->addUnion($unionType);
485 45
            }
486 45
        }
487
        $addCallback = function (SimpleType $unType) use ($type) {
488 45
            $type->addUnion($unType);
489 45
        };
490
491 45
        $this->loadTypeWithCallbackOnChildNodes(
492 45
            $type->getSchema(),
493 45
            $node,
494
            $addCallback
495 45
        );
496 45
    }
497
498 45
    private function loadExtensionChildNodes(
499
        BaseComplexType $type,
500
        DOMElement $node
501
    ) {
502 45
        static::againstDOMNodeList(
503 45
            $node,
504 View Code Duplication
            function (
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...
505
                DOMElement $node,
506
                DOMElement $childNode
507
            ) use (
508 45
                $type
509
            ) {
510 45
                switch ($childNode->localName) {
511 45
                    case 'sequence':
512 45
                    case 'choice':
513 45
                    case 'all':
514 45
                        if ($type instanceof ElementContainer) {
515 45
                            $this->loadSequence(
516 45
                                $type,
517
                                $childNode
518 45
                            );
519 45
                        }
520 45
                        break;
521 45
                    case 'attribute':
522 45
                        $this->addAttributeFromAttributeOrRef(
523 45
                            $type,
524 45
                            $childNode,
525 45
                            $type->getSchema(),
526
                            $node
527 45
                        );
528 45
                        break;
529 45
                    case 'attributeGroup':
530 45
                        $this->findSomethingLikeAttributeGroup(
531 45
                            $type->getSchema(),
532 45
                            $node,
533 45
                            $childNode,
534
                            $type
535 45
                        );
536 45
                        break;
537 45
                }
538 45
            }
539 45
        );
540 45
    }
541
542 45
    private function loadExtension(BaseComplexType $type, DOMElement $node)
543
    {
544 45
        $extension = new Extension();
545 45
        $type->setExtension($extension);
546
547 45
        if ($node->hasAttribute('base')) {
548 45
            $this->findAndSetSomeBase(
549 45
                $type,
550 45
                $extension,
551
                $node
552 45
            );
553 45
        }
554 45
        $this->loadExtensionChildNodes($type, $node);
555 45
    }
556
557 45
    private function loadRestriction(Type $type, DOMElement $node)
558
    {
559 45
        $restriction = new Restriction();
560 45
        $type->setRestriction($restriction);
561 45
        if ($node->hasAttribute('base')) {
562 45
            $this->findAndSetSomeBase($type, $restriction, $node);
563 45
        } else {
564
            $addCallback = function (Type $restType) use ($restriction) {
565 45
                $restriction->setBase($restType);
566 45
            };
567
568 45
            $this->loadTypeWithCallbackOnChildNodes(
569 45
                $type->getSchema(),
570 45
                $node,
571
                $addCallback
572 45
            );
573
        }
574 45
        self::againstDOMNodeList(
575 45
            $node,
576
            function (
577
                DOMElement $node,
578
                DOMElement $childNode
579
            ) use (
580 45
                $restriction
581
            ) {
582
                if (
583 45
                    in_array(
584 45
                        $childNode->localName,
585
                        [
586 45
                            'enumeration',
587 45
                            'pattern',
588 45
                            'length',
589 45
                            'minLength',
590 45
                            'maxLength',
591 45
                            'minInclusive',
592 45
                            'maxInclusive',
593 45
                            'minExclusive',
594 45
                            'maxExclusive',
595 45
                            'fractionDigits',
596 45
                            'totalDigits',
597 45
                            'whiteSpace',
598 45
                        ],
599
                        true
600 45
                    )
601 45
                ) {
602 45
                    $restriction->addCheck(
603 45
                        $childNode->localName,
604
                        [
605 45
                            'value' => $childNode->getAttribute('value'),
606 45
                            'doc' => self::getDocumentation($childNode),
607
                        ]
608 45
                    );
609 45
                }
610 45
            }
611 45
        );
612 45
    }
613
614
    /**
615
     * @return Closure
616
     */
617 45
    private function loadElementDef(Schema $schema, DOMElement $node)
618
    {
619 45
        return $this->loadAttributeOrElementDef($schema, $node, false);
620
    }
621
622
    /**
623
     * @param bool $checkAbstract
624
     */
625 45
    private function fillTypeNode(Type $type, DOMElement $node, $checkAbstract = false)
626
    {
627 45
        if ($checkAbstract) {
628 45
            $type->setAbstract($node->getAttribute('abstract') === 'true' || $node->getAttribute('abstract') === '1');
629 45
        }
630
        static $methods = [
631
            'restriction' => 'loadRestriction',
632
            'extension' => 'maybeLoadExtensionFromBaseComplexType',
633
            'simpleContent' => 'fillTypeNode',
634
            'complexContent' => 'fillTypeNode',
635 45
        ];
636
637
        /**
638
         * @var string[]
639
         */
640 45
        $methods = $methods;
641
642 45
        static::againstDOMNodeList(
643 45
            $node,
644 45
            $this->CallbackGeneratorMaybeCallMethodAgainstDOMNodeList(
645 45
                $type,
646
                $methods
647 45
            )
648 45
        );
649 45
    }
650
651 45
    private function fillItemNonLocalType(Item $element, DOMElement $node)
652
    {
653 45
        if ($node->getAttribute('type')) {
654
            /**
655
             * @var Type
656
             */
657 45
            $type = $this->findSomeType($element, $node, 'type');
658 45
        } else {
659
            /**
660
             * @var Type
661
             */
662 45
            $type = $this->findSomeTypeFromAttribute(
663 45
                $element,
664 45
                $node,
665 45
                ($node->lookupPrefix(self::XSD_NS).':anyType')
666 45
            );
667
        }
668
669 45
        $element->setType($type);
670 45
    }
671
672
    /**
673
     * @param string $attributeName
674
     *
675
     * @return SchemaItem
676
     */
677 45
    private function findSomeType(
678
        SchemaItem $fromThis,
679
        DOMElement $node,
680
        $attributeName
681
    ) {
682 45
        return $this->findSomeTypeFromAttribute(
683 45
            $fromThis,
684 45
            $node,
685 45
            $node->getAttribute($attributeName)
686 45
        );
687
    }
688
689
    /**
690
     * @param string $attributeName
691
     *
692
     * @return SchemaItem
693
     */
694 45
    private function findSomeTypeFromAttribute(
695
        SchemaItem $fromThis,
696
        DOMElement $node,
697
        $attributeName
698
    ) {
699
        /**
700
         * @var SchemaItem
701
         */
702 45
        $out = $this->findSomething(
703 45
            'findType',
704 45
            $fromThis->getSchema(),
705 45
            $node,
706
            $attributeName
707 45
        );
708
709 45
        return $out;
710
    }
711
712
    /**
713
     * @param Closure|null $callback
714
     *
715
     * @return Closure
716
     */
717 45
    private function makeCallbackCallback(
718
        Type $type,
719
        DOMElement $node,
720
        Closure $callbackCallback,
721
        $callback = null
722
    ) {
723
        return function (
724
        ) use (
725 45
            $type,
726 45
            $node,
727 45
            $callbackCallback,
728 45
            $callback
729
        ) {
730 45
            $this->fillTypeNode($type, $node, true);
731
732 45
            static::againstDOMNodeList($node, $callbackCallback);
733
734 45
            if ($callback) {
735 45
                call_user_func($callback, $type);
736 45
            }
737 45
        };
738
    }
739
740 45
    private function maybeLoadExtensionFromBaseComplexType(
741
        Type $type,
742
        DOMElement $childNode
743
    ) {
744 45
        if ($type instanceof BaseComplexType) {
745 45
            $this->loadExtension($type, $childNode);
746 45
        }
747 45
    }
748
749
    const XSD_NS = 'http://www.w3.org/2001/XMLSchema';
750
751
    const XML_NS = 'http://www.w3.org/XML/1998/namespace';
752
753
    /**
754
     * @var string[]
755
     */
756
    protected $knownLocationSchemas = [
757
        'http://www.w3.org/2001/xml.xsd' => (
758
            __DIR__.'/Resources/xml.xsd'
759
        ),
760
        'http://www.w3.org/2001/XMLSchema.xsd' => (
761
            __DIR__.'/Resources/XMLSchema.xsd'
762
        ),
763
        'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' => (
764
            __DIR__.'/Resources/oasis-200401-wss-wssecurity-secext-1.0.xsd'
765
        ),
766
        'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd' => (
767
            __DIR__.'/Resources/oasis-200401-wss-wssecurity-utility-1.0.xsd'
768
        ),
769
        'https://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd' => (
770
            __DIR__.'/Resources/xmldsig-core-schema.xsd'
771
        ),
772
        'http://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd' => (
773
            __DIR__.'/Resources/xmldsig-core-schema.xsd'
774
        ),
775
    ];
776
777
    /**
778
     * @var string[]
779
     */
780
    protected static $globalSchemaInfo = array(
781
        self::XML_NS => 'http://www.w3.org/2001/xml.xsd',
782
        self::XSD_NS => 'http://www.w3.org/2001/XMLSchema.xsd',
783
    );
784
785
    /**
786
     * @param string $remote
787
     * @param string $local
788
     */
789
    public function addKnownSchemaLocation($remote, $local)
790
    {
791
        $this->knownLocationSchemas[$remote] = $local;
792
    }
793
794
    /**
795
     * @param string $remote
796
     *
797
     * @return bool
798
     */
799 1
    private function hasKnownSchemaLocation($remote)
800
    {
801 1
        return isset($this->knownLocationSchemas[$remote]);
802
    }
803
804
    /**
805
     * @param string $remote
806
     *
807
     * @return string
808
     */
809
    private function getKnownSchemaLocation($remote)
810
    {
811
        return $this->knownLocationSchemas[$remote];
812
    }
813
814
    /**
815
     * @param DOMElement $node
816
     *
817
     * @return string
818
     */
819 45
    private static function getDocumentation(DOMElement $node)
820
    {
821 45
        $doc = '';
822 45
        static::againstDOMNodeList(
823 45
            $node,
824
            function (
825
                DOMElement $node,
826
                DOMElement $childNode
827
            ) use (
828
                &$doc
829
            ) {
830 45
                if ($childNode->localName == 'annotation') {
831 45
                    $doc .= static::getDocumentation($childNode);
832 45
                } elseif ($childNode->localName == 'documentation') {
833 45
                    $doc .= $childNode->nodeValue;
834 45
                }
835 45
            }
836 45
        );
837 45
        $doc = preg_replace('/[\t ]+/', ' ', $doc);
838
839 45
        return trim($doc);
840
    }
841
842
    /**
843
     * @param Schema     $schema
844
     * @param DOMElement $node
845
     * @param Schema     $parent
846
     *
847
     * @return Closure[]
848
     */
849 45
    private function schemaNode(Schema $schema, DOMElement $node, Schema $parent = null)
850
    {
851 45
        $this->setSchemaThingsFromNode($schema, $node, $parent);
852 45
        $functions = array();
853
854 45
        static::againstDOMNodeList(
855 45
            $node,
856
            function (
857
                DOMElement $node,
858
                DOMElement $childNode
859
            ) use (
860 45
                $schema,
861
                &$functions
862
            ) {
863 45
                $callback = null;
864
865 45
                switch ($childNode->localName) {
866 45
                    case 'attributeGroup':
867 45
                        $callback = $this->loadAttributeGroup($schema, $childNode);
868 45
                        break;
869 45
                    case 'include':
870 45
                    case 'import':
871 45
                        $callback = $this->loadImport($schema, $childNode);
872 45
                        break;
873 45
                    case 'element':
874 45
                        $callback = $this->loadElementDef($schema, $childNode);
875 45
                        break;
876 45
                    case 'attribute':
877 45
                        $callback = $this->loadAttributeDef($schema, $childNode);
878 45
                        break;
879 45
                    case 'group':
880 45
                        $callback = $this->loadGroup($schema, $childNode);
881 45
                        break;
882 45
                    case 'complexType':
883 45
                        $callback = $this->loadComplexType($schema, $childNode);
884 45
                        break;
885 45
                    case 'simpleType':
886 45
                        $callback = $this->loadSimpleType($schema, $childNode);
887 45
                        break;
888 45
                }
889
890 45
                if ($callback instanceof Closure) {
891 45
                    $functions[] = $callback;
892 45
                }
893 45
            }
894 45
        );
895
896 45
        return $functions;
897
    }
898
899
    /**
900
     * @return InterfaceSetMinMax
901
     */
902 45
    private static function maybeSetMax(InterfaceSetMinMax $ref, DOMElement $node)
903
    {
904
        if (
905 45
            $node->hasAttribute('maxOccurs')
906 45
        ) {
907 45
            $ref->setMax($node->getAttribute('maxOccurs') == 'unbounded' ? -1 : (int) $node->getAttribute('maxOccurs'));
908 45
        }
909
910 45
        return $ref;
911
    }
912
913
    /**
914
     * @return InterfaceSetMinMax
915
     */
916 45
    private static function maybeSetMin(InterfaceSetMinMax $ref, DOMElement $node)
917
    {
918 45
        if ($node->hasAttribute('minOccurs')) {
919 45
            $ref->setMin((int) $node->getAttribute('minOccurs'));
920 45
        }
921
922 45
        return $ref;
923
    }
924
925 45
    private function findAndSetSomeBase(
926
        Type $type,
927
        Base $setBaseOnThis,
928
        DOMElement $node
929
    ) {
930
        /**
931
         * @var Type
932
         */
933 45
        $parent = $this->findSomeType($type, $node, 'base');
934 45
        $setBaseOnThis->setBase($parent);
935 45
    }
936
937
    /**
938
     * @param string     $finder
939
     * @param Schema     $schema
940
     * @param DOMElement $node
941
     * @param string     $typeName
942
     *
943
     * @throws TypeException
944
     *
945
     * @return ElementItem|Group|AttributeItem|AttributeGroup|Type
946
     */
947 45
    private function findSomething($finder, Schema $schema, DOMElement $node, $typeName)
948
    {
949 45
        list($name, $namespace) = static::splitParts($node, $typeName);
950
951
        /**
952
         * @var string|null
953
         */
954 45
        $namespace = $namespace ?: $schema->getTargetNamespace();
955
956
        try {
957
            /**
958
             * @var ElementItem|Group|AttributeItem|AttributeGroup|Type
959
             */
960 45
            $out = $schema->$finder($name, $namespace);
961
962 45
            return $out;
963
        } catch (TypeNotFoundException $e) {
964
            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);
965
        }
966
    }
967
968 45
    private function fillItem(Item $element, DOMElement $node)
969
    {
970
        /**
971
         * @var bool
972
         */
973 45
        $skip = false;
974 45
        static::againstDOMNodeList(
975 45
            $node,
976
            function (
977
                DOMElement $node,
978
                DOMElement $childNode
979
            ) use (
980 45
                $element,
981
                &$skip
982
            ) {
983
                if (
984 45
                    !$skip &&
985 45
                    in_array(
986 45
                        $childNode->localName,
987
                        [
988 45
                            'complexType',
989 45
                            'simpleType',
990
                        ]
991 45
                    )
992 45
                ) {
993 45
                    $this->loadTypeWithCallback(
994 45
                        $element->getSchema(),
995 45
                        $childNode,
996
                        function (Type $type) use ($element) {
997 45
                            $element->setType($type);
998 45
                        }
999 45
                    );
1000 45
                    $skip = true;
1001 45
                }
1002 45
            }
1003 45
        );
1004 45
        if ($skip) {
1005 45
            return;
1006
        }
1007 45
        $this->fillItemNonLocalType($element, $node);
1008 45
    }
1009
1010
    /**
1011
     * @var Schema|null
1012
     */
1013
    protected $globalSchema;
1014
1015
    /**
1016
     * @return Schema[]
1017
     */
1018 45
    private function setupGlobalSchemas(array &$callbacks)
1019
    {
1020 45
        $globalSchemas = array();
1021 45
        foreach (self::$globalSchemaInfo as $namespace => $uri) {
1022 45
            self::setLoadedFile(
1023 45
                $uri,
1024 45
                $globalSchemas[$namespace] = $schema = new Schema()
1025 45
            );
1026 45
            if ($namespace === self::XSD_NS) {
1027 45
                $this->globalSchema = $schema;
1028 45
            }
1029 45
            $xml = $this->getDOM($this->knownLocationSchemas[$uri]);
1030 45
            $callbacks = array_merge($callbacks, $this->schemaNode($schema, $xml->documentElement));
1031 45
        }
1032
1033 45
        return $globalSchemas;
1034
    }
1035
1036
    /**
1037
     * @return string[]
1038
     */
1039 45
    public function getGlobalSchemaInfo()
1040
    {
1041 45
        return self::$globalSchemaInfo;
1042
    }
1043
1044
    /**
1045
     * @return Schema
1046
     */
1047 45
    public function getGlobalSchema()
1048
    {
1049 45
        if (!$this->globalSchema) {
1050 45
            $callbacks = array();
1051 45
            $globalSchemas = $this->setupGlobalSchemas($callbacks);
1052
1053 45
            $globalSchemas[static::XSD_NS]->addType(new SimpleType($globalSchemas[static::XSD_NS], 'anySimpleType'));
1054 45
            $globalSchemas[static::XSD_NS]->addType(new SimpleType($globalSchemas[static::XSD_NS], 'anyType'));
1055
1056 45
            $globalSchemas[static::XML_NS]->addSchema(
1057 45
                $globalSchemas[static::XSD_NS],
1058
                (string) static::XSD_NS
1059 45
            );
1060 45
            $globalSchemas[static::XSD_NS]->addSchema(
1061 45
                $globalSchemas[static::XML_NS],
1062
                (string) static::XML_NS
1063 45
            );
1064
1065
            /**
1066
             * @var Closure
1067
             */
1068 45
            foreach ($callbacks as $callback) {
1069 45
                $callback();
1070 45
            }
1071 45
        }
1072
1073
        /**
1074
         * @var Schema
1075
         */
1076 45
        $out = $this->globalSchema;
1077
1078 45
        return $out;
1079
    }
1080
1081
    /**
1082
     * @param DOMElement $node
1083
     * @param string     $file
1084
     *
1085
     * @return Schema
1086
     */
1087 45
    private function readNode(DOMElement $node, $file = 'schema.xsd')
1088
    {
1089 45
        $fileKey = $node->hasAttribute('targetNamespace') ? $this->getNamespaceSpecificFileIndex($file, $node->getAttribute('targetNamespace')) : $file;
1090 45
        self::setLoadedFile($fileKey, $rootSchema = new Schema());
1091
1092 45
        $rootSchema->addSchema($this->getGlobalSchema());
1093 45
        $callbacks = $this->schemaNode($rootSchema, $node);
1094
1095 45
        foreach ($callbacks as $callback) {
1096 39
            call_user_func($callback);
1097 45
        }
1098
1099 45
        return $rootSchema;
1100
    }
1101
1102
    /**
1103
     * It is possible that a single file contains multiple <xsd:schema/> nodes, for instance in a WSDL file.
1104
     *
1105
     * Each of these  <xsd:schema/> nodes typically target a specific namespace. Append the target namespace to the
1106
     * file to distinguish between multiple schemas in a single file.
1107
     *
1108
     * @param string $file
1109
     * @param string $targetNamespace
1110
     *
1111
     * @return string
1112
     */
1113 45
    private function getNamespaceSpecificFileIndex($file, $targetNamespace)
1114
    {
1115 45
        return $file.'#'.$targetNamespace;
1116
    }
1117
1118
    /**
1119
     * @param string $content
1120
     * @param string $file
1121
     *
1122
     * @return Schema
1123
     *
1124
     * @throws IOException
1125
     */
1126 44
    public function readString($content, $file = 'schema.xsd')
1127
    {
1128 44
        $xml = new DOMDocument('1.0', 'UTF-8');
1129 44
        if (!$xml->loadXML($content)) {
1130
            throw new IOException("Can't load the schema");
1131
        }
1132 44
        $xml->documentURI = $file;
1133
1134 44
        return $this->readNode($xml->documentElement, $file);
1135
    }
1136
1137
    /**
1138
     * @param string $file
1139
     *
1140
     * @return Schema
1141
     */
1142 1
    public function readFile($file)
1143
    {
1144 1
        $xml = $this->getDOM($file);
1145
1146 1
        return $this->readNode($xml->documentElement, $file);
1147
    }
1148
1149
    /**
1150
     * @param string $file
1151
     *
1152
     * @return DOMDocument
1153
     *
1154
     * @throws IOException
1155
     */
1156 45
    private function getDOM($file)
1157
    {
1158 45
        $xml = new DOMDocument('1.0', 'UTF-8');
1159 45
        if (!$xml->load($file)) {
1160
            throw new IOException("Can't load the file $file");
1161
        }
1162
1163 45
        return $xml;
1164
    }
1165
1166 45
    private static function againstDOMNodeList(
1167
        DOMElement $node,
1168
        Closure $againstNodeList
1169
    ) {
1170 45
        $limit = $node->childNodes->length;
1171 45
        for ($i = 0; $i < $limit; $i += 1) {
1172
            /**
1173
             * @var DOMNode
1174
             */
1175 45
            $childNode = $node->childNodes->item($i);
1176
1177 45
            if ($childNode instanceof DOMElement) {
1178 45
                $againstNodeList(
1179 45
                    $node,
1180
                    $childNode
1181 45
                );
1182 45
            }
1183 45
        }
1184 45
    }
1185
1186
    /**
1187
     * @return Closure
1188
     */
1189 45
    private function CallbackGeneratorMaybeCallMethodAgainstDOMNodeList(
1190
        SchemaItem $type,
1191
        array $methods
1192
    ) {
1193
        return function (
1194
            DOMElement $node,
1195
            DOMElement $childNode
1196
        ) use (
1197 45
            $methods,
1198 45
            $type
1199
        ) {
1200
            /**
1201
             * @var string[]
1202
             */
1203 45
            $methods = $methods;
1204 45
            if ($childNode instanceof DOMElement && isset($methods[$childNode->localName])) {
1205 45
                $method = $methods[$childNode->localName];
1206
1207 45
                $this->$method($type, $childNode);
1208 45
            }
1209 45
        };
1210
    }
1211
1212 45
    private function loadTypeWithCallbackOnChildNodes(
1213
        Schema $schema,
1214
        DOMElement $node,
1215
        Closure $callback
1216
    ) {
1217 45
        self::againstDOMNodeList(
1218 45
            $node,
1219
            function (
1220
                DOMElement $node,
1221
                DOMElement $childNode
1222
            ) use (
1223 45
                $schema,
1224 45
                $callback
1225
            ) {
1226 45
                $this->loadTypeWithCallback(
1227 45
                    $schema,
1228 45
                    $childNode,
1229
                    $callback
1230 45
                );
1231 45
            }
1232 45
        );
1233 45
    }
1234
1235 45
    private function loadTypeWithCallback(
1236
        Schema $schema,
1237
        DOMElement $childNode,
1238
        Closure $callback
1239
    ) {
1240
        $methods = [
0 ignored issues
show
Unused Code introduced by
The assignment to $methods is dead and can be removed.
Loading history...
1241 45
            'complexType' => 'loadComplexType',
1242 45
            'simpleType' => 'loadSimpleType',
1243 45
        ];
1244
1245
        /**
1246
         * @var Closure|null $func
1247
         */
1248 45
        $func = null;
1249
1250 45
        switch ($childNode->localName) {
1251 45
            case 'complexType':
1252 45
                $func = $this->loadComplexType($schema, $childNode, $callback);
1253 45
                break;
1254 45
            case 'simpleType':
1255 45
                $func = $this->loadSimpleType($schema, $childNode, $callback);
1256 45
                break;
1257 45
        }
1258
1259 45
        if ($func instanceof Closure) {
1260 45
            call_user_func($func);
1261 45
        }
1262 45
    }
1263
1264
    /**
1265
     * @param string $file
1266
     * @param string $namespace
1267
     *
1268
     * @return Closure
1269
     */
1270 45
    private function loadImport(
1271
        Schema $schema,
1272
        DOMElement $node
1273
    ) {
1274 45
        $base = urldecode($node->ownerDocument->documentURI);
1275 45
        $file = UrlUtils::resolveRelativeUrl($base, $node->getAttribute('schemaLocation'));
1276
1277 45
        $namespace = $node->getAttribute('namespace');
1278
1279 45
        $keys = $this->loadImportFreshKeys($namespace, $file);
1280
1281
        if (
1282 45
            self::hasLoadedFile(...$keys)
1283 45
        ) {
1284 45
            $schema->addSchema(self::getLoadedFile(...$keys));
1285
1286
            return function () {
1287 45
            };
1288
        }
1289
1290 1
        return $this->loadImportFresh($namespace, $schema, $file);
1291
    }
1292
1293
    /**
1294
     * @param string $namespace
1295
     * @param string $file
1296
     *
1297
     * @return mixed[]
1298
     */
1299 45
    private function loadImportFreshKeys(
1300
        $namespace,
1301
        $file
1302
    ) {
1303 45
        $globalSchemaInfo = $this->getGlobalSchemaInfo();
1304
1305 45
        $keys = [];
1306
1307 45
        if (isset($globalSchemaInfo[$namespace])) {
1308 45
            $keys[] = $globalSchemaInfo[$namespace];
1309 45
        }
1310
1311 45
        $keys[] = $this->getNamespaceSpecificFileIndex(
1312 45
            $file,
1313
            $namespace
1314 45
        );
1315
1316 45
        $keys[] = $file;
1317
1318 45
        return $keys;
1319
    }
1320
1321
    /**
1322
     * @param string $namespace
1323
     * @param string $file
1324
     *
1325
     * @return Schema
1326
     */
1327 1
    private function loadImportFreshCallbacksNewSchema(
1328
        $namespace,
1329
        Schema $schema,
1330
        $file
1331
    ) {
1332
        /**
1333
         * @var Schema $newSchema
1334
         */
1335 1
        $newSchema = self::setLoadedFile(
1336 1
            $file,
1337 1
            ($namespace ? new Schema() : $schema)
1338 1
        );
1339
1340 1
        if ($namespace) {
1341
            $newSchema->addSchema($this->getGlobalSchema());
1342
            $schema->addSchema($newSchema);
1343
        }
1344
1345 1
        return $newSchema;
1346
    }
1347
1348
    /**
1349
     * @param string $namespace
1350
     * @param string $file
1351
     *
1352
     * @return Closure[]
1353
     */
1354 1
    private function loadImportFreshCallbacks(
1355
        $namespace,
1356
        Schema $schema,
1357
        $file
1358
    ) {
1359
        /**
1360
         * @var string
1361
         */
1362 1
        $file = $file;
1363
1364 1
        return $this->schemaNode(
1365 1
            $this->loadImportFreshCallbacksNewSchema(
1366 1
                $namespace,
1367 1
                $schema,
1368
                $file
1369 1
            ),
1370 1
            $this->getDOM(
1371 1
                $this->hasKnownSchemaLocation($file)
1372 1
                    ? $this->getKnownSchemaLocation($file)
1373
                    : $file
1374 1
            )->documentElement,
1375
            $schema
1376 1
        );
1377
    }
1378
1379
    /**
1380
     * @param string $namespace
1381
     * @param string $file
1382
     *
1383
     * @return Closure
1384
     */
1385 1
    private function loadImportFresh(
1386
        $namespace,
1387
        Schema $schema,
1388
        $file
1389
    ) {
1390
        return function () use ($namespace, $schema, $file) {
1391
            foreach (
1392 1
                $this->loadImportFreshCallbacks(
1393 1
                    $namespace,
1394 1
                    $schema,
1395
                    $file
1396 1
                ) as $callback
1397 1
            ) {
1398 1
                $callback();
1399 1
            }
1400 1
        };
1401
    }
1402
1403
    /**
1404
     * @return Element
1405
     */
1406 45
    private function loadElement(
1407
        Schema $schema,
1408
        DOMElement $node
1409
    ) {
1410 45
        $element = new Element($schema, $node->getAttribute('name'));
1411 45
        $element->setDoc(self::getDocumentation($node));
1412
1413 45
        $this->fillItem($element, $node);
1414
1415 45
        self::maybeSetMax($element, $node);
1416 45
        self::maybeSetMin($element, $node);
1417
1418 45
        $xp = new \DOMXPath($node->ownerDocument);
1419 45
        $xp->registerNamespace('xs', 'http://www.w3.org/2001/XMLSchema');
1420
1421 45
        if ($xp->query('ancestor::xs:choice', $node)->length) {
1422 45
            $element->setMin(0);
1423 45
        }
1424
1425 45
        if ($node->hasAttribute('nillable')) {
1426 3
            $element->setNil($node->getAttribute('nillable') == 'true');
1427 3
        }
1428 45
        if ($node->hasAttribute('form')) {
1429 3
            $element->setQualified($node->getAttribute('form') == 'qualified');
1430 3
        }
1431
1432 45
        return $element;
1433
    }
1434
1435
    /**
1436
     * @return ElementRef
1437
     */
1438 45
    private static function loadElementRef(
1439
        ElementDef $referenced,
1440
        DOMElement $node
1441
    ) {
1442 45
        $ref = new ElementRef($referenced);
1443 45
        $ref->setDoc(self::getDocumentation($node));
1444
1445 45
        self::maybeSetMax($ref, $node);
1446 45
        self::maybeSetMin($ref, $node);
1447 45
        if ($node->hasAttribute('nillable')) {
1448
            $ref->setNil($node->getAttribute('nillable') == 'true');
1449
        }
1450 45
        if ($node->hasAttribute('form')) {
1451
            $ref->setQualified($node->getAttribute('form') == 'qualified');
1452
        }
1453
1454 45
        return $ref;
1455
    }
1456
1457
    /**
1458
     * @return \Closure
1459
     */
1460 45
    private function loadAttributeGroup(
1461
        Schema $schema,
1462
        DOMElement $node
1463
    ) {
1464 45
        $attGroup = new AttributeGroup($schema, $node->getAttribute('name'));
1465 45
        $attGroup->setDoc(self::getDocumentation($node));
1466 45
        $schema->addAttributeGroup($attGroup);
1467
1468
        return function () use ($schema, $node, $attGroup) {
1469 45
            SchemaReader::againstDOMNodeList(
1470 45
                $node,
1471 45
                function (
1472
                    DOMElement $node,
1473
                    DOMElement $childNode
1474
                ) use (
1475 45
                    $schema,
1476 45
                    $attGroup
1477
                ) {
1478 45
                    switch ($childNode->localName) {
1479 45
                        case 'attribute':
1480 45
                            $attribute = $this->getAttributeFromAttributeOrRef(
1481 45
                                $childNode,
1482 45
                                $schema,
1483
                                $node
1484 45
                            );
1485 45
                            $attGroup->addAttribute($attribute);
1486 45
                            break;
1487 45
                        case 'attributeGroup':
1488 1
                            $this->findSomethingLikeAttributeGroup(
1489 1
                                $schema,
1490 1
                                $node,
1491 1
                                $childNode,
1492
                                $attGroup
1493 1
                            );
1494 1
                            break;
1495 45
                    }
1496 45
                }
1497 45
            );
1498 45
        };
1499
    }
1500
1501
    /**
1502
     * @return AttributeItem
1503
     */
1504 45
    private function getAttributeFromAttributeOrRef(
1505
        DOMElement $childNode,
1506
        Schema $schema,
1507
        DOMElement $node
1508
    ) {
1509 45
        if ($childNode->hasAttribute('ref')) {
1510
            /**
1511
             * @var AttributeItem
1512
             */
1513 45
            $attribute = $this->findSomething('findAttribute', $schema, $node, $childNode->getAttribute('ref'));
1514 45
        } else {
1515
            /**
1516
             * @var Attribute
1517
             */
1518 45
            $attribute = $this->loadAttribute($schema, $childNode);
1519
        }
1520
1521 45
        return $attribute;
1522
    }
1523
1524
    /**
1525
     * @return Attribute
1526
     */
1527 45
    private function loadAttribute(
1528
        Schema $schema,
1529
        DOMElement $node
1530
    ) {
1531 45
        $attribute = new Attribute($schema, $node->getAttribute('name'));
1532 45
        $attribute->setDoc(self::getDocumentation($node));
1533 45
        $this->fillItem($attribute, $node);
1534
1535 45
        if ($node->hasAttribute('nillable')) {
1536 1
            $attribute->setNil($node->getAttribute('nillable') == 'true');
1537 1
        }
1538 45
        if ($node->hasAttribute('form')) {
1539 1
            $attribute->setQualified($node->getAttribute('form') == 'qualified');
1540 1
        }
1541 45
        if ($node->hasAttribute('use')) {
1542 45
            $attribute->setUse($node->getAttribute('use'));
1543 45
        }
1544
1545 45
        return $attribute;
1546
    }
1547
1548 45
    private function addAttributeFromAttributeOrRef(
1549
        BaseComplexType $type,
1550
        DOMElement $childNode,
1551
        Schema $schema,
1552
        DOMElement $node
1553
    ) {
1554 45
        $attribute = $this->getAttributeFromAttributeOrRef(
1555 45
            $childNode,
1556 45
            $schema,
1557
            $node
1558 45
        );
1559
1560 45
        $type->addAttribute($attribute);
1561 45
    }
1562
1563 45
    private function findSomethingLikeAttributeGroup(
1564
        Schema $schema,
1565
        DOMElement $node,
1566
        DOMElement $childNode,
1567
        AttributeContainer $addToThis
1568
    ) {
1569
        /**
1570
         * @var AttributeItem
1571
         */
1572 45
        $attribute = $this->findSomething('findAttributeGroup', $schema, $node, $childNode->getAttribute('ref'));
1573 45
        $addToThis->addAttribute($attribute);
1574 45
    }
1575
1576
    /**
1577
     * @var Schema[]
1578
     */
1579
    protected static $loadedFiles = array();
1580
1581
    /**
1582
     * @param string ...$keys
1583
     *
1584
     * @return bool
1585
     */
1586 45
    private static function hasLoadedFile(...$keys)
1587
    {
1588 45
        foreach ($keys as $key) {
1589 45
            if (isset(self::$loadedFiles[$key])) {
1590 45
                return true;
1591
            }
1592 1
        }
1593
1594 1
        return false;
1595
    }
1596
1597
    /**
1598
     * @param string ...$keys
1599
     *
1600
     * @return Schema
1601
     *
1602
     * @throws RuntimeException if loaded file not found
1603
     */
1604 45
    public static function getLoadedFile(...$keys)
1605
    {
1606 45
        foreach ($keys as $key) {
1607 45
            if (isset(self::$loadedFiles[$key])) {
1608 45
                return self::$loadedFiles[$key];
1609
            }
1610
        }
1611
1612
        throw new RuntimeException('Loaded file was not found!');
1613
    }
1614
1615
    /**
1616
     * @param string $key
1617
     *
1618
     * @return Schema
1619
     */
1620 45
    private static function setLoadedFile($key, Schema $schema)
1621
    {
1622 45
        self::$loadedFiles[$key] = $schema;
1623
1624 45
        return $schema;
1625
    }
1626
1627 45
    private function setSchemaThingsFromNode(
1628
        Schema $schema,
1629
        DOMElement $node,
1630
        Schema $parent = null
1631
    ) {
1632 45
        $schema->setDoc(self::getDocumentation($node));
1633
1634 45
        if ($node->hasAttribute('targetNamespace')) {
1635 45
            $schema->setTargetNamespace($node->getAttribute('targetNamespace'));
1636 45
        } elseif ($parent) {
1637
            $schema->setTargetNamespace($parent->getTargetNamespace());
1638
        }
1639 45
        $schema->setElementsQualification($node->getAttribute('elementFormDefault') == 'qualified');
1640 45
        $schema->setAttributesQualification($node->getAttribute('attributeFormDefault') == 'qualified');
1641 45
        $schema->setDoc(self::getDocumentation($node));
1642 45
    }
1643
}
1644