1 /**
2 License:
3     Boost Software License - Version 1.0 - August 17th, 2003
4 
5     Permission is hereby granted, free of charge, to any person or organization
6     obtaining a copy of the software and accompanying documentation covered by
7     this license (the "Software") to use, reproduce, display, distribute,
8     execute, and transmit the Software, and to prepare derivative works of the
9     Software, and to permit third-parties to whom the Software is furnished to
10     do so, all subject to the following:
11 
12     The copyright notices in the Software and this entire statement, including
13     the above license grant, this restriction and the following disclaimer,
14     must be included in all copies of the Software, in whole or in part, and
15     all derivative works of the Software, unless such copies or derivative
16     works are solely in the form of machine-executable object code generated by
17     a source language processor.
18 
19     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21     FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
22     SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
23     FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
24     ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25     DEALINGS IN THE SOFTWARE.
26 
27 Authors:
28     Alexandru Ermicioi
29 **/
30 module aermicioi.aedi_property_reader.xml.accessor;
31 
32 import std.xml;
33 import aermicioi.aedi.configurer.annotation.annotation;
34 import aermicioi.aedi_property_reader.convertor.convertor : Convertor;
35 import aermicioi.aedi_property_reader.convertor.accessor;
36 import aermicioi.aedi_property_reader.convertor.exception : NotFoundException;
37 import taggedalgebraic : TaggedAlgebraic;
38 import std.exception;
39 import std.algorithm;
40 import std.array;
41 import std.conv : to;
42 import std.experimental.logger;
43 import std.experimental.allocator;
44 import aermicioi.aedi_property_reader.convertor.traits : n;
45 
46 @component wrappingXmlAccessor(
47     XmlElementPropertyAccessor xmlElementPropertyAccessor,
48     XmlElementIndexAccessor xmlElementIndexAccessor,
49     XmlAttributePropertyAccessor xmlAttributePropertyAccessor,
50     Convertor defaultConvertor
51 ) {
52     return new WrappingComponentAccessor!(Element, Object)(
53         dsl(
54             new AggregatePropertyAccessor!(Object, Object)(
55                 new UnwrappingComponentAccessor!(Element, Object)(
56                     new WrappingFieldAccessor!(Element, Element)(xmlElementPropertyAccessor)
57                 ),
58                 new UnwrappingComponentAccessor!(Element, Object)(
59                     new WrappingFieldAccessor!(Element, string)(xmlAttributePropertyAccessor)
60                 )
61             ),
62             new UnwrappingComponentAccessor!(Element, Object)(
63                 new WrappingFieldAccessor!(Element, Item)(
64                     new KeyConvertingAccessor!(Element, Item, string, size_t)(xmlElementIndexAccessor, defaultConvertor)
65                 )
66             )
67         )
68     );
69 }
70 
71 @component xmlAccessor(
72     XmlElementPropertyAccessor xmlElementPropertyAccessor,
73 ) {
74     return dsl(
75             xmlElementPropertyAccessor,
76             xmlElementPropertyAccessor
77     );
78 }
79 
80 /**
81 An accessor allowing access to child xml elements out of a parent element by their name
82 **/
83 @component
84 class XmlElementPropertyAccessor : PropertyAccessor!Element {
85 
86     /**
87      Get a property out of component
88 
89      Params:
90          component = a component which has some properties identified by property.
91      Throws:
92          NotFoundException in case when no requested property is available.
93          InvalidArgumentException in case when passed arguments are somehow invalid for use.
94      Returns:
95          FieldType accessed property.
96      **/
97     Element access(Element component, in string property, RCIAllocator allocator = theAllocator) const {
98 
99         if (this.has(component, property)) {
100             return component.elements.find!(e => e.tag.name == property).front;
101         }
102 
103         throw new NotFoundException("Xml tag ${component} doesn't have child ${property}", property, component.to!string);
104     }
105 
106     /**
107      Check if requested property is present in component.
108 
109      Check if requested property is present in component.
110      The method could have allocation side effects due to the fact that
111      it is not restricted in calling access method of the accessor.
112 
113      Params:
114          component = component which is supposed to have property
115          property = speculated property that is to be tested if it is present in component
116      Returns:
117          true if property is in component
118      **/
119     bool has(Element component, string property, RCIAllocator allocator = theAllocator) const nothrow {
120         return (component !is null) && component.elements.canFind!(e => e.tag.name == property);
121     }
122 
123     /**
124      Identify the type of supported component.
125 
126      Identify the type of supported component. It returns type info of component
127      if it is supported by accessor, otherwise it will return typeid(void) denoting that
128      the type isn't supported by accessor. The accessor is not limited to returning the type
129      info of passed component, it can actually return type info of super type or any type
130      given the returned type is implicitly convertible or castable to ComponentType.
131 
132      Params:
133          component = the component for which accessor should identify the underlying type
134 
135      Returns:
136          TypeInfo type information about passed component, or typeid(void) if component is not supported.
137      **/
138     TypeInfo componentType(Element component) const nothrow {
139         return typeid(Element);
140     }
141 }
142 
143 /**
144 An accessor allowing access of child xml elements by their index in parent element
145 **/
146 @component
147 class XmlElementIndexAccessor : PropertyAccessor!(Element, Item, size_t) {
148 
149     /**
150      Get a property out of component
151 
152      Params:
153          component = a component which has some properties identified by property.
154      Throws:
155          NotFoundException in case when no requested property is available.
156          InvalidArgumentException in case when passed arguments are somehow invalid for use.
157      Returns:
158          FieldType accessed property.
159      **/
160     Item access(Element component, in size_t property, RCIAllocator allocator = theAllocator) const {
161 
162         if (this.has(component, property)) {
163             import std.conv : to;
164 
165             return component.items[property];
166         }
167 
168         throw new NotFoundException("Xml tag ${component} doesn't have child on index ${property}", property.to!string, component.to!string);
169     }
170 
171     /**
172      Check if requested property is present in component.
173 
174      Check if requested property is present in component.
175      The method could have allocation side effects due to the fact that
176      it is not restricted in calling access method of the accessor.
177 
178      Params:
179          component = component which is supposed to have property
180          property = speculated property that is to be tested if it is present in component
181      Returns:
182          true if property is in component
183      **/
184     bool has(Element component, in size_t property, RCIAllocator allocator = theAllocator) const nothrow {
185         return (component !is null) && (component.elements.length > property);
186     }
187 
188     /**
189      Identify the type of supported component.
190 
191      Identify the type of supported component. It returns type info of component
192      if it is supported by accessor, otherwise it will return typeid(void) denoting that
193      the type isn't supported by accessor. The accessor is not limited to returning the type
194      info of passed component, it can actually return type info of super type or any type
195      given the returned type is implicitly convertible or castable to ComponentType.
196 
197      Params:
198          component = the component for which accessor should identify the underlying type
199 
200      Returns:
201          TypeInfo type information about passed component, or typeid(void) if component is not supported.
202      **/
203     TypeInfo componentType(Element component) const {
204         return typeid(Element);
205     }
206 }
207 
208 /**
209 Xml element attribute property accessor
210 **/
211 @component
212 class XmlAttributePropertyAccessor : PropertyAccessor!(Element, string) {
213     /**
214      Get a property out of component
215 
216      Params:
217          component = a component which has some properties identified by property.
218      Throws:
219          NotFoundException in case when no requested property is available.
220          InvalidArgumentException in case when passed arguments are somehow invalid for use.
221      Returns:
222          FieldType accessed property.
223      **/
224     string access(Element component, in string property, RCIAllocator allocator = theAllocator) const {
225 
226         if (this.has(component, property)) {
227             return component.tag.attr[property];
228         }
229 
230         throw new NotFoundException("Xml tag ${component} doesn't have attribute ${property}", property, component.to!string);
231     }
232 
233     /**
234      Check if requested property is present in component.
235 
236      Check if requested property is present in component.
237      The method could have allocation side effects due to the fact that
238      it is not restricted in calling access method of the accessor.
239 
240      Params:
241          component = component which is supposed to have property
242          property = speculated property that is to be tested if it is present in component
243      Returns:
244          true if property is in component
245      **/
246     bool has(in Element component, in string property, RCIAllocator allocator = theAllocator) const nothrow {
247         return (component !is null) && ((property in component.tag.attr) !is null);
248     }
249 
250     /**
251      Identify the type of supported component.
252 
253      Identify the type of supported component. It returns type info of component
254      if it is supported by accessor, otherwise it will return typeid(void) denoting that
255      the type isn't supported by accessor. The accessor is not limited to returning the type
256      info of passed component, it can actually return type info of super type or any type
257      given the returned type is implicitly convertible or castable to ComponentType.
258 
259      Params:
260          component = the component for which accessor should identify the underlying type
261 
262      Returns:
263          TypeInfo type information about passed component, or typeid(void) if component is not supported.
264      **/
265     TypeInfo componentType(Element component) const nothrow {
266         return typeid(Element);
267     }
268 
269 }