1 /** 2 * Copyright © DiamondMVC 2019 3 * License: MIT (https://github.com/DiamondMVC/Diamond/blob/master/LICENSE) 4 * Author: Jacob Jensen (bausshf) 5 */ 6 module diamond.views.viewparser; 7 8 import diamond.core.apptype; 9 10 static if (isWebServer || !isWeb) 11 { 12 import diamond.views.viewformats; 13 import diamond.templates; 14 15 import std..string : strip, format; 16 import std.array : replace, split; 17 import std.conv : to; 18 19 /** 20 * Parses the view parts into a view class. 21 * Params: 22 * allParts = All the parsed parts of the view template. 23 * viewName = The name of the view. 24 * route = The route of the view. (This is null if no route is specified or if using stand-alone) 25 * Returns: 26 * A string equivalent to the generated view class. 27 */ 28 string parseViewParts(Part[][string] allParts, string viewName, out string route) 29 { 30 route = null; 31 32 string viewClassMembersGeneration = ""; 33 string viewConstructorGeneration = ""; 34 string viewModelGenerateGeneration = ""; 35 string viewCodeGeneration = ""; 36 string viewPlaceholderGeneration = ""; 37 bool hasController; 38 bool useBaseView; 39 bool hasDefaultSection; 40 41 foreach (sectionName,parts; allParts) 42 { 43 if (sectionName && sectionName.length) 44 { 45 viewCodeGeneration ~= "case \"" ~ sectionName ~ "\": 46 { 47 "; 48 } 49 else 50 { 51 hasDefaultSection = true; 52 viewCodeGeneration ~= "default: 53 { 54 "; 55 } 56 57 foreach (part; parts) 58 { 59 if (!part.content || !part.content.strip().length) 60 { 61 continue; 62 } 63 64 import diamond.extensions; 65 mixin ExtensionEmit!(ExtensionType.partParser, q{ 66 {{extensionEntry}}.parsePart( 67 part, 68 viewName, 69 viewClassMembersGeneration, viewConstructorGeneration, 70 viewModelGenerateGeneration, 71 viewCodeGeneration 72 ); 73 }); 74 emitExtension(); 75 76 switch (part.contentMode) 77 { 78 case ContentMode.appendContent: 79 { 80 viewCodeGeneration ~= parseAppendContent(part); 81 break; 82 } 83 84 case ContentMode.appendContentPlaceholder: 85 { 86 if (part.content[0] == '%' && part.content[$-1] == '%') 87 { 88 viewCodeGeneration ~= parseAppendTranslateContent(part); 89 } 90 else if (part.content[0] == '#' && part.content[$-1] == '#') 91 { 92 viewCodeGeneration ~= parseAppendPartialViewContent(part); 93 } 94 else 95 { 96 viewCodeGeneration ~= parseAppendPlaceholderContent(part); 97 } 98 break; 99 } 100 101 case ContentMode.mixinContent: 102 { 103 viewCodeGeneration ~= part.content; 104 break; 105 } 106 107 case ContentMode.metaContent: 108 { 109 parseMetaContent( 110 part, 111 viewName, 112 viewClassMembersGeneration, viewConstructorGeneration, 113 viewModelGenerateGeneration, viewPlaceholderGeneration, 114 useBaseView, 115 hasController, 116 route, 117 viewCodeGeneration 118 ); 119 break; 120 } 121 122 default : break; 123 } 124 } 125 126 viewCodeGeneration ~= "break; 127 } 128 "; 129 } 130 131 if (!hasDefaultSection) { 132 viewCodeGeneration ~= "default: break;"; 133 } 134 135 static if (isWebServer) 136 { 137 return viewClassFormat.format( 138 viewName, 139 viewClassMembersGeneration, 140 viewConstructorGeneration, 141 viewModelGenerateGeneration, 142 hasController ? controllerHandleFormat : "", 143 viewPlaceholderGeneration, 144 viewCodeGeneration, 145 endFormat 146 ); 147 } 148 else 149 { 150 return viewClassFormat.format( 151 viewName, 152 viewClassMembersGeneration, 153 viewConstructorGeneration, 154 viewModelGenerateGeneration, 155 viewPlaceholderGeneration, 156 viewCodeGeneration, 157 endFormat 158 ); 159 } 160 } 161 162 private: 163 /** 164 * Parses content that can be appended as a place holder. 165 * Params: 166 * part = The part to parse. 167 * Returns: 168 * The appended result. 169 */ 170 string parseAppendPlaceholderContent(Part part) 171 { 172 return appendFormat.format("getPlaceholder(`" ~ part.content ~ "`)"); 173 } 174 175 /** 176 * Parses content that can be appended as i18n. 177 * Params: 178 * part = The part to parse. 179 * Returns: 180 * The appended result. 181 */ 182 string parseAppendTranslateContent(Part part) 183 { 184 return appendFormat.format("i18n.getMessage(super.client, \"" ~ part.content[1 .. $-1] ~ "\")"); 185 } 186 187 /** 188 * Parses content that can be appended as partial view. 189 * Params: 190 * part = The part to parse. 191 * Returns: 192 * The appended result. 193 */ 194 string parseAppendPartialViewContent(Part part) 195 { 196 auto viewData = part.content[1 .. $-1].split(","); 197 198 if (viewData.length == 2) 199 { 200 return appendFormat.format("retrieveModel!\"%s\"(%s)".format(viewData[0], viewData[1])); 201 } 202 else 203 { 204 return appendFormat.format("retrieve(\"%s\")".format(viewData[0])); 205 } 206 } 207 208 /** 209 * Parses content that can be appended. 210 * Params: 211 * part = The part to parse. 212 * Returns: 213 * The appended result. 214 */ 215 string parseAppendContent(Part part) 216 { 217 switch (part.name) 218 { 219 case "expressionValue": 220 { 221 return appendFormat.format(part.content); 222 } 223 224 case "escapedValue": 225 { 226 return escapedFormat.format("`" ~ part.content ~ "`"); 227 } 228 229 case "expressionEscaped": 230 { 231 return escapedFormat.format(part.content); 232 } 233 234 default: 235 { 236 return appendFormat.format("`" ~ part.content ~ "`"); 237 } 238 } 239 } 240 241 /** 242 * Parses the meta content of a view. 243 * Params: 244 * part = The part of the meta content. 245 * viewClassMembersGeneration = The resulting string of the view's class members. 246 * viewConstructorGeneration = The resulting string of the view's constructor. 247 * viewModelGenerateGeneration = The resulting string of the view's model-generate function. 248 * viewPlaceholderGeneration = The resulting string of the view's placeholder generation. 249 * useBaseView = Boolean determining whether the view should use the base view for controllers. 250 * hasController = Boolean determining whether the view has a controller or not. 251 * route = The name of the view's route. (null if no route or if stand-alone.) 252 */ 253 void parseMetaContent(Part part, 254 string viewName, 255 ref string viewClassMembersGeneration, 256 ref string viewConstructorGeneration, 257 ref string viewModelGenerateGeneration, 258 ref string viewPlaceholderGeneration, 259 ref bool useBaseView, 260 ref bool hasController, 261 ref string route, 262 ref string viewCodeGeneration) 263 { 264 string[string] metaData; 265 auto metaContent = part.content.replace("\r", "").split("---"); 266 267 foreach (entry; metaContent) 268 { 269 if (entry && entry.length) 270 { 271 import std..string : indexOf; 272 273 auto keyIndex = entry.indexOf(':'); 274 auto key = entry[0 .. keyIndex].strip().replace("\n", ""); 275 276 metaData[key] = entry[keyIndex + 1 .. $].strip(); 277 } 278 } 279 280 foreach (key, value; metaData) 281 { 282 if (!value || !value.length) 283 { 284 continue; 285 } 286 287 switch (key) 288 { 289 case "placeholders": 290 { 291 viewPlaceholderGeneration = placeholderFormat.format(value); 292 break; 293 } 294 295 case "route": 296 { 297 import std..string : toLower; 298 route = value.replace("\n", "").toLower(); 299 break; 300 } 301 302 case "model": 303 { 304 viewModelGenerateGeneration = modelGenerateFormat.format(value); 305 viewClassMembersGeneration ~= modelMemberFormat.format(value); 306 viewClassMembersGeneration ~= updateModelFromRenderViewFormat.format(viewName); 307 break; 308 } 309 310 static if (isWebServer) 311 { 312 case "controllerUseBaseView": 313 { 314 useBaseView = to!bool(value); 315 break; 316 } 317 318 case "controller": 319 { 320 hasController = true; 321 viewClassMembersGeneration ~= controllerMemberFormat.format(value, useBaseView ? "View" : "view_" ~ viewName); 322 viewConstructorGeneration ~= controllerConstructorFormat.format(value, useBaseView ? "View" : "view_" ~ viewName); 323 break; 324 } 325 } 326 327 case "layout": 328 { 329 viewConstructorGeneration ~= layoutConstructorFormat.format(value.replace("\n", "")); 330 break; 331 } 332 333 case "cache": 334 { 335 if (to!bool(value)) 336 { 337 viewConstructorGeneration ~= "super.cached = true;\r\n"; 338 } 339 340 break; 341 } 342 343 case "staticCache": 344 { 345 if (to!bool(value)) 346 { 347 viewConstructorGeneration ~= "super.staticCache = true;\r\n"; 348 } 349 350 break; 351 } 352 353 case "staticCacheTime": 354 { 355 viewConstructorGeneration ~= "super.cacheTime = " ~ to!string(to!size_t(value)) ~ ";\r\n"; 356 357 break; 358 } 359 360 case "contentType": 361 { 362 viewCodeGeneration ~= "super.client.rawResponse.headers[\"Content-Type\"] = \"%s\";\r\n".format(value.replace("\n", "")); 363 break; 364 } 365 366 case "type": 367 { 368 switch (value.replace("\n", "")) 369 { 370 case "text": 371 { 372 viewCodeGeneration ~= "super.client.rawResponse.headers[\"Content-Type\"] = \"text/plain; charset=UTF-8\";\r\n"; 373 break; 374 } 375 376 case "xml": 377 { 378 viewCodeGeneration ~= "super.client.rawResponse.headers[\"Content-Type\"] = \"application/xml; charset=UTF-8\";\r\n"; 379 break; 380 } 381 382 case "rss": 383 { 384 viewCodeGeneration ~= "super.client.rawResponse.headers[\"Content-Type\"] = \"application/rss+xml; charset=UTF-8\";\r\n"; 385 break; 386 } 387 388 case "atom": 389 { 390 viewCodeGeneration ~= "super.client.rawResponse.headers[\"Content-Type\"] = \"application/atom+xml; charset=UTF-8\";\r\n"; 391 break; 392 } 393 394 case "json": 395 { 396 viewCodeGeneration ~= "super.client.rawResponse.headers[\"Content-Type\"] = \"application/json; charset=UTF-8\";\r\n"; 397 break; 398 } 399 400 default: break; 401 } 402 break; 403 } 404 405 case "title": 406 { 407 viewConstructorGeneration ~= "super.addPlaceholder(\"title\", \"" ~ value.strip() ~ "\");\r\n"; 408 break; 409 } 410 411 default: break; 412 } 413 } 414 } 415 }