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