Passed
Push — static-analysis ( c3aabf...c8fdad )
by SignpostMarv
03:22
created

AbstractSchemaReader::setupGlobalSchemas()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 3

Importance

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