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 }