Passed
Push — static-analysis ( e3e805...309dd4 )
by SignpostMarv
03:34
created

CallbackGeneratorMaybeCallMethodAgainstDOMNodeList()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 13
nc 1
nop 2
dl 0
loc 22
ccs 9
cts 9
cp 1
crap 1
rs 9.2
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
     * @param bool $attributeDef
98
     *
99
     * @return Closure
100
     */
101 1
    abstract protected function loadAttributeOrElementDef(
102
        Schema $schema,
103
        DOMElement $node,
104 1
        $attributeDef
105
    );
106
107
    /**
108
     * @return Closure
109
     */
110
    abstract protected function loadAttributeDef(Schema $schema, DOMElement $node);
111
112
    /**
113
     * @param DOMElement $node
114
     *
115
     * @return string
116
     */
117 45
    public static function getDocumentation(DOMElement $node)
118
    {
119 45
        $doc = '';
120 45
        static::againstDOMNodeList(
121 45
            $node,
122
            function (
123
                DOMElement $node,
124
                DOMElement $childNode
125
            ) use (
126
                &$doc
127
            ) {
128 45
                if ($childNode->localName == 'annotation') {
129 45
                    $doc .= static::getDocumentation($childNode);
130 45
                } elseif ($childNode->localName == 'documentation') {
131 45
                    $doc .= $childNode->nodeValue;
132 45
                }
133 45
            }
134 45
        );
135 45
        $doc = preg_replace('/[\t ]+/', ' ', $doc);
136
137 45
        return trim($doc);
138
    }
139
140
    /**
141
     * @param string[] $methods
142
     * @param string   $key
143
     *
144
     * @return Closure|null
145
     */
146 45
    public function maybeCallMethod(
147
        array $methods,
148
        $key,
149
        DOMNode $childNode,
150
        ...$args
151
    ) {
152 45
        if ($childNode instanceof DOMElement && isset($methods[$key])) {
153 45
            $method = $methods[$key];
154
155
            /**
156
             * @var Closure|null
157
             */
158 45
            $append = $this->$method(...$args);
159
160 45
            if ($append instanceof Closure) {
161 45
                return $append;
162
            }
163 45
        }
164 45
    }
165
166
    /**
167
     * @param Schema     $schema
168
     * @param DOMElement $node
169
     * @param Schema     $parent
170
     *
171
     * @return Closure[]
172
     */
173 45
    public function schemaNode(Schema $schema, DOMElement $node, Schema $parent = null)
174
    {
175 45
        $schema->setSchemaThingsFromNode($node, $parent);
176 45
        $functions = array();
177
178
        $schemaReaderMethods = [
179 45
            'include' => (Schema::class.'::loadImport'),
180 45
            'import' => (Schema::class.'::loadImport'),
181
            'attributeGroup' => (
182
                AttributeGroup::class.
183
                '::loadAttributeGroup'
184 45
            ),
185 45
        ];
186
187
        $thisMethods = [
188 45
            'element' => [$this, 'loadElementDef'],
189 45
            'attribute' => [$this, 'loadAttributeDef'],
190 45
            'group' => [$this, 'loadGroup'],
191 45
            'complexType' => [$this, 'loadComplexType'],
192 45
            'simpleType' => [$this, 'loadSimpleType'],
193 45
        ];
194
195 45
        static::againstDOMNodeList(
196 45
            $node,
197
            function (
198
                DOMElement $node,
199
                DOMElement $childNode
200
            ) use (
201 45
                $schemaReaderMethods,
202 45
                $schema,
203 45
                $thisMethods,
204
                &$functions
205
            ) {
206
                /**
207
                 * @var Closure|null
208
                 */
209 45
                $callback = $this->maybeCallCallableWithArgs(
210 45
                    $childNode,
211 45
                    [],
212 45
                    [],
213
                    [
214
                        [
215 45
                            $schemaReaderMethods,
216
                            [
217 45
                                $this,
218 45
                                $schema,
219 45
                                $childNode,
220 45
                            ],
221 45
                        ],
222
                        [
223 45
                            $thisMethods,
224
                            [
225 45
                                $schema,
226 45
                                $childNode,
227 45
                            ],
228 45
                        ],
229
                    ]
230 45
                );
231
232 45
                if ($callback instanceof Closure) {
233 45
                    $functions[] = $callback;
234 45
                }
235 45
            }
236 45
        );
237
238 45
        return $functions;
239
    }
240
241
    /**
242
     * @return InterfaceSetMinMax
243
     */
244 45
    public static function maybeSetMax(InterfaceSetMinMax $ref, DOMElement $node)
245
    {
246
        if (
247 45
            $node->hasAttribute('maxOccurs')
248 45
        ) {
249 45
            $ref->setMax($node->getAttribute('maxOccurs') == 'unbounded' ? -1 : (int) $node->getAttribute('maxOccurs'));
250 45
        }
251
252 45
        return $ref;
253
    }
254
255
    /**
256
     * @return InterfaceSetMinMax
257
     */
258 45
    public static function maybeSetMin(InterfaceSetMinMax $ref, DOMElement $node)
259
    {
260 45
        if ($node->hasAttribute('minOccurs')) {
261 45
            $ref->setMin((int) $node->getAttribute('minOccurs'));
262 45
        }
263
264 45
        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 loadExtension(BaseComplexType $type, DOMElement $node);
426
427 45
    public function findAndSetSomeBase(
428
        Type $type,
429
        Base $setBaseOnThis,
430
        DOMElement $node
431
    ) {
432
        /**
433
         * @var Type
434
         */
435 45
        $parent = $this->findSomeType($type, $node, 'base');
436 45
        $setBaseOnThis->setBase($parent);
437 45
    }
438
439
    abstract protected function maybeLoadExtensionFromBaseComplexType(
440
        Type $type,
441
        DOMElement $childNode
442
    );
443
444
    abstract protected function loadRestriction(Type $type, DOMElement $node);
445
446
    /**
447
     * @param string $typeName
448
     *
449
     * @return mixed[]
450
     */
451
    abstract protected static function splitParts(DOMElement $node, $typeName);
452
453
    /**
454
     * @param string     $finder
455
     * @param Schema     $schema
456
     * @param DOMElement $node
457
     * @param string     $typeName
458
     *
459
     * @throws TypeException
460
     *
461
     * @return ElementItem|Group|AttributeItem|AttributeGroup|Type
462
     */
463 45
    public function findSomething($finder, Schema $schema, DOMElement $node, $typeName)
464
    {
465 45
        list($name, $namespace) = static::splitParts($node, $typeName);
466
467
        /**
468
         * @var string|null
469
         */
470 45
        $namespace = $namespace ?: $schema->getTargetNamespace();
471
472
        try {
473
            /**
474
             * @var ElementItem|Group|AttributeItem|AttributeGroup|Type
475
             */
476 45
            $out = $schema->$finder($name, $namespace);
477
478 45
            return $out;
479
        } catch (TypeNotFoundException $e) {
480
            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);
481
        }
482
    }
483
484
    /**
485
     * @return Closure
486
     */
487
    abstract protected function loadElementDef(Schema $schema, DOMElement $node);
488
489 45
    public function fillItem(Item $element, DOMElement $node)
490
    {
491
        /**
492
         * @var bool
493
         */
494 45
        $skip = false;
495 45
        static::againstDOMNodeList(
496 45
            $node,
497
            function (
498
                DOMElement $node,
499 1
                DOMElement $childNode
500
            ) use (
501 45
                $element,
502
                &$skip
503
            ) {
504
                if (
505 45
                    !$skip &&
506 45
                    in_array(
507 45
                        $childNode->localName,
508
                        [
509 45
                            'complexType',
510 45
                            'simpleType',
511
                        ]
512 45
                    )
513 45
                ) {
514 45
                    Type::loadTypeWithCallback(
515 45
                        $this,
516 45
                        $element->getSchema(),
517 45
                        $childNode,
518
                        function (Type $type) use ($element) {
519 45
                            $element->setType($type);
520 45
                        }
521 45
                    );
522 45
                    $skip = true;
523 45
                }
524 45
            }
525 45
        );
526 45
        if ($skip) {
527 45
            return;
528
        }
529 45
        $this->fillItemNonLocalType($element, $node);
530 45
    }
531
532
    abstract protected function fillItemNonLocalType(Item $element, DOMElement $node);
533
534
    /**
535
     * @var Schema|null
536
     */
537
    protected $globalSchema;
538
539
    /**
540
     * @return Schema[]
541
     */
542 45
    protected function setupGlobalSchemas(array &$callbacks)
543
    {
544 45
        $globalSchemas = array();
545 45
        foreach (self::$globalSchemaInfo as $namespace => $uri) {
546 45
            Schema::setLoadedFile(
547 45
                $uri,
548 45
                $globalSchemas[$namespace] = $schema = new Schema()
549 45
            );
550 45
            if ($namespace === self::XSD_NS) {
551 45
                $this->globalSchema = $schema;
552 45
            }
553 45
            $xml = $this->getDOM($this->knownLocationSchemas[$uri]);
554 45
            $callbacks = array_merge($callbacks, $this->schemaNode($schema, $xml->documentElement));
555 45
        }
556
557 45
        return $globalSchemas;
558
    }
559
560
    /**
561
     * @return string[]
562
     */
563 45
    public function getGlobalSchemaInfo()
564
    {
565 45
        return self::$globalSchemaInfo;
566
    }
567
568
    /**
569
     * @return Schema
570
     */
571 45
    public function getGlobalSchema()
572
    {
573 45
        if (!$this->globalSchema) {
574 45
            $callbacks = array();
575 45
            $globalSchemas = $this->setupGlobalSchemas($callbacks);
576
577 45
            $globalSchemas[static::XSD_NS]->addType(new SimpleType($globalSchemas[static::XSD_NS], 'anySimpleType'));
578 45
            $globalSchemas[static::XSD_NS]->addType(new SimpleType($globalSchemas[static::XSD_NS], 'anyType'));
579
580 45
            $globalSchemas[static::XML_NS]->addSchema(
581 45
                $globalSchemas[static::XSD_NS],
582
                (string) static::XSD_NS
583 45
            );
584 45
            $globalSchemas[static::XSD_NS]->addSchema(
585 45
                $globalSchemas[static::XML_NS],
586
                (string) static::XML_NS
587 45
            );
588
589
            /**
590
             * @var Closure
591
             */
592 45
            foreach ($callbacks as $callback) {
593 45
                $callback();
594 45
            }
595 45
        }
596
597
        /**
598
         * @var Schema
599
         */
600 45
        $out = $this->globalSchema;
601
602 45
        return $out;
603
    }
604
605
    /**
606
     * @param DOMElement $node
607
     * @param string     $file
608
     *
609
     * @return Schema
610
     */
611 45
    public function readNode(DOMElement $node, $file = 'schema.xsd')
612
    {
613 45
        $fileKey = $node->hasAttribute('targetNamespace') ? $this->getNamespaceSpecificFileIndex($file, $node->getAttribute('targetNamespace')) : $file;
614 45
        Schema::setLoadedFile($fileKey, $rootSchema = new Schema());
615
616 45
        $rootSchema->addSchema($this->getGlobalSchema());
617 45
        $callbacks = $this->schemaNode($rootSchema, $node);
618
619 45
        foreach ($callbacks as $callback) {
620 39
            call_user_func($callback);
621 45
        }
622
623 45
        return $rootSchema;
624
    }
625
626
    /**
627
     * It is possible that a single file contains multiple <xsd:schema/> nodes, for instance in a WSDL file.
628
     *
629
     * Each of these  <xsd:schema/> nodes typically target a specific namespace. Append the target namespace to the
630
     * file to distinguish between multiple schemas in a single file.
631
     *
632
     * @param string $file
633
     * @param string $targetNamespace
634
     *
635
     * @return string
636
     */
637 45
    public function getNamespaceSpecificFileIndex($file, $targetNamespace)
638
    {
639 45
        return $file.'#'.$targetNamespace;
640
    }
641
642
    /**
643
     * @param string $content
644
     * @param string $file
645
     *
646
     * @return Schema
647
     *
648
     * @throws IOException
649
     */
650 44
    public function readString($content, $file = 'schema.xsd')
651
    {
652 44
        $xml = new DOMDocument('1.0', 'UTF-8');
653 44
        if (!$xml->loadXML($content)) {
654
            throw new IOException("Can't load the schema");
655
        }
656 44
        $xml->documentURI = $file;
657
658 44
        return $this->readNode($xml->documentElement, $file);
659
    }
660
661
    /**
662
     * @param string $file
663
     *
664
     * @return Schema
665
     */
666 1
    public function readFile($file)
667
    {
668 1
        $xml = $this->getDOM($file);
669
670 1
        return $this->readNode($xml->documentElement, $file);
671
    }
672
673
    /**
674
     * @param string $file
675
     *
676
     * @return DOMDocument
677
     *
678
     * @throws IOException
679
     */
680 45
    public function getDOM($file)
681
    {
682 45
        $xml = new DOMDocument('1.0', 'UTF-8');
683 45
        if (!$xml->load($file)) {
684
            throw new IOException("Can't load the file $file");
685
        }
686
687 45
        return $xml;
688
    }
689
690 45
    public static function againstDOMNodeList(
691
        DOMElement $node,
692
        Closure $againstNodeList
693
    ) {
694 45
        $limit = $node->childNodes->length;
695 45
        for ($i = 0; $i < $limit; $i += 1) {
696
            /**
697
             * @var DOMNode
698
             */
699 45
            $childNode = $node->childNodes->item($i);
700
701 45
            if ($childNode instanceof DOMElement) {
702 45
                $againstNodeList(
703 45
                    $node,
704
                    $childNode
705 45
                );
706 45
            }
707 45
        }
708 45
    }
709
710 45
    public function maybeCallMethodAgainstDOMNodeList(
711
        DOMElement $node,
712
        SchemaItem $type,
713
        array $methods
714
    ) {
715 45
        static::againstDOMNodeList(
716 45
            $node,
717 45
            $this->CallbackGeneratorMaybeCallMethodAgainstDOMNodeList(
718 45
                $type,
719
                $methods
720 45
            )
721 45
        );
722 45
    }
723
724
    /**
725
     * @return Closure
726
     */
727
    public function CallbackGeneratorMaybeCallMethodAgainstDOMNodeList(
728
        SchemaItem $type,
729
        array $methods
730
    ) {
731 45
        return function (
732
            DOMElement $node,
733
            DOMElement $childNode
734
        ) use (
735 45
            $methods,
736 45
            $type
737
        ) {
738
            /**
739
             * @var string[]
740
             */
741 45
            $methods = $methods;
742
743 45
            $this->maybeCallMethod(
744 45
                $methods,
745 45
                $childNode->localName,
746 45
                $childNode,
747 45
                $type,
748
                $childNode
749 45
            );
750 45
        };
751
    }
752
}
753