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 }