using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using Arebis.CodeGeneration;
using Arebis.Parsing.MultiContent;
using System.IO;
using System.Text.RegularExpressions;
using System.Collections.Specialized;
using System.Diagnostics;
using System.CodeDom.Compiler;
namespace Arebis.CodeGenerator.Templated
{
[System.Diagnostics.DebuggerStepThrough]
public class TemplateInfo : ITemplateInfo, IDisposable
{
#region Static fields
public static Dictionary<string, Type> CodeBuilders;
public static string CodeTemplateElementName = "CodeTemplate";
public static Regex RxComments = new Regex("<%--( |\\t)*\\r?\\n?(?<comment>(?s:.*?))( |\\t)*\\r?\\n?--%>(\\w*\\r?\\n)?");
public static Regex RxIncludes = new Regex("<!--(?i:\\s*#include\\s+file\\s*=\\s*(?<quote>[\"']?)(?<filename>[^\"']*?)\\k<quote>\\s*)-->(\\w*\\r?\\n)?");
public static Regex RxDirectives = new Regex("(<%@\\s*(?<elementName>\\w+)(\\s+(?<name>\\w+)=(?<quote>[\"']?)(?<value>[^\"']*)\\k<quote>)*\\s*%>(\\w*\\r?\\n)?)+");
public static Regex RxScriptlets = new Regex("<%(?![@=$#%])\\s*\\r?\\n?(?<body>[^=]((.|\\r|\\n)*?(<%=(.|\\r|\\n)*?%>)?)*)%>(\\w*\\r?\\n)?");
public static Regex RxScripts = new Regex("<%%\\s*\\r?\\n?(?<body>((.|\\r|\\n)*?(\\w*@[^\\r\\n]*\\r?\\n)*)*)%%>(\\w*\\r?\\n)?");
public static Regex RxEmbeddedBody = new Regex("(\\w*@(?<body>[^\\r\\n]*?\\r?\\n))+");
public static string[] ExpressionTokens = new string[] { "<%=", "%>" };
public static string DefaultNameSpace = "Arebis.DynamicAssembly";
public static string DefaultBaseClass = "Arebis.CodeGeneration.CodeTemplate";
public static string DefaultTemplateLanguage = "c#";
#endregion Static fields
#region Instance fields
// Main fields:
private GenerationHost host;
private FileInfo templatefileinfo;
private MixedContentFile fileContent;
private ICodeBuilder codeBuilder = null;
// Properties retrieved from template directives:
private Dictionary<string, IList<NameValueCollection>> directives;
#endregion Instance fields
static TemplateInfo()
{
CodeBuilders = new Dictionary<string, Type>();
CodeBuilders["vb"] = typeof(VBCodeBuilder);
CodeBuilders["c#"] = typeof(CSCodeBuilder);
}
public TemplateInfo(string filename, GenerationHost host)
{
this.templatefileinfo = new FileInfo(filename);
this.host = host;
}
public void Invoke(object[] parameters)
{
// Create compiled type:
if (this.codeBuilder == null)
{
this.host.Log("Parsing template: \"{0}\".", this.templatefileinfo.FullName);
this.fileContent = this.ReadAndParseFile();
this.ReadDirectives(this.fileContent);
this.host.Log("Compiling template: \"{0}\".", this.templatefileinfo.FullName);
this.codeBuilder = this.CreateCodeBuilder(this.GetCodeTemplateDirective()["Language"] ?? DefaultTemplateLanguage);
this.codeBuilder.TemplateInfo = this;
bool succeeded = this.codeBuilder.Compile();
foreach (CompilerError err in this.codeBuilder.CompilerErrors)
{
this.host.Log("Compile {0} {1}: {2}\r\n File \"{3}\", line {4}",
err.IsWarning ? "warning" : "error",
err.ErrorNumber,
err.ErrorText,
err.FileName,
err.Line);
}
if (succeeded == false)
{
throw new CompilationFailedException(this.templatefileinfo.FullName, this.codeBuilder.CompilerErrors);
}
}
// Construct full parameters (add host):
List<object> allparameters = new List<object>();
allparameters.Add(this.host);
allparameters.AddRange(parameters);
// Create instance and invoke:
this.host.Log("Invoking template: \"{0}\".", this.templatefileinfo.FullName);
CodeTemplate instance = (CodeTemplate)Activator.CreateInstance(this.codeBuilder.CompiledType, allparameters.ToArray());
try
{
instance.Generate();
}
catch (RuntimeException)
{
throw;
}
catch (CompilationFailedException)
{
throw;
}
catch (Exception ex)
{
this.host.Log(ex.ToString());
throw new RuntimeException(ex);
}
}
/// <summary>
/// Returns the full filename given a relative filename.
/// Used to resolve codeBehind and additionally compiled files.
/// </summary>
public string FindFile(string relativeName)
{
if (relativeName == null) return null;
return Path.Combine(Path.GetDirectoryName(templatefileinfo.FullName), relativeName);
}
#region ITemplateInfo Members
public IGenerationHost Host
{
get { return this.host; }
}
public NameValueCollection Settings
{
get { return this.host.Settings; }
}
public FileInfo TemplateFileInfo
{
get { return this.templatefileinfo; }
}
public MixedContentFile FileContent
{
get { return this.fileContent; }
}
public IList<NameValueCollection> GetDirectives(string directiveName)
{
if (this.directives.ContainsKey(directiveName))
return this.directives[directiveName];
else
return new List<NameValueCollection>();
}
public NameValueCollection GetCodeTemplateDirective()
{
IList<NameValueCollection> ctdirs = this.GetDirectives(CodeTemplateElementName);
if (ctdirs.Count > 0)
return ctdirs[0];
else
return new NameValueCollection();
}
#endregion ITemplateInfo Members
#region IDisposable Members
public void Dispose()
{
if (this.codeBuilder != null)
{
this.codeBuilder.Dispose();
this.codeBuilder = null;
}
}
#endregion
#region Private implementation
private MixedContentFile ReadAndParseFile()
{
// Read the file:
MixedContentFile file = new MixedContentFile(this.templatefileinfo.FullName, File.ReadAllText(this.templatefileinfo.FullName), TemplatePartTypes.TemplateBody);
// Process comments:
file.ApplyParserRegex(TemplatePartTypes.TemplateBody, TemplatePartTypes.Comment, RxComments);
file.ExtractPartsGroup(TemplatePartTypes.Comment, "comment", TemplatePartTypes.Comment);
// Pre-processor 'includes':
while (file.ApplyParserRegex(TemplatePartTypes.TemplateBody, TemplatePartTypes.IncludePragma, RxIncludes))
{
foreach (ContentPart part in file.Parts)
{
if ((TemplatePartTypes)part.Type == TemplatePartTypes.IncludePragma)
{
string fn = part.Data["filename"];
fn = Path.Combine(Path.GetDirectoryName(part.File.Filename), fn);
part.Substitute(new MixedContentFile(fn, File.ReadAllText(fn), TemplatePartTypes.TemplateBody), TemplatePartTypes.TemplateBody);
}
}
}
// Process declarations:
file.ApplyParserRegex(TemplatePartTypes.TemplateBody, TemplatePartTypes.Declaration, RxDirectives);
// Process scripts:
file.ApplyParserRegex(TemplatePartTypes.TemplateBody, TemplatePartTypes.Script, RxScripts);
file.ExtractPartsGroup(TemplatePartTypes.Script, "body", TemplatePartTypes.Script);
// Process scriptlets:
file.ApplyParserRegex(TemplatePartTypes.TemplateBody, TemplatePartTypes.Scriptlet, RxScriptlets);
file.ExtractPartsGroup(TemplatePartTypes.Scriptlet, "body", TemplatePartTypes.Scriptlet);
// Process embedded body:
file.ApplyParserRegex(TemplatePartTypes.Scriptlet, TemplatePartTypes.EmbeddedBody, RxEmbeddedBody);
file.ExtractPartsGroup(TemplatePartTypes.EmbeddedBody, "body", TemplatePartTypes.TemplateBody);
return file;
}
private static Regex directiveParser = new Regex("<%@\\s*(?<elementName>\\w+)(\\s+(?<name>\\w+)=\"(?<value>[^\"]*)\")*\\s*%>( |\\t)*\\r?\\n?");
private void ReadDirectives(MixedContentFile file)
{
// Initialize directives:
this.directives = new Dictionary<string, IList<NameValueCollection>>();
// Collect directives:
foreach (ContentPart part in file.FindPartsOfType(TemplatePartTypes.Declaration))
{
foreach (Match match in directiveParser.Matches(part.Content))
{
string name = match.Groups["elementName"].Value;
NameValueCollection attributes = new NameValueCollection();
for (int i = 0; i < match.Groups["name"].Captures.Count; i++)
{
attributes[match.Groups["name"].Captures[i].Value]
= match.Groups["value"].Captures[i].Value;
}
if (this.directives.ContainsKey(name) == false)
this.directives[name] = new List<NameValueCollection>();
this.directives[name].Add(attributes);
}
}
}
private ICodeBuilder CreateCodeBuilder(string language)
{
try
{
return (ICodeBuilder)Activator.CreateInstance(CodeBuilders[language.ToLower()]);
}
catch (KeyNotFoundException)
{
throw new ApplicationException(String.Format("Unknown template language '{0}'.", language));
}
}
#endregion Private implementation
}
}