1
|
|
|
<?php |
2
|
|
|
namespace GoetasWebservices\XML\XSDReader; |
3
|
|
|
|
4
|
|
|
use DOMDocument; |
5
|
|
|
use DOMElement; |
6
|
|
|
use DOMNode; |
7
|
|
|
use GoetasWebservices\XML\XSDReader\Exception\IOException; |
8
|
|
|
use GoetasWebservices\XML\XSDReader\Exception\TypeException; |
9
|
|
|
use GoetasWebservices\XML\XSDReader\Schema\Attribute\Attribute; |
10
|
|
|
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeDef; |
11
|
|
|
use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeRef; |
12
|
|
|
use GoetasWebservices\XML\XSDReader\Schema\Attribute\Group as AttributeGroup; |
13
|
|
|
use GoetasWebservices\XML\XSDReader\Schema\Element\Element; |
14
|
|
|
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementContainer; |
15
|
|
|
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementDef; |
16
|
|
|
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementItem; |
17
|
|
|
use GoetasWebservices\XML\XSDReader\Schema\Element\ElementRef; |
18
|
|
|
use GoetasWebservices\XML\XSDReader\Schema\Element\Group; |
19
|
|
|
use GoetasWebservices\XML\XSDReader\Schema\Element\GroupRef; |
20
|
|
|
use GoetasWebservices\XML\XSDReader\Schema\Exception\TypeNotFoundException; |
21
|
|
|
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Extension; |
22
|
|
|
use GoetasWebservices\XML\XSDReader\Schema\Inheritance\Restriction; |
23
|
|
|
use GoetasWebservices\XML\XSDReader\Schema\Item; |
24
|
|
|
use GoetasWebservices\XML\XSDReader\Schema\Schema; |
25
|
|
|
use GoetasWebservices\XML\XSDReader\Schema\Type\BaseComplexType; |
26
|
|
|
use GoetasWebservices\XML\XSDReader\Schema\Type\ComplexType; |
27
|
|
|
use GoetasWebservices\XML\XSDReader\Schema\Type\ComplexTypeSimpleContent; |
28
|
|
|
use GoetasWebservices\XML\XSDReader\Schema\Type\SimpleType; |
29
|
|
|
use GoetasWebservices\XML\XSDReader\Schema\Type\Type; |
30
|
|
|
use GoetasWebservices\XML\XSDReader\Utils\UrlUtils; |
31
|
|
|
|
32
|
|
|
class SchemaReader |
33
|
|
|
{ |
34
|
|
|
|
35
|
|
|
const XSD_NS = "http://www.w3.org/2001/XMLSchema"; |
36
|
|
|
|
37
|
|
|
const XML_NS = "http://www.w3.org/XML/1998/namespace"; |
38
|
|
|
|
39
|
|
|
private $loadedFiles = array(); |
40
|
|
|
|
41
|
|
|
private $knownLocationSchemas = array(); |
42
|
|
|
|
43
|
|
|
private static $globalSchemaInfo = array( |
44
|
|
|
self::XML_NS => 'http://www.w3.org/2001/xml.xsd', |
45
|
|
|
self::XSD_NS => 'http://www.w3.org/2001/XMLSchema.xsd' |
46
|
|
|
); |
47
|
|
|
|
48
|
53 |
|
public function __construct() |
49
|
|
|
{ |
50
|
53 |
|
$this->addKnownSchemaLocation('http://www.w3.org/2001/xml.xsd', __DIR__ . '/Resources/xml.xsd'); |
51
|
53 |
|
$this->addKnownSchemaLocation('http://www.w3.org/2001/XMLSchema.xsd', __DIR__ . '/Resources/XMLSchema.xsd'); |
52
|
53 |
|
$this->addKnownSchemaLocation('http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd', __DIR__ . '/Resources/oasis-200401-wss-wssecurity-secext-1.0.xsd'); |
53
|
53 |
|
$this->addKnownSchemaLocation('http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd', __DIR__ . '/Resources/oasis-200401-wss-wssecurity-utility-1.0.xsd'); |
54
|
53 |
|
$this->addKnownSchemaLocation('https://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd', __DIR__ . '/Resources/xmldsig-core-schema.xsd'); |
55
|
53 |
|
$this->addKnownSchemaLocation('http://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd', __DIR__ . '/Resources/xmldsig-core-schema.xsd'); |
56
|
53 |
|
} |
57
|
|
|
|
58
|
53 |
|
public function addKnownSchemaLocation($remote, $local) |
59
|
|
|
{ |
60
|
53 |
|
$this->knownLocationSchemas[$remote] = $local; |
61
|
53 |
|
} |
62
|
|
|
|
63
|
44 |
|
private function loadAttributeGroup(Schema $schema, DOMElement $node) |
64
|
|
|
{ |
65
|
44 |
|
$attGroup = new AttributeGroup($schema, $node->getAttribute("name")); |
66
|
44 |
|
$attGroup->setDoc($this->getDocumentation($node)); |
67
|
44 |
|
$schema->addAttributeGroup($attGroup); |
68
|
|
|
|
69
|
|
|
return function () use ($schema, $node, $attGroup) { |
70
|
44 |
|
foreach ($node->childNodes as $childNode) { |
71
|
44 |
|
switch ($childNode->localName) { |
72
|
44 |
View Code Duplication |
case 'attribute': |
|
|
|
|
73
|
44 |
|
if ($childNode->hasAttribute("ref")) { |
74
|
44 |
|
$attribute = $this->findSomething('findAttribute', $schema, $node, $childNode->getAttribute("ref")); |
75
|
44 |
|
} else { |
76
|
44 |
|
$attribute = $this->loadAttribute($schema, $childNode); |
77
|
|
|
} |
78
|
44 |
|
$attGroup->addAttribute($attribute); |
|
|
|
|
79
|
44 |
|
break; |
80
|
44 |
View Code Duplication |
case 'attributeGroup': |
|
|
|
|
81
|
|
|
|
82
|
1 |
|
$attribute = $this->findSomething('findAttributeGroup', $schema, $node, $childNode->getAttribute("ref")); |
83
|
1 |
|
$attGroup->addAttribute($attribute); |
|
|
|
|
84
|
1 |
|
break; |
85
|
44 |
|
} |
86
|
44 |
|
} |
87
|
44 |
|
}; |
88
|
|
|
} |
89
|
|
|
|
90
|
44 |
|
private function loadAttribute(Schema $schema, DOMElement $node) |
91
|
|
|
{ |
92
|
44 |
|
$attribute = new Attribute($schema, $node->getAttribute("name")); |
93
|
44 |
|
$attribute->setDoc($this->getDocumentation($node)); |
94
|
44 |
|
$this->fillItem($attribute, $node); |
95
|
|
|
|
96
|
44 |
|
if ($node->hasAttribute("nillable")) { |
97
|
1 |
|
$attribute->setNil($node->getAttribute("nillable") == "true"); |
98
|
1 |
|
} |
99
|
44 |
|
if ($node->hasAttribute("form")) { |
100
|
1 |
|
$attribute->setQualified($node->getAttribute("form") == "qualified"); |
101
|
1 |
|
} |
102
|
44 |
|
if ($node->hasAttribute("use")) { |
103
|
44 |
|
$attribute->setUse($node->getAttribute("use")); |
104
|
44 |
|
} |
105
|
44 |
|
return $attribute; |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
|
109
|
44 |
View Code Duplication |
private function loadAttributeDef(Schema $schema, DOMElement $node) |
|
|
|
|
110
|
|
|
{ |
111
|
44 |
|
$attribute = new AttributeDef($schema, $node->getAttribute("name")); |
112
|
|
|
|
113
|
44 |
|
$schema->addAttribute($attribute); |
114
|
|
|
|
115
|
|
|
return function () use ($attribute, $schema, $node) { |
116
|
44 |
|
$this->fillItem($attribute, $node); |
117
|
44 |
|
}; |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
/** |
121
|
|
|
* @param DOMElement $node |
122
|
|
|
* @return string |
123
|
|
|
*/ |
124
|
44 |
|
private function getDocumentation(DOMElement $node) |
125
|
|
|
{ |
126
|
44 |
|
$doc = ''; |
127
|
44 |
|
foreach ($node->childNodes as $childNode) { |
128
|
44 |
|
if ($childNode->localName == "annotation") { |
129
|
44 |
|
foreach ($childNode->childNodes as $subChildNode) { |
130
|
44 |
|
if ($subChildNode->localName == "documentation") { |
131
|
44 |
|
$doc .= ($subChildNode->nodeValue); |
132
|
44 |
|
} |
133
|
44 |
|
} |
134
|
44 |
|
} |
135
|
44 |
|
} |
136
|
44 |
|
$doc = preg_replace('/[\t ]+/', ' ', $doc); |
137
|
44 |
|
return trim($doc); |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
/** |
141
|
|
|
* |
142
|
|
|
* @param Schema $schema |
143
|
|
|
* @param DOMElement $node |
144
|
|
|
* @param Schema $parent |
145
|
|
|
* @return array |
146
|
|
|
*/ |
147
|
44 |
|
private function schemaNode(Schema $schema, DOMElement $node, Schema $parent = null) |
148
|
|
|
{ |
149
|
44 |
|
$schema->setDoc($this->getDocumentation($node)); |
150
|
|
|
|
151
|
44 |
|
if ($node->hasAttribute("targetNamespace")) { |
152
|
44 |
|
$schema->setTargetNamespace($node->getAttribute("targetNamespace")); |
153
|
44 |
|
} elseif ($parent) { |
154
|
|
|
$schema->setTargetNamespace($parent->getTargetNamespace()); |
155
|
|
|
} |
156
|
44 |
|
$schema->setElementsQualification($node->getAttribute("elementFormDefault") == "qualified"); |
157
|
44 |
|
$schema->setAttributesQualification($node->getAttribute("attributeFormDefault") == "qualified"); |
158
|
44 |
|
$schema->setDoc($this->getDocumentation($node)); |
159
|
44 |
|
$functions = array(); |
160
|
|
|
|
161
|
44 |
|
foreach ($node->childNodes as $childNode) { |
162
|
44 |
|
switch ($childNode->localName) { |
163
|
44 |
|
case 'include': |
164
|
44 |
|
case 'import': |
165
|
44 |
|
$functions[] = $this->loadImport($schema, $childNode); |
166
|
44 |
|
break; |
167
|
44 |
|
case 'element': |
168
|
44 |
|
$functions[] = $this->loadElementDef($schema, $childNode); |
169
|
44 |
|
break; |
170
|
44 |
|
case 'attribute': |
171
|
44 |
|
$functions[] = $this->loadAttributeDef($schema, $childNode); |
172
|
44 |
|
break; |
173
|
44 |
|
case 'attributeGroup': |
174
|
44 |
|
$functions[] = $this->loadAttributeGroup($schema, $childNode); |
175
|
44 |
|
break; |
176
|
44 |
|
case 'group': |
177
|
44 |
|
$functions[] = $this->loadGroup($schema, $childNode); |
178
|
44 |
|
break; |
179
|
44 |
|
case 'complexType': |
180
|
44 |
|
$functions[] = $this->loadComplexType($schema, $childNode); |
181
|
44 |
|
break; |
182
|
44 |
|
case 'simpleType': |
183
|
44 |
|
$functions[] = $this->loadSimpleType($schema, $childNode); |
184
|
44 |
|
break; |
185
|
44 |
|
} |
186
|
44 |
|
} |
187
|
|
|
|
188
|
44 |
|
return $functions; |
189
|
|
|
} |
190
|
|
|
|
191
|
44 |
|
private function loadElement(Schema $schema, DOMElement $node) |
192
|
|
|
{ |
193
|
44 |
|
$element = new Element($schema, $node->getAttribute("name")); |
194
|
44 |
|
$element->setDoc($this->getDocumentation($node)); |
195
|
|
|
|
196
|
44 |
|
$this->fillItem($element, $node); |
197
|
|
|
|
198
|
44 |
|
if ($node->hasAttribute("maxOccurs")) { |
199
|
44 |
|
$element->setMax($node->getAttribute("maxOccurs") == "unbounded" ? -1 : (int)$node->getAttribute("maxOccurs")); |
200
|
44 |
|
} |
201
|
44 |
|
if ($node->hasAttribute("minOccurs")) { |
202
|
44 |
|
$element->setMin((int)$node->getAttribute("minOccurs")); |
203
|
44 |
|
} |
204
|
|
|
|
205
|
44 |
|
$xp = new \DOMXPath($node->ownerDocument); |
206
|
44 |
|
$xp->registerNamespace('xs', 'http://www.w3.org/2001/XMLSchema'); |
207
|
|
|
|
208
|
44 |
|
if ($xp->query('ancestor::xs:choice', $node)->length) { |
209
|
44 |
|
$element->setMin(0); |
210
|
44 |
|
} |
211
|
|
|
|
212
|
44 |
|
if ($node->hasAttribute("nillable")) { |
213
|
3 |
|
$element->setNil($node->getAttribute("nillable") == "true"); |
214
|
3 |
|
} |
215
|
44 |
|
if ($node->hasAttribute("form")) { |
216
|
3 |
|
$element->setQualified($node->getAttribute("form") == "qualified"); |
217
|
3 |
|
} |
218
|
44 |
|
return $element; |
219
|
|
|
} |
220
|
|
|
|
221
|
44 |
|
private function loadGroupRef(Group $referenced, DOMElement $node) |
222
|
|
|
{ |
223
|
44 |
|
$ref = new GroupRef($referenced); |
224
|
44 |
|
$ref->setDoc($this->getDocumentation($node)); |
225
|
|
|
|
226
|
44 |
View Code Duplication |
if ($node->hasAttribute("maxOccurs")) { |
|
|
|
|
227
|
44 |
|
$ref->setMax($node->getAttribute("maxOccurs") == "unbounded" ? -1 : (int)$node->getAttribute("maxOccurs")); |
228
|
44 |
|
} |
229
|
44 |
|
if ($node->hasAttribute("minOccurs")) { |
230
|
44 |
|
$ref->setMin((int)$node->getAttribute("minOccurs")); |
231
|
44 |
|
} |
232
|
|
|
|
233
|
44 |
|
return $ref; |
234
|
|
|
} |
235
|
|
|
|
236
|
44 |
|
private function loadElementRef(ElementDef $referenced, DOMElement $node) |
237
|
|
|
{ |
238
|
44 |
|
$ref = new ElementRef($referenced); |
239
|
44 |
|
$ref->setDoc($this->getDocumentation($node)); |
240
|
|
|
|
241
|
44 |
View Code Duplication |
if ($node->hasAttribute("maxOccurs")) { |
|
|
|
|
242
|
44 |
|
$ref->setMax($node->getAttribute("maxOccurs") == "unbounded" ? -1 : (int)$node->getAttribute("maxOccurs")); |
243
|
44 |
|
} |
244
|
44 |
|
if ($node->hasAttribute("minOccurs")) { |
245
|
44 |
|
$ref->setMin((int)$node->getAttribute("minOccurs")); |
246
|
44 |
|
} |
247
|
44 |
|
if ($node->hasAttribute("nillable")) { |
248
|
|
|
$ref->setNil($node->getAttribute("nillable") == "true"); |
249
|
|
|
} |
250
|
44 |
|
if ($node->hasAttribute("form")) { |
251
|
|
|
$ref->setQualified($node->getAttribute("form") == "qualified"); |
252
|
|
|
} |
253
|
|
|
|
254
|
44 |
|
return $ref; |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
|
258
|
44 |
|
private function loadAttributeRef(AttributeDef $referencedAttribiute, DOMElement $node) |
259
|
|
|
{ |
260
|
44 |
|
$attribute = new AttributeRef($referencedAttribiute); |
261
|
44 |
|
$attribute->setDoc($this->getDocumentation($node)); |
262
|
|
|
|
263
|
44 |
|
if ($node->hasAttribute("nillable")) { |
264
|
|
|
$attribute->setNil($node->getAttribute("nillable") == "true"); |
265
|
|
|
} |
266
|
44 |
|
if ($node->hasAttribute("form")) { |
267
|
|
|
$attribute->setQualified($node->getAttribute("form") == "qualified"); |
268
|
|
|
} |
269
|
44 |
|
if ($node->hasAttribute("use")) { |
270
|
|
|
$attribute->setUse($node->getAttribute("use")); |
271
|
|
|
} |
272
|
44 |
|
return $attribute; |
273
|
|
|
} |
274
|
|
|
|
275
|
44 |
|
private function loadSequence(ElementContainer $elementContainer, DOMElement $node, $max = null) |
276
|
|
|
{ |
277
|
44 |
|
$max = $max || $node->getAttribute("maxOccurs") == "unbounded" || $node->getAttribute("maxOccurs") > 1 ? 2 : null; |
278
|
|
|
|
279
|
44 |
|
foreach ($node->childNodes as $childNode) { |
280
|
|
|
|
281
|
44 |
|
switch ($childNode->localName) { |
282
|
44 |
|
case 'choice': |
283
|
44 |
|
case 'sequence': |
284
|
44 |
|
case 'all': |
285
|
44 |
|
$this->loadSequence($elementContainer, $childNode, $max); |
286
|
44 |
|
break; |
287
|
44 |
|
case 'element': |
288
|
44 |
|
if ($childNode->hasAttribute("ref")) { |
289
|
44 |
|
$referencedElement = $this->findSomething('findElement', $elementContainer->getSchema(), $node, $childNode->getAttribute("ref")); |
290
|
44 |
|
$element = $this->loadElementRef($referencedElement, $childNode); |
|
|
|
|
291
|
44 |
|
} else { |
292
|
44 |
|
$element = $this->loadElement($elementContainer->getSchema(), $childNode); |
293
|
|
|
} |
294
|
44 |
|
if ($max) { |
|
|
|
|
295
|
44 |
|
$element->setMax($max); |
296
|
44 |
|
} |
297
|
44 |
|
$elementContainer->addElement($element); |
298
|
44 |
|
break; |
299
|
44 |
|
case 'group': |
300
|
44 |
|
$referencedGroup = $this->findSomething('findGroup', $elementContainer->getSchema(), $node, $childNode->getAttribute("ref")); |
301
|
|
|
|
302
|
44 |
|
$group = $this->loadGroupRef($referencedGroup, $childNode); |
|
|
|
|
303
|
44 |
|
$elementContainer->addElement($group); |
304
|
44 |
|
break; |
305
|
44 |
|
} |
306
|
44 |
|
} |
307
|
44 |
|
} |
308
|
|
|
|
309
|
44 |
|
private function loadGroup(Schema $schema, DOMElement $node) |
310
|
|
|
{ |
311
|
44 |
|
$group = new Group($schema, $node->getAttribute("name")); |
312
|
44 |
|
$group->setDoc($this->getDocumentation($node)); |
313
|
|
|
|
314
|
44 |
View Code Duplication |
if ($node->hasAttribute("maxOccurs")) { |
|
|
|
|
315
|
|
|
$group->setMax($node->getAttribute("maxOccurs") == "unbounded" ? -1 : (int)$node->getAttribute("maxOccurs")); |
316
|
|
|
} |
317
|
44 |
|
if ($node->hasAttribute("minOccurs")) { |
318
|
|
|
$group->setMin((int)$node->getAttribute("minOccurs")); |
319
|
|
|
} |
320
|
|
|
|
321
|
44 |
|
$schema->addGroup($group); |
322
|
|
|
|
323
|
|
|
return function () use ($group, $node) { |
324
|
44 |
|
foreach ($node->childNodes as $childNode) { |
325
|
44 |
|
switch ($childNode->localName) { |
326
|
44 |
|
case 'sequence': |
327
|
44 |
|
case 'choice': |
328
|
44 |
|
case 'all': |
329
|
44 |
|
$this->loadSequence($group, $childNode); |
330
|
44 |
|
break; |
331
|
44 |
|
} |
332
|
44 |
|
} |
333
|
44 |
|
}; |
334
|
|
|
} |
335
|
|
|
|
336
|
44 |
|
private function loadComplexType(Schema $schema, DOMElement $node, $callback = null) |
337
|
|
|
{ |
338
|
44 |
|
$isSimple = false; |
339
|
|
|
|
340
|
44 |
|
foreach ($node->childNodes as $childNode) { |
341
|
44 |
|
if ($childNode->localName === "simpleContent") { |
342
|
2 |
|
$isSimple = true; |
343
|
2 |
|
break; |
344
|
|
|
} |
345
|
44 |
|
} |
346
|
|
|
|
347
|
44 |
|
$type = $isSimple ? new ComplexTypeSimpleContent($schema, $node->getAttribute("name")) : new ComplexType($schema, $node->getAttribute("name")); |
348
|
|
|
|
349
|
44 |
|
$type->setDoc($this->getDocumentation($node)); |
350
|
44 |
|
if ($node->getAttribute("name")) { |
351
|
44 |
|
$schema->addType($type); |
352
|
44 |
|
} |
353
|
|
|
|
354
|
|
|
return function () use ($type, $node, $schema, $callback) { |
355
|
|
|
|
356
|
44 |
|
$this->fillTypeNode($type, $node); |
357
|
|
|
|
358
|
44 |
|
foreach ($node->childNodes as $childNode) { |
359
|
44 |
|
switch ($childNode->localName) { |
360
|
44 |
|
case 'sequence': |
361
|
44 |
|
case 'choice': |
362
|
44 |
|
case 'all': |
363
|
44 |
|
$this->loadSequence($type, $childNode); |
|
|
|
|
364
|
44 |
|
break; |
365
|
44 |
|
case 'attribute': |
366
|
44 |
|
if ($childNode->hasAttribute("ref")) { |
367
|
44 |
|
$referencedAttribute = $this->findSomething('findAttribute', $schema, $node, $childNode->getAttribute("ref")); |
368
|
44 |
|
$attribute = $this->loadAttributeRef($referencedAttribute, $childNode); |
|
|
|
|
369
|
44 |
|
} else { |
370
|
44 |
|
$attribute = $this->loadAttribute($schema, $childNode); |
371
|
|
|
} |
372
|
|
|
|
373
|
44 |
|
$type->addAttribute($attribute); |
374
|
44 |
|
break; |
375
|
44 |
View Code Duplication |
case 'attributeGroup': |
|
|
|
|
376
|
2 |
|
$attribute = $this->findSomething('findAttributeGroup', $schema, $node, $childNode->getAttribute("ref")); |
377
|
2 |
|
$type->addAttribute($attribute); |
|
|
|
|
378
|
2 |
|
break; |
379
|
44 |
|
} |
380
|
44 |
|
} |
381
|
|
|
|
382
|
44 |
|
if ($callback) { |
383
|
44 |
|
call_user_func($callback, $type); |
384
|
44 |
|
} |
385
|
44 |
|
}; |
386
|
|
|
} |
387
|
|
|
|
388
|
44 |
|
private function loadSimpleType(Schema $schema, DOMElement $node, $callback = null) |
389
|
|
|
{ |
390
|
44 |
|
$type = new SimpleType($schema, $node->getAttribute("name")); |
391
|
44 |
|
$type->setDoc($this->getDocumentation($node)); |
392
|
44 |
|
if ($node->getAttribute("name")) { |
393
|
44 |
|
$schema->addType($type); |
394
|
44 |
|
} |
395
|
|
|
|
396
|
|
|
return function () use ($type, $node, $callback) { |
397
|
44 |
|
$this->fillTypeNode($type, $node); |
398
|
|
|
|
399
|
44 |
|
foreach ($node->childNodes as $childNode) { |
400
|
44 |
|
switch ($childNode->localName) { |
401
|
44 |
|
case 'union': |
402
|
44 |
|
$this->loadUnion($type, $childNode); |
403
|
44 |
|
break; |
404
|
44 |
|
case 'list': |
405
|
44 |
|
$this->loadList($type, $childNode); |
406
|
44 |
|
break; |
407
|
44 |
|
} |
408
|
44 |
|
} |
409
|
|
|
|
410
|
44 |
|
if ($callback) { |
411
|
44 |
|
call_user_func($callback, $type); |
412
|
44 |
|
} |
413
|
44 |
|
}; |
414
|
|
|
} |
415
|
|
|
|
416
|
44 |
|
private function loadList(SimpleType $type, DOMElement $node) |
417
|
|
|
{ |
418
|
44 |
View Code Duplication |
if ($node->hasAttribute("itemType")) { |
|
|
|
|
419
|
44 |
|
$type->setList($this->findSomething('findType', $type->getSchema(), $node, $node->getAttribute("itemType"))); |
|
|
|
|
420
|
44 |
|
} else { |
421
|
|
|
$addCallback = function ($list) use ($type) { |
422
|
44 |
|
$type->setList($list); |
423
|
44 |
|
}; |
424
|
|
|
|
425
|
44 |
|
foreach ($node->childNodes as $childNode) { |
426
|
44 |
|
switch ($childNode->localName) { |
427
|
44 |
|
case 'simpleType': |
428
|
44 |
|
call_user_func($this->loadSimpleType($type->getSchema(), $childNode, $addCallback)); |
429
|
44 |
|
break; |
430
|
44 |
|
} |
431
|
44 |
|
} |
432
|
|
|
} |
433
|
44 |
|
} |
434
|
|
|
|
435
|
44 |
|
private function loadUnion(SimpleType $type, DOMElement $node) |
436
|
|
|
{ |
437
|
44 |
|
if ($node->hasAttribute("memberTypes")) { |
438
|
44 |
|
$types = preg_split('/\s+/', $node->getAttribute("memberTypes")); |
439
|
44 |
|
foreach ($types as $typeName) { |
440
|
44 |
|
$type->addUnion($this->findSomething('findType', $type->getSchema(), $node, $typeName)); |
|
|
|
|
441
|
44 |
|
} |
442
|
44 |
|
} |
443
|
|
|
$addCallback = function ($unType) use ($type) { |
444
|
44 |
|
$type->addUnion($unType); |
445
|
44 |
|
}; |
446
|
|
|
|
447
|
44 |
|
foreach ($node->childNodes as $childNode) { |
448
|
44 |
|
switch ($childNode->localName) { |
449
|
44 |
|
case 'simpleType': |
450
|
44 |
|
call_user_func($this->loadSimpleType($type->getSchema(), $childNode, $addCallback)); |
451
|
44 |
|
break; |
452
|
44 |
|
} |
453
|
44 |
|
} |
454
|
44 |
|
} |
455
|
|
|
|
456
|
44 |
|
private function fillTypeNode(Type $type, DOMElement $node, $checkAbstract = true) |
457
|
|
|
{ |
458
|
|
|
|
459
|
44 |
|
if ($checkAbstract) { |
460
|
44 |
|
$type->setAbstract($node->getAttribute("abstract") === "true" || $node->getAttribute("abstract") === "1"); |
461
|
44 |
|
} |
462
|
|
|
|
463
|
44 |
|
foreach ($node->childNodes as $childNode) { |
464
|
44 |
|
switch ($childNode->localName) { |
465
|
44 |
|
case 'restriction': |
466
|
44 |
|
$this->loadRestriction($type, $childNode); |
467
|
44 |
|
break; |
468
|
44 |
|
case 'extension': |
469
|
44 |
|
$this->loadExtension($type, $childNode); |
|
|
|
|
470
|
44 |
|
break; |
471
|
44 |
|
case 'simpleContent': |
472
|
44 |
|
case 'complexContent': |
473
|
44 |
|
$this->fillTypeNode($type, $childNode, false); |
474
|
44 |
|
break; |
475
|
44 |
|
} |
476
|
44 |
|
} |
477
|
44 |
|
} |
478
|
|
|
|
479
|
44 |
|
private function loadExtension(BaseComplexType $type, DOMElement $node) |
480
|
|
|
{ |
481
|
44 |
|
$extension = new Extension(); |
482
|
44 |
|
$type->setExtension($extension); |
483
|
|
|
|
484
|
44 |
|
if ($node->hasAttribute("base")) { |
485
|
44 |
|
$parent = $this->findSomething('findType', $type->getSchema(), $node, $node->getAttribute("base")); |
486
|
44 |
|
$extension->setBase($parent); |
|
|
|
|
487
|
44 |
|
} |
488
|
|
|
|
489
|
44 |
|
foreach ($node->childNodes as $childNode) { |
490
|
44 |
|
switch ($childNode->localName) { |
491
|
44 |
|
case 'sequence': |
492
|
44 |
|
case 'choice': |
493
|
44 |
|
case 'all': |
494
|
44 |
|
$this->loadSequence($type, $childNode); |
|
|
|
|
495
|
44 |
|
break; |
496
|
44 |
View Code Duplication |
case 'attribute': |
|
|
|
|
497
|
44 |
|
if ($childNode->hasAttribute("ref")) { |
498
|
44 |
|
$attribute = $this->findSomething('findAttribute', $type->getSchema(), $node, $childNode->getAttribute("ref")); |
499
|
44 |
|
} else { |
500
|
44 |
|
$attribute = $this->loadAttribute($type->getSchema(), $childNode); |
501
|
|
|
} |
502
|
44 |
|
$type->addAttribute($attribute); |
|
|
|
|
503
|
44 |
|
break; |
504
|
44 |
|
case 'attributeGroup': |
505
|
44 |
|
$attribute = $this->findSomething('findAttributeGroup', $type->getSchema(), $node, $childNode->getAttribute("ref")); |
506
|
44 |
|
$type->addAttribute($attribute); |
|
|
|
|
507
|
44 |
|
break; |
508
|
44 |
|
} |
509
|
44 |
|
} |
510
|
44 |
|
} |
511
|
|
|
|
512
|
44 |
|
private function loadRestriction(Type $type, DOMElement $node) |
513
|
|
|
{ |
514
|
44 |
|
$restriction = new Restriction(); |
515
|
44 |
|
$type->setRestriction($restriction); |
516
|
44 |
View Code Duplication |
if ($node->hasAttribute("base")) { |
|
|
|
|
517
|
44 |
|
$restrictedType = $this->findSomething('findType', $type->getSchema(), $node, $node->getAttribute("base")); |
518
|
44 |
|
$restriction->setBase($restrictedType); |
|
|
|
|
519
|
44 |
|
} else { |
520
|
|
|
$addCallback = function ($restType) use ($restriction) { |
521
|
44 |
|
$restriction->setBase($restType); |
522
|
44 |
|
}; |
523
|
|
|
|
524
|
44 |
|
foreach ($node->childNodes as $childNode) { |
525
|
44 |
|
switch ($childNode->localName) { |
526
|
44 |
|
case 'simpleType': |
527
|
44 |
|
call_user_func($this->loadSimpleType($type->getSchema(), $childNode, $addCallback)); |
528
|
44 |
|
break; |
529
|
44 |
|
} |
530
|
44 |
|
} |
531
|
|
|
} |
532
|
44 |
|
foreach ($node->childNodes as $childNode) { |
533
|
44 |
|
if (in_array($childNode->localName, |
534
|
|
|
[ |
535
|
44 |
|
'enumeration', |
536
|
44 |
|
'pattern', |
537
|
44 |
|
'length', |
538
|
44 |
|
'minLength', |
539
|
44 |
|
'maxLength', |
540
|
44 |
|
'minInclusive', |
541
|
44 |
|
'maxInclusive', |
542
|
44 |
|
'minExclusive', |
543
|
44 |
|
'maxExclusive', |
544
|
44 |
|
'fractionDigits', |
545
|
44 |
|
'totalDigits', |
546
|
|
|
'whiteSpace' |
547
|
44 |
|
], true)) { |
548
|
44 |
|
$restriction->addCheck($childNode->localName, |
549
|
|
|
[ |
550
|
44 |
|
'value' => $childNode->getAttribute("value"), |
551
|
44 |
|
'doc' => $this->getDocumentation($childNode) |
552
|
44 |
|
]); |
553
|
44 |
|
} |
554
|
44 |
|
} |
555
|
44 |
|
} |
556
|
|
|
|
557
|
44 |
|
private static function splitParts(DOMElement $node, $typeName) |
558
|
|
|
{ |
559
|
44 |
|
$namespace = null; |
|
|
|
|
560
|
44 |
|
$prefix = null; |
561
|
44 |
|
$name = $typeName; |
562
|
44 |
|
if (strpos($typeName, ':') !== false) { |
563
|
44 |
|
list ($prefix, $name) = explode(':', $typeName); |
564
|
44 |
|
} |
565
|
|
|
|
566
|
44 |
|
$namespace = $node->lookupNamespaceURI($prefix ?: null); |
567
|
|
|
return array( |
568
|
44 |
|
$name, |
569
|
44 |
|
$namespace, |
570
|
|
|
$prefix |
571
|
44 |
|
); |
572
|
|
|
} |
573
|
|
|
|
574
|
|
|
/** |
575
|
|
|
* |
576
|
|
|
* @param string $finder |
577
|
|
|
* @param Schema $schema |
578
|
|
|
* @param DOMElement $node |
579
|
|
|
* @param string $typeName |
580
|
|
|
* @throws TypeException |
581
|
|
|
* @return ElementItem|Group|AttributeItem|AttribiuteGroup|Type |
582
|
|
|
*/ |
583
|
44 |
|
private function findSomething($finder, Schema $schema, DOMElement $node, $typeName) |
584
|
|
|
{ |
585
|
44 |
|
list ($name, $namespace) = self::splitParts($node, $typeName); |
586
|
|
|
|
587
|
44 |
|
$namespace = $namespace ?: $schema->getTargetNamespace(); |
588
|
|
|
|
589
|
|
|
try { |
590
|
44 |
|
return $schema->$finder($name, $namespace); |
591
|
|
|
} catch (TypeNotFoundException $e) { |
592
|
|
|
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); |
593
|
|
|
} |
594
|
|
|
} |
595
|
|
|
|
596
|
44 |
View Code Duplication |
private function loadElementDef(Schema $schema, DOMElement $node) |
|
|
|
|
597
|
|
|
{ |
598
|
44 |
|
$element = new ElementDef($schema, $node->getAttribute("name")); |
599
|
44 |
|
$schema->addElement($element); |
600
|
|
|
|
601
|
|
|
return function () use ($element, $node) { |
602
|
44 |
|
$this->fillItem($element, $node); |
603
|
44 |
|
}; |
604
|
|
|
} |
605
|
|
|
|
606
|
44 |
|
private function fillItem(Item $element, DOMElement $node) |
607
|
|
|
{ |
608
|
44 |
|
$localType = null; |
609
|
44 |
|
foreach ($node->childNodes as $childNode) { |
610
|
44 |
|
switch ($childNode->localName) { |
611
|
44 |
|
case 'complexType': |
612
|
44 |
|
case 'simpleType': |
613
|
44 |
|
$localType = $childNode; |
614
|
44 |
|
break 2; |
615
|
44 |
|
} |
616
|
44 |
|
} |
617
|
|
|
|
618
|
44 |
|
if ($localType) { |
619
|
|
|
$addCallback = function ($type) use ($element) { |
620
|
44 |
|
$element->setType($type); |
621
|
44 |
|
}; |
622
|
44 |
|
switch ($localType->localName) { |
623
|
44 |
|
case 'complexType': |
624
|
44 |
|
call_user_func($this->loadComplexType($element->getSchema(), $localType, $addCallback)); |
625
|
44 |
|
break; |
626
|
44 |
|
case 'simpleType': |
627
|
44 |
|
call_user_func($this->loadSimpleType($element->getSchema(), $localType, $addCallback)); |
628
|
44 |
|
break; |
629
|
44 |
|
} |
630
|
44 |
|
} else { |
631
|
|
|
|
632
|
44 |
|
if ($node->getAttribute("type")) { |
633
|
44 |
|
$type = $this->findSomething('findType', $element->getSchema(), $node, $node->getAttribute("type")); |
634
|
44 |
|
} else { |
635
|
44 |
|
$type = $this->findSomething('findType', $element->getSchema(), $node, ($node->lookupPrefix(self::XSD_NS) . ":anyType")); |
636
|
|
|
} |
637
|
|
|
|
638
|
44 |
|
$element->setType($type); |
|
|
|
|
639
|
|
|
} |
640
|
44 |
|
} |
641
|
|
|
|
642
|
44 |
|
private function loadImport(Schema $schema, DOMElement $node) |
643
|
|
|
{ |
644
|
44 |
|
$base = urldecode($node->ownerDocument->documentURI); |
645
|
44 |
|
$file = UrlUtils::resolveRelativeUrl($base, $node->getAttribute("schemaLocation")); |
646
|
44 |
|
if ($node->hasAttribute("namespace") |
647
|
44 |
|
&& isset(self::$globalSchemaInfo[$node->getAttribute("namespace")]) |
648
|
44 |
|
&& isset($this->loadedFiles[self::$globalSchemaInfo[$node->getAttribute("namespace")]]) |
649
|
44 |
|
) { |
650
|
|
|
|
651
|
44 |
|
$schema->addSchema($this->loadedFiles[self::$globalSchemaInfo[$node->getAttribute("namespace")]]); |
652
|
|
|
|
653
|
|
|
return function () { |
654
|
44 |
|
}; |
655
|
3 |
|
} elseif ($node->hasAttribute("namespace") |
656
|
3 |
|
&& isset($this->loadedFiles[$this->getNamespaceSpecificFileIndex($file, $node->getAttribute("namespace"))])) { |
657
|
2 |
|
$schema->addSchema($this->loadedFiles[$this->getNamespaceSpecificFileIndex($file, $node->getAttribute("namespace"))]); |
658
|
|
|
return function () { |
659
|
2 |
|
}; |
660
|
1 |
|
} elseif (isset($this->loadedFiles[$file])) { |
661
|
|
|
$schema->addSchema($this->loadedFiles[$file]); |
662
|
|
|
return function () { |
663
|
|
|
}; |
664
|
|
|
} |
665
|
|
|
|
666
|
1 |
|
if (!$node->getAttribute("namespace")) { |
667
|
1 |
|
$this->loadedFiles[$file] = $newSchema = $schema; |
668
|
1 |
|
} else { |
669
|
|
|
$this->loadedFiles[$file] = $newSchema = new Schema(); |
670
|
|
|
$newSchema->addSchema($this->getGlobalSchema()); |
671
|
|
|
} |
672
|
|
|
|
673
|
1 |
|
$xml = $this->getDOM(isset($this->knownLocationSchemas[$file]) ? $this->knownLocationSchemas[$file] : $file); |
674
|
|
|
|
675
|
1 |
|
$callbacks = $this->schemaNode($newSchema, $xml->documentElement, $schema); |
676
|
|
|
|
677
|
1 |
|
if ($node->getAttribute("namespace")) { |
678
|
|
|
$schema->addSchema($newSchema); |
679
|
|
|
} |
680
|
|
|
|
681
|
|
|
|
682
|
1 |
|
return function () use ($callbacks) { |
683
|
1 |
|
foreach ($callbacks as $callback) { |
684
|
1 |
|
call_user_func($callback); |
685
|
1 |
|
} |
686
|
1 |
|
}; |
687
|
|
|
} |
688
|
|
|
|
689
|
|
|
private $globalSchema; |
690
|
|
|
|
691
|
|
|
/** |
692
|
|
|
* |
693
|
|
|
* @return Schema |
694
|
|
|
*/ |
695
|
44 |
|
public function getGlobalSchema() |
696
|
|
|
{ |
697
|
44 |
|
if (!$this->globalSchema) { |
698
|
44 |
|
$callbacks = array(); |
699
|
44 |
|
$globalSchemas = array(); |
700
|
44 |
|
foreach (self::$globalSchemaInfo as $namespace => $uri) { |
701
|
44 |
|
$this->loadedFiles[$uri] = $globalSchemas [$namespace] = $schema = new Schema(); |
702
|
44 |
|
if ($namespace === self::XSD_NS) { |
703
|
44 |
|
$this->globalSchema = $schema; |
704
|
44 |
|
} |
705
|
44 |
|
$xml = $this->getDOM($this->knownLocationSchemas[$uri]); |
706
|
44 |
|
$callbacks = array_merge($callbacks, $this->schemaNode($schema, $xml->documentElement)); |
707
|
44 |
|
} |
708
|
|
|
|
709
|
44 |
|
$globalSchemas[self::XSD_NS]->addType(new SimpleType($globalSchemas[self::XSD_NS], "anySimpleType")); |
710
|
44 |
|
$globalSchemas[self::XSD_NS]->addType(new SimpleType($globalSchemas[self::XSD_NS], "anyType")); |
711
|
|
|
|
712
|
44 |
|
$globalSchemas[self::XML_NS]->addSchema($globalSchemas[self::XSD_NS], self::XSD_NS); |
713
|
44 |
|
$globalSchemas[self::XSD_NS]->addSchema($globalSchemas[self::XML_NS], self::XML_NS); |
714
|
|
|
|
715
|
44 |
|
foreach ($callbacks as $callback) { |
716
|
44 |
|
$callback(); |
717
|
44 |
|
} |
718
|
44 |
|
} |
719
|
44 |
|
return $this->globalSchema; |
720
|
|
|
} |
721
|
|
|
|
722
|
|
|
/** |
723
|
|
|
* @param DOMNode $node |
724
|
|
|
* @param string $file |
725
|
|
|
* |
726
|
|
|
* @return Schema |
727
|
|
|
*/ |
728
|
44 |
|
public function readNode(DOMNode $node, $file = 'schema.xsd') |
729
|
|
|
{ |
730
|
44 |
|
$fileKey = $node instanceof DOMElement && $node->hasAttribute('targetNamespace') ? $this->getNamespaceSpecificFileIndex($file, $node->getAttribute('targetNamespace')) : $file; |
731
|
44 |
|
$this->loadedFiles[$fileKey] = $rootSchema = new Schema(); |
732
|
|
|
|
733
|
44 |
|
$rootSchema->addSchema($this->getGlobalSchema()); |
734
|
44 |
|
$callbacks = $this->schemaNode($rootSchema, $node); |
|
|
|
|
735
|
|
|
|
736
|
44 |
|
foreach ($callbacks as $callback) { |
737
|
38 |
|
call_user_func($callback); |
738
|
44 |
|
} |
739
|
|
|
|
740
|
44 |
|
return $rootSchema; |
741
|
|
|
} |
742
|
|
|
|
743
|
|
|
/** |
744
|
|
|
* It is possible that a single file contains multiple <xsd:schema/> nodes, for instance in a WSDL file. |
745
|
|
|
* |
746
|
|
|
* Each of these <xsd:schema/> nodes typically target a specific namespace. Append the target namespace to the |
747
|
|
|
* file to distinguish between multiple schemas in a single file. |
748
|
|
|
* |
749
|
|
|
* @param string $file |
750
|
|
|
* @param string $targetNamespace |
751
|
|
|
* |
752
|
|
|
* @return string |
753
|
|
|
*/ |
754
|
44 |
|
private function getNamespaceSpecificFileIndex($file, $targetNamespace) |
755
|
|
|
{ |
756
|
44 |
|
return $file . '#' . $targetNamespace; |
757
|
|
|
} |
758
|
|
|
|
759
|
|
|
/** |
760
|
|
|
* @param string $content |
761
|
|
|
* @param string $file |
762
|
|
|
* |
763
|
|
|
* @return Schema |
764
|
|
|
* |
765
|
|
|
* @throws IOException |
766
|
|
|
*/ |
767
|
43 |
|
public function readString($content, $file = 'schema.xsd') |
768
|
|
|
{ |
769
|
43 |
|
$xml = new DOMDocument('1.0', 'UTF-8'); |
770
|
43 |
|
if (!$xml->loadXML($content)) { |
771
|
|
|
throw new IOException("Can't load the schema"); |
772
|
|
|
} |
773
|
43 |
|
$xml->documentURI = $file; |
774
|
|
|
|
775
|
43 |
|
return $this->readNode($xml->documentElement, $file); |
776
|
|
|
} |
777
|
|
|
|
778
|
|
|
/** |
779
|
|
|
* @param string $file |
780
|
|
|
* |
781
|
|
|
* @return Schema |
782
|
|
|
*/ |
783
|
1 |
|
public function readFile($file) |
784
|
|
|
{ |
785
|
1 |
|
$xml = $this->getDOM($file); |
786
|
1 |
|
return $this->readNode($xml->documentElement, $file); |
787
|
|
|
} |
788
|
|
|
|
789
|
|
|
/** |
790
|
|
|
* @param string $file |
791
|
|
|
* |
792
|
|
|
* @return DOMDocument |
793
|
|
|
* |
794
|
|
|
* @throws IOException |
795
|
|
|
*/ |
796
|
44 |
|
private function getDOM($file) |
797
|
|
|
{ |
798
|
44 |
|
$xml = new DOMDocument('1.0', 'UTF-8'); |
799
|
44 |
|
if (!$xml->load($file)) { |
800
|
|
|
throw new IOException("Can't load the file $file"); |
801
|
|
|
} |
802
|
44 |
|
return $xml; |
803
|
|
|
} |
804
|
|
|
} |
805
|
|
|
|
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.