Passed
Push — static-analysis ( c8fdad...7d02a1 )
by SignpostMarv
03:21
created

getNamespaceSpecificFileIndex()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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