Passed
Pull Request — master (#56)
by
unknown
02:29
created

SchemaReader::setReportCallback()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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