Passed
Push — static-analysis ( 7ccf34...0b3993 )
by SignpostMarv
03:13
created

SchemaReader::loadElementRef()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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