1 /*
2 Copyright (c) 2013-2014 Andrey Penechko
3 
4 Boost Software License - Version 1.0 - August 17th, 2003
5 
6 Permission is hereby granted, free of charge, to any person or organization
7 obtaining a copy of the software and accompanying documentation covered by
8 this license the "Software" to use, reproduce, display, distribute,
9 execute, and transmit the Software, and to prepare derivative works of the
10 Software, and to permit third-parties to whom the Software is furnished to
11 do so, all subject to the following:
12 
13 The copyright notices in the Software and this entire statement, including
14 the above license grant, this restriction and the following disclaimer,
15 must be included in all copies of the Software, in whole or in part, and
16 all derivative works of the Software, unless such copies or derivative
17 works are solely in the form of machine-executable object code generated by
18 a source language processor.
19 
20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
23 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
24 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
25 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 DEALINGS IN THE SOFTWARE.
27 */
28 
29 module anchovy.gui.templates.templateparser;
30 
31 import sdlang;
32 
33 import anchovy.gui.templates.widgettemplate;
34 
35 import anchovy.gui;
36 
37 final class TemplateParser
38 {
39 	WidgetTemplate[] parse(string source, string filename = "")
40 	{
41 		WidgetTemplate[] templates;
42 
43 		Tag root;
44 	
45 		try
46 		{
47 			root = parseSource(source, filename);
48 		}
49 		catch(SDLangParseException e)
50 		{
51 			stderr.writeln(e.msg);
52 			return null;
53 		}
54 
55 		foreach(templ; root.maybe.namespaces["template"].tags)
56 		{
57 			templates ~= parseTemplate(templ);
58 		}
59 
60 		return templates;
61 	}
62 
63 	WidgetTemplate parseTemplate(Tag templateTag)
64 	{
65 		WidgetTemplate templ = new WidgetTemplate;
66 		Tag forwardedPropertiesTag;
67 		Tag treeTag;
68 		templ.name = templateTag.name;
69 
70 		foreach(section; templateTag.tags)
71 		{
72 			switch(section.name)
73 			{
74 				case "properties":
75 					forwardedPropertiesTag = section;
76 					break;
77 				case "tree":
78 					treeTag = section;
79 					break;
80 				default:
81 					writeln("template:", templ.name, " Error: unknown section found: ", section.name);
82 			}
83 		}
84 
85 		templ.baseType = "widget"; // default super is widget or widget factory with the same type.
86 
87 		templ.tree = parseTreeSection(treeTag, templ);
88 		templ.tree.properties["type"] = templateTag.name;
89 
90 		//----------------------- Parse template properties ------------------------
91 		SubwidgetTemplate childrenContainer;
92 
93 		foreach(prop; templateTag.attributes)
94 		{
95 			switch(prop.name)
96 			{
97 				case "extends":
98 					templ.baseType = prop.value.coerce!string;
99 					break;
100 				case "container":
101 					if (auto container = prop.value.coerce!string in templ.subwidgetsmap)
102 					{
103 						if (childrenContainer !is null)
104 							writeln("template:", templ.name, " Error: Multiple children containers not allowed. Overriding with last");
105 						childrenContainer = *container;
106 					}
107 					else
108 					{
109 						writeln("template:", templ.name, " Error: In template children container widget '", prop.name, "' not found");
110 					}
111 					break;
112 				default:
113 					writeln("template:", templ.name, " Error: In template unknown property found: ", prop.name);
114 			}
115 		}
116 
117 		if (childrenContainer)
118 		{
119 			childrenContainer.isContainer = true;
120 		}
121 
122 		if (forwardedPropertiesTag)
123 		{
124 			parseForwardedProperties(forwardedPropertiesTag, templ);
125 		}
126 
127 		return templ;
128 	}
129 
130 	SubwidgetTemplate parseTreeSection(Tag section, WidgetTemplate templ)
131 	{
132 		auto subwidget = new SubwidgetTemplate;
133 
134 		// Adding subwidgets.
135 		foreach(sub; section.tags)
136 		{
137 			auto subsub = parseTreeSection(sub, templ);
138 
139 			subwidget.subwidgets ~= subsub;
140 
141 			if (auto nameProperty = "name" in subsub.properties)
142 			{
143 				templ.subwidgetsmap[nameProperty.coerce!string] = subsub;
144 			}
145 		}
146 
147 		// Adding widget properties.
148 		foreach(prop; section.attributes)
149 		{
150 			subwidget.properties[prop.name] = cast(Variant)prop.value;
151 		}
152 
153 		// Adding widget flags.
154 		foreach(value; section.values)
155 		{
156 			subwidget.properties[value.coerce!string] = Variant(true);
157 		}
158 
159 		subwidget.properties["type"] = section.name;
160 
161 		return subwidget;
162 	}
163 
164 	void parseForwardedProperties(Tag section, WidgetTemplate templ)
165 	{
166 		// key target subtemplate. Key target property name, root property name.
167 		ForwardedProperty[][string] properties;
168 		
169 		// fetch all forwarded properties for template templ
170 		foreach(prop; section.tags)
171 		{
172 			ForwardedProperty property = ForwardedProperty(prop.name, prop.name);
173 			string subwidgetName;
174 
175 			foreach(attrib; prop.attributes)
176 			{
177 				switch(attrib.name)
178 				{
179 					case "subwidget":
180 						subwidgetName = attrib.value.get!string;
181 						break;
182 					case "property":
183 						property.targetPropertyName = attrib.value.get!string;
184 						break;
185 					default:
186 						writeln("template:", templ.name, " Error: unknown attribute '",
187 							attrib.name , "' for forwarded property '", prop.name, "' found");
188 				}
189 			}
190 
191 			if (subwidgetName == "")
192 			{
193 				writeln("template:", templ.name, " Error: no target widget for forwarded property: '",
194 					property.propertyName, "' specified, skipping property");
195 				continue; // Skip property.
196 			}
197 			else if (subwidgetName !in templ.subwidgetsmap)
198 			{
199 				writeln("template:", templ.name, " Error: target widget '",subwidgetName,
200 					"' for forwarded property '", property.propertyName, "' not found, skipping property");
201 				continue; // Skip property.
202 			}
203 
204 			if (auto fproparray = subwidgetName in properties)
205 			{
206 				import std.algorithm;
207 				if (canFind(*fproparray, property))
208 					writeln("template:", templ.name, " Error: duplicate for forwarded property '",
209 						property.targetPropertyName, ":", property.propertyName, "' found, skipping duplicate");
210 				continue; // Skip property.
211 			}
212 
213 			properties[subwidgetName] ~= property;
214 
215 			SubwidgetTemplate target = templ.subwidgetsmap[subwidgetName];
216 			// Add info about forwarded property to target subtemplate, so at instantiation time we can lookup it
217 			target.forwardedProperties ~= property;
218 		}
219 	}
220 }