1 /**
2 Copyright: Copyright (c) 2013-2014 Andrey Penechko.
3 License: a$(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4 Authors: Andrey Penechko.
5 */
6
7 module anchovy.gui.templates.templateparser;
8
9 import sdlang;
10
11 import anchovy.gui.templates.widgettemplate;
12
13 import anchovy.gui;
14
15 //version = TemplateParser_debug;
16
17 struct TemplateParserResult
18 {
19 WidgetTemplate[] parsedTemplates;
20 string[] filesToParse;
21 }
22
23 final class TemplateParser
24 {
25 TemplateParserResult parse(string source, string filename = "")
26 {
27 WidgetTemplate[] templates;
28
29 Tag root;
30
31 try
32 {
33 root = parseSource(source, filename);
34 }
35 catch(SDLangParseException e)
36 {
37 stderr.writeln(e.msg);
38 return TemplateParserResult();
39 }
40
41 string[] filesToParse;
42
43 foreach(templateImport; root.tags)
44 {
45 if (templateImport.name == "import")
46 {
47 if (templateImport.values.length == 0)
48 {
49 stderr.writefln("Invalid import statement at %s", templateImport.location);
50 continue;
51 }
52
53 auto value = templateImport.values[0];
54 if (value.type != typeid(string))
55 {
56 stderr.writefln("Invalid filename %s of import statement at %s", value, templateImport.location);
57 continue;
58 }
59
60 filesToParse ~= templateImport.values[0].get!string ~ ".sdl";
61 }
62 }
63
64 foreach(templ; root.maybe.namespaces["template"].tags)
65 {
66 templates ~= parseTemplate(templ);
67 }
68
69 return TemplateParserResult(templates, filesToParse);
70 }
71
72 WidgetTemplate parseTemplate(Tag templateTag)
73 {
74 WidgetTemplate templ = new WidgetTemplate;
75 Tag forwardedPropertiesTag;
76 Tag treeTag;
77 templ.name = templateTag.name;
78
79 foreach(section; templateTag.tags)
80 {
81 switch(section.name)
82 {
83 case "properties":
84 forwardedPropertiesTag = section;
85 break;
86 case "tree":
87 treeTag = section;
88 break;
89 default:
90 stderr.writeln("template:", templ.name, " Error: unknown section found: ", section.name);
91 }
92 }
93
94 templ.baseType = "widget"; // default super is widget or widget factory with the same type.
95
96 templ.tree = parseTreeSection(treeTag, templ);
97 templ.tree.properties["type"] = templateTag.name;
98
99 //----------------------- Parse template properties ------------------------
100 string childrenContainer;
101
102 foreach(prop; templateTag.attributes)
103 {
104 switch(prop.name)
105 {
106 case "extends":
107 templ.baseType = prop.value.coerce!string;
108 break;
109 case "container":
110 if (auto container = prop.value.coerce!string in templ.subwidgetsmap)
111 {
112 if (childrenContainer !is null)
113 {
114 stderr.writefln("template:%s Error: Multiple children containers not allowed. Overriding with last", templ.name);
115 }
116 childrenContainer = prop.value.coerce!string;
117 }
118 else
119 {
120 stderr.writeln("template:", templ.name, " Error: In template children container widget '", prop.name, "' not found");
121 }
122 break;
123 default:
124 stderr.writeln("template:", templ.name, " Error: In template unknown property found: ", prop.name);
125 }
126 }
127
128 if (childrenContainer)
129 {
130 templ.container = childrenContainer;
131 }
132
133 if (forwardedPropertiesTag)
134 {
135 parseForwardedProperties(forwardedPropertiesTag, templ);
136 }
137
138 return templ;
139 }
140
141 SubwidgetTemplate parseTreeSection(Tag section, WidgetTemplate templ)
142 {
143 auto subwidget = new SubwidgetTemplate;
144
145 // Adding subwidgets.
146 foreach(sub; section.tags)
147 {
148 auto subsub = parseTreeSection(sub, templ);
149
150 subwidget.subwidgets ~= subsub;
151
152 if (auto nameProperty = "name" in subsub.properties)
153 {
154 templ.subwidgetsmap[nameProperty.coerce!string] = subsub;
155 }
156 }
157
158 // Adding widget properties.
159 foreach(prop; section.attributes)
160 {
161 if (prop.value.type !is typeid(DateTimeFracUnknownZone))
162 subwidget.properties[prop.name] = *cast(Variant*)&prop.value;
163 }
164
165 // Adding widget flags.
166 foreach(value; section.values)
167 {
168 subwidget.properties[value.coerce!string] = Variant(true);
169 }
170
171 subwidget.properties["type"] = section.name;
172
173 return subwidget;
174 }
175
176 void parseForwardedProperties(Tag section, WidgetTemplate templ)
177 {
178 // key target subtemplate. Key target property name, root property name.
179 ForwardedProperty[][string] properties;
180
181 // fetch all forwarded properties for template templ
182 foreach(prop; section.tags)
183 {
184 ForwardedProperty property = ForwardedProperty(prop.name, prop.name);
185 string subwidgetName;
186
187 foreach(attrib; prop.attributes)
188 {
189 switch(attrib.name)
190 {
191 case "subwidget":
192 subwidgetName = attrib.value.get!string;
193 break;
194 case "property":
195 property.targetPropertyName = attrib.value.get!string;
196 break;
197 default:
198 stderr.writeln("template:", templ.name, " Error: unknown attribute '",
199 attrib.name , "' for forwarded property '", prop.name, "' found");
200 }
201 }
202
203 if (subwidgetName == "")
204 {
205 stderr.writeln("template:", templ.name, " Error: no target widget for forwarded property: '",
206 property.propertyName, "' specified, skipping property");
207 continue; // Skip property.
208 }
209 else if (subwidgetName !in templ.subwidgetsmap)
210 {
211 stderr.writeln("template:", templ.name, " Error: target widget '",subwidgetName,
212 "' for forwarded property '", property.propertyName, "' not found, skipping property");
213 continue; // Skip property.
214 }
215
216 if (auto forwardedProperties = subwidgetName in properties)
217 {
218 import std.algorithm;
219 if (canFind(*forwardedProperties, property))
220 {
221 stderr.writeln("template:", templ.name, " Error: duplicate for forwarded property '",
222 property.targetPropertyName, ":", property.propertyName, "' found, skipping duplicate");
223 continue; // Skip property.
224 }
225 }
226
227 properties[subwidgetName] ~= property;
228 version(TemplateParser_debug)writefln("Found forwarding %s.%s to %s.%s",
229 templ.name, property.propertyName, subwidgetName, property.targetPropertyName);
230
231 SubwidgetTemplate target = templ.subwidgetsmap[subwidgetName];
232 // Add info about forwarded property to target subtemplate, so at instantiation time we can lookup it
233 target.forwardedProperties ~= property;
234 }
235 }
236 }