Passed
Push — php-7.1 ( ed6d10...451fb2 )
by SignpostMarv
02:49
created

CallbackGeneratorMaybeCallMethodAgainstDOMNodeList()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 1

Importance

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