Passed
Push — static-analysis ( 935891...314368 )
by SignpostMarv
02:22
created

AbstractSchemaReader::readString()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2.0185

Importance

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