Passed
Pull Request — master (#70)
by Axel
12:26 queued 02:26
created

SchemaReader   F

Complexity

Total Complexity 206

Size/Duplication

Total Lines 1436
Duplicated Lines 0 %

Test Coverage

Coverage 98.16%

Importance

Changes 32
Bugs 4 Features 4
Metric Value
wmc 206
eloc 716
c 32
b 4
f 4
dl 0
loc 1436
ccs 695
cts 708
cp 0.9816
rs 1.884

62 Methods

Rating   Name   Duplication   Size   Complexity  
A setLoadedFile() 0 3 1
A addGroupAsElement() 0 14 1
A getDocumentation() 0 3 1
A maybeSetDefault() 0 4 2
A maybeSetMin() 0 6 4
B loadSequenceChildNode() 0 35 6
A maybeSetFixed() 0 4 2
A loadGroupRef() 0 9 1
A maybeSetMax() 0 4 3
B loadSequence() 0 34 7
B schemaNode() 0 48 10
A addKnownSchemaLocation() 0 3 1
A __construct() 0 6 2
A extractErrorMessage() 0 11 2
A loadAttribute() 0 8 1
A getAttributeFromAttributeOrRef() 0 15 2
A loadAttributeOrElement() 0 20 2
A fillAttribute() 0 17 6
A loadAttributeGroup() 0 35 3
A addKnownNamespaceSchemaLocation() 0 3 1
A loadSequenceChildNodeLoadElement() 0 29 4
A readFile() 0 5 1
A addAttributeFromAttributeOrRef() 0 13 1
A findAttributeItem() 0 18 3
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 loadElement() 0 8 1
A loadUnion() 0 29 3
A findAttributeGroup() 0 18 3
A findSomethingLikeAttributeGroup() 0 8 1
A findType() 0 25 5
C loadImport() 0 36 13
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 loadRestriction() 0 27 2
B loadComplexTypeFromChildNode() 0 45 9
A readNode() 0 18 3
A findElement() 0 13 3
A setLoadedSchema() 0 7 3
B readNodes() 0 29 7
A fillElement() 0 24 6
A findAndSetSomeBase() 0 10 1
A fillItem() 0 41 4
A loadChildAttributesAndAttributeGroups() 0 22 3
A splitParts() 0 17 2
A loadList() 0 22 2
A findSomeTypeFromAttribute() 0 12 1
A loadRestrictionChildNodes() 0 42 3
A readString() 0 11 2
A fillItemNonLocalType() 0 19 2
A setSchemaThingsFromNode() 0 15 3
A loadExtension() 0 9 2
A againstDOMNodeList() 0 15 3
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\AttributeItem;
18
use GoetasWebservices\XML\XSDReader\Schema\Attribute\Group as AttributeGroup;
19
use GoetasWebservices\XML\XSDReader\Schema\Element\AbstractElementSingle;
20
use GoetasWebservices\XML\XSDReader\Schema\Element\Element;
21
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementContainer;
22
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementRef;
23
use GoetasWebservices\XML\XSDReader\Schema\Element\Group;
24
use GoetasWebservices\XML\XSDReader\Schema\Element\GroupRef;
25
use GoetasWebservices\XML\XSDReader\Schema\Element\InterfaceSetDefault;
26
use GoetasWebservices\XML\XSDReader\Schema\Element\InterfaceSetFixed;
27
use GoetasWebservices\XML\XSDReader\Schema\Element\InterfaceSetMinMax;
28
use GoetasWebservices\XML\XSDReader\Schema\Exception\TypeNotFoundException;
29
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Base;
30
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Extension;
31
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Restriction;
32
use GoetasWebservices\XML\XSDReader\Schema\Item;
33
use GoetasWebservices\XML\XSDReader\Schema\Schema;
34
use GoetasWebservices\XML\XSDReader\Schema\SchemaItem;
35
use GoetasWebservices\XML\XSDReader\Schema\Type\BaseComplexType;
36
use GoetasWebservices\XML\XSDReader\Schema\Type\ComplexType;
37
use GoetasWebservices\XML\XSDReader\Schema\Type\ComplexTypeSimpleContent;
38
use GoetasWebservices\XML\XSDReader\Schema\Type\SimpleType;
39
use GoetasWebservices\XML\XSDReader\Schema\Type\Type;
40
use GoetasWebservices\XML\XSDReader\Utils\UrlUtils;
41
42
class SchemaReader
43
{
44
    public const XSD_NS = 'http://www.w3.org/2001/XMLSchema';
45
46
    public const XML_NS = 'http://www.w3.org/XML/1998/namespace';
47
48
    /**
49
     * @var DocumentationReader
50
     */
51
    private $documentationReader;
52
53
    /**
54
     * @var Schema[]
55
     */
56
    private $loadedFiles = [];
57
58
    /**
59
     * @var Schema[][]
60
     */
61
    private $loadedSchemas = [];
62
63
    /**
64
     * @var string[]
65
     */
66
    protected $knownLocationSchemas = [
67
        'http://www.w3.org/2001/xml.xsd' => (
68
            __DIR__.'/Resources/xml.xsd'
69
        ),
70
        'http://www.w3.org/2001/XMLSchema.xsd' => (
71
            __DIR__.'/Resources/XMLSchema.xsd'
72
        ),
73
        'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' => (
74
            __DIR__.'/Resources/oasis-200401-wss-wssecurity-secext-1.0.xsd'
75
        ),
76
        'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd' => (
77
            __DIR__.'/Resources/oasis-200401-wss-wssecurity-utility-1.0.xsd'
78
        ),
79
        'https://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd' => (
80
            __DIR__.'/Resources/xmldsig-core-schema.xsd'
81
        ),
82
        'http://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd' => (
83
            __DIR__.'/Resources/xmldsig-core-schema.xsd'
84
        ),
85
    ];
86
87
    /**
88
     * @var string[]
89
     */
90
    protected $knownNamespaceSchemaLocations = [
91
        'http://www.w3.org/2000/09/xmldsig#' => (
92
            __DIR__.'/Resources/xmldsig-core-schema.xsd'
93
        ),
94
    ];
95
96
    /**
97
     * @var string[]
98
     */
99
    protected static $globalSchemaInfo = [
100 2
        self::XML_NS => 'http://www.w3.org/2001/xml.xsd',
101
        self::XSD_NS => 'http://www.w3.org/2001/XMLSchema.xsd',
102 2
    ];
103
104 2
    private function extractErrorMessage(): \Exception
105 1
    {
106
        $errors = [];
107 2
108 2
        foreach (libxml_get_errors() as $error) {
109
            $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);
110 2
        }
111
        $e = new \Exception(implode('; ', $errors));
112
        libxml_use_internal_errors(false);
113 85
114
        return $e;
115 85
    }
116 85
117
    public function __construct(DocumentationReader $documentationReader = null)
118 85
    {
119 85
        if (null === $documentationReader) {
120
            $documentationReader = new StandardDocumentationReader();
121
        }
122
        $this->documentationReader = $documentationReader;
123
    }
124
125
    /**
126
     * Override remote location with a local file.
127 1
     *
128
     * @param string $remote remote schema URL
129 1
     * @param string $local  local file path
130 1
     */
131
    public function addKnownSchemaLocation(string $remote, string $local): void
132
    {
133
        $this->knownLocationSchemas[$remote] = $local;
134
    }
135
136
    /**
137
     * Specify schema location by namespace.
138
     * This can be used for schemas which import namespaces but do not specify schemaLocation attributes.
139 1
     *
140
     * @param string $namespace namespace
141 1
     * @param string $location  schema URL
142 1
     */
143
    public function addKnownNamespaceSchemaLocation(string $namespace, string $location): void
144 74
    {
145
        $this->knownNamespaceSchemaLocations[$namespace] = $location;
146
    }
147
148 74
    private function loadAttributeGroup(
149 74
        Schema $schema,
150 74
        DOMElement $node
151
    ): Closure {
152
        $attGroup = new AttributeGroup($schema, $node->getAttribute('name'));
153 74
        $attGroup->setDoc($this->getDocumentation($node));
154 74
        $schema->addAttributeGroup($attGroup);
155
156
        return function () use ($schema, $node, $attGroup): void {
157
            SchemaReader::againstDOMNodeList(
158
                $node,
159 74
                function (
160 74
                    DOMElement $node,
161
                    DOMElement $childNode
162 74
                ) use (
163 74
                    $schema,
164 74
                    $attGroup
165 74
                ): void {
166 74
                    switch ($childNode->localName) {
167 74
                        case 'attribute':
168
                            $attribute = $this->getAttributeFromAttributeOrRef(
169 74
                                $childNode,
170 74
                                $schema,
171 74
                                $node
172 1
                            );
173 1
                            $attGroup->addAttribute($attribute);
174 1
                            break;
175 1
                        case 'attributeGroup':
176 1
                            $this->findSomethingLikeAttributeGroup(
177
                                $schema,
178 1
                                $node,
179
                                $childNode,
180 74
                                $attGroup
181
                            );
182 74
                            break;
183
                    }
184
                }
185 74
            );
186
        };
187
    }
188
189
    private function getAttributeFromAttributeOrRef(
190 74
        DOMElement $childNode,
191 74
        Schema $schema,
192
        DOMElement $node
193
    ): AttributeItem {
194
        if ($childNode->hasAttribute('ref')) {
195
            $attribute = $this->findAttributeItem($schema, $node, $childNode->getAttribute('ref'));
196 74
        } else {
197
            /**
198
             * @var Attribute
199 74
             */
200
            $attribute = $this->loadAttribute($schema, $childNode);
201
        }
202 74
203
        return $attribute;
204
    }
205
206 74
    private function loadAttribute(Schema $schema, DOMElement $node): Attribute
207 74
    {
208 74
        $attribute = new Attribute($schema, $node->getAttribute('name'));
209
        $attribute->setDoc($this->getDocumentation($node));
210 74
        $this->fillItem($attribute, $node);
211 1
        $this->fillAttribute($attribute, $node);
212
213 74
        return $attribute;
214 1
    }
215
216 74
    private function fillAttribute(Attribute $attribute, DOMElement $node): void
217 74
    {
218
        if ($node->hasAttribute('fixed')) {
219
            $attribute->setFixed($node->getAttribute('fixed'));
220 74
        }
221
        if ($node->hasAttribute('default')) {
222
            $attribute->setDefault($node->getAttribute('default'));
223 74
        }
224
225
        if ($node->hasAttribute('nillable')) {
226
            $attribute->setNil($node->getAttribute('nillable') == 'true');
227
        }
228 74
        if ($node->hasAttribute('form')) {
229 74
            $attribute->setQualified($node->getAttribute('form') == 'qualified');
230 74
        }
231 74
        if ($node->hasAttribute('use')) {
232
            $attribute->setUse($node->getAttribute('use'));
233 74
        }
234 74
    }
235 74
236
    private function loadAttributeOrElement(
237
        Schema $schema,
238
        DOMElement $node,
239 74
        bool $isAttribute
240 74
    ): Closure {
241
        $name = $node->getAttribute('name');
242
        if ($isAttribute) {
243 74
            $attribute = new Attribute($schema, $name);
244
            $attribute->setDoc($this->getDocumentation($node));
245 74
            $this->fillAttribute($attribute, $node);
246
            $schema->addAttribute($attribute);
0 ignored issues
show
Bug introduced by
$attribute of type GoetasWebservices\XML\XS...ema\Attribute\Attribute is incompatible with the type GoetasWebservices\XML\XS...\Attribute\AttributeDef expected by parameter $attribute of GoetasWebservices\XML\XS...\Schema::addAttribute(). ( Ignorable by Annotation )

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

246
            $schema->addAttribute(/** @scrutinizer ignore-type */ $attribute);
Loading history...
247
        } else {
248 74
            $attribute = new Element($schema, $name);
249
            $attribute->setDoc($this->getDocumentation($node));
250 74
            $this->fillElement($attribute, $node);
251
            $schema->addElement($attribute);
0 ignored issues
show
Bug introduced by
$attribute of type GoetasWebservices\XML\XS...\Schema\Element\Element is incompatible with the type GoetasWebservices\XML\XS...hema\Element\ElementDef expected by parameter $element of GoetasWebservices\XML\XS...ma\Schema::addElement(). ( Ignorable by Annotation )

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

251
            $schema->addElement(/** @scrutinizer ignore-type */ $attribute);
Loading history...
252
        }
253
254
        return function () use ($attribute, $node): void {
255
            $this->fillItem($attribute, $node);
256 74
        };
257
    }
258 74
259 74
    private function getDocumentation(DOMElement $node): string
260
    {
261 74
        return $this->documentationReader->get($node);
262 74
    }
263
264
    /**
265
     * @return Closure[]
266
     */
267 74
    private function schemaNode(Schema $schema, DOMElement $node, Schema $parent = null): array
268 74
    {
269
        $this->setSchemaThingsFromNode($schema, $node, $parent);
270 74
        $functions = [];
271
272 74
        self::againstDOMNodeList(
273 74
            $node,
274 74
            function (
275 74
                DOMElement $node,
276 74
                DOMElement $childNode
277 74
            ) use (
278 74
                $schema,
279 74
                &$functions
280 74
            ): void {
281 74
                $callback = null;
282 74
283 74
                switch ($childNode->localName) {
284 74
                    case 'attributeGroup':
285 74
                        $callback = $this->loadAttributeGroup($schema, $childNode);
286 74
                        break;
287 74
                    case 'include':
288 74
                    case 'import':
289 74
                        $callback = $this->loadImport($schema, $childNode);
290 74
                        break;
291 74
                    case 'element':
292 74
                        $callback = $this->loadAttributeOrElement($schema, $childNode, false);
293 74
                        break;
294 74
                    case 'attribute':
295
                        $callback = $this->loadAttributeOrElement($schema, $childNode, true);
296
                        break;
297 74
                    case 'group':
298 74
                        $callback = $this->loadGroup($schema, $childNode);
299
                        break;
300 74
                    case 'complexType':
301
                        $callback = $this->loadComplexType($schema, $childNode);
302
                        break;
303 74
                    case 'simpleType':
304
                        $callback = $this->loadSimpleType($schema, $childNode);
305
                        break;
306 74
                }
307
308 74
                if ($callback instanceof Closure) {
309 74
                    $functions[] = $callback;
310
                }
311 74
            }
312 74
        );
313
314 74
        return $functions;
315
    }
316
317 74
    private function loadGroupRef(Group $referenced, DOMElement $node): GroupRef
318
    {
319 74
        $ref = new GroupRef($referenced);
320 74
        $ref->setDoc($this->getDocumentation($node));
321
322 74
        self::maybeSetMax($ref, $node);
323
        self::maybeSetMin($ref, $node);
324 74
325
        return $ref;
326 74
    }
327 74
328 74
    private static function maybeSetMax(InterfaceSetMinMax $ref, DOMElement $node): void
329 7
    {
330
        if ($node->hasAttribute('maxOccurs')) {
331
            $ref->setMax($node->getAttribute('maxOccurs') == 'unbounded' ? -1 : (int) $node->getAttribute('maxOccurs'));
332 74
        }
333
    }
334 74
335
    private static function maybeSetMin(InterfaceSetMinMax $ref, DOMElement $node): void
336 74
    {
337 1
        if ($node->hasAttribute('minOccurs')) {
338
            $ref->setMin((int) $node->getAttribute('minOccurs'));
339 74
            if ($ref->getMin() > $ref->getMax() && $ref->getMax() !== -1) {
340
                $ref->setMax($ref->getMin());
341 74
            }
342
        }
343
    }
344
345 74
    private static function maybeSetFixed(InterfaceSetFixed $ref, DOMElement $node): void
346 74
    {
347 74
        if ($node->hasAttribute('fixed')) {
348
            $ref->setFixed($node->getAttribute('fixed'));
349 74
        }
350 74
    }
351
352
    private static function maybeSetDefault(InterfaceSetDefault $ref, DOMElement $node): void
353 74
    {
354 74
        if ($node->hasAttribute('default')) {
355
            $ref->setDefault($node->getAttribute('default'));
356 74
        }
357 74
    }
358
359 74
    private function loadSequence(ElementContainer $elementContainer, DOMElement $node, int $max = null, int $min = null): void
360 74
    {
361
        $max =
362
            (
363
                (is_int($max) && (bool) $max) ||
364
                $node->getAttribute('maxOccurs') == 'unbounded' ||
365 74
                $node->getAttribute('maxOccurs') > 1
366 74
            )
367 74
                ? 2
368
                : null;
369 74
        $min =
370 74
            (
371 74
                $min === null &&
372 74
                !$node->hasAttribute('minOccurs')
373 74
            )
374 74
                ? null
375
                : (int) max((int) $min, $node->getAttribute('minOccurs'));
376 74
377
        self::againstDOMNodeList(
378 74
            $node,
379
            function (
380 74
                DOMElement $node,
381
                DOMElement $childNode
382
            ) use (
383
                $elementContainer,
384
                $max,
385
                $min
386
            ): void {
387 74
                $this->loadSequenceChildNode(
388 74
                    $elementContainer,
389 74
                    $node,
390 74
                    $childNode,
391 74
                    $max,
392 74
                    $min
393 74
                );
394 74
            }
395 74
        );
396
    }
397 74
398 74
    private function loadSequenceChildNode(
399 74
        ElementContainer $elementContainer,
400 74
        DOMElement $node,
401 74
        DOMElement $childNode,
402 74
        ?int $max,
403 74
        ?int $min = null
404 74
    ): void {
405
        switch ($childNode->localName) {
406 74
            case 'sequence':
407 74
            case 'choice':
408 74
            case 'all':
409 74
                $this->loadSequence(
410 74
                    $elementContainer,
411 74
                    $childNode,
412 74
                    $max,
413
                    $min
414 74
                );
415
                break;
416 74
            case 'element':
417
                $this->loadSequenceChildNodeLoadElement(
418 74
                    $elementContainer,
419
                    $node,
420
                    $childNode,
421
                    $max,
422
                    $min
423
                );
424
                break;
425 74
            case 'group':
426 74
                $this->addGroupAsElement(
427 74
                    $elementContainer->getSchema(),
428 74
                    $node,
429
                    $childNode,
430 74
                    $elementContainer
431 74
                );
432
                break;
433 74
        }
434 74
    }
435
436 74
    private function loadSequenceChildNodeLoadElement(
437 74
        ElementContainer $elementContainer,
438
        DOMElement $node,
439
        DOMElement $childNode,
440 74
        ?int $max,
441
        ?int $min
442
    ): void {
443 74
        if ($childNode->hasAttribute('ref')) {
444 74
            $element = $this->findElement($elementContainer->getSchema(), $node, $childNode->getAttribute('ref'));
445
            $element = new ElementRef($element);
0 ignored issues
show
Bug introduced by
$element of type GoetasWebservices\XML\XS...\Schema\Element\Element is incompatible with the type GoetasWebservices\XML\XS...hema\Element\ElementDef expected by parameter $element of GoetasWebservices\XML\XS...ementRef::__construct(). ( Ignorable by Annotation )

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

445
            $element = new ElementRef(/** @scrutinizer ignore-type */ $element);
Loading history...
446
            $element->setDoc($this->getDocumentation($childNode));
447 74
            $this->fillElement($element, $childNode);
448 74
        } else {
449 74
            $element = $this->loadElement($elementContainer->getSchema(), $childNode);
450
        }
451
452
        if ($min !== null) {
453 74
            $element->setMin($min);
454 74
        }
455
456
        if ($max > 1) {
457 74
            /*
458
            * although one might think the typecast is not needed with $max being `? int $max` after passing > 1,
459
            * phpstan@a4f89fa still thinks it's possibly null.
460
            * see https://github.com/phpstan/phpstan/issues/577 for related issue
461
            */
462
            $element->setMax((int) $max);
463 74
        }
464
        $elementContainer->addElement($element);
465 74
    }
466 74
467
    private function addGroupAsElement(
468 74
        Schema $schema,
469
        DOMElement $node,
470
        DOMElement $childNode,
471
        ElementContainer $elementContainer
472
    ): void {
473
        $referencedGroup = $this->findGroup(
474 74
            $schema,
475 74
            $node,
476 74
            $childNode->getAttribute('ref')
477 74
        );
478
479
        $group = $this->loadGroupRef($referencedGroup, $childNode);
480 74
        $elementContainer->addElement($group);
481 74
    }
482 74
483
    private function loadGroup(Schema $schema, DOMElement $node): Closure
484 74
    {
485
        $group = new Group($schema, $node->getAttribute('name'));
486 74
        $group->setDoc($this->getDocumentation($node));
487 74
        $groupOriginal = $group;
488 74
489
        if ($node->hasAttribute('maxOccurs') || $node->hasAttribute('minOccurs')) {
490 74
            $group = $this->loadGroupRef($group, $node);
491 1
        }
492
493 1
        $schema->addGroup($group);
494 1
495
        return function () use ($groupOriginal, $node): void {
496 1
            static::againstDOMNodeList(
497 1
                $node,
498
                function (DOMelement $node, DOMElement $childNode) use ($groupOriginal): void {
499
                    switch ($childNode->localName) {
500
                        case 'sequence':
501 74
                        case 'choice':
502
                        case 'all':
503
                            $this->loadSequence($groupOriginal, $childNode);
504 74
                            break;
505 74
                    }
506
                }
507 74
            );
508 74
        };
509 74
    }
510 74
511 74
    private function loadComplexType(Schema $schema, DOMElement $node, Closure $callback = null): Closure
512 74
    {
513
        /**
514 74
         * @var bool
515
         */
516 74
        $isSimple = false;
517
518
        self::againstDOMNodeList(
519 74
            $node,
520
            function (
521
                DOMElement $node,
522
                DOMElement $childNode
523
            ) use (
524 74
                &$isSimple
525
            ): void {
526 74
                if ($isSimple) {
527 74
                    return;
528
                }
529
                if ($childNode->localName === 'simpleContent') {
530
                    $isSimple = true;
531
                }
532 74
            }
533
        );
534 74
535 1
        $type = $isSimple ? new ComplexTypeSimpleContent($schema, $node->getAttribute('name')) : new ComplexType($schema, $node->getAttribute('name'));
536
537 74
        $type->setDoc($this->getDocumentation($node));
538 2
        if ($node->getAttribute('name')) {
539
            $schema->addType($type);
540 74
        }
541
542
        return function () use ($type, $node, $schema, $callback): void {
543 74
            $this->fillTypeNode($type, $node, true);
544
545 74
            self::againstDOMNodeList(
546 74
                $node,
547 74
                function (
548
                    DOMElement $node,
549
                    DOMElement $childNode
550
                ) use (
551 74
                    $schema,
552
                    $type
553 74
                ): void {
554 74
                    $this->loadComplexTypeFromChildNode(
555
                        $type,
556
                        $node,
557
                        $childNode,
558
                        $schema
559 74
                    );
560 74
                }
561
            );
562 74
563 74
            if ($callback instanceof Closure) {
564 74
                call_user_func($callback, $type);
565 74
            }
566 74
        };
567
    }
568 74
569
    private function loadComplexTypeFromChildNode(
570
        BaseComplexType $type,
571 74
        DOMElement $node,
572 74
        DOMElement $childNode,
573
        Schema $schema
574 74
    ): void {
575
        switch ($childNode->localName) {
576
            case 'sequence':
577 74
            case 'choice':
578
            case 'all':
579
                if ($type instanceof ElementContainer) {
580
                    $this->loadSequence(
581
                        $type,
582
                        $childNode
583 74
                    );
584 74
                }
585 74
                break;
586 74
            case 'attribute':
587 74
                $this->addAttributeFromAttributeOrRef(
588 74
                    $type,
589 74
                    $childNode,
590 74
                    $schema,
591
                    $node
592
                );
593 74
                break;
594 74
            case 'attributeGroup':
595 74
                $this->findSomethingLikeAttributeGroup(
596 74
                    $schema,
597 74
                    $node,
598 74
                    $childNode,
599 74
                    $type
600
                );
601 74
                break;
602 74
            case 'group':
603 2
                if (
604 2
                    $type instanceof ComplexType
605 2
                ) {
606 2
                    $this->addGroupAsElement(
607 2
                        $schema,
608
                        $node,
609 2
                        $childNode,
610 74
                        $type
611
                    );
612 1
                }
613
                break;
614 1
        }
615 1
    }
616 1
617 1
    private function loadSimpleType(Schema $schema, DOMElement $node, Closure $callback = null): Closure
618 1
    {
619
        $type = new SimpleType($schema, $node->getAttribute('name'));
620
        $type->setDoc($this->getDocumentation($node));
621 1
        if ($node->getAttribute('name')) {
622
            $schema->addType($type);
623 74
        }
624
625 74
        return function () use ($type, $node, $callback): void {
626
            $this->fillTypeNode($type, $node, true);
627 74
628 74
            self::againstDOMNodeList(
629 74
                $node,
630 74
                function (DOMElement $node, DOMElement $childNode) use ($type): void {
631
                    switch ($childNode->localName) {
632
                        case 'union':
633
                            $this->loadUnion($type, $childNode);
634 74
                            break;
635
                        case 'list':
636 74
                            $this->loadList($type, $childNode);
637 74
                            break;
638
                    }
639 74
                }
640 74
            );
641 74
642 74
            if ($callback instanceof Closure) {
643 74
                call_user_func($callback, $type);
644 74
            }
645 74
        };
646
    }
647 74
648
    private function loadList(SimpleType $type, DOMElement $node): void
649
    {
650 74
        if ($node->hasAttribute('itemType')) {
651 74
            /**
652
             * @var SimpleType
653 74
             */
654
            $listType = $this->findSomeType($type, $node, 'itemType');
655
            $type->setList($listType);
656 74
        } else {
657
            self::againstDOMNodeList(
658 74
                $node,
659
                function (
660
                    DOMElement $node,
661
                    DOMElement $childNode
662 74
                ) use (
663 74
                    $type
664
                ): void {
665 74
                    $this->loadTypeWithCallback(
666 74
                        $type->getSchema(),
667
                        $childNode,
668
                        function (SimpleType $list) use ($type): void {
669
                            $type->setList($list);
670
                        }
671 74
                    );
672
                }
673 74
            );
674 74
        }
675 74
    }
676
677 74
    private function findSomeType(
678 74
        SchemaItem $fromThis,
679
        DOMElement $node,
680 74
        string $attributeName
681
    ): SchemaItem {
682
        return $this->findSomeTypeFromAttribute(
683 74
            $fromThis,
684
            $node,
685 74
            $node->getAttribute($attributeName)
686
        );
687
    }
688
689
    private function findSomeTypeFromAttribute(
690 74
        SchemaItem $fromThis,
691 74
        DOMElement $node,
692 74
        string $attributeName
693 74
    ): SchemaItem {
694
        $out = $this->findType(
695
            $fromThis->getSchema(),
696
            $node,
697 74
            $attributeName
698
        );
699
700
        return $out;
701
    }
702 74
703 74
    private function loadUnion(SimpleType $type, DOMElement $node): void
704 74
    {
705 74
        if ($node->hasAttribute('memberTypes')) {
706
            $types = preg_split('/\s+/', $node->getAttribute('memberTypes'));
707
            foreach ($types as $typeName) {
708 74
                /**
709
                 * @var SimpleType
710
                 */
711 74
                $unionType = $this->findSomeTypeFromAttribute(
712
                    $type,
713 74
                    $node,
714 74
                    $typeName
715 74
                );
716
                $type->addUnion($unionType);
717
            }
718
        }
719 74
        self::againstDOMNodeList(
720 74
            $node,
721 74
            function (
722 74
                DOMElement $node,
723
                DOMElement $childNode
724 74
            ) use (
725
                $type
726
            ): void {
727 74
                $this->loadTypeWithCallback(
728 74
                    $type->getSchema(),
729
                    $childNode,
730
                    function (SimpleType $unType) use ($type): void {
731
                        $type->addUnion($unType);
732
                    }
733 74
                );
734
            }
735 74
        );
736 74
    }
737 74
738
    private function fillTypeNode(Type $type, DOMElement $node, bool $checkAbstract = false): void
739 74
    {
740 74
        if ($checkAbstract) {
741
            $type->setAbstract($node->getAttribute('abstract') === 'true' || $node->getAttribute('abstract') === '1');
742 74
        }
743
744 74
        self::againstDOMNodeList(
745
            $node,
746 74
            function (DOMElement $node, DOMElement $childNode) use ($type): void {
747
                switch ($childNode->localName) {
748 74
                    case 'restriction':
749 74
                        $this->loadRestriction($type, $childNode);
750
                        break;
751
                    case 'extension':
752 74
                        if ($type instanceof BaseComplexType) {
753 74
                            $this->loadExtension($type, $childNode);
754
                        }
755 74
                        break;
756 74
                    case 'simpleContent':
757 74
                    case 'complexContent':
758 74
                        $this->fillTypeNode($type, $childNode);
759 74
                        break;
760 74
                }
761 74
            }
762
        );
763 74
    }
764 74
765 74
    private function loadExtension(BaseComplexType $type, DOMElement $node): void
766 74
    {
767 74
        $extension = new Extension();
768
        $type->setExtension($extension);
769 74
770
        if ($node->hasAttribute('base')) {
771 74
            $this->findAndSetSomeBase($type, $extension, $node);
772
        }
773 74
        $this->loadExtensionChildNodes($type, $node);
774
    }
775 74
776 74
    private function findAndSetSomeBase(
777
        Type $type,
778 74
        Base $setBaseOnThis,
779 74
        DOMElement $node
780 74
    ): void {
781 74
        /**
782 74
         * @var Type
783
         */
784
        $parent = $this->findSomeType($type, $node, 'base');
785 74
        $setBaseOnThis->setBase($parent);
786 74
    }
787
788 74
    private function loadExtensionChildNodes(
789
        BaseComplexType $type,
790
        DOMElement $node
791
    ): void {
792
        self::againstDOMNodeList(
793
            $node,
794
            function (
795
                DOMElement $node,
796 74
                DOMElement $childNode
797 74
            ) use (
798 74
                $type
799
            ): void {
800 74
                switch ($childNode->localName) {
801
                    case 'sequence':
802
                    case 'choice':
803
                    case 'all':
804 74
                        if ($type instanceof ElementContainer) {
805 74
                            $this->loadSequence($type, $childNode);
806
                        }
807
                        break;
808
                }
809
                $this->loadChildAttributesAndAttributeGroups($type, $node, $childNode);
810 74
            }
811
        );
812 74
    }
813 74
814 74
    private function loadChildAttributesAndAttributeGroups(
815 74
        BaseComplexType $type,
816 74
        DOMElement $node,
817 74
        DOMElement $childNode
818 74
    ): void {
819 74
        switch ($childNode->localName) {
820
            case 'attribute':
821
                $this->addAttributeFromAttributeOrRef(
822 74
                    $type,
823 74
                    $childNode,
824 74
                    $type->getSchema(),
825 74
                    $node
826 74
                );
827 74
                break;
828 74
            case 'attributeGroup':
829
                $this->findSomethingLikeAttributeGroup(
830 74
                    $type->getSchema(),
831 74
                    $node,
832 74
                    $childNode,
833 74
                    $type
834 74
                );
835 74
                break;
836 74
        }
837
    }
838 74
839
    private function loadRestriction(Type $type, DOMElement $node): void
840 74
    {
841
        $restriction = new Restriction();
842 74
        $type->setRestriction($restriction);
843
        if ($node->hasAttribute('base')) {
844 74
            $this->findAndSetSomeBase($type, $restriction, $node);
845
        } else {
846 74
            self::againstDOMNodeList(
847 74
                $node,
848 74
                function (
849 74
                    DOMElement $node,
850
                    DOMElement $childNode
851 74
                ) use (
852 74
                    $type,
853
                    $restriction
854
                ): void {
855
                    $this->loadTypeWithCallback(
856
                        $type->getSchema(),
857 74
                        $childNode,
858 74
                        function (Type $restType) use ($restriction): void {
859
                            $restriction->setBase($restType);
860 74
                        }
861 74
                    );
862 74
                }
863
            );
864 74
        }
865 74
        $this->loadRestrictionChildNodes($type, $restriction, $node);
866
    }
867 74
868
    private function loadRestrictionChildNodes(
869
        Type $type,
870 74
        Restriction $restriction,
871 74
        DOMElement $node
872
    ): void {
873
        self::againstDOMNodeList(
874
            $node,
875
            function (
876 74
                DOMElement $node,
877
                DOMElement $childNode
878
            ) use (
879 74
                $type,
880 74
                $restriction
881
            ): void {
882 74
                if ($type instanceof BaseComplexType) {
883
                    $this->loadChildAttributesAndAttributeGroups($type, $node, $childNode);
884
                }
885
                if (
886
                in_array(
887
                    $childNode->localName,
888
                    [
889
                        'enumeration',
890
                        'pattern',
891
                        'length',
892
                        'minLength',
893
                        'maxLength',
894
                        'minInclusive',
895 74
                        'maxInclusive',
896
                        'minExclusive',
897
                        'maxExclusive',
898 74
                        'fractionDigits',
899 74
                        'totalDigits',
900
                        'whiteSpace',
901 74
                    ],
902 74
                    true
903
                )
904
                ) {
905
                    $restriction->addCheck(
906 74
                        $childNode->localName,
907
                        [
908 74
                            'value' => $childNode->getAttribute('value'),
909
                            'doc' => $this->getDocumentation($childNode),
910
                        ]
911
                    );
912
                }
913 74
            }
914
        );
915 74
    }
916 74
917 74
    /**
918 74
     * @return mixed[]
919
     */
920
    private static function splitParts(DOMElement $node, string $typeName): array
921
    {
922
        $prefix = null;
923
        $name = $typeName;
924 74
        if (strpos($typeName, ':') !== false) {
925
            [$prefix, $name] = explode(':', $typeName);
926
        }
927 74
928 74
        /**
929 74
         * @psalm-suppress PossiblyNullArgument
930
         */
931
        $namespace = $node->lookupNamespaceUri($prefix);
932
933 74
        return [
934
            $name,
935 74
            $namespace,
936
            $prefix,
937
        ];
938
    }
939
940 74
    private function findAttributeItem(Schema $schema, DOMElement $node, string $typeName): AttributeItem
941
    {
942
        [$name, $namespace] = static::splitParts($node, $typeName);
943
944
        /**
945
         * @var string|null $namespace
946 74
         */
947
        $namespace = $namespace ?: $schema->getTargetNamespace();
948 74
949
        try {
950
            /**
951
             * @var AttributeItem $out
952
             */
953
            $out = $schema->findAttribute((string) $name, $namespace);
954 74
955
            return $out;
956 74
        } catch (TypeNotFoundException $e) {
957
            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);
958
        }
959
    }
960
961 74
    private function findAttributeGroup(Schema $schema, DOMElement $node, string $typeName): AttributeGroup
962
    {
963
        [$name, $namespace] = static::splitParts($node, $typeName);
964
965
        /**
966
         * @var string|null $namespace
967 74
         */
968
        $namespace = $namespace ?: $schema->getTargetNamespace();
969 74
970
        try {
971
            /**
972
             * @var AttributeGroup $out
973
             */
974
            $out = $schema->findAttributeGroup((string) $name, $namespace);
975 74
976
            return $out;
977 74
        } catch (TypeNotFoundException $e) {
978
            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);
979
        }
980
    }
981
982 74
    private function findElement(Schema $schema, DOMElement $node, string $typeName): Element
983
    {
984
        [$name, $namespace] = static::splitParts($node, $typeName);
985 74
986
        /**
987
         * @var string|null $namespace
988
         */
989
        $namespace = $namespace ?: $schema->getTargetNamespace();
990
991 74
        try {
992
            return $schema->findElement((string) $name, $namespace);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $schema->findElem...ring)$name, $namespace) returns the type GoetasWebservices\XML\XS...hema\Element\ElementDef which is incompatible with the type-hinted return GoetasWebservices\XML\XS...\Schema\Element\Element.
Loading history...
993 74
        } catch (TypeNotFoundException $e) {
994
            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);
995
        }
996
    }
997
998 74
    private function findGroup(Schema $schema, DOMElement $node, string $typeName): Group
999
    {
1000
        [$name, $namespace] = static::splitParts($node, $typeName);
1001
1002
        /**
1003
         * @var string|null $namespace
1004 74
         */
1005
        $namespace = $namespace ?: $schema->getTargetNamespace();
1006 74
1007
        try {
1008
            /**
1009
             * @var Group $out
1010
             */
1011
            $out = $schema->findGroup((string) $name, $namespace);
1012 74
1013
            return $out;
1014 74
        } catch (TypeNotFoundException $e) {
1015
            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);
1016
        }
1017
    }
1018
1019 74
    private function findType(Schema $schema, DOMElement $node, string $typeName): SchemaItem
1020
    {
1021
        [$name, $namespace] = static::splitParts($node, $typeName);
1022
1023 74
        /**
1024 1
         * @var string|null $namespace
1025 1
         */
1026
        $namespace = $namespace ?: $schema->getTargetNamespace();
1027 74
1028
        $tryFindType = static function (Schema $schema, string $name, ?string $namespace): ?SchemaItem {
1029 74
            try {
1030 74
                return $schema->findType($name, $namespace);
1031 74
            } catch (TypeNotFoundException $e) {
1032 74
                return null;
1033
            }
1034
        };
1035
1036
        $interestingSchemas = array_merge([$schema], $this->loadedSchemas[$namespace] ?? []);
1037
        foreach ($interestingSchemas as $interestingSchema) {
1038
            if ($result = $tryFindType($interestingSchema, $name, $namespace)) {
1039 74
                return $result;
1040
            }
1041 74
        }
1042
1043
        throw new TypeException(sprintf("Can't find %s named {%s}#%s, at line %d in %s ", 'type', $namespace, $name, $node->getLineNo(), $node->ownerDocument->documentURI));
1044 74
    }
1045
1046
    private function fillItem(Item $element, DOMElement $node): void
1047
    {
1048
        /**
1049 74
         * @var bool
1050 74
         */
1051 74
        $skip = false;
1052
        self::againstDOMNodeList(
1053
            $node,
1054
            function (
1055
                DOMElement $node,
1056 74
                DOMElement $childNode
1057 74
            ) use (
1058
                $element,
1059
                &$skip
1060 74
            ): void {
1061 74
                if (
1062 74
                    !$skip &&
1063
                    in_array(
1064 74
                        $childNode->localName,
1065
                        [
1066
                            'complexType',
1067 74
                            'simpleType',
1068
                        ],
1069
                        true
1070 74
                    )
1071 74
                ) {
1072 74
                    $this->loadTypeWithCallback(
1073
                        $element->getSchema(),
1074 74
                        $childNode,
1075 74
                        function (Type $type) use ($element): void {
1076
                            $element->setType($type);
1077 74
                        }
1078
                    );
1079 74
                    $skip = true;
1080
                }
1081 74
            }
1082 74
        );
1083
        if ($skip) {
1084 74
            return;
1085 74
        }
1086
        $this->fillItemNonLocalType($element, $node);
1087 74
    }
1088
1089 74
    private function fillItemNonLocalType(Item $element, DOMElement $node): void
1090
    {
1091
        if ($node->getAttribute('type')) {
1092
            /**
1093 74
             * @var Type
1094
             */
1095
            $type = $this->findSomeType($element, $node, 'type');
1096
        } else {
1097
            /**
1098 74
             * @var Type
1099 74
             */
1100 74
            $type = $this->findSomeTypeFromAttribute(
1101 74
                $element,
1102
                $node,
1103
                ($node->lookupPrefix(self::XSD_NS).':anyType')
1104
            );
1105 74
        }
1106 74
1107
        $element->setType($type);
1108 74
    }
1109
1110
    private function loadImport(
1111
        Schema $schema,
1112 74
        DOMElement $node
1113 74
    ): Closure {
1114 74
        $namespace = $node->getAttribute('namespace');
1115 1
        $schemaLocation = $node->getAttribute('schemaLocation');
1116
        if (!$schemaLocation && isset($this->knownNamespaceSchemaLocations[$namespace])) {
1117
            $schemaLocation = $this->knownNamespaceSchemaLocations[$namespace];
1118
        }
1119 74
1120
        // postpone schema loading
1121 3
        if ($namespace && !$schemaLocation && !isset(self::$globalSchemaInfo[$namespace])) {
1122 3
            return function () use ($schema, $namespace): void {
1123 3
                if (!empty($this->loadedSchemas[$namespace])) {
1124
                    foreach ($this->loadedSchemas[$namespace] as $s) {
1125
                        $schema->addSchema($s, $namespace);
1126 3
                    }
1127 74
                }
1128 1
            };
1129 1
        } elseif ($namespace && !$schemaLocation && !empty($this->loadedSchemas[$namespace])) {
1130
            foreach ($this->loadedSchemas[$namespace] as $s) {
1131
                $schema->addSchema($s, $namespace);
1132
            }
1133 74
        }
1134 74
1135
        $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

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

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