Passed
Push — static-analysis ( 8e814c...85afd6 )
by SignpostMarv
03:37
created

AbstractSchemaReader::againstDOMNodeList()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 7

Duplication

Lines 10
Ratio 66.67 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 3
nop 2
dl 10
loc 15
ccs 8
cts 8
cp 1
crap 3
rs 9.4285
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
    public function getKnownSchemaLocation($remote)
102
    {
103
        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
        $limit = $node->childNodes->length;
135 135
        for ($i = 0; $i < $limit; $i += 1) {
136 135
            $childNode = $node->childNodes->item($i);
137 135
            if ($childNode->localName == "annotation") {
138 135
                $doc .= static::getDocumentation($childNode);
139 135
            } elseif ($childNode->localName == 'documentation') {
140 135
                $doc .= (string) $childNode->nodeValue;
141 45
            }
142 45
        }
143 135
        $doc = preg_replace('/[\t ]+/', ' ', $doc);
144 135
        return trim($doc);
145
    }
146
147
    /**
148
    * @param string[] $methods
149
    * @param string $key
150
    *
151
    * @return Closure|null
152
    */
153 135
    public function maybeCallMethod(
154
        array $methods,
155
        $key,
156
        DOMNode $childNode,
157
        ...$args
158
    ) {
159 135
        if ($childNode instanceof DOMElement && isset($methods[$key])) {
160 135
            $method = $methods[$key];
161
162
            /**
163
            * @var Closure|null $append
164
            */
165 135
            $append = $this->$method(...$args);
166
167 135
            if ($append instanceof Closure) {
168 135
                return $append;
169
            }
170 45
        }
171 135
    }
172
173
    /**
174
     *
175
     * @param Schema $schema
176
     * @param DOMElement $node
177
     * @param Schema $parent
178
     * @return Closure[]
179
     */
180 135
    public function schemaNode(Schema $schema, DOMElement $node, Schema $parent = null)
181
    {
182 135
        $schema->setSchemaThingsFromNode($node, $parent);
183 135
        $functions = array();
184
185
        $schemaReaderMethods = [
186 135
            'include' => (Schema::class . '::loadImport'),
187 45
            'import' => (Schema::class . '::loadImport'),
188 45
        ];
189
190
        $thisMethods = [
191 135
            'element' => [$this, 'loadElementDef'],
192 135
            'attribute' => [$this, 'loadAttributeDef'],
193 135
            'attributeGroup' => [$this, 'loadAttributeGroup'],
194 135
            'group' => [$this, 'loadGroup'],
195 135
            'complexType' => [$this, 'loadComplexType'],
196 135
            'simpleType' => [$this, 'loadSimpleType'],
197 45
        ];
198
199 135
        $limit = $node->childNodes->length;
200 135
        for ($i = 0; $i < $limit; $i += 1) {
201
            /**
202
            * @var DOMNode $childNode
203
            */
204 135
            $childNode = $node->childNodes->item($i);
205 135
            if ($childNode instanceof DOMElement) {
206
                /**
207
                * @var Closure|null $callback
208
                */
209 135
                $callback = $this->maybeCallCallableWithArgs(
210 135
                    $childNode,
211 135
                        [],
212 135
                        [],
213
                        [
214
                            [
215 135
                                $schemaReaderMethods,
216
                                [
217 135
                                    $this,
218 135
                                    $schema,
219 135
                                    $childNode,
220
                                ]
221 45
                            ],
222
                            [
223 135
                                $thisMethods,
224
                                [
225 135
                                    $schema,
226 90
                                    $childNode
227 45
                                ],
228 45
                            ],
229
                        ]
230 45
                );
231
232 135
                if ($callback instanceof Closure) {
233 135
                    $functions[] = $callback;
234 45
                }
235 45
            }
236 45
        }
237
238 135
        return $functions;
239
    }
240
241
    /**
242
    * @return InterfaceSetMinMax
243
    */
244 135
    public static function maybeSetMax(InterfaceSetMinMax $ref, DOMElement $node)
245
    {
246
        if (
247 135
            $node->hasAttribute("maxOccurs")
248 45
        ) {
249 135
            $ref->setMax($node->getAttribute("maxOccurs") == "unbounded" ? -1 : (int)$node->getAttribute("maxOccurs"));
250 45
        }
251
252 135
        return $ref;
253
    }
254
255
    /**
256
    * @return InterfaceSetMinMax
257
    */
258 135
    public static function maybeSetMin(InterfaceSetMinMax $ref, DOMElement $node)
259
    {
260 135
        if ($node->hasAttribute("minOccurs")) {
261 135
            $ref->setMin((int) $node->getAttribute("minOccurs"));
262 45
        }
263
264 135
        return $ref;
265
    }
266
267
    /**
268
    * @param int|null $max
269
    *
270
    * @return int|null
271
    */
272
    abstract protected static function loadSequenceNormaliseMax(DOMElement $node, $max);
273
274
    /**
275
    * @param int|null $max
276
    */
277
    abstract protected function loadSequence(ElementContainer $elementContainer, DOMElement $node, $max = null);
278
279
    /**
280
    * @param int|null $max
281
    */
282
    abstract protected function loadSequenceChildNode(
283
        ElementContainer $elementContainer,
284
        DOMElement $node,
285
        DOMElement $childNode,
286
        $max
287
    );
288
289
    /**
290
    * @param mixed[][] $methods
291
    *
292
    * @return mixed
293
    */
294
    abstract protected function maybeCallCallableWithArgs(
295
        DOMElement $childNode,
296
        array $commonMethods = [],
297
        array $methods = [],
298
        array $commonArguments = []
299
    );
300
301
    /**
302
    * @param int|null $max
303
    */
304
    abstract protected function loadSequenceChildNodeLoadSequence(
305
        ElementContainer $elementContainer,
306
        DOMElement $childNode,
307
        $max
308
    );
309
310
    /**
311
    * @param int|null $max
312
    */
313
    abstract protected function loadSequenceChildNodeLoadElement(
314
        ElementContainer $elementContainer,
315
        DOMElement $node,
316
        DOMElement $childNode,
317
        $max
318
    );
319
320
    abstract protected function loadSequenceChildNodeLoadGroup(
321
        ElementContainer $elementContainer,
322
        DOMElement $node,
323
        DOMElement $childNode
324
    );
325
326
    abstract protected function addGroupAsElement(
327
        Schema $schema,
328
        DOMElement $node,
329
        DOMElement $childNode,
330
        ElementContainer $elementContainer
331
    );
332
333
    abstract protected function maybeLoadSequenceFromElementContainer(
334
        BaseComplexType $type,
335
        DOMElement $childNode
336
    );
337
338
    /**
339
    * @return Closure
340
    */
341
    abstract protected function loadGroup(Schema $schema, DOMElement $node);
342
343
    /**
344
    * @return BaseComplexType
345
    */
346
    abstract protected function loadComplexTypeBeforeCallbackCallback(
347
        Schema $schema,
348
        DOMElement $node
349
    );
350
351
    /**
352
    * @param Closure|null $callback
353
    *
354
    * @return Closure
355
    */
356
    abstract protected function loadComplexType(Schema $schema, DOMElement $node, $callback = null);
357
358
    /**
359
    * @param Closure|null $callback
360
    *
361
    * @return Closure
362
    */
363
    abstract protected function makeCallbackCallback(
364
        Type $type,
365
        DOMElement $node,
366
        Closure $callbackCallback,
367
        $callback = null
368
    );
369
370
    /**
371
    * @param Closure|null $callback
372
    */
373
    abstract protected function runCallbackAgainstDOMNodeList(
374
        Type $type,
375
        DOMElement $node,
376
        Closure $againstNodeList,
377
        $callback = null
378
    );
379
380
    abstract protected function loadComplexTypeFromChildNode(
381
        BaseComplexType $type,
382
        DOMElement $node,
383
        DOMElement $childNode,
384
        Schema $schema
385
    );
386
387
    /**
388
    * @param Closure|null $callback
389
    *
390
    * @return Closure
391
    */
392
    abstract protected function loadSimpleType(Schema $schema, DOMElement $node, $callback = null);
393
394
    abstract protected function loadList(SimpleType $type, DOMElement $node);
395
396
    /**
397
    * @param string $attributeName
398
    *
399
    * @return SchemaItem
400
    */
401
    abstract protected function findSomeType(
402
        SchemaItem $fromThis,
403
        DOMElement $node,
404
        $attributeName
405
    );
406
407
    /**
408
    * @param string $attributeName
409
    *
410
    * @return SchemaItem
411
    */
412
    abstract protected function findSomeTypeFromAttribute(
413
        SchemaItem $fromThis,
414
        DOMElement $node,
415
        $attributeName
416
    );
417
418
    abstract protected function loadUnion(SimpleType $type, DOMElement $node);
419
420
    /**
421
    * @param bool $checkAbstract
422
    */
423
    abstract protected function fillTypeNode(Type $type, DOMElement $node, $checkAbstract = false);
424
425
    abstract protected function loadExtensionChildNode(
426
        BaseComplexType $type,
427
        DOMElement $node,
428
        DOMElement $childNode
429
    );
430
431
    abstract protected function loadExtension(BaseComplexType $type, DOMElement $node);
432
433
    abstract protected function loadExtensionChildNodes(
434
        BaseComplexType $type,
435
        DOMNodeList $childNodes,
436
        DOMElement $node
437
    );
438
439 135
    public function findAndSetSomeBase(
440
        Type $type,
441
        Base $setBaseOnThis,
442
        DOMElement $node
443
    ) {
444
        /**
445
        * @var Type $parent
446
        */
447 135
        $parent = $this->findSomeType($type, $node, 'base');
448 135
        $setBaseOnThis->setBase($parent);
449 135
    }
450
451
    abstract protected function maybeLoadExtensionFromBaseComplexType(
452
        Type $type,
453
        DOMElement $childNode
454
    );
455
456
    abstract protected function loadRestriction(Type $type, DOMElement $node);
457
458
    /**
459
    * @param string $typeName
460
    *
461
    * @return mixed[]
462
    */
463
    abstract protected static function splitParts(DOMElement $node, $typeName);
464
465
    /**
466
     *
467
     * @param string $finder
468
     * @param Schema $schema
469
     * @param DOMElement $node
470
     * @param string $typeName
471
     * @throws TypeException
472
     * @return ElementItem|Group|AttributeItem|AttributeGroup|Type
473
     */
474 135
    public function findSomething($finder, Schema $schema, DOMElement $node, $typeName)
475
    {
476 135
        list ($name, $namespace) = static::splitParts($node, $typeName);
477
478
        /**
479
        * @var string|null $namespace
480
        */
481 135
        $namespace = $namespace ?: $schema->getTargetNamespace();
482
483
        try {
484
            /**
485
            * @var ElementItem|Group|AttributeItem|AttributeGroup|Type $out
486
            */
487 135
            $out = $schema->$finder($name, $namespace);
488
489 135
            return $out;
490
        } catch (TypeNotFoundException $e) {
491
            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);
492
        }
493
    }
494
495
    /**
496
    * @return Closure
497
    */
498
    abstract protected function loadElementDef(Schema $schema, DOMElement $node);
499
500 135
    public function fillItem(Item $element, DOMElement $node)
501
    {
502 135
        $limit = $node->childNodes->length;
503 135
        for ($i = 0; $i < $limit; $i += 1) {
504 135
            $childNode = $node->childNodes->item($i);
505
            if (
506 135
                in_array(
507 135
                    $childNode->localName,
508
                    [
509 135
                        'complexType',
510 45
                        'simpleType',
511
                    ]
512 45
                )
513 45
            ) {
514 135
                Type::loadTypeWithCallback(
515 135
                    $this,
516 135
                    $element->getSchema(),
517 135
                    $childNode,
518 135
                    function (Type $type) use ($element) {
519 135
                        $element->setType($type);
520 135
                    }
521 45
                );
522 135
                return;
523
            }
524 45
        }
525
526 135
        $this->fillItemNonLocalType($element, $node);
527 135
    }
528
529
    abstract protected function fillItemNonLocalType(Item $element, DOMElement $node);
530
531
    /**
532
    * @var Schema|null
533
    */
534
    protected $globalSchema;
535
536
    /**
537
    * @return Schema[]
538
    */
539 135
    protected function setupGlobalSchemas(array & $callbacks)
540
    {
541 135
        $globalSchemas = array();
542 135
        foreach (self::$globalSchemaInfo as $namespace => $uri) {
543 135
            Schema::setLoadedFile(
544 135
                $uri,
545 135
                $globalSchemas[$namespace] = $schema = new Schema()
546 45
            );
547 135
            if ($namespace === self::XSD_NS) {
548 135
                $this->globalSchema = $schema;
549 45
            }
550 135
            $xml = $this->getDOM($this->knownLocationSchemas[$uri]);
551 135
            $callbacks = array_merge($callbacks, $this->schemaNode($schema, $xml->documentElement));
552 45
        }
553
554 135
        return $globalSchemas;
555
    }
556
557
    /**
558
    * @return string[]
559
    */
560 135
    public function getGlobalSchemaInfo()
561
    {
562 135
        return self::$globalSchemaInfo;
563
    }
564
565
    /**
566
     *
567
     * @return Schema
568
     */
569 135
    public function getGlobalSchema()
570
    {
571 135
        if (!$this->globalSchema) {
572 135
            $callbacks = array();
573 135
            $globalSchemas = $this->setupGlobalSchemas($callbacks);
574
575 135
            $globalSchemas[static::XSD_NS]->addType(new SimpleType($globalSchemas[static::XSD_NS], "anySimpleType"));
576 135
            $globalSchemas[static::XSD_NS]->addType(new SimpleType($globalSchemas[static::XSD_NS], "anyType"));
577
578 135
            $globalSchemas[static::XML_NS]->addSchema(
579 135
                $globalSchemas[static::XSD_NS],
580 90
                (string) static::XSD_NS
581 45
            );
582 135
            $globalSchemas[static::XSD_NS]->addSchema(
583 135
                $globalSchemas[static::XML_NS],
584 90
                (string) static::XML_NS
585 45
            );
586
587
            /**
588
            * @var Closure $callback
589
            */
590 135
            foreach ($callbacks as $callback) {
591 135
                $callback();
592 45
            }
593 45
        }
594
595
        /**
596
        * @var Schema $out
597
        */
598 135
        $out = $this->globalSchema;
599
600 135
        return $out;
601
    }
602
603
    /**
604
     * @param DOMElement $node
605
     * @param string  $file
606
     *
607
     * @return Schema
608
     */
609 135
    public function readNode(DOMElement $node, $file = 'schema.xsd')
610
    {
611 135
        $fileKey = $node->hasAttribute('targetNamespace') ? $this->getNamespaceSpecificFileIndex($file, $node->getAttribute('targetNamespace')) : $file;
612 135
        Schema::setLoadedFile($fileKey, $rootSchema = new Schema());
613
614 135
        $rootSchema->addSchema($this->getGlobalSchema());
615 135
        $callbacks = $this->schemaNode($rootSchema, $node);
616
617 135
        foreach ($callbacks as $callback) {
618 117
            call_user_func($callback);
619 45
        }
620
621 135
        return $rootSchema;
622
    }
623
624
    /**
625
     * It is possible that a single file contains multiple <xsd:schema/> nodes, for instance in a WSDL file.
626
     *
627
     * Each of these  <xsd:schema/> nodes typically target a specific namespace. Append the target namespace to the
628
     * file to distinguish between multiple schemas in a single file.
629
     *
630
     * @param string $file
631
     * @param string $targetNamespace
632
     *
633
     * @return string
634
     */
635 135
    public function getNamespaceSpecificFileIndex($file, $targetNamespace)
636
    {
637 135
        return $file . '#' . $targetNamespace;
638
    }
639
640
    /**
641
     * @param string $content
642
     * @param string $file
643
     *
644
     * @return Schema
645
     *
646
     * @throws IOException
647
     */
648 132
    public function readString($content, $file = 'schema.xsd')
649
    {
650 132
        $xml = new DOMDocument('1.0', 'UTF-8');
651 132
        if (!$xml->loadXML($content)) {
652
            throw new IOException("Can't load the schema");
653
        }
654 132
        $xml->documentURI = $file;
655
656 132
        return $this->readNode($xml->documentElement, $file);
657
    }
658
659
    /**
660
     * @param string $file
661
     *
662
     * @return Schema
663
     */
664 3
    public function readFile($file)
665
    {
666 3
        $xml = $this->getDOM($file);
667 3
        return $this->readNode($xml->documentElement, $file);
668
    }
669
670
    /**
671
     * @param string $file
672
     *
673
     * @return DOMDocument
674
     *
675
     * @throws IOException
676
     */
677 135
    public function getDOM($file)
678
    {
679 135
        $xml = new DOMDocument('1.0', 'UTF-8');
680 135
        if (!$xml->load($file)) {
681
            throw new IOException("Can't load the file $file");
682
        }
683 135
        return $xml;
684
    }
685
686 135
    public static function againstDOMNodeList(
687
        DOMElement $node,
688
        Closure $againstNodeList
689
    ) {
690 135
        $limit = $node->childNodes->length;
691 135 View Code Duplication
        for ($i = 0; $i < $limit; $i += 1) {
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...
692
            /**
693
            * @var DOMNode $childNode
694
            */
695 135
            $childNode = $node->childNodes->item($i);
696
697 135
            if ($childNode instanceof DOMElement) {
698 135
                $againstNodeList(
699 135
                    $node,
700 90
                    $childNode
701 45
                );
702 45
            }
703 45
        }
704 135
    }
705
}
706