Completed
Pull Request — master (#48)
by Carsten
02:16
created

SchemaReader   F

Complexity

Total Complexity 202

Size/Duplication

Total Lines 1484
Duplicated Lines 0 %

Test Coverage

Coverage 92.63%

Importance

Changes 0
Metric Value
wmc 202
eloc 751
dl 0
loc 1484
ccs 716
cts 773
cp 0.9263
rs 1.849
c 0
b 0
f 0

60 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 2
A extractErrorMessage() 0 11 2
A loadAttributeDef() 0 3 1
A addKnownSchemaLocation() 0 3 1
A getDocumentation() 0 3 1
A loadAttribute() 0 19 4
A getAttributeFromAttributeOrRef() 0 15 2
A loadAttributeGroup() 0 35 3
A addKnownNamespaceSchemaLocation() 0 3 1
A loadAttributeOrElementDef() 0 17 2
A loadUnion() 0 29 3
C loadImport() 0 36 13
B loadExtensionChildNodes() 0 39 7
B loadComplexType() 0 54 6
B loadComplexTypeFromChildNode() 0 45 9
B loadSequenceChildNode() 0 32 6
A loadSequence() 0 25 5
A loadList() 0 22 2
A findSomeTypeFromAttribute() 0 12 1
A fillItemNonLocalType() 0 19 2
A loadExtension() 0 13 2
A findAttributeItem() 0 28 3
A addGroupAsElement() 0 14 1
B loadGroup() 0 29 8
A findGroup() 0 28 3
A findAttributeGroup() 0 28 3
A loadSequenceChildNodeLoadElement() 0 35 5
A findType() 0 28 3
A maybeSetDefault() 0 4 2
B fillTypeNode() 0 22 8
A findSomeType() 0 9 1
A loadSimpleType() 0 27 5
A maybeSetMin() 0 6 4
A loadRestriction() 0 59 3
A findElement() 0 23 3
A loadGroupRef() 0 9 1
A findAndSetSomeBase() 0 10 1
A fillItem() 0 41 4
A maybeSetMax() 0 4 3
A splitParts() 0 17 2
A loadElementDef() 0 3 1
B schemaNode() 0 51 11
A readFile() 0 5 1
A addAttributeFromAttributeOrRef() 0 13 1
A setLoadedSchemaFromElement() 0 4 2
B getGlobalSchema() 0 46 6
A createOrUseSchemaForNs() 0 13 2
A loadElement() 0 28 4
A loadRedefine() 0 24 4
A findSomethingLikeAttributeGroup() 0 8 1
A loadTypeWithCallback() 0 21 4
A loadImportFresh() 0 20 3
A readNode() 0 18 3
A setLoadedSchema() 0 7 3
B readNodes() 0 29 7
A setLoadedFile() 0 3 1
A readString() 0 11 2
A setSchemaThingsFromNode() 0 15 3
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\AttributeDef;
18
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeItem;
19
use GoetasWebservices\XML\XSDReader\Schema\Attribute\Group as AttributeGroup;
20
use GoetasWebservices\XML\XSDReader\Schema\Element\Element;
21
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementContainer;
22
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementDef;
23
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementRef;
24
use GoetasWebservices\XML\XSDReader\Schema\Element\Group;
25
use GoetasWebservices\XML\XSDReader\Schema\Element\GroupRef;
26
use GoetasWebservices\XML\XSDReader\Schema\Element\InterfaceSetMinMax;
27
use GoetasWebservices\XML\XSDReader\Schema\Element\InterfaceSetDefault;
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 = array();
57
58
    /**
59
     * @var Schema[][]
60
     */
61
    private $loadedSchemas = array();
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
92
    /**
93
     * @var string[]
94
     */
95
    protected static $globalSchemaInfo = array(
96
        self::XML_NS => 'http://www.w3.org/2001/xml.xsd',
97
        self::XSD_NS => 'http://www.w3.org/2001/XMLSchema.xsd',
98
    );
99
100 2
    private function extractErrorMessage(): \Exception
101
    {
102 2
        $errors = array();
103
104 2
        foreach (libxml_get_errors() as $error) {
105 1
            $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);
106
        }
107 2
        $e = new \Exception(implode('; ', $errors));
108 2
        libxml_use_internal_errors(false);
109
110 2
        return $e;
111
    }
112
113 77
    public function __construct(DocumentationReader $documentationReader = null)
114
    {
115 77
        if (null === $documentationReader) {
116 77
            $documentationReader = new StandardDocumentationReader();
117
        }
118 77
        $this->documentationReader = $documentationReader;
119 77
    }
120
121
    /**
122
     * Override remote location with a local file.
123
     *
124
     * @param string $remote remote schema URL
125
     * @param string $local  local file path
126
     */
127 1
    public function addKnownSchemaLocation(string $remote, string $local): void
128
    {
129 1
        $this->knownLocationSchemas[$remote] = $local;
130 1
    }
131
132
    /**
133
     * Specify schema location by namespace.
134
     * This can be used for schemas which import namespaces but do not specify schemaLocation attributes.
135
     *
136
     * @param string $namespace namespace
137
     * @param string $location  schema URL
138
     */
139 1
    public function addKnownNamespaceSchemaLocation(string $namespace, string $location): void
140
    {
141 1
        $this->knownNamespaceSchemaLocations[$namespace] = $location;
142 1
    }
143
144 66
    private function loadAttributeGroup(
145
        Schema $schema,
146
        DOMElement $node
147
    ): Closure {
148 66
        $attGroup = new AttributeGroup($schema, $node->getAttribute('name'));
149 66
        $attGroup->setDoc($this->getDocumentation($node));
150 66
        $schema->addAttributeGroup($attGroup);
151
152 66
        return function () use ($schema, $node, $attGroup): void {
153 66
            SchemaReader::againstDOMNodeList(
154 66
                $node,
155 66
                function (
156
                    DOMElement $node,
157
                    DOMElement $childNode
158
                ) use (
159 66
                    $schema,
160 66
                    $attGroup
161
                ): void {
162 66
                    switch ($childNode->localName) {
163 66
                        case 'attribute':
164 66
                            $attribute = $this->getAttributeFromAttributeOrRef(
165 66
                                $childNode,
166 66
                                $schema,
167 66
                                $node
168
                            );
169 66
                            $attGroup->addAttribute($attribute);
170 66
                            break;
171 66
                        case 'attributeGroup':
172 1
                            $this->findSomethingLikeAttributeGroup(
173 1
                                $schema,
174 1
                                $node,
175 1
                                $childNode,
176 1
                                $attGroup
177
                            );
178 1
                            break;
179
                    }
180 66
                }
181
            );
182 66
        };
183
    }
184
185 66
    private function getAttributeFromAttributeOrRef(
186
        DOMElement $childNode,
187
        Schema $schema,
188
        DOMElement $node
189
    ): AttributeItem {
190 66
        if ($childNode->hasAttribute('ref')) {
191 66
            $attribute = $this->findAttributeItem($schema, $node, $childNode->getAttribute('ref'));
192
        } else {
193
            /**
194
             * @var Attribute
195
             */
196 66
            $attribute = $this->loadAttribute($schema, $childNode);
197
        }
198
199 66
        return $attribute;
200
    }
201
202 66
    private function loadAttribute(
203
        Schema $schema,
204
        DOMElement $node
205
    ): Attribute {
206 66
        $attribute = new Attribute($schema, $node->getAttribute('name'));
207 66
        $attribute->setDoc($this->getDocumentation($node));
208 66
        $this->fillItem($attribute, $node);
209
210 66
        if ($node->hasAttribute('nillable')) {
211 1
            $attribute->setNil($node->getAttribute('nillable') == 'true');
212
        }
213 66
        if ($node->hasAttribute('form')) {
214 1
            $attribute->setQualified($node->getAttribute('form') == 'qualified');
215
        }
216 66
        if ($node->hasAttribute('use')) {
217 66
            $attribute->setUse($node->getAttribute('use'));
218
        }
219
220 66
        return $attribute;
221
    }
222
223 66
    private function loadAttributeOrElementDef(
224
        Schema $schema,
225
        DOMElement $node,
226
        bool $attributeDef
227
    ): Closure {
228 66
        $name = $node->getAttribute('name');
229 66
        if ($attributeDef) {
230 66
            $attribute = new AttributeDef($schema, $name);
231 66
            $schema->addAttribute($attribute);
232
        } else {
233 66
            $attribute = new ElementDef($schema, $name);
234 66
            $attribute->setDoc($this->getDocumentation($node));
235 66
            $schema->addElement($attribute);
236
        }
237
238 66
        return function () use ($attribute, $node): void {
239 66
            $this->fillItem($attribute, $node);
240 66
        };
241
    }
242
243 66
    private function loadAttributeDef(Schema $schema, DOMElement $node): Closure
244
    {
245 66
        return $this->loadAttributeOrElementDef($schema, $node, true);
246
    }
247
248 66
    private function getDocumentation(DOMElement $node): string
249
    {
250 66
        return $this->documentationReader->get($node);
251
    }
252
253
    /**
254
     * @return Closure[]
255
     */
256 66
    private function schemaNode(Schema $schema, DOMElement $node, Schema $parent = null): array
257
    {
258 66
        $this->setSchemaThingsFromNode($schema, $node, $parent);
259 66
        $functions = array();
260
261 66
        self::againstDOMNodeList(
262 66
            $node,
263 66
            function (
264
                DOMElement $node,
265
                DOMElement $childNode
266
            ) use (
267 66
                $schema,
268 66
                &$functions
269
            ): void {
270 66
                $callback = null;
271
272 66
                switch ($childNode->localName) {
273 66
                    case 'attributeGroup':
274 66
                        $callback = $this->loadAttributeGroup($schema, $childNode);
275 66
                        break;
276 66
                    case 'include':
277 66
                    case 'import':
278 66
                        $callback = $this->loadImport($schema, $childNode);
279 66
                        break;
280 66
                    case 'redefine':
281 2
                        $callback = $this->loadRedefine($schema, $childNode);
282 2
                        break;
283 66
                    case 'element':
284 66
                        $callback = $this->loadElementDef($schema, $childNode);
285 66
                        break;
286 66
                    case 'attribute':
287 66
                        $callback = $this->loadAttributeDef($schema, $childNode);
288 66
                        break;
289 66
                    case 'group':
290 66
                        $callback = $this->loadGroup($schema, $childNode);
291 66
                        break;
292 66
                    case 'complexType':
293 66
                        $callback = $this->loadComplexType($schema, $childNode);
294 66
                        break;
295 66
                    case 'simpleType':
296 66
                        $callback = $this->loadSimpleType($schema, $childNode);
297 66
                        break;
298
                }
299
300 66
                if ($callback instanceof Closure) {
301 66
                    $functions[] = $callback;
302
                }
303 66
            }
304
        );
305
306 66
        return $functions;
307
    }
308
309 66
    private function loadGroupRef(Group $referenced, DOMElement $node): GroupRef
310
    {
311 66
        $ref = new GroupRef($referenced);
312 66
        $ref->setDoc($this->getDocumentation($node));
313
314 66
        self::maybeSetMax($ref, $node);
315 66
        self::maybeSetMin($ref, $node);
316
317 66
        return $ref;
318
    }
319
320 66
    private static function maybeSetMax(InterfaceSetMinMax $ref, DOMElement $node): void
321
    {
322 66
        if ($node->hasAttribute('maxOccurs')) {
323 66
            $ref->setMax($node->getAttribute('maxOccurs') == 'unbounded' ? -1 : (int) $node->getAttribute('maxOccurs'));
324
        }
325 66
    }
326
327 66
    private static function maybeSetMin(InterfaceSetMinMax $ref, DOMElement $node): void
328
    {
329 66
        if ($node->hasAttribute('minOccurs')) {
330 66
            $ref->setMin((int) $node->getAttribute('minOccurs'));
331 66
            if ($ref->getMin() > $ref->getMax() && $ref->getMax() !== -1) {
332 7
                $ref->setMax($ref->getMin());
333
            }
334
        }
335 66
    }
336
337 66
    private static function maybeSetDefault(InterfaceSetDefault $ref, DOMElement $node): void
338
    {
339 66
        if ($node->hasAttribute('default')) {
340 1
            $ref->setDefault($node->getAttribute('default'));
341
        }
342 66
    }
343
344 66
    private function loadSequence(ElementContainer $elementContainer, DOMElement $node, int $max = null): void
345
    {
346
        $max =
347
            (
348 66
                (is_int($max) && (bool) $max) ||
349 66
                $node->getAttribute('maxOccurs') == 'unbounded' ||
350 66
                $node->getAttribute('maxOccurs') > 1
351
            )
352 66
                ? 2
353 66
                : null;
354
355 66
        self::againstDOMNodeList(
356 66
            $node,
357 66
            function (
358
                DOMElement $node,
359
                DOMElement $childNode
360
            ) use (
361 66
                $elementContainer,
362 66
                $max
363
            ): void {
364 66
                $this->loadSequenceChildNode(
365 66
                    $elementContainer,
366 66
                    $node,
367 66
                    $childNode,
368 66
                    $max
369
                );
370 66
            }
371
        );
372 66
    }
373
374 66
    private function loadSequenceChildNode(
375
        ElementContainer $elementContainer,
376
        DOMElement $node,
377
        DOMElement $childNode,
378
        ? int $max
379
    ): void {
380 66
        switch ($childNode->localName) {
381 66
            case 'sequence':
382 66
            case 'choice':
383 66
            case 'all':
384 66
                $this->loadSequence(
385 66
                    $elementContainer,
386 66
                    $childNode,
387 66
                    $max
388
                );
389 66
                break;
390 66
            case 'element':
391 66
                $this->loadSequenceChildNodeLoadElement(
392 66
                    $elementContainer,
393 66
                    $node,
394 66
                    $childNode,
395 66
                    $max
396
                );
397 66
                break;
398 66
            case 'group':
399 66
                $this->addGroupAsElement(
400 66
                    $elementContainer->getSchema(),
401 66
                    $node,
402 66
                    $childNode,
403 66
                    $elementContainer
404
                );
405 66
                break;
406
        }
407 66
    }
408
409 66
    private function loadSequenceChildNodeLoadElement(
410
        ElementContainer $elementContainer,
411
        DOMElement $node,
412
        DOMElement $childNode,
413
        ? int $max
414
    ): void {
415 66
        if ($childNode->hasAttribute('ref')) {
416 66
            $element = new ElementRef(
417 66
                $this->findElement($elementContainer->getSchema(), $node, $childNode->getAttribute('ref'))
418
            );
419 66
            $element->setDoc($this->getDocumentation($childNode));
420
421 66
            self::maybeSetMax($element, $childNode);
422 66
            self::maybeSetMin($element, $childNode);
423 66
            if ($childNode->hasAttribute('nillable')) {
424
                $element->setNil($childNode->getAttribute('nillable') == 'true');
425
            }
426 66
            if ($childNode->hasAttribute('form')) {
427 66
                $element->setQualified($childNode->getAttribute('form') == 'qualified');
428
            }
429
        } else {
430 66
            $element = $this->loadElement(
431 66
                $elementContainer->getSchema(),
432 66
                $childNode
433
            );
434
        }
435 66
        if ($max > 1) {
436
            /*
437
            * although one might think the typecast is not needed with $max being `? int $max` after passing > 1,
438
            * phpstan@a4f89fa still thinks it's possibly null.
439
            * see https://github.com/phpstan/phpstan/issues/577 for related issue
440
            */
441 66
            $element->setMax((int) $max);
442
        }
443 66
        $elementContainer->addElement($element);
444 66
    }
445
446 66
    private function addGroupAsElement(
447
        Schema $schema,
448
        DOMElement $node,
449
        DOMElement $childNode,
450
        ElementContainer $elementContainer
451
    ): void {
452 66
        $referencedGroup = $this->findGroup(
453 66
            $schema,
454 66
            $node,
455 66
            $childNode->getAttribute('ref')
456
        );
457
458 66
        $group = $this->loadGroupRef($referencedGroup, $childNode);
459 66
        $elementContainer->addElement($group);
460 66
    }
461
462 66
    private function loadGroup(Schema $schema, DOMElement $node): Closure
463
    {
464 66
        $group = new Group($schema, $node->getAttribute('name'));
465 66
        $group->setDoc($this->getDocumentation($node));
466 66
        $groupOriginal = $group;
467
468 66
        if ($node->hasAttribute('maxOccurs') || $node->hasAttribute('maxOccurs')) {
469 1
            $group = new GroupRef($group);
470
471 1
            if ($node->hasAttribute('maxOccurs')) {
472 1
                self::maybeSetMax($group, $node);
473
            }
474 1
            if ($node->hasAttribute('minOccurs')) {
475 1
                self::maybeSetMin($group, $node);
476
            }
477
        }
478
479 66
        $schema->addGroup($group);
480
481 66
        return function () use ($groupOriginal, $node): void {
482 66
            static::againstDOMNodeList(
483 66
                $node,
484 66
                function (DOMelement $node, DOMElement $childNode) use ($groupOriginal): void {
485 66
                    switch ($childNode->localName) {
486 66
                        case 'sequence':
487 66
                        case 'choice':
488 66
                        case 'all':
489 66
                            $this->loadSequence($groupOriginal, $childNode);
490 66
                            break;
491
                    }
492 66
                }
493
            );
494 66
        };
495
    }
496
497 66
    private function loadComplexType(Schema $schema, DOMElement $node, Closure $callback = null): Closure
498
    {
499
        /**
500
         * @var bool
501
         */
502 66
        $isSimple = false;
503
504 66
        self::againstDOMNodeList(
505 66
            $node,
506 66
            function (
507
                DOMElement $node,
508
                DOMElement $childNode
509
            ) use (
510 66
                &$isSimple
511
            ): void {
512 66
                if ($isSimple) {
513 1
                    return;
514
                }
515 66
                if ($childNode->localName === 'simpleContent') {
516 2
                    $isSimple = true;
517
                }
518 66
            }
519
        );
520
521 66
        $type = $isSimple ? new ComplexTypeSimpleContent($schema, $node->getAttribute('name')) : new ComplexType($schema, $node->getAttribute('name'));
522
523 66
        $type->setDoc($this->getDocumentation($node));
524 66
        if ($node->getAttribute('name')) {
525 66
            $schema->addType($type);
526
        }
527
528 66
        return function () use ($type, $node, $schema, $callback): void {
529 66
            $this->fillTypeNode($type, $node, true);
530
531 66
            self::againstDOMNodeList(
532 66
                $node,
533 66
                function (
534
                    DOMElement $node,
535
                    DOMElement $childNode
536
                ) use (
537 66
                    $schema,
538 66
                    $type
539
                ): void {
540 66
                    $this->loadComplexTypeFromChildNode(
541 66
                        $type,
542 66
                        $node,
543 66
                        $childNode,
544 66
                        $schema
545
                    );
546 66
                }
547
            );
548
549 66
            if ($callback instanceof Closure) {
550 66
                call_user_func($callback, $type);
551
            }
552 66
        };
553
    }
554
555 66
    private function loadComplexTypeFromChildNode(
556
        BaseComplexType $type,
557
        DOMElement $node,
558
        DOMElement $childNode,
559
        Schema $schema
560
    ): void {
561 66
        switch ($childNode->localName) {
562 66
            case 'sequence':
563 66
            case 'choice':
564 66
            case 'all':
565 66
                if ($type instanceof ElementContainer) {
566 66
                    $this->loadSequence(
567 66
                        $type,
568 66
                        $childNode
569
                    );
570
                }
571 66
                break;
572 66
            case 'attribute':
573 66
                $this->addAttributeFromAttributeOrRef(
574 66
                    $type,
575 66
                    $childNode,
576 66
                    $schema,
577 66
                    $node
578
                );
579 66
                break;
580 66
            case 'attributeGroup':
581 2
                $this->findSomethingLikeAttributeGroup(
582 2
                    $schema,
583 2
                    $node,
584 2
                    $childNode,
585 2
                    $type
586
                );
587 2
                break;
588 66
            case 'group':
589
                if (
590 1
                    $type instanceof ComplexType
591
                ) {
592 1
                    $this->addGroupAsElement(
593 1
                        $schema,
594 1
                        $node,
595 1
                        $childNode,
596 1
                        $type
597
                    );
598
                }
599 1
                break;
600
        }
601 66
    }
602
603 66
    private function loadSimpleType(Schema $schema, DOMElement $node, Closure $callback = null): Closure
604
    {
605 66
        $type = new SimpleType($schema, $node->getAttribute('name'));
606 66
        $type->setDoc($this->getDocumentation($node));
607 66
        if ($node->getAttribute('name')) {
608 66
            $schema->addType($type);
609
        }
610
611 66
        return function () use ($type, $node, $callback): void {
612 66
            $this->fillTypeNode($type, $node, true);
613
614 66
            self::againstDOMNodeList(
615 66
                $node,
616 66
                function (DOMElement $node, DOMElement $childNode) use ($type): void {
617 66
                    switch ($childNode->localName) {
618 66
                        case 'union':
619 66
                            $this->loadUnion($type, $childNode);
620 66
                            break;
621 66
                        case 'list':
622 66
                            $this->loadList($type, $childNode);
623 66
                            break;
624
                    }
625 66
                }
626
            );
627
628 66
            if ($callback instanceof Closure) {
629 66
                call_user_func($callback, $type);
630
            }
631 66
        };
632
    }
633
634 66
    private function loadList(SimpleType $type, DOMElement $node): void
635
    {
636 66
        if ($node->hasAttribute('itemType')) {
637
            /**
638
             * @var SimpleType
639
             */
640 66
            $listType = $this->findSomeType($type, $node, 'itemType');
641 66
            $type->setList($listType);
642
        } else {
643 66
            self::againstDOMNodeList(
644 66
                $node,
645 66
                function (
646
                    DOMElement $node,
647
                    DOMElement $childNode
648
                ) use (
649 66
                    $type
650
                ): void {
651 66
                    $this->loadTypeWithCallback(
652 66
                        $type->getSchema(),
653 66
                        $childNode,
654 66
                        function (SimpleType $list) use ($type): void {
655 66
                            $type->setList($list);
656 66
                        }
657
                    );
658 66
                }
659
            );
660
        }
661 66
    }
662
663 66
    private function findSomeType(
664
        SchemaItem $fromThis,
665
        DOMElement $node,
666
        string $attributeName
667
    ): SchemaItem {
668 66
        return $this->findSomeTypeFromAttribute(
669 66
            $fromThis,
670 66
            $node,
671 66
            $node->getAttribute($attributeName)
672
        );
673
    }
674
675 66
    private function findSomeTypeFromAttribute(
676
        SchemaItem $fromThis,
677
        DOMElement $node,
678
        string $attributeName
679
    ): SchemaItem {
680 66
        $out = $this->findType(
681 66
            $fromThis->getSchema(),
682 66
            $node,
683 66
            $attributeName
684
        );
685
686 66
        return $out;
687
    }
688
689 66
    private function loadUnion(SimpleType $type, DOMElement $node): void
690
    {
691 66
        if ($node->hasAttribute('memberTypes')) {
692 66
            $types = preg_split('/\s+/', $node->getAttribute('memberTypes'));
693 66
            foreach ($types as $typeName) {
694
                /**
695
                 * @var SimpleType
696
                 */
697 66
                $unionType = $this->findSomeTypeFromAttribute(
698 66
                    $type,
699 66
                    $node,
700 66
                    $typeName
701
                );
702 66
                $type->addUnion($unionType);
703
            }
704
        }
705 66
        self::againstDOMNodeList(
706 66
            $node,
707 66
            function (
708
                DOMElement $node,
709
                DOMElement $childNode
710
            ) use (
711 66
                $type
712
            ): void {
713 66
                $this->loadTypeWithCallback(
714 66
                    $type->getSchema(),
715 66
                    $childNode,
716 66
                    function (SimpleType $unType) use ($type): void {
717 66
                        $type->addUnion($unType);
718 66
                    }
719
                );
720 66
            }
721
        );
722 66
    }
723
724 66
    private function fillTypeNode(Type $type, DOMElement $node, bool $checkAbstract = false): void
725
    {
726 66
        if ($checkAbstract) {
727 66
            $type->setAbstract($node->getAttribute('abstract') === 'true' || $node->getAttribute('abstract') === '1');
728
        }
729
730 66
        self::againstDOMNodeList(
731 66
            $node,
732 66
            function (DOMElement $node, DOMElement $childNode) use ($type): void {
733 66
                switch ($childNode->localName) {
734 66
                    case 'restriction':
735 66
                        $this->loadRestriction($type, $childNode);
736 66
                        break;
737 66
                    case 'extension':
738 66
                        if ($type instanceof BaseComplexType) {
739 66
                            $this->loadExtension($type, $childNode);
740
                        }
741 66
                        break;
742 66
                    case 'simpleContent':
743 66
                    case 'complexContent':
744 66
                        $this->fillTypeNode($type, $childNode);
745 66
                        break;
746
                }
747 66
            }
748
        );
749 66
    }
750
751 66
    private function loadExtension(BaseComplexType $type, DOMElement $node): void
752
    {
753 66
        $extension = new Extension();
754 66
        $type->setExtension($extension);
755
756 66
        if ($node->hasAttribute('base')) {
757 66
            $this->findAndSetSomeBase(
758 66
                $type,
759 66
                $extension,
760 66
                $node
761
            );
762
        }
763 66
        $this->loadExtensionChildNodes($type, $node);
764 66
    }
765
766 66
    private function findAndSetSomeBase(
767
        Type $type,
768
        Base $setBaseOnThis,
769
        DOMElement $node
770
    ): void {
771
        /**
772
         * @var Type
773
         */
774 66
        $parent = $this->findSomeType($type, $node, 'base');
775 66
        $setBaseOnThis->setBase($parent);
776 66
    }
777
778 66
    private function loadExtensionChildNodes(
779
        BaseComplexType $type,
780
        DOMElement $node
781
    ): void {
782 66
        self::againstDOMNodeList(
783 66
            $node,
784 66
            function (
785
                DOMElement $node,
786
                DOMElement $childNode
787
            ) use (
788 66
                $type
789
            ): void {
790 66
                switch ($childNode->localName) {
791 66
                    case 'sequence':
792 66
                    case 'choice':
793 66
                    case 'all':
794 66
                        if ($type instanceof ElementContainer) {
795 66
                            $this->loadSequence(
796 66
                                $type,
797 66
                                $childNode
798
                            );
799
                        }
800 66
                        break;
801 66
                    case 'attribute':
802 66
                        $this->addAttributeFromAttributeOrRef(
803 66
                            $type,
804 66
                            $childNode,
805 66
                            $type->getSchema(),
806 66
                            $node
807
                        );
808 66
                        break;
809 66
                    case 'attributeGroup':
810 66
                        $this->findSomethingLikeAttributeGroup(
811 66
                            $type->getSchema(),
812 66
                            $node,
813 66
                            $childNode,
814 66
                            $type
815
                        );
816 66
                        break;
817
                }
818 66
            }
819
        );
820 66
    }
821
822 66
    private function loadRestriction(Type $type, DOMElement $node): void
823
    {
824 66
        $restriction = new Restriction();
825 66
        $type->setRestriction($restriction);
826 66
        if ($node->hasAttribute('base')) {
827 66
            $this->findAndSetSomeBase($type, $restriction, $node);
828
        } else {
829 66
            self::againstDOMNodeList(
830 66
                $node,
831 66
                function (
832
                    DOMElement $node,
833
                    DOMElement $childNode
834
                ) use (
835 66
                    $type,
836 66
                    $restriction
837
                ): void {
838 66
                    $this->loadTypeWithCallback(
839 66
                        $type->getSchema(),
840 66
                        $childNode,
841 66
                        function (Type $restType) use ($restriction): void {
842 66
                            $restriction->setBase($restType);
843 66
                        }
844
                    );
845 66
                }
846
            );
847
        }
848 66
        self::againstDOMNodeList(
849 66
            $node,
850 66
            function (
851
                DOMElement $node,
852
                DOMElement $childNode
853
            ) use (
854 66
                $restriction
855
            ): void {
856
                if (
857 66
                in_array(
858 66
                    $childNode->localName,
859
                    [
860 66
                        'enumeration',
861
                        'pattern',
862
                        'length',
863
                        'minLength',
864
                        'maxLength',
865
                        'minInclusive',
866
                        'maxInclusive',
867
                        'minExclusive',
868
                        'maxExclusive',
869
                        'fractionDigits',
870
                        'totalDigits',
871
                        'whiteSpace',
872
                    ],
873 66
                    true
874
                )
875
                ) {
876 66
                    $restriction->addCheck(
877 66
                        $childNode->localName,
878
                        [
879 66
                            'value' => $childNode->getAttribute('value'),
880 66
                            'doc' => $this->getDocumentation($childNode),
881
                        ]
882
                    );
883
                }
884 66
            }
885
        );
886 66
    }
887
888
    /**
889
     * @return mixed[]
890
     */
891 66
    private static function splitParts(DOMElement $node, string $typeName): array
892
    {
893 66
        $prefix = null;
894 66
        $name = $typeName;
895 66
        if (strpos($typeName, ':') !== false) {
896 66
            list($prefix, $name) = explode(':', $typeName);
897
        }
898
899
        /**
900
         * @psalm-suppress PossiblyNullArgument
901
         */
902 66
        $namespace = $node->lookupNamespaceUri($prefix);
903
904
        return array(
905 66
            $name,
906 66
            $namespace,
907 66
            $prefix,
908
        );
909
    }
910
911 66
    private function findAttributeItem(Schema $schema, DOMElement $node, string $typeName): AttributeItem
912
    {
913 66
        list($name, $namespace) = static::splitParts($node, $typeName);
914
915
        /**
916
         * @var string|null $namespace
917
         */
918 66
        $namespace = $namespace ?: $schema->getTargetNamespace();
919
920
        try {
921
            /**
922
             * @var AttributeItem $out
923
             */
924 66
            $out = $schema->findAttribute((string) $name, $namespace);
925
926 66
            return $out;
927
        } catch (TypeNotFoundException $e) {
928
            throw new TypeException(
929
                sprintf(
930
                    "Can't find %s named {%s}#%s, at line %d in %s ",
931
                    'attribute',
932
                    $namespace,
933
                    $name,
934
                    $node->getLineNo(),
935
                    $node->ownerDocument->documentURI
936
                ),
937
                0,
938
                $e
939
            );
940
        }
941
    }
942
943 66
    private function findAttributeGroup(Schema $schema, DOMElement $node, string $typeName): AttributeGroup
944
    {
945 66
        list($name, $namespace) = static::splitParts($node, $typeName);
946
947
        /**
948
         * @var string|null $namespace
949
         */
950 66
        $namespace = $namespace ?: $schema->getTargetNamespace();
951
952
        try {
953
            /**
954
             * @var AttributeGroup $out
955
             */
956 66
            $out = $schema->findAttributeGroup((string) $name, $namespace);
957
958 66
            return $out;
959
        } catch (TypeNotFoundException $e) {
960
            throw new TypeException(
961
                sprintf(
962
                    "Can't find %s named {%s}#%s, at line %d in %s ",
963
                    'attributegroup',
964
                    $namespace,
965
                    $name,
966
                    $node->getLineNo(),
967
                    $node->ownerDocument->documentURI
968
                ),
969
                0,
970
                $e
971
            );
972
        }
973
    }
974
975 66
    private function findElement(Schema $schema, DOMElement $node, string $typeName): ElementDef
976
    {
977 66
        list($name, $namespace) = static::splitParts($node, $typeName);
978
979
        /**
980
         * @var string|null $namespace
981
         */
982 66
        $namespace = $namespace ?: $schema->getTargetNamespace();
983
984
        try {
985 66
            return $schema->findElement((string) $name, $namespace);
986
        } catch (TypeNotFoundException $e) {
987
            throw new TypeException(
988
                sprintf(
989
                    "Can't find %s named {%s}#%s, at line %d in %s ",
990
                    'element',
991
                    $namespace,
992
                    $name,
993
                    $node->getLineNo(),
994
                    $node->ownerDocument->documentURI
995
                ),
996
                0,
997
                $e
998
            );
999
        }
1000
    }
1001
1002 66
    private function findGroup(Schema $schema, DOMElement $node, string $typeName): Group
1003
    {
1004 66
        list($name, $namespace) = static::splitParts($node, $typeName);
1005
1006
        /**
1007
         * @var string|null $namespace
1008
         */
1009 66
        $namespace = $namespace ?: $schema->getTargetNamespace();
1010
1011
        try {
1012
            /**
1013
             * @var Group $out
1014
             */
1015 66
            $out = $schema->findGroup((string) $name, $namespace);
1016
1017 66
            return $out;
1018
        } catch (TypeNotFoundException $e) {
1019
            throw new TypeException(
1020
                sprintf(
1021
                    "Can't find %s named {%s}#%s, at line %d in %s ",
1022
                    'group',
1023
                    $namespace,
1024
                    $name,
1025
                    $node->getLineNo(),
1026
                    $node->ownerDocument->documentURI
1027
                ),
1028
                0,
1029
                $e
1030
            );
1031
        }
1032
    }
1033
1034 66
    private function findType(Schema $schema, DOMElement $node, string $typeName): SchemaItem
1035
    {
1036 66
        list($name, $namespace) = static::splitParts($node, $typeName);
1037
1038
        /**
1039
         * @var string|null $namespace
1040
         */
1041 66
        $namespace = $namespace ?: $schema->getTargetNamespace();
1042
1043
        try {
1044
            /**
1045
             * @var SchemaItem $out
1046
             */
1047 66
            $out = $schema->findType((string) $name, $namespace);
1048
1049 66
            return $out;
1050
        } catch (TypeNotFoundException $e) {
1051
            throw new TypeException(
1052
                sprintf(
1053
                    "Can't find %s named {%s}#%s, at line %d in %s ",
1054
                    'type',
1055
                    $namespace,
1056
                    $name,
1057
                    $node->getLineNo(),
1058
                    $node->ownerDocument->documentURI
1059
                ),
1060
                0,
1061
                $e
1062
            );
1063
        }
1064
    }
1065
1066
    /**
1067
     * @return Closure
1068
     */
1069 66
    private function loadElementDef(Schema $schema, DOMElement $node): Closure
1070
    {
1071 66
        return $this->loadAttributeOrElementDef($schema, $node, false);
1072
    }
1073
1074 66
    private function fillItem(Item $element, DOMElement $node): void
1075
    {
1076
        /**
1077
         * @var bool
1078
         */
1079 66
        $skip = false;
1080 66
        self::againstDOMNodeList(
1081 66
            $node,
1082 66
            function (
1083
                DOMElement $node,
1084
                DOMElement $childNode
1085
            ) use (
1086 66
                $element,
1087 66
                &$skip
1088
            ): void {
1089
                if (
1090 66
                    !$skip &&
1091 66
                    in_array(
1092 66
                        $childNode->localName,
1093
                        [
1094 66
                            'complexType',
1095
                            'simpleType',
1096
                        ],
1097 66
                        true
1098
                    )
1099
                ) {
1100 66
                    $this->loadTypeWithCallback(
1101 66
                        $element->getSchema(),
1102 66
                        $childNode,
1103 66
                        function (Type $type) use ($element): void {
1104 66
                            $element->setType($type);
1105 66
                        }
1106
                    );
1107 66
                    $skip = true;
1108
                }
1109 66
            }
1110
        );
1111 66
        if ($skip) {
1112 66
            return;
1113
        }
1114 66
        $this->fillItemNonLocalType($element, $node);
1115 66
    }
1116
1117 66
    private function fillItemNonLocalType(Item $element, DOMElement $node): void
1118
    {
1119 66
        if ($node->getAttribute('type')) {
1120
            /**
1121
             * @var Type
1122
             */
1123 66
            $type = $this->findSomeType($element, $node, 'type');
1124
        } else {
1125
            /**
1126
             * @var Type
1127
             */
1128 66
            $type = $this->findSomeTypeFromAttribute(
1129 66
                $element,
1130 66
                $node,
1131 66
                ($node->lookupPrefix(self::XSD_NS).':anyType')
1132
            );
1133
        }
1134
1135 66
        $element->setType($type);
1136 66
    }
1137
1138 66
    private function loadImport(
1139
        Schema $schema,
1140
        DOMElement $node
1141
    ): Closure {
1142 66
        $namespace = $node->getAttribute('namespace');
1143 66
        $schemaLocation = $node->getAttribute('schemaLocation');
1144 66
        if (!$schemaLocation && isset($this->knownNamespaceSchemaLocations[$namespace])) {
1145 1
            $schemaLocation = $this->knownNamespaceSchemaLocations[$namespace];
1146
        }
1147
1148
        // postpone schema loading
1149 66
        if ($namespace && !$schemaLocation && !isset(self::$globalSchemaInfo[$namespace])) {
1150 3
            return function () use ($schema, $namespace): void {
1151 3
                if (!empty($this->loadedSchemas[$namespace])) {
1152 3
                    foreach ($this->loadedSchemas[$namespace] as $s) {
1153 3
                        $schema->addSchema($s, $namespace);
1154
                    }
1155
                }
1156 3
            };
1157 66
        } elseif ($namespace && !$schemaLocation && !empty($this->loadedSchemas[$namespace])) {
1158 1
            foreach ($this->loadedSchemas[$namespace] as $s) {
1159 1
                $schema->addSchema($s, $namespace);
1160
            }
1161
        }
1162
1163 66
        $base = urldecode($node->ownerDocument->documentURI);
1164 66
        $file = UrlUtils::resolveRelativeUrl($base, $schemaLocation);
1165
1166 66
        if (isset($this->loadedFiles[$file])) {
1167 66
            $schema->addSchema($this->loadedFiles[$file]);
1168
1169 66
            return function (): void {
1170 66
            };
1171
        }
1172
1173 3
        return $this->loadImportFresh($namespace, $schema, $file);
1174
    }
1175
1176 2
    private function loadRedefine(Schema $schema, DOMElement $node): Closure
1177
    {
1178 2
        $base = urldecode($node->ownerDocument->documentURI);
1179 2
        $file = UrlUtils::resolveRelativeUrl($base, $node->getAttribute('schemaLocation'));
1180
1181 2
        if (isset($this->loadedFiles[$file])) {
1182
            /* @var $redefined Schema */
1183 1
            $redefined = clone $this->loadedFiles[$file];
1184
1185 1
            if ($schema->getTargetNamespace() !== $redefined->getTargetNamespace()) {
1186 1
                $redefined->setTargetNamespace($schema->getTargetNamespace());
1187
            }
1188
1189 1
            $schema->addSchema($redefined);
1190
1191 1
            return function () use ($redefined, $node, $schema): void {
1192 1
                $callbacks = $this->schemaNode($redefined, $node, $schema);
1193 1
                foreach ($callbacks as $callback) {
1194 1
                    $callback();
1195
                }
1196 1
            };
1197
        }
1198
1199 1
        return $this->loadImportFresh((string) $schema->getTargetNamespace(), $schema, $file);
1200
    }
1201
1202 4
    private function createOrUseSchemaForNs(
1203
        Schema $schema,
1204
        string $namespace
1205
    ): Schema {
1206 4
        if (('' !== trim($namespace))) {
1207 3
            $newSchema = new Schema();
1208 3
            $newSchema->addSchema($this->getGlobalSchema());
1209 3
            $schema->addSchema($newSchema);
1210
        } else {
1211 1
            $newSchema = $schema;
1212
        }
1213
1214 4
        return $newSchema;
1215
    }
1216
1217
    private function loadImportFresh(
1218
        string $namespace,
1219
        Schema $schema,
1220
        string $file
1221
    ): Closure {
1222 4
        return function () use ($namespace, $schema, $file): void {
1223 4
            $dom = $this->getDOM(
1224 4
                isset($this->knownLocationSchemas[$file])
1225 1
                    ? $this->knownLocationSchemas[$file]
1226 4
                    : $file
1227
            );
1228
1229 4
            $schemaNew = $this->createOrUseSchemaForNs($schema, $namespace);
1230
1231 4
            $this->setLoadedFile($file, $schemaNew);
1232
1233 4
            $callbacks = $this->schemaNode($schemaNew, $dom->documentElement, $schema);
1234
1235 4
            foreach ($callbacks as $callback) {
1236 4
                $callback();
1237
            }
1238 4
        };
1239
    }
1240
1241
    /**
1242
     * @var Schema|null
1243
     */
1244
    protected $globalSchema;
1245
1246 66
    public function getGlobalSchema(): Schema
1247
    {
1248 66
        if (!($this->globalSchema instanceof Schema)) {
1249 66
            $callbacks = array();
1250 66
            $globalSchemas = array();
1251
            /**
1252
             * @var string $namespace
1253
             */
1254 66
            foreach (self::$globalSchemaInfo as $namespace => $uri) {
1255 66
                $this->setLoadedFile(
1256 66
                    $uri,
1257 66
                    $globalSchemas[$namespace] = $schema = new Schema()
1258
                );
1259 66
                $this->setLoadedSchema($namespace, $schema);
1260 66
                if ($namespace === self::XSD_NS) {
1261 66
                    $this->globalSchema = $schema;
1262
                }
1263 66
                $xml = $this->getDOM($this->knownLocationSchemas[$uri]);
1264 66
                $callbacks = array_merge($callbacks, $this->schemaNode($schema, $xml->documentElement));
1265
            }
1266
1267 66
            $globalSchemas[(string) static::XSD_NS]->addType(new SimpleType($globalSchemas[(string) static::XSD_NS], 'anySimpleType'));
1268 66
            $globalSchemas[(string) static::XSD_NS]->addType(new SimpleType($globalSchemas[(string) static::XSD_NS], 'anyType'));
1269
1270 66
            $globalSchemas[(string) static::XML_NS]->addSchema(
1271 66
                $globalSchemas[(string) static::XSD_NS],
1272 66
                (string) static::XSD_NS
1273
            );
1274 66
            $globalSchemas[(string) static::XSD_NS]->addSchema(
1275 66
                $globalSchemas[(string) static::XML_NS],
1276 66
                (string) static::XML_NS
1277
            );
1278
1279
            /**
1280
             * @var Closure
1281
             */
1282 66
            foreach ($callbacks as $callback) {
1283 66
                $callback();
1284
            }
1285
        }
1286
1287 66
        if (!($this->globalSchema instanceof Schema)) {
1288
            throw new TypeException('Global schema not discovered');
1289
        }
1290
1291 66
        return $this->globalSchema;
1292
    }
1293
1294
    /**
1295
     * @param DOMNode[] $nodes
1296
     */
1297 1
    public function readNodes(array $nodes, string $file = null): Schema
1298
    {
1299 1
        $rootSchema = new Schema();
1300 1
        $rootSchema->addSchema($this->getGlobalSchema());
1301
1302 1
        if ($file !== null) {
1303 1
            $this->setLoadedFile($file, $rootSchema);
1304
        }
1305
1306 1
        $all = array();
1307 1
        foreach ($nodes as $k => $node) {
1308 1
            if (($node instanceof \DOMElement) && $node->namespaceURI === self::XSD_NS && $node->localName == 'schema') {
1309 1
                $holderSchema = new Schema();
1310 1
                $holderSchema->addSchema($this->getGlobalSchema());
1311
1312 1
                $this->setLoadedSchemaFromElement($node, $holderSchema);
1313
1314 1
                $rootSchema->addSchema($holderSchema);
1315
1316 1
                $callbacks = $this->schemaNode($holderSchema, $node);
1317 1
                $all = array_merge($callbacks, $all);
1318
            }
1319
        }
1320
1321 1
        foreach ($all as $callback) {
1322 1
            call_user_func($callback);
1323
        }
1324
1325 1
        return $rootSchema;
1326
    }
1327
1328 65
    public function readNode(DOMElement $node, string $file = null): Schema
1329
    {
1330 65
        $rootSchema = new Schema();
1331 65
        $rootSchema->addSchema($this->getGlobalSchema());
1332
1333 65
        if ($file !== null) {
1334 64
            $this->setLoadedFile($file, $rootSchema);
1335
        }
1336
1337 65
        $this->setLoadedSchemaFromElement($node, $rootSchema);
1338
1339 65
        $callbacks = $this->schemaNode($rootSchema, $node);
1340
1341 65
        foreach ($callbacks as $callback) {
1342 59
            call_user_func($callback);
1343
        }
1344
1345 65
        return $rootSchema;
1346
    }
1347
1348
    /**
1349
     * @throws IOException
1350
     */
1351 62
    public function readString(string $content, string $file = 'schema.xsd'): Schema
1352
    {
1353 62
        $xml = new DOMDocument('1.0', 'UTF-8');
1354 62
        libxml_use_internal_errors(true);
1355 62
        if (!@$xml->loadXML($content)) {
1356 1
            throw new IOException("Can't load the schema", 0, $this->extractErrorMessage());
1357
        }
1358 61
        libxml_use_internal_errors(false);
1359 61
        $xml->documentURI = $file;
1360
1361 61
        return $this->readNode($xml->documentElement, $file);
1362
    }
1363
1364
    /**
1365
     * @throws IOException
1366
     */
1367 4
    public function readFile(string $file): Schema
1368
    {
1369 4
        $xml = $this->getDOM($file);
1370
1371 3
        return $this->readNode($xml->documentElement, $file);
1372
    }
1373
1374
    /**
1375
     * @throws IOException
1376
     */
1377 67
    private function getDOM(string $file): DOMDocument
1378
    {
1379 67
        $xml = new DOMDocument('1.0', 'UTF-8');
1380 67
        libxml_use_internal_errors(true);
1381 67
        if (!@$xml->load($file)) {
1382 1
            libxml_use_internal_errors(false);
1383 1
            throw new IOException("Can't load the file '$file'", 0, $this->extractErrorMessage());
1384
        }
1385 66
        libxml_use_internal_errors(false);
1386
1387 66
        return $xml;
1388
    }
1389
1390 66
    private static function againstDOMNodeList(
1391
        DOMElement $node,
1392
        Closure $againstNodeList
1393
    ): void {
1394 66
        $limit = $node->childNodes->length;
1395 66
        for ($i = 0; $i < $limit; ++$i) {
1396
            /**
1397
             * @var DOMNode
1398
             */
1399 66
            $childNode = $node->childNodes->item($i);
1400
1401 66
            if ($childNode instanceof DOMElement) {
1402 66
                $againstNodeList(
1403 66
                    $node,
1404 66
                    $childNode
1405
                );
1406
            }
1407
        }
1408 66
    }
1409
1410 66
    private function loadTypeWithCallback(
1411
        Schema $schema,
1412
        DOMElement $childNode,
1413
        Closure $callback
1414
    ): void {
1415
        /**
1416
         * @var Closure|null $func
1417
         */
1418 66
        $func = null;
1419
1420 66
        switch ($childNode->localName) {
1421 66
            case 'complexType':
1422 66
                $func = $this->loadComplexType($schema, $childNode, $callback);
1423 66
                break;
1424 66
            case 'simpleType':
1425 66
                $func = $this->loadSimpleType($schema, $childNode, $callback);
1426 66
                break;
1427
        }
1428
1429 66
        if ($func instanceof Closure) {
1430 66
            call_user_func($func);
1431
        }
1432 66
    }
1433
1434 66
    private function loadElement(
1435
        Schema $schema,
1436
        DOMElement $node
1437
    ): Element {
1438 66
        $element = new Element($schema, $node->getAttribute('name'));
1439 66
        $element->setDoc($this->getDocumentation($node));
1440
1441 66
        $this->fillItem($element, $node);
1442
1443 66
        self::maybeSetMax($element, $node);
1444 66
        self::maybeSetMin($element, $node);
1445 66
        self::maybeSetDefault($element, $node);
1446
1447 66
        $xp = new \DOMXPath($node->ownerDocument);
1448 66
        $xp->registerNamespace('xs', 'http://www.w3.org/2001/XMLSchema');
1449
1450 66
        if ($xp->query('ancestor::xs:choice', $node)->length) {
1451 66
            $element->setMin(0);
1452
        }
1453
1454 66
        if ($node->hasAttribute('nillable')) {
1455 4
            $element->setNil($node->getAttribute('nillable') == 'true');
1456
        }
1457 66
        if ($node->hasAttribute('form')) {
1458 5
            $element->setQualified($node->getAttribute('form') == 'qualified');
1459
        }
1460
1461 66
        return $element;
1462
    }
1463
1464 66
    private function addAttributeFromAttributeOrRef(
1465
        BaseComplexType $type,
1466
        DOMElement $childNode,
1467
        Schema $schema,
1468
        DOMElement $node
1469
    ): void {
1470 66
        $attribute = $this->getAttributeFromAttributeOrRef(
1471 66
            $childNode,
1472 66
            $schema,
1473 66
            $node
1474
        );
1475
1476 66
        $type->addAttribute($attribute);
1477 66
    }
1478
1479 66
    private function findSomethingLikeAttributeGroup(
1480
        Schema $schema,
1481
        DOMElement $node,
1482
        DOMElement $childNode,
1483
        AttributeContainer $addToThis
1484
    ): void {
1485 66
        $attribute = $this->findAttributeGroup($schema, $node, $childNode->getAttribute('ref'));
1486 66
        $addToThis->addAttribute($attribute);
1487 66
    }
1488
1489 66
    private function setLoadedFile(string $key, Schema $schema): void
1490
    {
1491 66
        $this->loadedFiles[$key] = $schema;
1492 66
    }
1493
1494 66
    private function setLoadedSchemaFromElement(DOMElement $node, Schema $schema): void
1495
    {
1496 66
        if ($node->hasAttribute('targetNamespace')) {
1497 66
            $this->setLoadedSchema($node->getAttribute('targetNamespace'), $schema);
1498
        }
1499 66
    }
1500
1501 66
    private function setLoadedSchema(string $namespace, Schema $schema): void
1502
    {
1503 66
        if (!isset($this->loadedSchemas[$namespace])) {
1504 66
            $this->loadedSchemas[$namespace] = array();
1505
        }
1506 66
        if (!in_array($schema, $this->loadedSchemas[$namespace], true)) {
1507 66
            $this->loadedSchemas[$namespace][] = $schema;
1508
        }
1509 66
    }
1510
1511 66
    private function setSchemaThingsFromNode(
1512
        Schema $schema,
1513
        DOMElement $node,
1514
        Schema $parent = null
1515
    ): void {
1516 66
        $schema->setDoc($this->getDocumentation($node));
1517
1518 66
        if ($node->hasAttribute('targetNamespace')) {
1519 66
            $schema->setTargetNamespace($node->getAttribute('targetNamespace'));
1520 1
        } elseif ($parent instanceof Schema) {
1521 1
            $schema->setTargetNamespace($parent->getTargetNamespace());
1522
        }
1523 66
        $schema->setElementsQualification($node->getAttribute('elementFormDefault') == 'qualified');
1524 66
        $schema->setAttributesQualification($node->getAttribute('attributeFormDefault') == 'qualified');
1525 66
        $schema->setDoc($this->getDocumentation($node));
1526 66
    }
1527
}
1528