PageRenderTime 74ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/SharpTAL/TemplateProgram/ProgramGenerator.cs

https://bitbucket.org/rlacko/sharptal
C# | 1141 lines | 826 code | 138 blank | 177 comment | 133 complexity | 8b50057da66867592dbe174ebdad143e MD5 | raw file
Possible License(s): Apache-2.0
  1. //
  2. // ProgramGenerator.cs
  3. //
  4. // Author:
  5. // Roman Lacko (backup.rlacko@gmail.com)
  6. //
  7. // Copyright (c) 2010 - 2013 Roman Lacko
  8. //
  9. // Permission is hereby granted, free of charge, to any person obtaining
  10. // a copy of this software and associated documentation files (the
  11. // "Software"), to deal in the Software without restriction, including
  12. // without limitation the rights to use, copy, modify, merge, publish,
  13. // distribute, sublicense, and/or sell copies of the Software, and to
  14. // permit persons to whom the Software is furnished to do so, subject to
  15. // the following conditions:
  16. //
  17. // The above copyright notice and this permission notice shall be
  18. // included in all copies or substantial portions of the Software.
  19. //
  20. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  21. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  22. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  23. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  24. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  25. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  26. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  27. //
  28. namespace SharpTAL.TemplateProgram
  29. {
  30. using System;
  31. using System.Linq;
  32. using System.Collections.Generic;
  33. using System.Collections.Specialized;
  34. using System.IO;
  35. using System.Reflection;
  36. using System.Text.RegularExpressions;
  37. using SharpTAL.TemplateParser;
  38. using SharpTAL.TemplateProgram.Commands;
  39. /// <summary>
  40. /// ZPT (Zope Page Template) parser and Template program generator
  41. /// </summary>
  42. public class ProgramGenerator : AbstractTemplateParser
  43. {
  44. class TagStackItem
  45. {
  46. public TagStackItem(Tag tag)
  47. {
  48. Tag = tag;
  49. EndTagCommandLocation = null;
  50. PopFunctionList = null;
  51. UseMacroCommandLocation = -1;
  52. }
  53. public Tag Tag { get; set; }
  54. public int? EndTagCommandLocation { get; set; }
  55. public List<Action> PopFunctionList { get; set; }
  56. public int UseMacroCommandLocation { get; set; }
  57. }
  58. /// <summary>
  59. /// Contains compiled template programs. The key is the template body hash.
  60. /// </summary>
  61. static Dictionary<string, Program> templateProgramCache = new Dictionary<string, Program>();
  62. static object templateProgramCacheLock = new object();
  63. const string MAIN_PROGRAM_NAMESPACE = "template";
  64. const string MAIN_TEMPLATE_PATH = "<main>";
  65. const string DEFAULT_VALUE_EXPRESSION = "default";
  66. static readonly Regex TAL_DEFINE_REGEX = new Regex("(?<!;);(?!;)");
  67. static readonly Regex TAL_ATTRIBUTES_REGEX = new Regex("(?<!;);(?!;)");
  68. static readonly Regex METAL_DEFINE_PARAM_REGEX = new Regex("(?<!;);(?!;)");
  69. static readonly Regex METAL_FILL_PARAM_REGEX = new Regex("(?<!;);(?!;)");
  70. static readonly Regex METAL_IMPORT_REGEX = new Regex("(?<!;);(?!;)");
  71. static readonly Regex METAL_NAME_REGEX = new Regex("[a-zA-Z_][a-zA-Z0-9_]*");
  72. static readonly Dictionary<string, string> defaultNamespaces = new Dictionary<string, string> {
  73. { "xmlns", Namespaces.XMLNS_NS },
  74. { "xml", Namespaces.XML_NS },
  75. { "meta", Namespaces.META_NS},
  76. { "tal", Namespaces.TAL_NS },
  77. { "metal", Namespaces.METAL_NS } };
  78. Dictionary<CommandType, Func<List<TagAttribute>, List<Command>>> talAttributeHandlers;
  79. // Per-template compiling state (including inline templates compiling)
  80. string meta_namespace_prefix;
  81. List<string> meta_namespace_prefix_stack;
  82. Dictionary<string, CommandType> meta_attribute_map;
  83. string tal_namespace_prefix;
  84. List<string> tal_namespace_prefix_stack;
  85. Dictionary<string, CommandType> tal_attribute_map;
  86. string metal_namespace_prefix;
  87. List<string> metal_namespace_prefix_stack;
  88. Dictionary<string, CommandType> metal_attribute_map;
  89. // Per-template-body compiling state
  90. HashSet<string> importMacroCommands = null;
  91. List<ICommand> programCommands;
  92. Dictionary<int, int> endTagsCommandMap;
  93. Dictionary<string, IProgram> macroMap;
  94. List<TagStackItem> tagStack;
  95. int endTagCommandLocationCounter;
  96. Tag currentStartTag;
  97. public ProgramGenerator()
  98. {
  99. talAttributeHandlers = new Dictionary<CommandType, Func<List<TagAttribute>, List<Command>>>();
  100. talAttributeHandlers.Add(CommandType.META_INTERPOLATION, Handle_META_INTERPOLATION);
  101. talAttributeHandlers.Add(CommandType.METAL_USE_MACRO, Handle_METAL_USE_MACRO);
  102. talAttributeHandlers.Add(CommandType.METAL_DEFINE_SLOT, Handle_METAL_DEFINE_SLOT);
  103. talAttributeHandlers.Add(CommandType.METAL_FILL_SLOT, Handle_METAL_FILL_SLOT);
  104. talAttributeHandlers.Add(CommandType.METAL_DEFINE_MACRO, Handle_METAL_DEFINE_MACRO);
  105. talAttributeHandlers.Add(CommandType.METAL_DEFINE_PARAM, Handle_METAL_DEFINE_PARAM);
  106. talAttributeHandlers.Add(CommandType.METAL_FILL_PARAM, Handle_METAL_FILL_PARAM);
  107. talAttributeHandlers.Add(CommandType.METAL_IMPORT, Handle_METAL_IMPORT);
  108. talAttributeHandlers.Add(CommandType.TAL_DEFINE, Handle_TAL_DEFINE);
  109. talAttributeHandlers.Add(CommandType.TAL_CONDITION, Handle_TAL_CONDITION);
  110. talAttributeHandlers.Add(CommandType.TAL_REPEAT, Handle_TAL_REPEAT);
  111. talAttributeHandlers.Add(CommandType.TAL_CONTENT, Handle_TAL_CONTENT);
  112. talAttributeHandlers.Add(CommandType.TAL_REPLACE, Handle_TAL_REPLACE);
  113. talAttributeHandlers.Add(CommandType.TAL_ATTRIBUTES, Handle_TAL_ATTRIBUTES);
  114. talAttributeHandlers.Add(CommandType.TAL_OMITTAG, Handle_TAL_OMITTAG);
  115. }
  116. public void GenerateTemplateProgram(ref TemplateInfo ti)
  117. {
  118. // Init per-template compiling state (including inline templates compiling)
  119. // Default namespaces
  120. SetMETAPrefix("meta");
  121. meta_namespace_prefix_stack = new List<string>();
  122. meta_namespace_prefix_stack.Add("meta");
  123. SetTALPrefix("tal");
  124. tal_namespace_prefix_stack = new List<string>();
  125. tal_namespace_prefix_stack.Add("tal");
  126. SetMETALPrefix("metal");
  127. metal_namespace_prefix_stack = new List<string>();
  128. metal_namespace_prefix_stack.Add("metal");
  129. ti.ImportedPrograms = new Dictionary<string, Program>();
  130. ti.ImportedNamespaces = new Dictionary<string, HashSet<string>>();
  131. // Compile main template body
  132. ti.MainProgram = GetTemplateProgram(ti.TemplateBody, MAIN_TEMPLATE_PATH);
  133. // Compile imported templates
  134. CompileImportedTemplates(ti, ti.MainProgram);
  135. }
  136. void CompileImportedTemplates(TemplateInfo ti, Program program)
  137. {
  138. foreach (string importCmd in program.ImportMacroCommands)
  139. {
  140. // Parse import command
  141. string programNamespace = importCmd.Split(new char[] { ':' }, 2)[0];
  142. // TODO: Cesta k sablone, ktoru chcem importnut je relativna voci sablone, v ktorej volam import, takze tu musim vyskaldat absolutnu cestu
  143. // ak je sablona vytvorena zo stringu, treba takej sablone nastavit cestu podla assembly kde v ktorej sa vytvorila, ...alebo ?
  144. string templatePath = importCmd.Split(new char[] { ':' }, 2)[1];
  145. Program importedProgram;
  146. ti.ImportedPrograms.TryGetValue(templatePath, out importedProgram);
  147. // Compile template program from template body
  148. if (importedProgram == null)
  149. {
  150. // TODO: Implement the template loader (see TODO.txt) - load from filesystem by default
  151. string templateBody = File.ReadAllText(templatePath);
  152. importedProgram = GetTemplateProgram(templateBody, templatePath);
  153. ti.ImportedPrograms.Add(templatePath, importedProgram);
  154. }
  155. // Compile imports of imported template
  156. CompileImportedTemplates(ti, importedProgram);
  157. // Save info about Imported program by namespace and path
  158. if (!ti.ImportedNamespaces.ContainsKey(programNamespace))
  159. ti.ImportedNamespaces.Add(programNamespace, new HashSet<string>() { templatePath });
  160. else if (!ti.ImportedNamespaces[programNamespace].Contains(templatePath))
  161. ti.ImportedNamespaces[programNamespace].Add(templatePath);
  162. }
  163. }
  164. Program GetTemplateProgram(string templateBody, string templatePath)
  165. {
  166. // Init per-template-body compiling state
  167. importMacroCommands = new HashSet<string>();
  168. // Try to get template program from cache
  169. string bodyHash = Utils.ComputeHash(templateBody);
  170. Program program = null;
  171. lock (templateProgramCacheLock)
  172. {
  173. if (templateProgramCache.TryGetValue(bodyHash, out program))
  174. return program;
  175. }
  176. if (program != null)
  177. return program;
  178. // Per-template-body compiling state
  179. programCommands = new List<ICommand>();
  180. endTagsCommandMap = new Dictionary<int, int>();
  181. macroMap = new Dictionary<string, IProgram>();
  182. tagStack = new List<TagStackItem>();
  183. endTagCommandLocationCounter = 0;
  184. currentStartTag = null;
  185. // Parse template
  186. ParseTemplate(templateBody, templatePath, defaultNamespaces);
  187. // Create template program instance
  188. program = new Program(templateBody, templatePath, bodyHash, programCommands, endTagsCommandMap, macroMap, importMacroCommands);
  189. // Put template program to cache
  190. lock (templateProgramCacheLock)
  191. {
  192. if (!templateProgramCache.ContainsKey(bodyHash))
  193. templateProgramCache.Add(bodyHash, program);
  194. }
  195. return program;
  196. }
  197. #region AbstractTemplateParser implementation
  198. protected override void HandleStartTag(Tag tag)
  199. {
  200. // Note down the tag we are handling, it will be used for error handling during compilation
  201. currentStartTag = new Tag(tag);
  202. // Expand HTML entity references in attribute values
  203. foreach (TagAttribute att in currentStartTag.Attributes)
  204. att.Value = att.UnescapedValue;
  205. // Sorted dictionary of TAL attributes grouped by attribute type. The dictionary is sorted by the attribute type.
  206. SortedDictionary<CommandType, List<TagAttribute>> talAttributesDictionary = new SortedDictionary<CommandType, List<TagAttribute>>(new CommandTypeComparer());
  207. // Clean HTML/XML attributes
  208. List<TagAttribute> cleanAttributes = new List<TagAttribute>();
  209. List<Action> popFunctionList = new List<Action>();
  210. bool isTALElementNameSpace = false;
  211. string prefixToAdd = "";
  212. // Resolve TAL/METAL namespace declarations from attributes
  213. foreach (var att in currentStartTag.Attributes)
  214. {
  215. if (att.Name.Length > 4 && att.Name.Substring(0, 5) == "xmlns")
  216. {
  217. // We have a namespace declaration.
  218. string prefix = att.Name.Length > 5 ? att.Name.Substring(6) : "";
  219. if (att.Value == Namespaces.META_NS)
  220. {
  221. // It's a META namespace declaration
  222. if (prefix.Length > 0)
  223. {
  224. meta_namespace_prefix_stack.Add(meta_namespace_prefix);
  225. SetMETAPrefix(prefix);
  226. // We want this function called when the scope ends
  227. popFunctionList.Add(PopMETANamespace);
  228. }
  229. else
  230. {
  231. // We don't allow META/METAL/TAL to be declared as a default
  232. string msg = "Can not use META name space by default, a prefix must be provided.";
  233. throw new TemplateParseException(currentStartTag, msg);
  234. }
  235. }
  236. else if (att.Value == Namespaces.METAL_NS)
  237. {
  238. // It's a METAL namespace declaration
  239. if (prefix.Length > 0)
  240. {
  241. metal_namespace_prefix_stack.Add(metal_namespace_prefix);
  242. SetMETALPrefix(prefix);
  243. // We want this function called when the scope ends
  244. popFunctionList.Add(PopMETALNamespace);
  245. }
  246. else
  247. {
  248. // We don't allow META/METAL/TAL to be declared as a default
  249. string msg = "Can not use METAL name space by default, a prefix must be provided.";
  250. throw new TemplateParseException(currentStartTag, msg);
  251. }
  252. }
  253. else if (att.Value == Namespaces.TAL_NS)
  254. {
  255. // TAL this time
  256. if (prefix.Length > 0)
  257. {
  258. tal_namespace_prefix_stack.Add(tal_namespace_prefix);
  259. SetTALPrefix(prefix);
  260. // We want this function called when the scope ends
  261. popFunctionList.Add(PopTALNamespace);
  262. }
  263. else
  264. {
  265. // We don't allow META/METAL/TAL to be declared as a default
  266. string msg = "Can not use TAL name space by default, a prefix must be provided.";
  267. throw new TemplateParseException(currentStartTag, msg);
  268. }
  269. }
  270. else
  271. {
  272. // It's nothing special, just an ordinary namespace declaration
  273. cleanAttributes.Add(att);
  274. }
  275. }
  276. }
  277. // Determine whether this element is in either the METAL or TAL namespace
  278. if (tag.Name.IndexOf(':') > 0)
  279. {
  280. // We have a namespace involved, so let's look to see if its one of ours
  281. string _namespace = tag.Name.Substring(0, tag.Name.IndexOf(':'));
  282. if (_namespace == metal_namespace_prefix)
  283. {
  284. isTALElementNameSpace = true;
  285. prefixToAdd = metal_namespace_prefix + ":";
  286. }
  287. else if (_namespace == tal_namespace_prefix)
  288. {
  289. isTALElementNameSpace = true;
  290. prefixToAdd = tal_namespace_prefix + ":";
  291. }
  292. if (isTALElementNameSpace)
  293. {
  294. // We should treat this an implicit omit-tag
  295. // Will go to default, i.e. yes
  296. talAttributesDictionary[CommandType.TAL_OMITTAG] = new List<TagAttribute>() { new TALTagAttribute { Value = "", CommandType = CommandType.TAL_OMITTAG } };
  297. }
  298. }
  299. // Look for TAL/METAL attributes
  300. foreach (var att in currentStartTag.Attributes)
  301. {
  302. if (att.Name.Length > 4 && att.Name.Substring(0, 5) == "xmlns")
  303. // We have a namespace declaration.
  304. continue;
  305. string talCommandName = "";
  306. if (isTALElementNameSpace && att.Name.IndexOf(':') < 0)
  307. // This means that the attribute name does not have a namespace, so use the prefix for this tag.
  308. talCommandName = prefixToAdd + att.Name;
  309. else
  310. talCommandName = att.Name;
  311. if (tal_attribute_map.ContainsKey(talCommandName))
  312. {
  313. // It's a TAL attribute
  314. CommandType cmdType = tal_attribute_map[talCommandName];
  315. if (cmdType == CommandType.TAL_OMITTAG && isTALElementNameSpace)
  316. {
  317. // Supressing omit-tag command present on TAL or METAL element
  318. }
  319. else
  320. {
  321. if (!talAttributesDictionary.ContainsKey(cmdType))
  322. talAttributesDictionary.Add(cmdType, new List<TagAttribute>());
  323. talAttributesDictionary[cmdType].Add(new TALTagAttribute(att) { CommandType = cmdType });
  324. }
  325. }
  326. else if (metal_attribute_map.ContainsKey(talCommandName))
  327. {
  328. // It's a METAL attribute
  329. CommandType cmdType = metal_attribute_map[talCommandName];
  330. if (!talAttributesDictionary.ContainsKey(cmdType))
  331. talAttributesDictionary.Add(cmdType, new List<TagAttribute>());
  332. talAttributesDictionary[cmdType].Add(new TALTagAttribute(att) { CommandType = cmdType });
  333. }
  334. else if (meta_attribute_map.ContainsKey(talCommandName))
  335. {
  336. // It's a META attribute
  337. CommandType cmdType = meta_attribute_map[talCommandName];
  338. if (!talAttributesDictionary.ContainsKey(cmdType))
  339. talAttributesDictionary.Add(cmdType, new List<TagAttribute>());
  340. talAttributesDictionary[cmdType].Add(new TALTagAttribute(att) { CommandType = cmdType });
  341. }
  342. else
  343. {
  344. // It's normal HTML/XML attribute
  345. cleanAttributes.Add(att);
  346. }
  347. }
  348. if (cleanAttributes.Count > 0)
  349. {
  350. // Insert normal HTML/XML attributes BEFORE other TAL/METAL TAL_ATTRIBUTES commands
  351. // as fake TAL_ATTRIBUTES commands to enable string expressions interpolation on normal HTML/XML attributes.
  352. if (!talAttributesDictionary.ContainsKey(CommandType.TAL_ATTRIBUTES))
  353. talAttributesDictionary.Add(CommandType.TAL_ATTRIBUTES, new List<TagAttribute>());
  354. talAttributesDictionary[CommandType.TAL_ATTRIBUTES].InsertRange(0, cleanAttributes);
  355. }
  356. // Create a symbol for the end of the tag - we don't know what the offset is yet
  357. endTagCommandLocationCounter++;
  358. TagStackItem tagStackItem = null;
  359. foreach (CommandType cmdType in talAttributesDictionary.Keys)
  360. {
  361. // Resolve program commands from tal attributes
  362. var commands = talAttributeHandlers[cmdType](talAttributesDictionary[cmdType]);
  363. if (commands != null)
  364. foreach (Command cmd in commands)
  365. {
  366. if (tagStackItem == null)
  367. {
  368. // The first command needs to add the tag to the tag stack
  369. tagStackItem = AddTagToStack(tag, cleanAttributes);
  370. // Save metal:use-macro command position
  371. if (cmd.CommandType == CommandType.METAL_USE_MACRO)
  372. tagStackItem.UseMacroCommandLocation = programCommands.Count + 1;
  373. // Append command to create new scope for the tag
  374. Command startScopeCmd = new CMDStartScope(currentStartTag);
  375. programCommands.Add(startScopeCmd);
  376. }
  377. // All others just append
  378. programCommands.Add(cmd);
  379. }
  380. }
  381. if (tagStackItem == null)
  382. {
  383. tagStackItem = AddTagToStack(tag, cleanAttributes);
  384. // Append command to create new scope for the tag
  385. programCommands.Add(new CMDStartScope(currentStartTag));
  386. }
  387. // Save pop functions and end tag command location for this tag
  388. tagStackItem.PopFunctionList = popFunctionList;
  389. tagStackItem.EndTagCommandLocation = endTagCommandLocationCounter;
  390. // Finally, append start tag command
  391. programCommands.Add(new CMDStartTag(currentStartTag));
  392. }
  393. protected override void HandleEndTag(Tag tag)
  394. {
  395. while (tagStack.Count > 0)
  396. {
  397. TagStackItem tagStackItem = tagStack.Last();
  398. tagStack.RemoveAt(tagStack.Count - 1);
  399. Tag oldTag = tagStackItem.Tag;
  400. int? endTagCommandLocation = tagStackItem.EndTagCommandLocation;
  401. List<Action> popFunctionList = tagStackItem.PopFunctionList;
  402. if (popFunctionList != null)
  403. {
  404. foreach (Action func in popFunctionList)
  405. func();
  406. }
  407. if (oldTag.Name == tag.Name)
  408. {
  409. // We've found the right tag, now check to see if we have any TAL commands on it
  410. if (endTagCommandLocation != null)
  411. {
  412. // We have a command (it's a TAL tag)
  413. // Note where the end tag command location should point (i.e. the next command)
  414. endTagsCommandMap[(int)endTagCommandLocation] = programCommands.Count;
  415. // We need a "close scope and tag" command
  416. programCommands.Add(new CMDEntTagEndScope(tag));
  417. return;
  418. }
  419. else if (!tag.Singleton)
  420. {
  421. // We are popping off an un-interesting tag, just add the close as text
  422. programCommands.Add(new CMDOutput(tag, "</" + tag.Name + ">"));
  423. return;
  424. }
  425. else
  426. {
  427. // We are suppressing the output of this tag, so just return
  428. return;
  429. }
  430. }
  431. else
  432. {
  433. // We have a different tag, which means something like <br> which never closes is in
  434. // between us and the real tag.
  435. // If the tag that we did pop off has a command though it means un-balanced TAL tags!
  436. if (endTagCommandLocation != null)
  437. {
  438. // ERROR
  439. string msg = string.Format("TAL/METAL Elements must be balanced - found close tag {0} expecting {1}", tag.Name, oldTag.Name);
  440. throw new TemplateParseException(oldTag, msg);
  441. }
  442. }
  443. }
  444. throw new TemplateParseException(null,
  445. string.Format("</{0}> {1}", tag.Name, "Close tag encountered with no corresponding open tag."));
  446. }
  447. protected override void HandleData(string data)
  448. {
  449. // Just add it as an output
  450. programCommands.Add(new CMDOutput(currentStartTag, data));
  451. }
  452. protected override void HandleComment(string data)
  453. {
  454. HandleData(data);
  455. }
  456. protected override void HandleCData(string data)
  457. {
  458. HandleData(data);
  459. }
  460. protected override void HandleProcessingInstruction(Element e)
  461. {
  462. var name = ((Token)e.StartTagTokens["name"]).ToString();
  463. var text = ((Token)e.StartTagTokens["text"]).ToString();
  464. if (name != "csharp")
  465. {
  466. HandleData(string.Format("<?{0}{1}?>", name, text));
  467. return;
  468. }
  469. programCommands.Add(new CMDCodeBlock(name, text));
  470. }
  471. protected override void HandleDefault(string data)
  472. {
  473. HandleData(data);
  474. }
  475. #endregion
  476. TagStackItem AddTagToStack(Tag tag, List<TagAttribute> cleanAttributes)
  477. {
  478. // Set tag attributes to contain only normal HTML/XML attributes (TAL/METAL attributes are removed)
  479. tag.Attributes = cleanAttributes;
  480. // Add tag to tag stack
  481. TagStackItem tagStackItem = new TagStackItem(tag);
  482. tagStack.Add(tagStackItem);
  483. return tagStackItem;
  484. }
  485. void SetMETAPrefix(string prefix)
  486. {
  487. meta_namespace_prefix = prefix;
  488. meta_attribute_map = new Dictionary<string, CommandType>(); ;
  489. meta_attribute_map.Add(string.Format("{0}:interpolation", prefix), CommandType.META_INTERPOLATION);
  490. }
  491. void SetTALPrefix(string prefix)
  492. {
  493. tal_namespace_prefix = prefix;
  494. tal_attribute_map = new Dictionary<string, CommandType>(); ;
  495. tal_attribute_map.Add(string.Format("{0}:attributes", prefix), CommandType.TAL_ATTRIBUTES);
  496. tal_attribute_map.Add(string.Format("{0}:content", prefix), CommandType.TAL_CONTENT);
  497. tal_attribute_map.Add(string.Format("{0}:define", prefix), CommandType.TAL_DEFINE);
  498. tal_attribute_map.Add(string.Format("{0}:replace", prefix), CommandType.TAL_REPLACE);
  499. tal_attribute_map.Add(string.Format("{0}:omit-tag", prefix), CommandType.TAL_OMITTAG);
  500. tal_attribute_map.Add(string.Format("{0}:condition", prefix), CommandType.TAL_CONDITION);
  501. tal_attribute_map.Add(string.Format("{0}:repeat", prefix), CommandType.TAL_REPEAT);
  502. }
  503. void SetMETALPrefix(string prefix)
  504. {
  505. metal_namespace_prefix = prefix;
  506. metal_attribute_map = new Dictionary<string, CommandType>(); ;
  507. metal_attribute_map.Add(string.Format("{0}:define-macro", prefix), CommandType.METAL_DEFINE_MACRO);
  508. metal_attribute_map.Add(string.Format("{0}:use-macro", prefix), CommandType.METAL_USE_MACRO);
  509. metal_attribute_map.Add(string.Format("{0}:define-slot", prefix), CommandType.METAL_DEFINE_SLOT);
  510. metal_attribute_map.Add(string.Format("{0}:fill-slot", prefix), CommandType.METAL_FILL_SLOT);
  511. metal_attribute_map.Add(string.Format("{0}:define-param", prefix), CommandType.METAL_DEFINE_PARAM);
  512. metal_attribute_map.Add(string.Format("{0}:fill-param", prefix), CommandType.METAL_FILL_PARAM);
  513. metal_attribute_map.Add(string.Format("{0}:import", prefix), CommandType.METAL_IMPORT);
  514. }
  515. void PopMETANamespace()
  516. {
  517. string newPrefix = meta_namespace_prefix_stack[meta_namespace_prefix_stack.Count - 1];
  518. meta_namespace_prefix_stack.RemoveAt(meta_namespace_prefix_stack.Count - 1);
  519. SetMETAPrefix(newPrefix);
  520. }
  521. void PopTALNamespace()
  522. {
  523. string newPrefix = tal_namespace_prefix_stack[tal_namespace_prefix_stack.Count - 1];
  524. tal_namespace_prefix_stack.RemoveAt(tal_namespace_prefix_stack.Count - 1);
  525. SetTALPrefix(newPrefix);
  526. }
  527. void PopMETALNamespace()
  528. {
  529. string newPrefix = metal_namespace_prefix_stack[metal_namespace_prefix_stack.Count - 1];
  530. metal_namespace_prefix_stack.RemoveAt(metal_namespace_prefix_stack.Count - 1);
  531. SetMETALPrefix(newPrefix);
  532. }
  533. List<Command> Handle_META_INTERPOLATION(List<TagAttribute> attributes)
  534. {
  535. // Only last declared attribute is valid
  536. string argument = attributes[attributes.Count - 1].Value;
  537. if (string.IsNullOrEmpty(argument))
  538. {
  539. // No argument passed
  540. string msg = "No argument passed! meta:interpolation command must be of the form: meta:interpolation='true|false'";
  541. throw new TemplateParseException(currentStartTag, msg);
  542. }
  543. if (argument == "true")
  544. return new List<Command> { new METAInterpolation(currentStartTag, true) };
  545. if (argument == "false")
  546. return new List<Command> { new METAInterpolation(currentStartTag, false) };
  547. throw new TemplateParseException(currentStartTag,
  548. string.Format("Invalid command value '{0}'. Command meta:interpolation must be of the form: meta:interpolation='true|false'", argument));
  549. }
  550. List<Command> Handle_METAL_DEFINE_MACRO(List<TagAttribute> attributes)
  551. {
  552. // Only last declared attribute is valid
  553. string macroName = attributes[attributes.Count - 1].Value;
  554. if (string.IsNullOrEmpty(macroName))
  555. {
  556. // No argument passed
  557. string msg = "No argument passed! define-macro commands must be of the form: 'define-macro: name'";
  558. throw new TemplateParseException(currentStartTag, msg);
  559. }
  560. // Check that the name of the macro is valid
  561. if (METAL_NAME_REGEX.Match(macroName).Length != macroName.Length)
  562. {
  563. string msg = string.Format("Macro name {0} is invalid.", macroName);
  564. throw new TemplateParseException(currentStartTag, msg);
  565. }
  566. if (macroMap.ContainsKey(macroName))
  567. {
  568. string msg = string.Format("Macro name {0} is already defined!", macroName);
  569. throw new TemplateParseException(currentStartTag, msg);
  570. }
  571. // The macro starts at the next command.
  572. IProgram macro = new ProgramMacro(macroName, programCommands.Count, endTagCommandLocationCounter);
  573. macroMap.Add(macroName, macro);
  574. return null;
  575. }
  576. List<Command> Handle_METAL_USE_MACRO(List<TagAttribute> attributes)
  577. {
  578. // Only last declared attribute is valid
  579. string argument = attributes[attributes.Count - 1].Value;
  580. // Sanity check
  581. if (argument.Length == 0)
  582. {
  583. // No argument passed
  584. string msg = "No argument passed! use-macro commands must be of the form: 'use-macro: path'";
  585. throw new TemplateParseException(currentStartTag, msg);
  586. }
  587. return new List<Command> { new METALUseMacro(currentStartTag, argument, new Dictionary<string, ProgramSlot>(), new List<METALDefineParam>()) };
  588. }
  589. List<Command> Handle_METAL_DEFINE_SLOT(List<TagAttribute> attributes)
  590. {
  591. // Only last declared attribute is valid
  592. string slotName = attributes[attributes.Count - 1].Value;
  593. // Compile a define-slot command.
  594. if (slotName.Length == 0)
  595. {
  596. // No argument passed
  597. string msg = "No argument passed! define-slot commands must be of the form: 'name'";
  598. throw new TemplateParseException(currentStartTag, msg);
  599. }
  600. // Check that the name of the slot is valid
  601. if (METAL_NAME_REGEX.Match(slotName).Length != slotName.Length)
  602. {
  603. string msg = string.Format("Slot name {0} is invalid.", slotName);
  604. throw new TemplateParseException(currentStartTag, msg);
  605. }
  606. return new List<Command> { new METALDefineSlot(currentStartTag, slotName) };
  607. }
  608. List<Command> Handle_METAL_FILL_SLOT(List<TagAttribute> attributes)
  609. {
  610. // Only last declared attribute is valid
  611. string slotName = attributes[attributes.Count - 1].Value;
  612. if (slotName.Length == 0)
  613. {
  614. // No argument passed
  615. string msg = "No argument passed! fill-slot commands must be of the form: 'fill-slot: name'";
  616. throw new TemplateParseException(currentStartTag, msg);
  617. }
  618. // Check that the name of the slot is valid
  619. if (METAL_NAME_REGEX.Match(slotName).Length != slotName.Length)
  620. {
  621. string msg = string.Format("Slot name {0} is invalid.", slotName);
  622. throw new TemplateParseException(currentStartTag, msg);
  623. }
  624. // Determine what use-macro statement this belongs to by working through the list backwards
  625. int ourMacroLocation = -1;
  626. int location = tagStack.Count - 1;
  627. while (ourMacroLocation == -1)
  628. {
  629. int macroLocation = tagStack[location].UseMacroCommandLocation;
  630. if (macroLocation != -1)
  631. {
  632. ourMacroLocation = macroLocation;
  633. }
  634. else
  635. {
  636. location -= 1;
  637. if (location < 0)
  638. {
  639. string msg = string.Format("metal:fill-slot must be used inside a metal:use-macro call");
  640. throw new TemplateParseException(currentStartTag, msg);
  641. }
  642. }
  643. }
  644. // Update the metal:use-macro command slot definitions
  645. METALUseMacro useMacroCmd = (METALUseMacro)programCommands[ourMacroLocation];
  646. if (useMacroCmd.Slots.ContainsKey(slotName))
  647. {
  648. string msg = string.Format("Slot {0} has already been filled!", slotName);
  649. throw new TemplateParseException(currentStartTag, msg);
  650. }
  651. int slotCommandStart = programCommands.Count; // The slot starts at the next command.
  652. ProgramSlot slot = new ProgramSlot(slotName, slotCommandStart, endTagCommandLocationCounter);
  653. useMacroCmd.Slots.Add(slotName, slot);
  654. return null;
  655. }
  656. List<Command> Handle_METAL_DEFINE_PARAM(List<TagAttribute> attributes)
  657. {
  658. // Join attributes for commands that support multiple attributes
  659. string argument = string.Join(";", attributes.Select(a => a.Value).ToArray());
  660. List<Command> commands = new List<Command>();
  661. // We only want to match semi-colons that are not escaped
  662. foreach (string defStmt in METAL_DEFINE_PARAM_REGEX.Split(argument))
  663. {
  664. // remove any leading space and un-escape any semi-colons
  665. string defineStmt = defStmt.TrimStart().Replace(";;", ";");
  666. // Break each defineStmt into pieces "[local|global] varName expression"
  667. List<string> stmtBits = new List<string>(defineStmt.Split(new char[] { ' ' }));
  668. string varType;
  669. string varName;
  670. string expression;
  671. if (stmtBits.Count < 3)
  672. {
  673. // Error, badly formed define-param command
  674. string msg = string.Format("Badly formed define-param command '{0}'. Define commands must be of the form: 'varType varName expression[;varType varName expression]'", argument);
  675. throw new TemplateParseException(currentStartTag, msg);
  676. }
  677. varType = stmtBits[0];
  678. varName = stmtBits[1];
  679. expression = string.Join(" ", stmtBits.GetRange(2, stmtBits.Count - 2).ToArray());
  680. commands.Add(new METALDefineParam(currentStartTag, varType, varName, expression));
  681. }
  682. return commands;
  683. }
  684. List<Command> Handle_METAL_FILL_PARAM(List<TagAttribute> attributes)
  685. {
  686. // Join attributes for commands that support multiple attributes
  687. string argument = string.Join(";", attributes.Select(a => a.Value).ToArray());
  688. List<METALDefineParam> fillParamsCommands = new List<METALDefineParam>();
  689. // We only want to match semi-colons that are not escaped
  690. foreach (string defStmt in METAL_FILL_PARAM_REGEX.Split(argument))
  691. {
  692. // remove any leading space and un-escape any semi-colons
  693. string defineStmt = defStmt.TrimStart().Replace(";;", ";");
  694. // Break each defineStmt into pieces "[local|global] varName expression"
  695. List<string> stmtBits = new List<string>(defineStmt.Split(new char[] { ' ' }));
  696. string varName;
  697. string expression;
  698. if (stmtBits.Count < 2)
  699. {
  700. // Error, badly formed fill-param command
  701. string msg = string.Format("Badly formed fill-param command '{0}'. Fill-param commands must be of the form: 'varName expression[;varName expression]'", argument);
  702. throw new TemplateParseException(currentStartTag, msg);
  703. }
  704. varName = stmtBits[0];
  705. expression = string.Join(" ", stmtBits.GetRange(1, stmtBits.Count - 1).ToArray());
  706. fillParamsCommands.Add(new METALDefineParam(currentStartTag, "", varName, expression));
  707. }
  708. // Determine what metal:use-macro statement this belongs to by working through the list backwards
  709. int ourMacroLocation = -1;
  710. int stackIndex = tagStack.Count - 1;
  711. while (ourMacroLocation == -1)
  712. {
  713. int macroLocation = tagStack[stackIndex].UseMacroCommandLocation;
  714. if (macroLocation != -1)
  715. {
  716. ourMacroLocation = macroLocation;
  717. }
  718. else
  719. {
  720. stackIndex -= 1;
  721. if (stackIndex < 0)
  722. {
  723. string msg = string.Format("metal:fill-param must be used inside a metal:use-macro call");
  724. throw new TemplateParseException(currentStartTag, msg);
  725. }
  726. }
  727. }
  728. // Update the metal:use-macro command param definitions
  729. METALUseMacro useMacroCmd = (METALUseMacro)programCommands[ourMacroLocation];
  730. useMacroCmd.Parameters.AddRange(fillParamsCommands);
  731. return null;
  732. }
  733. List<Command> Handle_METAL_IMPORT(List<TagAttribute> attributes)
  734. {
  735. // Join attributes for commands that support multiple attributes
  736. string argument = string.Join(";", attributes.Select(a => a.Value).ToArray());
  737. // Compile a import command, resulting argument is:
  738. // Argument: [([importNs] importPath),...], endTagCommandLocation
  739. // Sanity check
  740. if (string.IsNullOrEmpty(argument))
  741. {
  742. // No argument passed
  743. string msg = "No argument passed! Metal import commands must be of the form: 'path'";
  744. throw new TemplateParseException(currentStartTag, msg);
  745. }
  746. // Break up the list of imports first
  747. // We only want to match semi-colons that are not escaped
  748. foreach (string impStmt in METAL_IMPORT_REGEX.Split(argument))
  749. {
  750. // remove any leading space and un-escape any semi-colons
  751. string importStmt = impStmt.Trim().Replace(";;", ";");
  752. string importNs = MAIN_PROGRAM_NAMESPACE;
  753. string importPath;
  754. // Check if import path is legal rooted path
  755. if (Path.IsPathRooted(importStmt) && File.Exists(importStmt))
  756. {
  757. // Import statement contains only legal rooted path, no namespace definition
  758. importPath = importStmt;
  759. }
  760. else
  761. {
  762. // Break each importStmt into pieces "importNs:importPath"
  763. List<string> stmtBits = new List<string>(importStmt.Split(new char[] { ':' }, 2));
  764. if (stmtBits.Count < 1)
  765. {
  766. // Error, badly formed import command
  767. string msg = string.Format("Badly formed import command '{0}'. Import commands must be of the form: '(importNs:)importPath[;(importNs:)importPath]'", argument);
  768. throw new TemplateParseException(currentStartTag, msg);
  769. }
  770. // We have namespace
  771. if (stmtBits.Count > 1)
  772. {
  773. importNs = stmtBits[0];
  774. importPath = string.Join(" ", stmtBits.GetRange(1, stmtBits.Count - 1).ToArray());
  775. }
  776. else
  777. {
  778. // No namespace
  779. importPath = stmtBits[0];
  780. }
  781. // Normalize and check the path to xml stored in path attribute
  782. if (!Path.IsPathRooted(importPath))
  783. {
  784. importPath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), importPath));
  785. }
  786. if (!File.Exists(importPath))
  787. {
  788. // Invalid path
  789. string msg = "Path specified by import argument does not exists: " + importPath;
  790. throw new TemplateParseException(currentStartTag, msg);
  791. }
  792. }
  793. // Save import command to cache
  794. string importCmd = string.Format("{0}:{1}", importNs, importPath);
  795. if (!importMacroCommands.Contains(importCmd))
  796. {
  797. importMacroCommands.Add(importCmd);
  798. }
  799. }
  800. return null;
  801. }
  802. List<Command> Handle_TAL_DEFINE(List<TagAttribute> attributes)
  803. {
  804. // Join attributes for commands that support multiple attributes
  805. string argument = string.Join(";", attributes.Select(a => a.Value).ToArray());
  806. List<Command> commands = new List<Command>();
  807. // We only want to match semi-colons that are not escaped
  808. foreach (string defStmt in TAL_DEFINE_REGEX.Split(argument))
  809. {
  810. // remove any leading space and un-escape any semi-colons
  811. string defineStmt = defStmt.TrimStart().Replace(";;", ";");
  812. // Break each defineStmt into pieces "[local|global] varName expression"
  813. List<string> stmtBits = new List<string>(defineStmt.Split(new char[] { ' ' }));
  814. TALDefine.VariableScope varScope = TALDefine.VariableScope.Local;
  815. string varName;
  816. string expression;
  817. if (stmtBits.Count < 2)
  818. {
  819. // Error, badly formed define command
  820. string msg = string.Format("Badly formed define command '{0}'. Define commands must be of the form: '[local|nonlocal|global] varName expression[;[local|nonlocal|global] varName expression]'", argument);
  821. throw new TemplateParseException(currentStartTag, msg);
  822. }
  823. // Assume to start with that >2 elements means a local|global flag
  824. if (stmtBits.Count > 2)
  825. {
  826. if (stmtBits[0] == "global")
  827. {
  828. varScope = TALDefine.VariableScope.Global;
  829. varName = stmtBits[1];
  830. expression = string.Join(" ", stmtBits.GetRange(2, stmtBits.Count - 2).ToArray());
  831. }
  832. else if (stmtBits[0] == "local")
  833. {
  834. varScope = TALDefine.VariableScope.Local;
  835. varName = stmtBits[1];
  836. expression = string.Join(" ", stmtBits.GetRange(2, stmtBits.Count - 2).ToArray());
  837. }
  838. else if (stmtBits[0] == "nonlocal")
  839. {
  840. varScope = TALDefine.VariableScope.NonLocal;
  841. varName = stmtBits[1];
  842. expression = string.Join(" ", stmtBits.GetRange(2, stmtBits.Count - 2).ToArray());
  843. }
  844. else
  845. {
  846. // Must be a space in the expression that caused the >3 thing
  847. varName = stmtBits[0];
  848. expression = string.Join(" ", stmtBits.GetRange(1, stmtBits.Count - 1).ToArray());
  849. }
  850. }
  851. else
  852. {
  853. // Only two bits
  854. varName = stmtBits[0];
  855. expression = string.Join(" ", stmtBits.GetRange(1, stmtBits.Count - 1).ToArray());
  856. }
  857. commands.Add(new TALDefine(currentStartTag, varScope, varName, expression));
  858. }
  859. return commands;
  860. }
  861. List<Command> Handle_TAL_CONDITION(List<TagAttribute> attributes)
  862. {
  863. // Only last declared attribute is valid
  864. string expression = attributes[attributes.Count - 1].Value;
  865. // Sanity check
  866. if (expression.Length == 0)
  867. {
  868. // No argument passed
  869. string msg = "No argument passed! condition commands must be of the form: 'path'";
  870. throw new TemplateParseException(currentStartTag, msg);
  871. }
  872. return new List<Command> { new TALCondition(currentStartTag, expression) };
  873. }
  874. List<Command> Handle_TAL_REPEAT(List<TagAttribute> attributes)
  875. {
  876. // Only last declared attribute is valid
  877. string argument = attributes[attributes.Count - 1].Value;
  878. List<string> attProps = new List<string>(argument.Split(new char[] { ' ' }));
  879. // Sanity check
  880. if (attProps.Count < 2)
  881. {
  882. // Error, badly formed repeat command
  883. string msg = string.Format("Badly formed repeat command '{0}'. Repeat commands must be of the form: 'variable path'", argument);
  884. throw new TemplateParseException(currentStartTag, msg);
  885. }
  886. string varName = attProps[0];
  887. string expression = string.Join(" ", attProps.GetRange(1, attProps.Count - 1).ToArray());
  888. return new List<Command> { new TALRepeat(currentStartTag, varName, expression) };
  889. }
  890. List<Command> Handle_TAL_CONTENT(List<TagAttribute> attributes)
  891. {
  892. return Handle_TAL_CONTENT(attributes, false);
  893. }
  894. List<Command> Handle_TAL_CONTENT(List<TagAttribute> attributes, bool replace)
  895. {
  896. // Only last declared attribute is valid
  897. string argument = attributes[attributes.Count - 1].Value;
  898. // Compile a content or replace command
  899. // Sanity check
  900. if (argument.Length == 0)
  901. {
  902. // No argument passed
  903. string msg = "No argument passed! content/replace commands must be of the form: 'path'";
  904. throw new TemplateParseException(currentStartTag, msg);
  905. }
  906. bool structure = false;
  907. string expression = "";
  908. string[] attProps = argument.Split(new char[] { ' ' });
  909. if (attProps.Length > 1)
  910. {
  911. if (attProps[0] == "structure")
  912. {
  913. structure = true;
  914. expression = string.Join(" ", attProps, 1, attProps.Length - 1);
  915. }
  916. else if (attProps[1] == "text")
  917. {
  918. structure = false;
  919. expression = string.Join(" ", attProps, 1, attProps.Length - 1);
  920. }
  921. else
  922. {
  923. // It's not a type selection after all - assume it's part of the path
  924. expression = argument;
  925. }
  926. }
  927. else
  928. expression = argument;
  929. if (replace)
  930. return new List<Command> { new TALReplace(currentStartTag, expression, structure) };
  931. else
  932. return new List<Command> { new TALContent(currentStartTag, expression, structure) };
  933. }
  934. List<Command> Handle_TAL_REPLACE(List<TagAttribute> attributes)
  935. {
  936. return Handle_TAL_CONTENT(attributes, true);
  937. }
  938. List<Command> Handle_TAL_ATTRIBUTES(List<TagAttribute> attributes)
  939. {
  940. // Compile tal:attributes into attribute command
  941. List<TagAttribute> attrList = new List<TagAttribute>();
  942. foreach (TagAttribute att in attributes)
  943. {
  944. if (att is TALTagAttribute)
  945. {
  946. // This is TAL command attribute
  947. // Break up the attribute args to list of TALTagAttributes
  948. // We only want to match semi-colons that are not escaped
  949. foreach (string attStmt in TAL_ATTRIBUTES_REGEX.Split(att.Value))
  950. {
  951. // Remove any leading space and un-escape any semi-colons
  952. // Break each attributeStmt into name and expression
  953. List<string> stmtBits = new List<string>(attStmt.TrimStart().Replace(";;", ";").Split(' '));
  954. if (stmtBits.Count < 2)
  955. {
  956. // Error, badly formed attributes command
  957. string msg = string.Format(
  958. "Badly formed attributes command '{0}'. Attributes commands must be of the form: 'name expression[;name expression]'",
  959. att.Value);
  960. throw new TemplateParseException(currentStartTag, msg);
  961. }
  962. TALTagAttribute talTagAttr = new TALTagAttribute
  963. {
  964. CommandType = ((TALTagAttribute)att).CommandType,
  965. Name = stmtBits[0].Trim(' ', '\r', '\n'),
  966. Value = string.Join(" ", stmtBits.GetRange(1, stmtBits.Count - 1).ToArray()),
  967. Eq = @"=",
  968. Quote = @"""",
  969. QuoteEntity = Utils.Char2Entity(@"""")
  970. };
  971. attrList.Add(talTagAttr);
  972. }
  973. }
  974. else
  975. {
  976. // This is clean html/xml tag attribute (no TAL/METAL command)
  977. attrList.Add(att);
  978. }
  979. }
  980. TALAttributes cmd = new TALAttributes(currentStartTag, attrList);
  981. return new List<Command> { cmd };
  982. }
  983. List<Command> Handle_TAL_OMITTAG(List<TagAttribute> attributes)
  984. {
  985. // Only last declared attribute is valid
  986. string argument = attributes[attributes.Count - 1].Value;
  987. // Compile a condition command.
  988. // If no argument is given then set the path to default
  989. string expression = "";
  990. if (argument.Length == 0)
  991. expression = DEFAULT_VALUE_EXPRESSION;
  992. else
  993. expression = argument;
  994. return new List<Command> { new TALOmitTag(currentStartTag, expression) };
  995. }
  996. }
  997. }