Passed
Push — static-analysis ( ae4331...8e814c )
by SignpostMarv
03:31
created

AbstractSchemaReader::getGlobalSchema()   B

Complexity

Conditions 3
Paths 2

Size

Total Lines 32
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 15
c 0
b 0
f 0
nc 2
nop 0
dl 0
loc 32
ccs 20
cts 20
cp 1
crap 3
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
    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