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