SchemaReader   F
last analyzed

Complexity

Total Complexity 208

Size/Duplication

Total Lines 1445
Duplicated Lines 0 %

Test Coverage

Coverage 98.16%

Importance

Changes 35
Bugs 4 Features 4
Metric Value
wmc 208
eloc 718
dl 0
loc 1445
ccs 695
cts 708
cp 0.9816
c 35
b 4
f 4
rs 1.882

64 Methods

Rating   Name   Duplication   Size   Complexity  
A findAndSetSomeBase() 0 10 1
A readFile() 0 5 1
A addAttributeFromAttributeOrRef() 0 13 1
A findAttributeItem() 0 18 3
A addGroupAsElement() 0 14 1
A setLoadedSchemaFromElement() 0 4 2
B getGlobalSchema() 0 46 6
A loadGroup() 0 22 6
A createOrUseSchemaForNs() 0 13 2
A findGroup() 0 18 3
A loadAttributeDef() 0 3 1
A addKnownSchemaLocation() 0 3 1
A loadElement() 0 8 1
A __construct() 0 6 2
A loadUnion() 0 29 3
A getDocumentation() 0 3 1
A findAttributeGroup() 0 18 3
A findSomethingLikeAttributeGroup() 0 8 1
A loadSequenceChildNodeLoadElement() 0 29 4
A findType() 0 25 5
A extractErrorMessage() 0 11 2
C loadImport() 0 36 13
A maybeSetDefault() 0 4 2
A loadTypeWithCallback() 0 21 4
A loadExtensionChildNodes() 0 22 5
B loadComplexType() 0 54 6
B fillTypeNode() 0 22 8
A loadImportFresh() 0 19 2
A findSomeType() 0 9 1
A loadSimpleType() 0 27 5
A maybeSetMin() 0 6 4
A loadAttribute() 0 8 1
A loadRestriction() 0 27 2
B loadComplexTypeFromChildNode() 0 45 9
B loadSequenceChildNode() 0 35 6
A maybeSetFixed() 0 4 2
A readNode() 0 18 3
A findElement() 0 13 3
A setLoadedSchema() 0 7 3
B readNodes() 0 29 7
A setLoadedFile() 0 3 1
A loadGroupRef() 0 9 1
A fillElement() 0 24 6
A fillItem() 0 41 4
A maybeSetMax() 0 4 3
A loadChildAttributesAndAttributeGroups() 0 22 3
A getAttributeFromAttributeOrRef() 0 15 2
B loadSequence() 0 34 7
A splitParts() 0 17 2
A loadList() 0 22 2
A findSomeTypeFromAttribute() 0 12 1
A loadElementDef() 0 3 1
A loadRestrictionChildNodes() 0 42 3
A readString() 0 11 2
A fillAttribute() 0 16 6
A fillItemNonLocalType() 0 19 2
A setSchemaThingsFromNode() 0 15 3
A loadAttributeGroup() 0 35 3
A loadExtension() 0 9 2
A againstDOMNodeList() 0 15 3
A addKnownNamespaceSchemaLocation() 0 3 1
A loadAttributeOrElementDef() 0 20 2
B schemaNode() 0 48 10
A getDOM() 0 11 2

How to fix   Complexity   

Complex Class

Complex classes like SchemaReader often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SchemaReader, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace GoetasWebservices\XML\XSDReader;
6
7
use Closure;
8
use DOMDocument;
9
use DOMElement;
10
use DOMNode;
11
use GoetasWebservices\XML\XSDReader\Documentation\DocumentationReader;
12
use GoetasWebservices\XML\XSDReader\Documentation\StandardDocumentationReader;
13
use GoetasWebservices\XML\XSDReader\Exception\IOException;
14
use GoetasWebservices\XML\XSDReader\Exception\TypeException;
15
use GoetasWebservices\XML\XSDReader\Schema\Attribute\Attribute;
16
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeContainer;
17
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeDef;
18
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeItem;
19
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeSingle;
20
use GoetasWebservices\XML\XSDReader\Schema\Attribute\Group as AttributeGroup;
21
use GoetasWebservices\XML\XSDReader\Schema\Element\AbstractElementSingle;
22
use GoetasWebservices\XML\XSDReader\Schema\Element\Element;
23
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementContainer;
24
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementDef;
25
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementRef;
26
use GoetasWebservices\XML\XSDReader\Schema\Element\Group;
27
use GoetasWebservices\XML\XSDReader\Schema\Element\GroupRef;
28
use GoetasWebservices\XML\XSDReader\Schema\Element\InterfaceSetDefault;
29
use GoetasWebservices\XML\XSDReader\Schema\Element\InterfaceSetFixed;
30
use GoetasWebservices\XML\XSDReader\Schema\Element\InterfaceSetMinMax;
31
use GoetasWebservices\XML\XSDReader\Schema\Exception\TypeNotFoundException;
32
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Base;
33
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Extension;
34
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Restriction;
35
use GoetasWebservices\XML\XSDReader\Schema\Item;
36
use GoetasWebservices\XML\XSDReader\Schema\Schema;
37
use GoetasWebservices\XML\XSDReader\Schema\SchemaItem;
38
use GoetasWebservices\XML\XSDReader\Schema\Type\BaseComplexType;
39
use GoetasWebservices\XML\XSDReader\Schema\Type\ComplexType;
40
use GoetasWebservices\XML\XSDReader\Schema\Type\ComplexTypeSimpleContent;
41
use GoetasWebservices\XML\XSDReader\Schema\Type\SimpleType;
42
use GoetasWebservices\XML\XSDReader\Schema\Type\Type;
43
use GoetasWebservices\XML\XSDReader\Utils\UrlUtils;
44
45
class SchemaReader
46
{
47
    public const XSD_NS = 'http://www.w3.org/2001/XMLSchema';
48
49
    public const XML_NS = 'http://www.w3.org/XML/1998/namespace';
50
51
    /**
52
     * @var DocumentationReader
53
     */
54
    private $documentationReader;
55
56
    /**
57
     * @var Schema[]
58
     */
59
    private $loadedFiles = [];
60
61
    /**
62
     * @var Schema[][]
63
     */
64
    private $loadedSchemas = [];
65
66
    /**
67
     * @var string[]
68
     */
69
    protected $knownLocationSchemas = [
70
        'http://www.w3.org/2001/xml.xsd' => (
71
            __DIR__.'/Resources/xml.xsd'
72
        ),
73
        'http://www.w3.org/2001/XMLSchema.xsd' => (
74
            __DIR__.'/Resources/XMLSchema.xsd'
75
        ),
76
        'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' => (
77
            __DIR__.'/Resources/oasis-200401-wss-wssecurity-secext-1.0.xsd'
78
        ),
79
        'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd' => (
80
            __DIR__.'/Resources/oasis-200401-wss-wssecurity-utility-1.0.xsd'
81
        ),
82
        'https://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd' => (
83
            __DIR__.'/Resources/xmldsig-core-schema.xsd'
84
        ),
85
        'http://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd' => (
86
            __DIR__.'/Resources/xmldsig-core-schema.xsd'
87
        ),
88
    ];
89
90
    /**
91
     * @var string[]
92
     */
93
    protected $knownNamespaceSchemaLocations = [
94
        'http://www.w3.org/2000/09/xmldsig#' => (
95
            __DIR__.'/Resources/xmldsig-core-schema.xsd'
96
        ),
97
    ];
98
99
    /**
100 2
     * @var string[]
101
     */
102 2
    protected static $globalSchemaInfo = [
103
        self::XML_NS => 'http://www.w3.org/2001/xml.xsd',
104 2
        self::XSD_NS => 'http://www.w3.org/2001/XMLSchema.xsd',
105 1
    ];
106
107 2
    private function extractErrorMessage(): \Exception
108 2
    {
109
        $errors = [];
110 2
111
        foreach (libxml_get_errors() as $error) {
112
            $errors[] = sprintf("Error[%s] code %s: %s in '%s' at position %s:%s", $error->level, $error->code, trim($error->message), $error->file, $error->line, $error->column);
113 85
        }
114
        $e = new \Exception(implode('; ', $errors));
115 85
        libxml_use_internal_errors(false);
116 85
117
        return $e;
118 85
    }
119 85
120
    public function __construct(DocumentationReader $documentationReader = null)
121
    {
122
        if (null === $documentationReader) {
123
            $documentationReader = new StandardDocumentationReader();
124
        }
125
        $this->documentationReader = $documentationReader;
126
    }
127 1
128
    /**
129 1
     * Override remote location with a local file.
130 1
     *
131
     * @param string $remote remote schema URL
132
     * @param string $local  local file path
133
     */
134
    public function addKnownSchemaLocation(string $remote, string $local): void
135
    {
136
        $this->knownLocationSchemas[$remote] = $local;
137
    }
138
139 1
    /**
140
     * Specify schema location by namespace.
141 1
     * This can be used for schemas which import namespaces but do not specify schemaLocation attributes.
142 1
     *
143
     * @param string $namespace namespace
144 74
     * @param string $location  schema URL
145
     */
146
    public function addKnownNamespaceSchemaLocation(string $namespace, string $location): void
147
    {
148 74
        $this->knownNamespaceSchemaLocations[$namespace] = $location;
149 74
    }
150 74
151
    private function loadAttributeGroup(
152
        Schema $schema,
153 74
        DOMElement $node
154 74
    ): Closure {
155
        $attGroup = new AttributeGroup($schema, $node->getAttribute('name'));
156
        $attGroup->setDoc($this->getDocumentation($node));
157
        $schema->addAttributeGroup($attGroup);
158
159 74
        return function () use ($schema, $node, $attGroup): void {
160 74
            SchemaReader::againstDOMNodeList(
161
                $node,
162 74
                function (
163 74
                    DOMElement $node,
164 74
                    DOMElement $childNode
165 74
                ) use (
166 74
                    $schema,
167 74
                    $attGroup
168
                ): void {
169 74
                    switch ($childNode->localName) {
170 74
                        case 'attribute':
171 74
                            $attribute = $this->getAttributeFromAttributeOrRef(
172 1
                                $childNode,
173 1
                                $schema,
174 1
                                $node
175 1
                            );
176 1
                            $attGroup->addAttribute($attribute);
177
                            break;
178 1
                        case 'attributeGroup':
179
                            $this->findSomethingLikeAttributeGroup(
180 74
                                $schema,
181
                                $node,
182 74
                                $childNode,
183
                                $attGroup
184
                            );
185 74
                            break;
186
                    }
187
                }
188
            );
189
        };
190 74
    }
191 74
192
    private function getAttributeFromAttributeOrRef(
193
        DOMElement $childNode,
194
        Schema $schema,
195
        DOMElement $node
196 74
    ): AttributeItem {
197
        if ($childNode->hasAttribute('ref')) {
198
            $attribute = $this->findAttributeItem($schema, $node, $childNode->getAttribute('ref'));
199 74
        } else {
200
            /**
201
             * @var Attribute
202 74
             */
203
            $attribute = $this->loadAttribute($schema, $childNode);
204
        }
205
206 74
        return $attribute;
207 74
    }
208 74
209
    private function loadAttribute(Schema $schema, DOMElement $node): Attribute
210 74
    {
211 1
        $attribute = new Attribute($schema, $node->getAttribute('name'));
212
        $attribute->setDoc($this->getDocumentation($node));
213 74
        $this->fillItem($attribute, $node);
214 1
        $this->fillAttribute($attribute, $node);
215
216 74
        return $attribute;
217 74
    }
218
219
    private function fillAttribute(AttributeSingle $attribute, DOMElement $node): void
220 74
    {
221
        if ($node->hasAttribute('fixed')) {
222
            $attribute->setFixed($node->getAttribute('fixed'));
0 ignored issues
show
Bug introduced by
The method setFixed() does not exist on GoetasWebservices\XML\XS...tribute\AttributeSingle. Since it exists in all sub-types, consider adding an abstract or default implementation to GoetasWebservices\XML\XS...tribute\AttributeSingle. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

222
            $attribute->/** @scrutinizer ignore-call */ 
223
                        setFixed($node->getAttribute('fixed'));
Loading history...
223 74
        }
224
        if ($node->hasAttribute('default')) {
225
            $attribute->setDefault($node->getAttribute('default'));
0 ignored issues
show
Bug introduced by
The method setDefault() does not exist on GoetasWebservices\XML\XS...tribute\AttributeSingle. Since it exists in all sub-types, consider adding an abstract or default implementation to GoetasWebservices\XML\XS...tribute\AttributeSingle. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

225
            $attribute->/** @scrutinizer ignore-call */ 
226
                        setDefault($node->getAttribute('default'));
Loading history...
226
        }
227
        if ($node->hasAttribute('nillable')) {
228 74
            $attribute->setNil($node->getAttribute('nillable') == 'true');
229 74
        }
230 74
        if ($node->hasAttribute('form')) {
231 74
            $attribute->setQualified($node->getAttribute('form') == 'qualified');
232
        }
233 74
        if ($node->hasAttribute('use')) {
234 74
            $attribute->setUse($node->getAttribute('use'));
235 74
        }
236
    }
237
238
    private function loadAttributeOrElementDef(
239 74
        Schema $schema,
240 74
        DOMElement $node,
241
        bool $isAttribute
242
    ): Closure {
243 74
        $name = $node->getAttribute('name');
244
        if ($isAttribute) {
245 74
            $attribute = new AttributeDef($schema, $name);
246
            $attribute->setDoc($this->getDocumentation($node));
247
            $this->fillAttribute($attribute, $node);
248 74
            $schema->addAttribute($attribute);
249
        } else {
250 74
            $attribute = new ElementDef($schema, $name);
251
            $attribute->setDoc($this->getDocumentation($node));
252
            $this->fillElement($attribute, $node);
253
            $schema->addElement($attribute);
254
        }
255
256 74
        return function () use ($attribute, $node): void {
257
            $this->fillItem($attribute, $node);
258 74
        };
259 74
    }
260
261 74
    private function loadElementDef(Schema $schema, DOMElement $node): Closure
262 74
    {
263
        return $this->loadAttributeOrElementDef($schema, $node, false);
264
    }
265
266
    private function loadAttributeDef(Schema $schema, DOMElement $node): Closure
267 74
    {
268 74
        return $this->loadAttributeOrElementDef($schema, $node, true);
269
    }
270 74
271
    private function getDocumentation(DOMElement $node): string
272 74
    {
273 74
        return $this->documentationReader->get($node);
274 74
    }
275 74
276 74
    /**
277 74
     * @return Closure[]
278 74
     */
279 74
    private function schemaNode(Schema $schema, DOMElement $node, Schema $parent = null): array
280 74
    {
281 74
        $this->setSchemaThingsFromNode($schema, $node, $parent);
282 74
        $functions = [];
283 74
284 74
        self::againstDOMNodeList(
285 74
            $node,
286 74
            function (
287 74
                DOMElement $node,
288 74
                DOMElement $childNode
289 74
            ) use (
290 74
                $schema,
291 74
                &$functions
292 74
            ): void {
293 74
                $callback = null;
294 74
295
                switch ($childNode->localName) {
296
                    case 'attributeGroup':
297 74
                        $callback = $this->loadAttributeGroup($schema, $childNode);
298 74
                        break;
299
                    case 'include':
300 74
                    case 'import':
301
                        $callback = $this->loadImport($schema, $childNode);
302
                        break;
303 74
                    case 'element':
304
                        $callback = $this->loadElementDef($schema, $childNode);
305
                        break;
306 74
                    case 'attribute':
307
                        $callback = $this->loadAttributeDef($schema, $childNode);
308 74
                        break;
309 74
                    case 'group':
310
                        $callback = $this->loadGroup($schema, $childNode);
311 74
                        break;
312 74
                    case 'complexType':
313
                        $callback = $this->loadComplexType($schema, $childNode);
314 74
                        break;
315
                    case 'simpleType':
316
                        $callback = $this->loadSimpleType($schema, $childNode);
317 74
                        break;
318
                }
319 74
320 74
                if ($callback instanceof Closure) {
321
                    $functions[] = $callback;
322 74
                }
323
            }
324 74
        );
325
326 74
        return $functions;
327 74
    }
328 74
329 7
    private function loadGroupRef(Group $referenced, DOMElement $node): GroupRef
330
    {
331
        $ref = new GroupRef($referenced);
332 74
        $ref->setDoc($this->getDocumentation($node));
333
334 74
        self::maybeSetMax($ref, $node);
335
        self::maybeSetMin($ref, $node);
336 74
337 1
        return $ref;
338
    }
339 74
340
    private static function maybeSetMax(InterfaceSetMinMax $ref, DOMElement $node): void
341 74
    {
342
        if ($node->hasAttribute('maxOccurs')) {
343
            $ref->setMax($node->getAttribute('maxOccurs') == 'unbounded' ? -1 : (int) $node->getAttribute('maxOccurs'));
344
        }
345 74
    }
346 74
347 74
    private static function maybeSetMin(InterfaceSetMinMax $ref, DOMElement $node): void
348
    {
349 74
        if ($node->hasAttribute('minOccurs')) {
350 74
            $ref->setMin((int) $node->getAttribute('minOccurs'));
351
            if ($ref->getMin() > $ref->getMax() && $ref->getMax() !== -1) {
352
                $ref->setMax($ref->getMin());
353 74
            }
354 74
        }
355
    }
356 74
357 74
    private static function maybeSetFixed(InterfaceSetFixed $ref, DOMElement $node): void
358
    {
359 74
        if ($node->hasAttribute('fixed')) {
360 74
            $ref->setFixed($node->getAttribute('fixed'));
361
        }
362
    }
363
364
    private static function maybeSetDefault(InterfaceSetDefault $ref, DOMElement $node): void
365 74
    {
366 74
        if ($node->hasAttribute('default')) {
367 74
            $ref->setDefault($node->getAttribute('default'));
368
        }
369 74
    }
370 74
371 74
    private function loadSequence(ElementContainer $elementContainer, DOMElement $node, int $max = null, int $min = null): void
372 74
    {
373 74
        $max =
374 74
            (
375
                (is_int($max) && (bool) $max) ||
376 74
                $node->getAttribute('maxOccurs') == 'unbounded' ||
377
                $node->getAttribute('maxOccurs') > 1
378 74
            )
379
                ? 2
380 74
                : null;
381
        $min =
382
            (
383
                $min === null &&
384
                !$node->hasAttribute('minOccurs')
385
            )
386
                ? null
387 74
                : (int) max((int) $min, $node->getAttribute('minOccurs'));
388 74
389 74
        self::againstDOMNodeList(
390 74
            $node,
391 74
            function (
392 74
                DOMElement $node,
393 74
                DOMElement $childNode
394 74
            ) use (
395 74
                $elementContainer,
396
                $max,
397 74
                $min
398 74
            ): void {
399 74
                $this->loadSequenceChildNode(
400 74
                    $elementContainer,
401 74
                    $node,
402 74
                    $childNode,
403 74
                    $max,
404 74
                    $min
405
                );
406 74
            }
407 74
        );
408 74
    }
409 74
410 74
    private function loadSequenceChildNode(
411 74
        ElementContainer $elementContainer,
412 74
        DOMElement $node,
413
        DOMElement $childNode,
414 74
        ?int $max,
415
        ?int $min = null
416 74
    ): void {
417
        switch ($childNode->localName) {
418 74
            case 'sequence':
419
            case 'choice':
420
            case 'all':
421
                $this->loadSequence(
422
                    $elementContainer,
423
                    $childNode,
424
                    $max,
425 74
                    $min
426 74
                );
427 74
                break;
428 74
            case 'element':
429
                $this->loadSequenceChildNodeLoadElement(
430 74
                    $elementContainer,
431 74
                    $node,
432
                    $childNode,
433 74
                    $max,
434 74
                    $min
435
                );
436 74
                break;
437 74
            case 'group':
438
                $this->addGroupAsElement(
439
                    $elementContainer->getSchema(),
440 74
                    $node,
441
                    $childNode,
442
                    $elementContainer
443 74
                );
444 74
                break;
445
        }
446
    }
447 74
448 74
    private function loadSequenceChildNodeLoadElement(
449 74
        ElementContainer $elementContainer,
450
        DOMElement $node,
451
        DOMElement $childNode,
452
        ?int $max,
453 74
        ?int $min
454 74
    ): void {
455
        if ($childNode->hasAttribute('ref')) {
456
            $elementDef = $this->findElement($elementContainer->getSchema(), $node, $childNode->getAttribute('ref'));
457 74
            $element = new ElementRef($elementDef);
458
            $element->setDoc($this->getDocumentation($childNode));
459
            $this->fillElement($element, $childNode);
460
        } else {
461
            $element = $this->loadElement($elementContainer->getSchema(), $childNode);
462
        }
463 74
464
        if ($min !== null) {
465 74
            $element->setMin($min);
466 74
        }
467
468 74
        if ($max > 1) {
469
            /*
470
            * although one might think the typecast is not needed with $max being `? int $max` after passing > 1,
471
            * phpstan@a4f89fa still thinks it's possibly null.
472
            * see https://github.com/phpstan/phpstan/issues/577 for related issue
473
            */
474 74
            $element->setMax((int) $max);
475 74
        }
476 74
        $elementContainer->addElement($element);
477 74
    }
478
479
    private function addGroupAsElement(
480 74
        Schema $schema,
481 74
        DOMElement $node,
482 74
        DOMElement $childNode,
483
        ElementContainer $elementContainer
484 74
    ): void {
485
        $referencedGroup = $this->findGroup(
486 74
            $schema,
487 74
            $node,
488 74
            $childNode->getAttribute('ref')
489
        );
490 74
491 1
        $group = $this->loadGroupRef($referencedGroup, $childNode);
492
        $elementContainer->addElement($group);
493 1
    }
494 1
495
    private function loadGroup(Schema $schema, DOMElement $node): Closure
496 1
    {
497 1
        $group = new Group($schema, $node->getAttribute('name'));
498
        $group->setDoc($this->getDocumentation($node));
499
        $groupOriginal = $group;
500
501 74
        if ($node->hasAttribute('maxOccurs') || $node->hasAttribute('minOccurs')) {
502
            $group = $this->loadGroupRef($group, $node);
503
        }
504 74
505 74
        $schema->addGroup($group);
506
507 74
        return function () use ($groupOriginal, $node): void {
508 74
            static::againstDOMNodeList(
509 74
                $node,
510 74
                function (DOMelement $node, DOMElement $childNode) use ($groupOriginal): void {
511 74
                    switch ($childNode->localName) {
512 74
                        case 'sequence':
513
                        case 'choice':
514 74
                        case 'all':
515
                            $this->loadSequence($groupOriginal, $childNode);
516 74
                            break;
517
                    }
518
                }
519 74
            );
520
        };
521
    }
522
523
    private function loadComplexType(Schema $schema, DOMElement $node, Closure $callback = null): Closure
524 74
    {
525
        /**
526 74
         * @var bool
527 74
         */
528
        $isSimple = false;
529
530
        self::againstDOMNodeList(
531
            $node,
532 74
            function (
533
                DOMElement $node,
534 74
                DOMElement $childNode
535 1
            ) use (
536
                &$isSimple
537 74
            ): void {
538 2
                if ($isSimple) {
539
                    return;
540 74
                }
541
                if ($childNode->localName === 'simpleContent') {
542
                    $isSimple = true;
543 74
                }
544
            }
545 74
        );
546 74
547 74
        $type = $isSimple ? new ComplexTypeSimpleContent($schema, $node->getAttribute('name')) : new ComplexType($schema, $node->getAttribute('name'));
548
549
        $type->setDoc($this->getDocumentation($node));
550
        if ($node->getAttribute('name')) {
551 74
            $schema->addType($type);
552
        }
553 74
554 74
        return function () use ($type, $node, $schema, $callback): void {
555
            $this->fillTypeNode($type, $node, true);
556
557
            self::againstDOMNodeList(
558
                $node,
559 74
                function (
560 74
                    DOMElement $node,
561
                    DOMElement $childNode
562 74
                ) use (
563 74
                    $schema,
564 74
                    $type
565 74
                ): void {
566 74
                    $this->loadComplexTypeFromChildNode(
567
                        $type,
568 74
                        $node,
569
                        $childNode,
570
                        $schema
571 74
                    );
572 74
                }
573
            );
574 74
575
            if ($callback instanceof Closure) {
576
                call_user_func($callback, $type);
577 74
            }
578
        };
579
    }
580
581
    private function loadComplexTypeFromChildNode(
582
        BaseComplexType $type,
583 74
        DOMElement $node,
584 74
        DOMElement $childNode,
585 74
        Schema $schema
586 74
    ): void {
587 74
        switch ($childNode->localName) {
588 74
            case 'sequence':
589 74
            case 'choice':
590 74
            case 'all':
591
                if ($type instanceof ElementContainer) {
592
                    $this->loadSequence(
593 74
                        $type,
594 74
                        $childNode
595 74
                    );
596 74
                }
597 74
                break;
598 74
            case 'attribute':
599 74
                $this->addAttributeFromAttributeOrRef(
600
                    $type,
601 74
                    $childNode,
602 74
                    $schema,
603 2
                    $node
604 2
                );
605 2
                break;
606 2
            case 'attributeGroup':
607 2
                $this->findSomethingLikeAttributeGroup(
608
                    $schema,
609 2
                    $node,
610 74
                    $childNode,
611
                    $type
612 1
                );
613
                break;
614 1
            case 'group':
615 1
                if (
616 1
                    $type instanceof ComplexType
617 1
                ) {
618 1
                    $this->addGroupAsElement(
619
                        $schema,
620
                        $node,
621 1
                        $childNode,
622
                        $type
623 74
                    );
624
                }
625 74
                break;
626
        }
627 74
    }
628 74
629 74
    private function loadSimpleType(Schema $schema, DOMElement $node, Closure $callback = null): Closure
630 74
    {
631
        $type = new SimpleType($schema, $node->getAttribute('name'));
632
        $type->setDoc($this->getDocumentation($node));
633
        if ($node->getAttribute('name')) {
634 74
            $schema->addType($type);
635
        }
636 74
637 74
        return function () use ($type, $node, $callback): void {
638
            $this->fillTypeNode($type, $node, true);
639 74
640 74
            self::againstDOMNodeList(
641 74
                $node,
642 74
                function (DOMElement $node, DOMElement $childNode) use ($type): void {
643 74
                    switch ($childNode->localName) {
644 74
                        case 'union':
645 74
                            $this->loadUnion($type, $childNode);
646
                            break;
647 74
                        case 'list':
648
                            $this->loadList($type, $childNode);
649
                            break;
650 74
                    }
651 74
                }
652
            );
653 74
654
            if ($callback instanceof Closure) {
655
                call_user_func($callback, $type);
656 74
            }
657
        };
658 74
    }
659
660
    private function loadList(SimpleType $type, DOMElement $node): void
661
    {
662 74
        if ($node->hasAttribute('itemType')) {
663 74
            /**
664
             * @var SimpleType
665 74
             */
666 74
            $listType = $this->findSomeType($type, $node, 'itemType');
667
            $type->setList($listType);
668
        } else {
669
            self::againstDOMNodeList(
670
                $node,
671 74
                function (
672
                    DOMElement $node,
673 74
                    DOMElement $childNode
674 74
                ) use (
675 74
                    $type
676
                ): void {
677 74
                    $this->loadTypeWithCallback(
678 74
                        $type->getSchema(),
679
                        $childNode,
680 74
                        function (SimpleType $list) use ($type): void {
681
                            $type->setList($list);
682
                        }
683 74
                    );
684
                }
685 74
            );
686
        }
687
    }
688
689
    private function findSomeType(
690 74
        SchemaItem $fromThis,
691 74
        DOMElement $node,
692 74
        string $attributeName
693 74
    ): SchemaItem {
694
        return $this->findSomeTypeFromAttribute(
695
            $fromThis,
696
            $node,
697 74
            $node->getAttribute($attributeName)
698
        );
699
    }
700
701
    private function findSomeTypeFromAttribute(
702 74
        SchemaItem $fromThis,
703 74
        DOMElement $node,
704 74
        string $attributeName
705 74
    ): SchemaItem {
706
        $out = $this->findType(
707
            $fromThis->getSchema(),
708 74
            $node,
709
            $attributeName
710
        );
711 74
712
        return $out;
713 74
    }
714 74
715 74
    private function loadUnion(SimpleType $type, DOMElement $node): void
716
    {
717
        if ($node->hasAttribute('memberTypes')) {
718
            $types = preg_split('/\s+/', $node->getAttribute('memberTypes'));
719 74
            foreach ($types as $typeName) {
720 74
                /**
721 74
                 * @var SimpleType
722 74
                 */
723
                $unionType = $this->findSomeTypeFromAttribute(
724 74
                    $type,
725
                    $node,
726
                    $typeName
727 74
                );
728 74
                $type->addUnion($unionType);
729
            }
730
        }
731
        self::againstDOMNodeList(
732
            $node,
733 74
            function (
734
                DOMElement $node,
735 74
                DOMElement $childNode
736 74
            ) use (
737 74
                $type
738
            ): void {
739 74
                $this->loadTypeWithCallback(
740 74
                    $type->getSchema(),
741
                    $childNode,
742 74
                    function (SimpleType $unType) use ($type): void {
743
                        $type->addUnion($unType);
744 74
                    }
745
                );
746 74
            }
747
        );
748 74
    }
749 74
750
    private function fillTypeNode(Type $type, DOMElement $node, bool $checkAbstract = false): void
751
    {
752 74
        if ($checkAbstract) {
753 74
            $type->setAbstract($node->getAttribute('abstract') === 'true' || $node->getAttribute('abstract') === '1');
754
        }
755 74
756 74
        self::againstDOMNodeList(
757 74
            $node,
758 74
            function (DOMElement $node, DOMElement $childNode) use ($type): void {
759 74
                switch ($childNode->localName) {
760 74
                    case 'restriction':
761 74
                        $this->loadRestriction($type, $childNode);
762
                        break;
763 74
                    case 'extension':
764 74
                        if ($type instanceof BaseComplexType) {
765 74
                            $this->loadExtension($type, $childNode);
766 74
                        }
767 74
                        break;
768
                    case 'simpleContent':
769 74
                    case 'complexContent':
770
                        $this->fillTypeNode($type, $childNode);
771 74
                        break;
772
                }
773 74
            }
774
        );
775 74
    }
776 74
777
    private function loadExtension(BaseComplexType $type, DOMElement $node): void
778 74
    {
779 74
        $extension = new Extension();
780 74
        $type->setExtension($extension);
781 74
782 74
        if ($node->hasAttribute('base')) {
783
            $this->findAndSetSomeBase($type, $extension, $node);
784
        }
785 74
        $this->loadExtensionChildNodes($type, $node);
786 74
    }
787
788 74
    private function findAndSetSomeBase(
789
        Type $type,
790
        Base $setBaseOnThis,
791
        DOMElement $node
792
    ): void {
793
        /**
794
         * @var Type
795
         */
796 74
        $parent = $this->findSomeType($type, $node, 'base');
797 74
        $setBaseOnThis->setBase($parent);
798 74
    }
799
800 74
    private function loadExtensionChildNodes(
801
        BaseComplexType $type,
802
        DOMElement $node
803
    ): void {
804 74
        self::againstDOMNodeList(
805 74
            $node,
806
            function (
807
                DOMElement $node,
808
                DOMElement $childNode
809
            ) use (
810 74
                $type
811
            ): void {
812 74
                switch ($childNode->localName) {
813 74
                    case 'sequence':
814 74
                    case 'choice':
815 74
                    case 'all':
816 74
                        if ($type instanceof ElementContainer) {
817 74
                            $this->loadSequence($type, $childNode);
818 74
                        }
819 74
                        break;
820
                }
821
                $this->loadChildAttributesAndAttributeGroups($type, $node, $childNode);
822 74
            }
823 74
        );
824 74
    }
825 74
826 74
    private function loadChildAttributesAndAttributeGroups(
827 74
        BaseComplexType $type,
828 74
        DOMElement $node,
829
        DOMElement $childNode
830 74
    ): void {
831 74
        switch ($childNode->localName) {
832 74
            case 'attribute':
833 74
                $this->addAttributeFromAttributeOrRef(
834 74
                    $type,
835 74
                    $childNode,
836 74
                    $type->getSchema(),
837
                    $node
838 74
                );
839
                break;
840 74
            case 'attributeGroup':
841
                $this->findSomethingLikeAttributeGroup(
842 74
                    $type->getSchema(),
843
                    $node,
844 74
                    $childNode,
845
                    $type
846 74
                );
847 74
                break;
848 74
        }
849 74
    }
850
851 74
    private function loadRestriction(Type $type, DOMElement $node): void
852 74
    {
853
        $restriction = new Restriction();
854
        $type->setRestriction($restriction);
855
        if ($node->hasAttribute('base')) {
856
            $this->findAndSetSomeBase($type, $restriction, $node);
857 74
        } else {
858 74
            self::againstDOMNodeList(
859
                $node,
860 74
                function (
861 74
                    DOMElement $node,
862 74
                    DOMElement $childNode
863
                ) use (
864 74
                    $type,
865 74
                    $restriction
866
                ): void {
867 74
                    $this->loadTypeWithCallback(
868
                        $type->getSchema(),
869
                        $childNode,
870 74
                        function (Type $restType) use ($restriction): void {
871 74
                            $restriction->setBase($restType);
872
                        }
873
                    );
874
                }
875
            );
876 74
        }
877
        $this->loadRestrictionChildNodes($type, $restriction, $node);
878
    }
879 74
880 74
    private function loadRestrictionChildNodes(
881
        Type $type,
882 74
        Restriction $restriction,
883
        DOMElement $node
884
    ): void {
885
        self::againstDOMNodeList(
886
            $node,
887
            function (
888
                DOMElement $node,
889
                DOMElement $childNode
890
            ) use (
891
                $type,
892
                $restriction
893
            ): void {
894
                if ($type instanceof BaseComplexType) {
895 74
                    $this->loadChildAttributesAndAttributeGroups($type, $node, $childNode);
896
                }
897
                if (
898 74
                in_array(
899 74
                    $childNode->localName,
900
                    [
901 74
                        'enumeration',
902 74
                        'pattern',
903
                        'length',
904
                        'minLength',
905
                        'maxLength',
906 74
                        'minInclusive',
907
                        'maxInclusive',
908 74
                        'minExclusive',
909
                        'maxExclusive',
910
                        'fractionDigits',
911
                        'totalDigits',
912
                        'whiteSpace',
913 74
                    ],
914
                    true
915 74
                )
916 74
                ) {
917 74
                    $restriction->addCheck(
918 74
                        $childNode->localName,
919
                        [
920
                            'value' => $childNode->getAttribute('value'),
921
                            'doc' => $this->getDocumentation($childNode),
922
                        ]
923
                    );
924 74
                }
925
            }
926
        );
927 74
    }
928 74
929 74
    /**
930
     * @return mixed[]
931
     */
932
    private static function splitParts(DOMElement $node, string $typeName): array
933 74
    {
934
        $prefix = null;
935 74
        $name = $typeName;
936
        if (strpos($typeName, ':') !== false) {
937
            [$prefix, $name] = explode(':', $typeName);
938
        }
939
940 74
        /**
941
         * @psalm-suppress PossiblyNullArgument
942
         */
943
        $namespace = $node->lookupNamespaceUri($prefix);
944
945
        return [
946 74
            $name,
947
            $namespace,
948 74
            $prefix,
949
        ];
950
    }
951
952
    private function findAttributeItem(Schema $schema, DOMElement $node, string $typeName): AttributeItem
953
    {
954 74
        [$name, $namespace] = static::splitParts($node, $typeName);
955
956 74
        /**
957
         * @var string|null $namespace
958
         */
959
        $namespace = $namespace ?: $schema->getTargetNamespace();
960
961 74
        try {
962
            /**
963
             * @var AttributeItem $out
964
             */
965
            $out = $schema->findAttribute((string) $name, $namespace);
966
967 74
            return $out;
968
        } catch (TypeNotFoundException $e) {
969 74
            throw new TypeException(sprintf("Can't find %s named {%s}#%s, at line %d in %s ", 'attribute', $namespace, $name, $node->getLineNo(), $node->ownerDocument->documentURI), 0, $e);
970
        }
971
    }
972
973
    private function findAttributeGroup(Schema $schema, DOMElement $node, string $typeName): AttributeGroup
974
    {
975 74
        [$name, $namespace] = static::splitParts($node, $typeName);
976
977 74
        /**
978
         * @var string|null $namespace
979
         */
980
        $namespace = $namespace ?: $schema->getTargetNamespace();
981
982 74
        try {
983
            /**
984
             * @var AttributeGroup $out
985 74
             */
986
            $out = $schema->findAttributeGroup((string) $name, $namespace);
987
988
            return $out;
989
        } catch (TypeNotFoundException $e) {
990
            throw new TypeException(sprintf("Can't find %s named {%s}#%s, at line %d in %s ", 'attributegroup', $namespace, $name, $node->getLineNo(), $node->ownerDocument->documentURI), 0, $e);
991 74
        }
992
    }
993 74
994
    private function findElement(Schema $schema, DOMElement $node, string $typeName): ElementDef
995
    {
996
        [$name, $namespace] = static::splitParts($node, $typeName);
997
998 74
        /**
999
         * @var string|null $namespace
1000
         */
1001
        $namespace = $namespace ?: $schema->getTargetNamespace();
1002
1003
        try {
1004 74
            return $schema->findElement((string) $name, $namespace);
1005
        } catch (TypeNotFoundException $e) {
1006 74
            throw new TypeException(sprintf("Can't find %s named {%s}#%s, at line %d in %s ", 'element', $namespace, $name, $node->getLineNo(), $node->ownerDocument->documentURI), 0, $e);
1007
        }
1008
    }
1009
1010
    private function findGroup(Schema $schema, DOMElement $node, string $typeName): Group
1011
    {
1012 74
        [$name, $namespace] = static::splitParts($node, $typeName);
1013
1014 74
        /**
1015
         * @var string|null $namespace
1016
         */
1017
        $namespace = $namespace ?: $schema->getTargetNamespace();
1018
1019 74
        try {
1020
            /**
1021
             * @var Group $out
1022
             */
1023 74
            $out = $schema->findGroup((string) $name, $namespace);
1024 1
1025 1
            return $out;
1026
        } catch (TypeNotFoundException $e) {
1027 74
            throw new TypeException(sprintf("Can't find %s named {%s}#%s, at line %d in %s ", 'group', $namespace, $name, $node->getLineNo(), $node->ownerDocument->documentURI), 0, $e);
1028
        }
1029 74
    }
1030 74
1031 74
    private function findType(Schema $schema, DOMElement $node, string $typeName): SchemaItem
1032 74
    {
1033
        [$name, $namespace] = static::splitParts($node, $typeName);
1034
1035
        /**
1036
         * @var string|null $namespace
1037
         */
1038
        $namespace = $namespace ?: $schema->getTargetNamespace();
1039 74
1040
        $tryFindType = static function (Schema $schema, string $name, ?string $namespace): ?SchemaItem {
1041 74
            try {
1042
                return $schema->findType($name, $namespace);
1043
            } catch (TypeNotFoundException $e) {
1044 74
                return null;
1045
            }
1046
        };
1047
1048
        $interestingSchemas = array_merge([$schema], $this->loadedSchemas[$namespace] ?? []);
1049 74
        foreach ($interestingSchemas as $interestingSchema) {
1050 74
            if ($result = $tryFindType($interestingSchema, $name, $namespace)) {
1051 74
                return $result;
1052
            }
1053
        }
1054
1055
        throw new TypeException(sprintf("Can't find %s named {%s}#%s, at line %d in %s ", 'type', $namespace, $name, $node->getLineNo(), $node->ownerDocument->documentURI));
1056 74
    }
1057 74
1058
    private function fillItem(Item $element, DOMElement $node): void
1059
    {
1060 74
        /**
1061 74
         * @var bool
1062 74
         */
1063
        $skip = false;
1064 74
        self::againstDOMNodeList(
1065
            $node,
1066
            function (
1067 74
                DOMElement $node,
1068
                DOMElement $childNode
1069
            ) use (
1070 74
                $element,
1071 74
                &$skip
1072 74
            ): void {
1073
                if (
1074 74
                    !$skip &&
1075 74
                    in_array(
1076
                        $childNode->localName,
1077 74
                        [
1078
                            'complexType',
1079 74
                            'simpleType',
1080
                        ],
1081 74
                        true
1082 74
                    )
1083
                ) {
1084 74
                    $this->loadTypeWithCallback(
1085 74
                        $element->getSchema(),
1086
                        $childNode,
1087 74
                        function (Type $type) use ($element): void {
1088
                            $element->setType($type);
1089 74
                        }
1090
                    );
1091
                    $skip = true;
1092
                }
1093 74
            }
1094
        );
1095
        if ($skip) {
1096
            return;
1097
        }
1098 74
        $this->fillItemNonLocalType($element, $node);
1099 74
    }
1100 74
1101 74
    private function fillItemNonLocalType(Item $element, DOMElement $node): void
1102
    {
1103
        if ($node->getAttribute('type')) {
1104
            /**
1105 74
             * @var Type
1106 74
             */
1107
            $type = $this->findSomeType($element, $node, 'type');
1108 74
        } else {
1109
            /**
1110
             * @var Type
1111
             */
1112 74
            $type = $this->findSomeTypeFromAttribute(
1113 74
                $element,
1114 74
                $node,
1115 1
                ($node->lookupPrefix(self::XSD_NS).':anyType')
1116
            );
1117
        }
1118
1119 74
        $element->setType($type);
1120
    }
1121 3
1122 3
    private function loadImport(
1123 3
        Schema $schema,
1124
        DOMElement $node
1125
    ): Closure {
1126 3
        $namespace = $node->getAttribute('namespace');
1127 74
        $schemaLocation = $node->getAttribute('schemaLocation');
1128 1
        if (!$schemaLocation && isset($this->knownNamespaceSchemaLocations[$namespace])) {
1129 1
            $schemaLocation = $this->knownNamespaceSchemaLocations[$namespace];
1130
        }
1131
1132
        // postpone schema loading
1133 74
        if ($namespace && !$schemaLocation && !isset(self::$globalSchemaInfo[$namespace])) {
1134 74
            return function () use ($schema, $namespace): void {
1135
                if (!empty($this->loadedSchemas[$namespace])) {
1136 74
                    foreach ($this->loadedSchemas[$namespace] as $s) {
1137 74
                        $schema->addSchema($s, $namespace);
1138
                    }
1139
                }
1140 74
            };
1141
        } elseif ($namespace && !$schemaLocation && !empty($this->loadedSchemas[$namespace])) {
1142
            foreach ($this->loadedSchemas[$namespace] as $s) {
1143 3
                $schema->addSchema($s, $namespace);
1144
            }
1145
        }
1146 3
1147
        $base = urldecode($node->ownerDocument->documentURI);
0 ignored issues
show
Bug introduced by
It seems like $node->ownerDocument->documentURI can also be of type null; however, parameter $string of urldecode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1147
        $base = urldecode(/** @scrutinizer ignore-type */ $node->ownerDocument->documentURI);
Loading history...
1148
        $file = UrlUtils::resolveRelativeUrl($base, $schemaLocation);
1149
1150 3
        if (isset($this->loadedFiles[$file])) {
1151 2
            $schema->addSchema($this->loadedFiles[$file]);
1152 2
1153 2
            return function (): void {
1154
            };
1155 1
        }
1156
1157
        return $this->loadImportFresh($namespace, $schema, $file);
1158 3
    }
1159
1160
    private function createOrUseSchemaForNs(
1161 3
        Schema $schema,
1162
        string $namespace
1163
    ): Schema {
1164
        if (('' !== trim($namespace))) {
1165
            $newSchema = new Schema();
1166
            $newSchema->addSchema($this->getGlobalSchema());
1167 3
            $schema->addSchema($newSchema);
1168 3
        } else {
1169 3
            $newSchema = $schema;
1170
        }
1171
1172 3
        return $newSchema;
1173
    }
1174 3
1175
    private function loadImportFresh(
1176 3
        string $namespace,
1177
        Schema $schema,
1178 3
        string $file
1179 3
    ): Closure {
1180
        return function () use ($namespace, $schema, $file): void {
1181 3
            $dom = $this->getDOM(
1182
                $this->knownLocationSchemas[$file]
1183
                    ?? $file
1184
            );
1185
1186
            $schemaNew = $this->createOrUseSchemaForNs($schema, $namespace);
1187
1188
            $this->setLoadedFile($file, $schemaNew);
1189 74
1190
            $callbacks = $this->schemaNode($schemaNew, $dom->documentElement, $schema);
1191 74
1192 74
            foreach ($callbacks as $callback) {
1193 74
                $callback();
1194
            }
1195
        };
1196
    }
1197 74
1198 74
    /**
1199 74
     * @var Schema|null
1200 74
     */
1201
    protected $globalSchema;
1202 74
1203 74
    public function getGlobalSchema(): Schema
1204 74
    {
1205
        if (!($this->globalSchema instanceof Schema)) {
1206 74
            $callbacks = [];
1207 74
            $globalSchemas = [];
1208
            /**
1209
             * @var string $namespace
1210 74
             */
1211 74
            foreach (self::$globalSchemaInfo as $namespace => $uri) {
1212
                $this->setLoadedFile(
1213 74
                    $uri,
1214 74
                    $globalSchemas[$namespace] = $schema = new Schema()
1215 74
                );
1216
                $this->setLoadedSchema($namespace, $schema);
1217 74
                if ($namespace === self::XSD_NS) {
1218 74
                    $this->globalSchema = $schema;
1219 74
                }
1220
                $xml = $this->getDOM($this->knownLocationSchemas[$uri]);
1221
                $callbacks = array_merge($callbacks, $this->schemaNode($schema, $xml->documentElement));
1222
            }
1223
1224
            $globalSchemas[(string) static::XSD_NS]->addType(new SimpleType($globalSchemas[(string) static::XSD_NS], 'anySimpleType'));
1225 74
            $globalSchemas[(string) static::XSD_NS]->addType(new SimpleType($globalSchemas[(string) static::XSD_NS], 'anyType'));
1226 74
1227
            $globalSchemas[(string) static::XML_NS]->addSchema(
1228
                $globalSchemas[(string) static::XSD_NS],
1229
                (string) static::XSD_NS
1230 74
            );
1231
            $globalSchemas[(string) static::XSD_NS]->addSchema(
1232
                $globalSchemas[(string) static::XML_NS],
1233
                (string) static::XML_NS
1234 74
            );
1235
1236
            /**
1237
             * @var Closure $callback
1238
             */
1239
            foreach ($callbacks as $callback) {
1240 2
                $callback();
1241
            }
1242 2
        }
1243 2
1244
        if (!($this->globalSchema instanceof Schema)) {
1245 2
            throw new TypeException('Global schema not discovered');
1246 2
        }
1247
1248
        return $this->globalSchema;
1249 2
    }
1250 2
1251 2
    /**
1252 2
     * @param DOMNode[] $nodes
1253 2
     */
1254
    public function readNodes(array $nodes, string $file = null): Schema
1255 2
    {
1256
        $rootSchema = new Schema();
1257 2
        $rootSchema->addSchema($this->getGlobalSchema());
1258
1259 2
        if ($file !== null) {
1260 2
            $this->setLoadedFile($file, $rootSchema);
1261
        }
1262
1263
        $all = [];
1264 2
        foreach ($nodes as $k => $node) {
1265 2
            if (($node instanceof \DOMElement) && $node->namespaceURI === self::XSD_NS && $node->localName == 'schema') {
1266
                $holderSchema = new Schema();
1267
                $holderSchema->addSchema($this->getGlobalSchema());
1268 2
1269
                $this->setLoadedSchemaFromElement($node, $holderSchema);
1270
1271 72
                $rootSchema->addSchema($holderSchema);
1272
1273 72
                $callbacks = $this->schemaNode($holderSchema, $node);
1274 72
                $all = array_merge($callbacks, $all);
1275
            }
1276 72
        }
1277 71
1278
        foreach ($all as $callback) {
1279
            call_user_func($callback);
1280 72
        }
1281
1282 72
        return $rootSchema;
1283
    }
1284 72
1285 66
    public function readNode(DOMElement $node, string $file = null): Schema
1286
    {
1287
        $rootSchema = new Schema();
1288 72
        $rootSchema->addSchema($this->getGlobalSchema());
1289
1290
        if ($file !== null) {
1291
            $this->setLoadedFile($file, $rootSchema);
1292
        }
1293
1294 68
        $this->setLoadedSchemaFromElement($node, $rootSchema);
1295
1296 68
        $callbacks = $this->schemaNode($rootSchema, $node);
1297 68
1298 68
        foreach ($callbacks as $callback) {
1299 1
            call_user_func($callback);
1300
        }
1301 67
1302 67
        return $rootSchema;
1303
    }
1304 67
1305
    /**
1306
     * @throws IOException
1307
     */
1308
    public function readString(string $content, string $file = 'schema.xsd'): Schema
1309
    {
1310 5
        $xml = new DOMDocument('1.0', 'UTF-8');
1311
        libxml_use_internal_errors(true);
1312 5
        if (!@$xml->loadXML($content)) {
1313
            throw new IOException("Can't load the schema", 0, $this->extractErrorMessage());
1314 4
        }
1315
        libxml_use_internal_errors(false);
1316
        $xml->documentURI = $file;
1317
1318
        return $this->readNode($xml->documentElement, $file);
1319
    }
1320 75
1321
    /**
1322 75
     * @throws IOException
1323 75
     */
1324 75
    public function readFile(string $file): Schema
1325 1
    {
1326 1
        $xml = $this->getDOM($file);
1327
1328 74
        return $this->readNode($xml->documentElement, $file);
1329
    }
1330 74
1331
    /**
1332
     * @throws IOException
1333 74
     */
1334
    private function getDOM(string $file): DOMDocument
1335
    {
1336
        $xml = new DOMDocument('1.0', 'UTF-8');
1337 74
        libxml_use_internal_errors(true);
1338 74
        if (!@$xml->load($file)) {
1339
            libxml_use_internal_errors(false);
1340
            throw new IOException("Can't load the file '$file'", 0, $this->extractErrorMessage());
1341
        }
1342 74
        libxml_use_internal_errors(false);
1343
1344 74
        return $xml;
1345 74
    }
1346 74
1347 74
    private static function againstDOMNodeList(
1348
        DOMElement $node,
1349
        Closure $againstNodeList
1350
    ): void {
1351 74
        $limit = $node->childNodes->length;
1352
        for ($i = 0; $i < $limit; ++$i) {
1353 74
            /**
1354
             * @var DOMNode
1355
             */
1356
            $childNode = $node->childNodes->item($i);
1357
1358
            if ($childNode instanceof DOMElement) {
1359
                $againstNodeList(
1360
                    $node,
1361 74
                    $childNode
1362
                );
1363 74
            }
1364 74
        }
1365 74
    }
1366 74
1367 74
    private function loadTypeWithCallback(
1368 74
        Schema $schema,
1369 74
        DOMElement $childNode,
1370
        Closure $callback
1371
    ): void {
1372 74
        /**
1373 74
         * @var Closure|null $func
1374
         */
1375 74
        $func = null;
1376
1377 74
        switch ($childNode->localName) {
1378
            case 'complexType':
1379
                $func = $this->loadComplexType($schema, $childNode, $callback);
1380
                break;
1381 74
            case 'simpleType':
1382 74
                $func = $this->loadSimpleType($schema, $childNode, $callback);
1383
                break;
1384 74
        }
1385
1386 74
        if ($func instanceof Closure) {
1387 74
            call_user_func($func);
1388 74
        }
1389
    }
1390 74
1391 74
    private function loadElement(Schema $schema, DOMElement $node): Element
1392
    {
1393 74
        $element = new Element($schema, $node->getAttribute('name'));
1394 74
        $element->setDoc($this->getDocumentation($node));
1395
        $this->fillItem($element, $node);
1396
        $this->fillElement($element, $node);
1397 74
1398 4
        return $element;
1399
    }
1400 74
1401 5
    private function fillElement(AbstractElementSingle $element, DOMElement $node): void
1402
    {
1403
        self::maybeSetMax($element, $node);
1404 74
        self::maybeSetMin($element, $node);
1405
        self::maybeSetFixed($element, $node);
1406 74
        self::maybeSetDefault($element, $node);
1407 74
1408
        $xp = new \DOMXPath($node->ownerDocument);
0 ignored issues
show
Bug introduced by
It seems like $node->ownerDocument can also be of type null; however, parameter $document of DOMXPath::__construct() does only seem to accept DOMDocument, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1408
        $xp = new \DOMXPath(/** @scrutinizer ignore-type */ $node->ownerDocument);
Loading history...
1409
        $xp->registerNamespace('xs', self::XSD_NS);
1410 74
1411
        if ($xp->query('ancestor::xs:choice', $node)->length) {
1412
            $element->setMin(0);
1413 74
        }
1414
1415
        if ($node->hasAttribute('nillable')) {
1416
            $element->setNil($node->getAttribute('nillable') == 'true');
1417
        }
1418
        if ($node->hasAttribute('form')) {
1419 74
            $element->setQualified($node->getAttribute('form') == 'qualified');
1420 74
        }
1421 74
1422 74
        $parentNode = $node->parentNode;
1423
        if ($parentNode->localName != 'schema' || $parentNode->namespaceURI != self::XSD_NS) {
1424
            $element->setLocal(true);
1425 74
        }
1426 74
    }
1427
1428 74
    private function addAttributeFromAttributeOrRef(
1429
        BaseComplexType $type,
1430
        DOMElement $childNode,
1431
        Schema $schema,
1432
        DOMElement $node
1433
    ): void {
1434 74
        $attribute = $this->getAttributeFromAttributeOrRef(
1435 74
            $childNode,
1436 74
            $schema,
1437
            $node
1438 74
        );
1439
1440 74
        $type->addAttribute($attribute);
1441 74
    }
1442
1443 74
    private function findSomethingLikeAttributeGroup(
1444
        Schema $schema,
1445 74
        DOMElement $node,
1446 74
        DOMElement $childNode,
1447
        AttributeContainer $addToThis
1448 74
    ): void {
1449
        $attribute = $this->findAttributeGroup($schema, $node, $childNode->getAttribute('ref'));
1450 74
        $addToThis->addAttribute($attribute);
1451
    }
1452 74
1453 74
    private function setLoadedFile(string $key, Schema $schema): void
1454
    {
1455 74
        $this->loadedFiles[$key] = $schema;
1456 74
    }
1457
1458 74
    private function setLoadedSchemaFromElement(DOMElement $node, Schema $schema): void
1459
    {
1460 74
        if ($node->hasAttribute('targetNamespace')) {
1461
            $this->setLoadedSchema($node->getAttribute('targetNamespace'), $schema);
1462
        }
1463
    }
1464
1465 74
    private function setLoadedSchema(string $namespace, Schema $schema): void
1466
    {
1467 74
        if (!isset($this->loadedSchemas[$namespace])) {
1468 74
            $this->loadedSchemas[$namespace] = [];
1469
        }
1470
        if (!in_array($schema, $this->loadedSchemas[$namespace], true)) {
1471
            $this->loadedSchemas[$namespace][] = $schema;
1472 74
        }
1473 74
    }
1474 74
1475 74
    private function setSchemaThingsFromNode(
1476
        Schema $schema,
1477
        DOMElement $node,
1478
        Schema $parent = null
1479
    ): void {
1480
        $schema->setDoc($this->getDocumentation($node));
1481
1482
        if ($node->hasAttribute('targetNamespace')) {
1483
            $schema->setTargetNamespace($node->getAttribute('targetNamespace'));
1484
        } elseif ($parent instanceof Schema) {
1485
            $schema->setTargetNamespace($parent->getTargetNamespace());
1486
        }
1487
        $schema->setElementsQualification($node->getAttribute('elementFormDefault') == 'qualified');
1488
        $schema->setAttributesQualification($node->getAttribute('attributeFormDefault') == 'qualified');
1489
        $schema->setDoc($this->getDocumentation($node));
1490
    }
1491
}
1492