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

maybeCallMethodAgainstDOMNodeList()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 25
Code Lines 15

Duplication

Lines 18
Ratio 72 %

Code Coverage

Tests 13
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 15
c 0
b 0
f 0
nc 1
nop 3
dl 18
loc 25
ccs 13
cts 13
cp 1
crap 1
rs 8.8571
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
                        ]
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 View Code Duplication
            function (
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
726
                DOMElement $node,
727
                DOMElement $childNode
728
            ) use (
729 135
                $methods,
730 135
                $type
731
            ) {
732
                /**
733
                * @var string[] $methods
734
                */
735 135
                $methods = $methods;
736
737 135
                $this->maybeCallMethod(
738 135
                    $methods,
739 135
                    $childNode->localName,
740 135
                    $childNode,
741 135
                    $type,
742 90
                    $childNode
743 45
                );
744 135
            }
745 45
        );
746 135
    }
747
}
748