1 /**
2 * Copyright © DiamondMVC 2019
3 * License: MIT (https://github.com/DiamondMVC/Diamond/blob/master/LICENSE)
4 * Author: Jacob Jensen (bausshf)
5 */
6 module diamond.web.soap.service.parser.typeparser;
7 
8 import std..string : toLower, strip, format;
9 import std.array : split;
10 import std.algorithm : endsWith;
11 
12 import diamond.dom;
13 import diamond.xml;
14 import diamond.web.soap.service.soaptype;
15 import diamond.web.soap.service.complextype;
16 import diamond.web.soap.service.simpletype;
17 import diamond.web.soap.service.element;
18 import diamond.web.soap.service.aliastype;
19 import diamond.errors.exceptions.soapexception;
20 
21 package(diamond.web.soap.service.parser):
22 /**
23 * Parses a schema.
24 * Params:
25 *   schema =    The schema to parse.
26 * Returns:
27 *   The parsed soap types from the schema.
28 */
29 SoapType[] parseSchema(XmlNode schema)
30 {
31   auto elementFormDefaultAttribute = schema.getAttribute("elementFormDefault");
32   auto elementFormDefault = elementFormDefaultAttribute ? elementFormDefaultAttribute.value : "";
33 
34   auto targetNamespaceAttribute = schema.getAttribute("targetNamespace");
35   auto targetNamespace = targetNamespaceAttribute ? targetNamespaceAttribute.value : "";
36 
37   if (!schema.children || !schema.children.length)
38   {
39     return null;
40   }
41 
42   SoapType[] result;
43 
44   foreach (child; schema.children)
45   {
46     switch (child.name.toLower())
47     {
48       case "xs:complextype":
49       case "xsd:complextype":
50       {
51         result ~= parseComplexType(child);
52         break;
53       }
54 
55       case "xs:simpletype":
56       case "xsd:simpletype":
57       {
58         result ~= parseSimpleType(child);
59         break;
60       }
61 
62       case "xs:attribute": break; // Although unsupported, attributes such as unions shouldn't break parsing.
63       case "xsd:attribute": break; // Although unsupported, attributes such as unions shouldn't break parsing.
64 
65       case "xs:element":
66       case "xsd:element":
67       {
68         auto elementType = parseElementType(child);
69 
70         if (elementType)
71         {
72           result ~= elementType;
73         }
74         break;
75       }
76 
77       default:
78       {
79         throw new SoapException("Unsupported type definition.");
80       }
81     }
82   }
83 
84   return result;
85 }
86 
87 /**
88 * Parses an element type.
89 * Params:
90 *   elementTypeNode = The element node.
91 * Returns:
92 *   A soap type equivalent to the parsed element type.
93 */
94 SoapType parseElementType(XmlNode elementTypeNode)
95 {
96   auto nameAttribute = elementTypeNode.getAttribute("name");
97   auto name = nameAttribute ? nameAttribute.value.strip() : null;
98 
99   if (!name || !name.length)
100   {
101     throw new SoapException("Expected an element type name.");
102   }
103 
104   if (!elementTypeNode.children || !elementTypeNode.children.length)
105   {
106     // The type might use an alias ...
107 
108     auto aliasType = elementTypeNode.getAttribute("type");
109 
110     if (aliasType && aliasType.value.split(":").length == 2)
111     {
112       return new SoapAliasType(name, aliasType.value.split(":")[1]);
113     }
114 
115     return null;
116   }
117 
118   auto type = elementTypeNode.children[0];
119 
120   if (type.name.toLower() != "xs:complextype" && type.name.toLower() != "xs:simpletype" && type.name.toLower() != "xsd:complextype" && type.name.toLower() != "xsd:simpletype")
121   {
122     throw new SoapException("Expected a simple or complex type for '%s'.".format(name));
123   }
124 
125   final switch (type.name.toLower())
126   {
127     case "xs:complextype":
128     case "xsd:complextype":
129     {
130       if (!type.hasAttribute("name"))
131       {
132         type.addAttribute("name", name);
133       }
134 
135       auto complexType = parseComplexType(type);
136 
137       return complexType;
138     }
139 
140     case "xs:simpletype":
141     case "xsd:simpletype":
142     {
143       if (!type.hasAttribute("name"))
144       {
145         type.addAttribute("name", name);
146       }
147 
148       auto simpleType = parseSimpleType(type);
149 
150       return simpleType;
151     }
152   }
153 }
154 
155 /**
156 * Parses a simple type.
157 * Params:
158 *   simpleTypeNode = The simple type node.
159 * Returns:
160 *   The simple typed parsed.
161 */
162 SoapSimpleType parseSimpleType(XmlNode simpleTypeNode)
163 {
164   auto nameAttribute = simpleTypeNode.getAttribute("name");
165   auto name = nameAttribute ? nameAttribute.value.strip() : null;
166 
167   if (!name || !name.length)
168   {
169     throw new SoapException("Expected a simple type name.");
170   }
171 
172   if (!simpleTypeNode.children || simpleTypeNode.children.length != 1)
173   {
174     throw new SoapException("'%s' is either an empty simple type or has too many defintions.".format(name));
175   }
176 
177   auto type = simpleTypeNode.children[0];
178 
179   switch (type.name.toLower())
180   {
181     case "xs:restriction":
182     case "xsd:restriction":
183     {
184       auto baseAttribute = type.getAttribute("base");
185       auto base = baseAttribute ? baseAttribute.value.strip() : null;
186 
187       if (!base || base.split(":").length != 2)
188       {
189         throw new SoapException("'%s' has no valid base type.");
190       }
191 
192       base = base.split(":")[1];
193 
194       return new SoapSimpleType(name, base, SoapSimpleTypeDefinition.restriction);
195     }
196 
197     case "xs:list":
198     case "xsd:list":
199     {
200       auto itemTypeAttribute = type.getAttribute("itemType");
201       auto itemType = itemTypeAttribute ? itemTypeAttribute.value.strip() : null;
202 
203       if (!itemType || itemType.split(":").length != 2)
204       {
205         throw new SoapException("'%s' has no valid item type.");
206       }
207 
208       itemType = itemType.split(":")[1];
209 
210       return new SoapSimpleType(name, itemType, SoapSimpleTypeDefinition.list);
211     }
212 
213     default:
214     {
215       throw new SoapException("Unsupported simple type definition.");
216     }
217   }
218 }
219 
220 /**
221 * Parses a complex type.
222 * Params:
223 *   complexTypeNode = The complex node.
224 * Returns:
225 *   The complex node parsed.
226 */
227 SoapComplexType parseComplexType(XmlNode complexTypeNode)
228 {
229   auto nameAttribute = complexTypeNode.getAttribute("name");
230   auto name = nameAttribute ? nameAttribute.value.strip() : null;
231 
232   if (!name || !name.length)
233   {
234     throw new SoapException("Expected a complex type name.");
235   }
236 
237   if (!complexTypeNode.children || !complexTypeNode.children.length)
238   {
239     throw new SoapException("'%s' is an empty complex type.".format(name));
240   }
241 
242   auto sequence = complexTypeNode.children[0];
243 
244   if (sequence.name.toLower() != "xs:sequence" && sequence.name.toLower() != "xsd:sequence")
245   {
246     throw new SoapException("Expected a sequence element for '%s'.".format(name));
247   }
248 
249   auto complexType = new SoapComplexType(name);
250 
251   auto elements = sequence.getByTagName("xs:element");
252 
253   if (!elements)
254   {
255     elements = sequence.getByTagName("xsd:element");
256   }
257 
258   if (elements && elements.length)
259   {
260     foreach (elementNode; elements)
261     {
262       auto element = parseElement(elementNode);
263 
264       complexType.addElement(element);
265     }
266   }
267 
268   return complexType;
269 }
270 
271 /**
272 * Parses an element.
273 * Params:
274 *   elementNode = The element.
275 * Returns:
276 *   A soap element equivalent to the element node.
277 */
278 SoapElement parseElement(XmlNode elementNode)
279 {
280   auto nameAttribute = elementNode.getAttribute("name");
281   auto name = nameAttribute ? nameAttribute.value.strip() : null;
282 
283   if (!name || !name.length)
284   {
285     throw new SoapException("Expected an element name.");
286   }
287 
288   auto typeAttribute = elementNode.getAttribute("type");
289   auto type = typeAttribute ? typeAttribute.value.strip() : null;
290 
291   if (!type || type.split(":").length != 2)
292   {
293     throw new SoapException("Element '%s' has no type.".format(name));
294   }
295 
296   type = type.split(":")[1];
297 
298   if (elementNode.hasAttribute("maxOccurs") && elementNode.getAttribute("maxOccurs").value.strip().toLower() == "unbounded" && type != "string")
299   {
300     type ~= "[]";
301   }
302 
303   return new SoapElement(name, type);
304 }
305 
306 /**
307 * Parses an array of soap types into d types.
308 * Params:
309 *   soapTypes = The array of soap types.
310 * Returns:
311 *   A string equivalent to all the d types.
312 */
313 string parseDTypes(SoapType[] soapTypes)
314 {
315   if (!soapTypes || !soapTypes.length)
316   {
317     return "";
318   }
319 
320   string result;
321 
322   enum classTypeFormat = q{
323 final class %s : SoapEnvelopeType
324 {
325   public:
326   final:
327   this() {}
328 
329 %s}
330   };
331 
332   enum classTypeElementFormat = "  %s %s;\r\n";
333 
334   enum aliasTypeFormat = "public alias %s = %s;\r\n";
335 
336   foreach (type; soapTypes)
337   {
338     auto complex = cast(SoapComplexType)type;
339 
340     if (complex)
341     {
342       string elementResult = "";
343 
344       if (complex.elements)
345       {
346         foreach (element; complex.elements)
347         {
348           if (complex.elements.length == 1 && element.type.endsWith("[]"))
349           {
350             elementResult ~= classTypeElementFormat.format(element.type, "_array_");
351             elementResult ~= classTypeElementFormat.format("alias", "_array_ this");
352           }
353           else if (element.type == element.name)
354           {
355             elementResult ~= classTypeElementFormat.format(element.type, "_%s_".format(element.name));
356           }
357           else
358           {
359             elementResult ~= classTypeElementFormat.format(element.type, element.name);
360           }
361         }
362       }
363 
364       result ~= classTypeFormat.format(complex.name, elementResult);
365       continue;
366     }
367 
368     auto simpleType = cast(SoapSimpleType)type;
369 
370     if (simpleType)
371     {
372       final switch (simpleType.definition)
373       {
374         case SoapSimpleTypeDefinition.restriction:
375         {
376           result ~= aliasTypeFormat.format(simpleType.name, simpleType.typeName);
377           break;
378         }
379 
380         case SoapSimpleTypeDefinition.list:
381         {
382           auto listTypeResult = classTypeElementFormat.format(simpleType.typeName, "_value_");
383           listTypeResult ~= classTypeElementFormat.format("alias", "_value_ this");
384 
385           result ~= classTypeFormat.format(simpleType.name, listTypeResult);
386           break;
387         }
388       }
389       continue;
390     }
391 
392     auto aliasType = cast(SoapAliasType)type;
393 
394     if (aliasType)
395     {
396       result ~= aliasTypeFormat.format(aliasType.name, aliasType.aliasName);
397       continue;
398     }
399   }
400 
401   return result ? result : "";
402 }