Passed
Push — static-analysis ( 309dd4...661417 )
by SignpostMarv
03:28
created

SchemaReader::loadSequenceChildNodeLoadElement()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 26
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 4

Importance

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