Passed
Push — static-analysis ( 4098e3...ccdceb )
by SignpostMarv
03:33
created

CallbackGeneratorMaybeCallMethodAgainstDOMNodeList()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 13
nc 1
nop 2
dl 0
loc 22
ccs 10
cts 10
cp 1
crap 1
rs 9.2
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 DOMNodeList;
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\GroupRef;
22
use GoetasWebservices\XML\XSDReader\Schema\Element\InterfaceSetMinMax;
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 GoetasWebservices\XML\XSDReader\Utils\UrlUtils;
36
use RuntimeException;
37
38
abstract class AbstractSchemaReader
39
{
40
41
    const XSD_NS = "http://www.w3.org/2001/XMLSchema";
42
43
    const XML_NS = "http://www.w3.org/XML/1998/namespace";
44
45
    /**
46
    * @var string[]
47
    */
48
    protected $knownLocationSchemas = [
49
        'http://www.w3.org/2001/xml.xsd' => (
50
            __DIR__ . '/Resources/xml.xsd'
51
        ),
52
        'http://www.w3.org/2001/XMLSchema.xsd' => (
53
            __DIR__ . '/Resources/XMLSchema.xsd'
54
        ),
55
        'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' => (
56
            __DIR__ . '/Resources/oasis-200401-wss-wssecurity-secext-1.0.xsd'
57
        ),
58
        'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd' => (
59
            __DIR__ . '/Resources/oasis-200401-wss-wssecurity-utility-1.0.xsd'
60
        ),
61
        'https://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd' => (
62
            __DIR__ . '/Resources/xmldsig-core-schema.xsd'
63
        ),
64
        'http://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd' => (
65
            __DIR__ . '/Resources/xmldsig-core-schema.xsd'
66
        ),
67
    ];
68
69
    /**
70
    * @var string[]
71
    */
72
    protected static $globalSchemaInfo = array(
73
        self::XML_NS => 'http://www.w3.org/2001/xml.xsd',
74
        self::XSD_NS => 'http://www.w3.org/2001/XMLSchema.xsd'
75
    );
76
77
    /**
78
    * @param string $remote
79
    * @param string $local
80
    */
81
    public function addKnownSchemaLocation($remote, $local)
82
    {
83
        $this->knownLocationSchemas[$remote] = $local;
84
    }
85
86
    /**
87
    * @param string $remote
88
    *
89
    * @return bool
90
    */
91 3
    public function hasKnownSchemaLocation($remote)
92
    {
93 3
        return isset($this->knownLocationSchemas[$remote]);
94
    }
95
96
    /**
97
    * @param string $remote
98
    *
99
    * @return string
100
    */
101 1
    public function getKnownSchemaLocation($remote)
102
    {
103 1
        return $this->knownLocationSchemas[$remote];
104
    }
105
106
    /**
107
    * @return Closure
108
    */
109
    abstract protected function loadAttributeGroup(Schema $schema, DOMElement $node);
110
111
    /**
112
    * @param bool $attributeDef
113
    *
114
    * @return Closure
115
    */
116
    abstract protected function loadAttributeOrElementDef(
117
        Schema $schema,
118
        DOMElement $node,
119
        $attributeDef
120
    );
121
122
    /**
123
    * @return Closure
124
    */
125
    abstract protected function loadAttributeDef(Schema $schema, DOMElement $node);
126
127
    /**
128
     * @param DOMElement $node
129
     * @return string
130
     */
131 135
    public static function getDocumentation(DOMElement $node)
132
    {
133 135
        $doc = '';
134 135
        static::againstDOMNodeList(
135 135
            $node,
136
            function (
137
                DOMElement $node,
138
                DOMElement $childNode
139
            ) use (
140 45
                & $doc
141
            ) {
142 135
                if ($childNode->localName == "annotation") {
143 135
                    $doc .= static::getDocumentation($childNode);
144 135
                } elseif ($childNode->localName == 'documentation') {
145 135
                    $doc .= $childNode->nodeValue;
146 45
                }
147 135
            }
148 45
        );
149 135
        $doc = preg_replace('/[\t ]+/', ' ', $doc);
150 135
        return trim($doc);
151
    }
152
153
    /**
154
    * @param string[] $methods
155
    * @param string $key
156
    *
157
    * @return Closure|null
158
    */
159 135
    public function maybeCallMethod(
160
        array $methods,
161
        $key,
162
        DOMNode $childNode,
163
        ...$args
164
    ) {
165 135
        if ($childNode instanceof DOMElement && isset($methods[$key])) {
166 135
            $method = $methods[$key];
167
168
            /**
169
            * @var Closure|null $append
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
    public function schemaNode(Schema $schema, DOMElement $node, Schema $parent = null)
187
    {
188 135
        $schema->setSchemaThingsFromNode($node, $parent);
189 135
        $functions = array();
190
191
        $schemaReaderMethods = [
192 135
            'include' => (Schema::class . '::loadImport'),
193 45
            'import' => (Schema::class . '::loadImport'),
194 45
        ];
195
196
        $thisMethods = [
197 135
            'element' => [$this, 'loadElementDef'],
198 135
            'attribute' => [$this, 'loadAttributeDef'],
199 135
            'attributeGroup' => [$this, 'loadAttributeGroup'],
200 135
            'group' => [$this, 'loadGroup'],
201 135
            'complexType' => [$this, 'loadComplexType'],
202 135
            'simpleType' => [$this, 'loadSimpleType'],
203 45
        ];
204
205 135
        static::againstDOMNodeList(
206 135
            $node,
207
            function (
208
                DOMElement $node,
209
                DOMElement $childNode
210
            ) use (
211 135
                $schemaReaderMethods,
212 135
                $schema,
213 135
                $thisMethods,
214 45
                & $functions
215
            ) {
216
                /**
217
                * @var Closure|null $callback
218
                */
219 135
                $callback = $this->maybeCallCallableWithArgs(
220 135
                    $childNode,
221 135
                        [],
222 135
                        [],
223
                        [
224
                            [
225 135
                                $schemaReaderMethods,
226
                                [
227 135
                                    $this,
228 135
                                    $schema,
229 135
                                    $childNode,
230
                                ]
231 45
                            ],
232
                            [
233 135
                                $thisMethods,
234
                                [
235 135
                                    $schema,
236 90
                                    $childNode
237 45
                                ],
238 45
                            ],
239
                        ]
240 45
                );
241
242 135
                if ($callback instanceof Closure) {
243 135
                    $functions[] = $callback;
244 45
                }
245 135
            }
246 45
        );
247
248 135
        return $functions;
249
    }
250
251
    /**
252
    * @return InterfaceSetMinMax
253
    */
254 135
    public static function maybeSetMax(InterfaceSetMinMax $ref, DOMElement $node)
255
    {
256
        if (
257 135
            $node->hasAttribute("maxOccurs")
258 45
        ) {
259 135
            $ref->setMax($node->getAttribute("maxOccurs") == "unbounded" ? -1 : (int)$node->getAttribute("maxOccurs"));
260 45
        }
261
262 135
        return $ref;
263
    }
264
265
    /**
266
    * @return InterfaceSetMinMax
267
    */
268 135
    public static function maybeSetMin(InterfaceSetMinMax $ref, DOMElement $node)
269
    {
270 135
        if ($node->hasAttribute("minOccurs")) {
271 135
            $ref->setMin((int) $node->getAttribute("minOccurs"));
272 45
        }
273
274 135
        return $ref;
275
    }
276
277
    /**
278
    * @param int|null $max
279
    *
280
    * @return int|null
281
    */
282
    abstract protected static function loadSequenceNormaliseMax(DOMElement $node, $max);
283
284
    /**
285
    * @param int|null $max
286
    */
287
    abstract protected function loadSequence(ElementContainer $elementContainer, DOMElement $node, $max = null);
288
289
    /**
290
    * @param int|null $max
291
    */
292
    abstract protected function loadSequenceChildNode(
293
        ElementContainer $elementContainer,
294
        DOMElement $node,
295
        DOMElement $childNode,
296
        $max
297
    );
298
299
    /**
300
    * @param mixed[][] $methods
301
    *
302
    * @return mixed
303
    */
304
    abstract protected function maybeCallCallableWithArgs(
305
        DOMElement $childNode,
306
        array $commonMethods = [],
307
        array $methods = [],
308
        array $commonArguments = []
309
    );
310
311
    /**
312
    * @param int|null $max
313
    */
314
    abstract protected function loadSequenceChildNodeLoadSequence(
315
        ElementContainer $elementContainer,
316
        DOMElement $childNode,
317
        $max
318
    );
319
320
    /**
321
    * @param int|null $max
322
    */
323
    abstract protected function loadSequenceChildNodeLoadElement(
324
        ElementContainer $elementContainer,
325
        DOMElement $node,
326
        DOMElement $childNode,
327
        $max
328
    );
329
330
    abstract protected function loadSequenceChildNodeLoadGroup(
331
        ElementContainer $elementContainer,
332
        DOMElement $node,
333
        DOMElement $childNode
334
    );
335
336
    abstract protected function addGroupAsElement(
337
        Schema $schema,
338
        DOMElement $node,
339
        DOMElement $childNode,
340
        ElementContainer $elementContainer
341
    );
342
343
    abstract protected function maybeLoadSequenceFromElementContainer(
344
        BaseComplexType $type,
345
        DOMElement $childNode
346
    );
347
348
    /**
349
    * @return Closure
350
    */
351
    abstract protected function loadGroup(Schema $schema, DOMElement $node);
352
353
    /**
354
    * @return BaseComplexType
355
    */
356
    abstract protected function loadComplexTypeBeforeCallbackCallback(
357
        Schema $schema,
358
        DOMElement $node
359
    );
360
361
    /**
362
    * @param Closure|null $callback
363
    *
364
    * @return Closure
365
    */
366
    abstract protected function loadComplexType(Schema $schema, DOMElement $node, $callback = null);
367
368
    /**
369
    * @param Closure|null $callback
370
    *
371
    * @return Closure
372
    */
373
    abstract protected function makeCallbackCallback(
374
        Type $type,
375
        DOMElement $node,
376
        Closure $callbackCallback,
377
        $callback = null
378
    );
379
380
    /**
381
    * @param Closure|null $callback
382
    */
383
    abstract protected function runCallbackAgainstDOMNodeList(
384
        Type $type,
385
        DOMElement $node,
386
        Closure $againstNodeList,
387
        $callback = null
388
    );
389
390
    abstract protected function loadComplexTypeFromChildNode(
391
        BaseComplexType $type,
392
        DOMElement $node,
393
        DOMElement $childNode,
394
        Schema $schema
395
    );
396
397
    /**
398
    * @param Closure|null $callback
399
    *
400
    * @return Closure
401
    */
402
    abstract protected function loadSimpleType(Schema $schema, DOMElement $node, $callback = null);
403
404
    abstract protected function loadList(SimpleType $type, DOMElement $node);
405
406
    /**
407
    * @param string $attributeName
408
    *
409
    * @return SchemaItem
410
    */
411
    abstract protected function findSomeType(
412
        SchemaItem $fromThis,
413
        DOMElement $node,
414
        $attributeName
415
    );
416
417
    /**
418
    * @param string $attributeName
419
    *
420
    * @return SchemaItem
421
    */
422
    abstract protected function findSomeTypeFromAttribute(
423
        SchemaItem $fromThis,
424
        DOMElement $node,
425
        $attributeName
426
    );
427
428
    abstract protected function loadUnion(SimpleType $type, DOMElement $node);
429
430
    /**
431
    * @param bool $checkAbstract
432
    */
433
    abstract protected function fillTypeNode(Type $type, DOMElement $node, $checkAbstract = false);
434
435
    abstract protected function loadExtension(BaseComplexType $type, DOMElement $node);
436
437 135
    public function findAndSetSomeBase(
438
        Type $type,
439
        Base $setBaseOnThis,
440
        DOMElement $node
441
    ) {
442
        /**
443
        * @var Type $parent
444
        */
445 135
        $parent = $this->findSomeType($type, $node, 'base');
446 135
        $setBaseOnThis->setBase($parent);
447 135
    }
448
449
    abstract protected function maybeLoadExtensionFromBaseComplexType(
450
        Type $type,
451
        DOMElement $childNode
452
    );
453
454
    abstract protected function loadRestriction(Type $type, DOMElement $node);
455
456
    /**
457
    * @param string $typeName
458
    *
459
    * @return mixed[]
460
    */
461
    abstract protected static function splitParts(DOMElement $node, $typeName);
462
463
    /**
464
     *
465
     * @param string $finder
466
     * @param Schema $schema
467
     * @param DOMElement $node
468
     * @param string $typeName
469
     * @throws TypeException
470
     * @return ElementItem|Group|AttributeItem|AttributeGroup|Type
471
     */
472 135
    public function findSomething($finder, Schema $schema, DOMElement $node, $typeName)
473
    {
474 135
        list ($name, $namespace) = static::splitParts($node, $typeName);
475
476
        /**
477
        * @var string|null $namespace
478
        */
479 135
        $namespace = $namespace ?: $schema->getTargetNamespace();
480
481
        try {
482
            /**
483
            * @var ElementItem|Group|AttributeItem|AttributeGroup|Type $out
484
            */
485 135
            $out = $schema->$finder($name, $namespace);
486
487 135
            return $out;
488
        } catch (TypeNotFoundException $e) {
489
            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);
490
        }
491
    }
492
493
    /**
494
    * @return Closure
495
    */
496
    abstract protected function loadElementDef(Schema $schema, DOMElement $node);
497
498 135
    public function fillItem(Item $element, DOMElement $node)
499
    {
500
        /**
501
        * @var bool $skip
502
        */
503 135
        $skip = false;
504 135
        static::againstDOMNodeList(
505 135
            $node,
506
            function (
507
                DOMElement $node,
508
                DOMElement $childNode
509
            ) use (
510 135
                $element,
511 45
                & $skip
512
            ) {
513
                if (
514 135
                    ! $skip &&
515 135
                    in_array(
516 135
                        $childNode->localName,
517
                        [
518 135
                            'complexType',
519 45
                            'simpleType',
520 1
                        ]
521 45
                    )
522 45
                ) {
523 135
                    Type::loadTypeWithCallback(
524 135
                        $this,
525 135
                        $element->getSchema(),
526 135
                        $childNode,
527
                        function (Type $type) use ($element) {
528 135
                            $element->setType($type);
529 135
                        }
530 45
                    );
531 135
                    $skip = true;
532 45
                }
533 135
            }
534 45
        );
535 135
        if ($skip) {
536 135
            return;
537
        }
538 135
        $this->fillItemNonLocalType($element, $node);
539 135
    }
540
541
    abstract protected function fillItemNonLocalType(Item $element, DOMElement $node);
542
543
    /**
544
    * @var Schema|null
545
    */
546
    protected $globalSchema;
547
548
    /**
549
    * @return Schema[]
550
    */
551 135
    protected function setupGlobalSchemas(array & $callbacks)
552
    {
553 135
        $globalSchemas = array();
554 135
        foreach (self::$globalSchemaInfo as $namespace => $uri) {
555 135
            Schema::setLoadedFile(
556 135
                $uri,
557 135
                $globalSchemas[$namespace] = $schema = new Schema()
558 45
            );
559 135
            if ($namespace === self::XSD_NS) {
560 135
                $this->globalSchema = $schema;
561 45
            }
562 135
            $xml = $this->getDOM($this->knownLocationSchemas[$uri]);
563 135
            $callbacks = array_merge($callbacks, $this->schemaNode($schema, $xml->documentElement));
564 45
        }
565
566 135
        return $globalSchemas;
567
    }
568
569
    /**
570
    * @return string[]
571
    */
572 135
    public function getGlobalSchemaInfo()
573
    {
574 135
        return self::$globalSchemaInfo;
575
    }
576
577
    /**
578
     *
579
     * @return Schema
580
     */
581 135
    public function getGlobalSchema()
582
    {
583 135
        if (!$this->globalSchema) {
584 135
            $callbacks = array();
585 135
            $globalSchemas = $this->setupGlobalSchemas($callbacks);
586
587 135
            $globalSchemas[static::XSD_NS]->addType(new SimpleType($globalSchemas[static::XSD_NS], "anySimpleType"));
588 135
            $globalSchemas[static::XSD_NS]->addType(new SimpleType($globalSchemas[static::XSD_NS], "anyType"));
589
590 135
            $globalSchemas[static::XML_NS]->addSchema(
591 135
                $globalSchemas[static::XSD_NS],
592 90
                (string) static::XSD_NS
593 45
            );
594 135
            $globalSchemas[static::XSD_NS]->addSchema(
595 135
                $globalSchemas[static::XML_NS],
596 90
                (string) static::XML_NS
597 45
            );
598
599
            /**
600
            * @var Closure $callback
601
            */
602 135
            foreach ($callbacks as $callback) {
603 135
                $callback();
604 45
            }
605 45
        }
606
607
        /**
608
        * @var Schema $out
609
        */
610 135
        $out = $this->globalSchema;
611
612 135
        return $out;
613
    }
614
615
    /**
616
     * @param DOMElement $node
617
     * @param string  $file
618
     *
619
     * @return Schema
620
     */
621 135
    public function readNode(DOMElement $node, $file = 'schema.xsd')
622
    {
623 135
        $fileKey = $node->hasAttribute('targetNamespace') ? $this->getNamespaceSpecificFileIndex($file, $node->getAttribute('targetNamespace')) : $file;
624 135
        Schema::setLoadedFile($fileKey, $rootSchema = new Schema());
625
626 135
        $rootSchema->addSchema($this->getGlobalSchema());
627 135
        $callbacks = $this->schemaNode($rootSchema, $node);
628
629 135
        foreach ($callbacks as $callback) {
630 117
            call_user_func($callback);
631 45
        }
632
633 135
        return $rootSchema;
634
    }
635
636
    /**
637
     * It is possible that a single file contains multiple <xsd:schema/> nodes, for instance in a WSDL file.
638
     *
639
     * Each of these  <xsd:schema/> nodes typically target a specific namespace. Append the target namespace to the
640
     * file to distinguish between multiple schemas in a single file.
641
     *
642
     * @param string $file
643
     * @param string $targetNamespace
644
     *
645
     * @return string
646
     */
647 135
    public function getNamespaceSpecificFileIndex($file, $targetNamespace)
648
    {
649 135
        return $file . '#' . $targetNamespace;
650
    }
651
652
    /**
653
     * @param string $content
654
     * @param string $file
655
     *
656
     * @return Schema
657
     *
658
     * @throws IOException
659
     */
660 132
    public function readString($content, $file = 'schema.xsd')
661
    {
662 132
        $xml = new DOMDocument('1.0', 'UTF-8');
663 132
        if (!$xml->loadXML($content)) {
664
            throw new IOException("Can't load the schema");
665
        }
666 132
        $xml->documentURI = $file;
667
668 132
        return $this->readNode($xml->documentElement, $file);
669
    }
670
671
    /**
672
     * @param string $file
673
     *
674
     * @return Schema
675
     */
676 3
    public function readFile($file)
677
    {
678 3
        $xml = $this->getDOM($file);
679 3
        return $this->readNode($xml->documentElement, $file);
680
    }
681
682
    /**
683
     * @param string $file
684
     *
685
     * @return DOMDocument
686
     *
687
     * @throws IOException
688
     */
689 135
    public function getDOM($file)
690
    {
691 135
        $xml = new DOMDocument('1.0', 'UTF-8');
692 135
        if (!$xml->load($file)) {
693
            throw new IOException("Can't load the file $file");
694
        }
695 135
        return $xml;
696
    }
697
698 135
    public static function againstDOMNodeList(
699
        DOMElement $node,
700
        Closure $againstNodeList
701
    ) {
702 135
        $limit = $node->childNodes->length;
703 135
        for ($i = 0; $i < $limit; $i += 1) {
704
            /**
705
            * @var DOMNode $childNode
706
            */
707 135
            $childNode = $node->childNodes->item($i);
708
709 135
            if ($childNode instanceof DOMElement) {
710 135
                $againstNodeList(
711 135
                    $node,
712 90
                    $childNode
713 45
                );
714 45
            }
715 45
        }
716 135
    }
717
718 135
    public function maybeCallMethodAgainstDOMNodeList(
719
        DOMElement $node,
720
        SchemaItem $type,
721
        array $methods
722
    ) {
723 135
        static::againstDOMNodeList(
724 135
            $node,
725 135
            $this->CallbackGeneratorMaybeCallMethodAgainstDOMNodeList(
726 135
                $type,
727 90
                $methods
728 45
            )
729 45
        );
730 135
    }
731
732
    /**
733
    * @return Closure
734
    */
735
    public function CallbackGeneratorMaybeCallMethodAgainstDOMNodeList(
736
        SchemaItem $type,
737
        array $methods
738
    ) {
739 135
        return function (
740
            DOMElement $node,
741
            DOMElement $childNode
742
        ) use (
743 135
            $methods,
744 135
            $type
745
        ) {
746
            /**
747
            * @var string[] $methods
748
            */
749 135
            $methods = $methods;
750
751 135
            $this->maybeCallMethod(
752 135
                $methods,
753 135
                $childNode->localName,
754 135
                $childNode,
755 135
                $type,
756 90
                $childNode
757 45
            );
758 135
        };
759
    }
760
}
761