Passed
Push — master ( 092a85...d47c33 )
by Asmir
02:02
created

SchemaReader::loadExtensionChildNodes()   C

Complexity

Conditions 7
Paths 1

Size

Total Lines 39
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 7

Importance

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