<#@ template language="C#" debug="false" hostspecific="true"#> <#@ output extension=".js" encoding="utf-8"#> <#@ assembly name="System" #> <#@ assembly name="System.Core" #> <#@ assembly name="System.Xml" #> <#@ assembly name="EnvDTE" #> <#@ assembly name="$(SolutionDir)packages\HtmlAgilityPack.1.4.6\lib\Net45\HtmlAgilityPack.dll" #> <#@ import namespace="System" #> <#@ import namespace="System.Collections.Generic" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.IO" #> <#@ import namespace="System.Globalization" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Text.RegularExpressions" #> <#@ import namespace="System.Xml" #> <#@ import namespace="System.Xml.XPath" #> <#@ import namespace="HtmlAgilityPack" #> <# var scope = "nesterovskyBros.templates"; var root = "scripts/templates"; var suffix = ".tmpl.html"; var defaultScript = true; var defaultWithBlock = true; var project = Host.ResolveAssemblyReference("$(ProjectDir)"); var serviceProvider = Host as IServiceProvider; var dte = serviceProvider == null ? null : (EnvDTE.DTE)serviceProvider.GetService(typeof(EnvDTE.DTE)); var rootPath = string.IsNullOrEmpty(root) ? project : Path.Combine(project, root); var c = rootPath[rootPath.Length - 1]; if ((c != Path.DirectorySeparatorChar) && (c != Path.AltDirectorySeparatorChar) && (c != Path.VolumeSeparatorChar)) { rootPath += Path.DirectorySeparatorChar; } var scripts = new List(); var scopeParts = scope.Split('.'); for(var i = 0; i < scopeParts.Length - 1; i++) { var part = i == 0 ? "window." + scopeParts[0] : string.Join(".", scopeParts, 0, i + 1); Write(part); Write("||("); Write(part); WriteLine("={});"); } if (scopeParts.Length == 1) { Write("window."); } Write(scope); WriteLine("="); WriteLine("{"); var rootUri = new Uri(rootPath); var first = true; foreach(var path in Directory.EnumerateFiles( rootPath, "*" + suffix, SearchOption.AllDirectories)) { if ((dte == null) || (dte.Solution.FindProjectItem(path) != null)) { var pathUri = new Uri(path); var relativeUri = rootUri.MakeRelativeUri(pathUri); var relativePath = Uri.UnescapeDataString(relativeUri.ToString()); var id = relativePath.Substring(0, relativePath.Length - suffix.Length); foreach(var item in GetTemplates(path)) { var templateId = string.IsNullOrEmpty(item.Item1) ? id : id + "-" + item.Item1; var html = escapePattern.Replace(item.Item2, "&\\#$1;"); templateId = Escape(templateId); if (first) { first = false; } else { WriteLine(","); } Write("\""); Write(templateId); Write("\":"); Write(Compile(html, item.Item4 ?? defaultWithBlock)); if (item.Item3 ?? defaultScript) { scripts.Add(templateId); } } } } WriteLine(""); WriteLine("};"); if (scripts.Count > 0) { Write("document.write('"); foreach(var templateId in scripts) { Write(""); } WriteLine("');"); } #> <#+ public IEnumerable> GetTemplates(string path) { var navigator = new HtmlNodeNavigator(path); return navigator.Select(BodySelector).Cast(). SelectMany( item => { var ids = navigator.Select(IdSelector). Cast().ToArray(); return ids.Length == 0 ? new[] { item } : ids; }). Select( item => { var currentNode = item.CurrentNode; var id = currentNode. GetAttributeValue("data-template-id", null as string); var dataScript = currentNode. GetAttributeValue("data-script", null as string); var useWithBlock = currentNode. GetAttributeValue("data-with-block", null as string); var space = currentNode. GetAttributeValue("xml:space", null as string); var preserveSpace = space == "preserve"; if (!preserveSpace) { foreach(var node in item.Select(NodeSelector).Cast(). Select(n => n.CurrentNode)) { switch(node.NodeType) { case HtmlNodeType.Element: { if (node.HasAttributes) { foreach(var attribute in node.Attributes) { var value = attribute.Value; if (value != null) { var attributeValue = linePattern.Replace(value, " "); if (value != attributeValue) { attribute.Value = attributeValue.Trim(spaces); } } } } break; } case HtmlNodeType.Text: { var text = node.InnerHtml; if (text != null) { var trimmed = text.Trim(spaces); if (trimmed != text) { node.InnerHtml = trimmed; } } break; } } } } var html = currentNode.InnerHtml; if (!preserveSpace) { html = html.Trim(spaces); } return Tuple.Create( id, html, dataScript == null ? null : XmlConvert.ToBoolean(dataScript) as bool?, useWithBlock == null ? null : XmlConvert.ToBoolean(useWithBlock) as bool?); }); } private static string Escape(string value, int options = 1) { if (value == null) { return null; } StringBuilder builder = null; int startIndex = 0; int count = 0; String escape; for(int i = 0; i < value.Length; i++) { var c = value[i]; switch(c) { case '\b': { escape = "\\b"; break; } case '\t': { escape = "\\t"; break; } case '\n': { escape = "\\n"; break; } case '\f': { escape = "\\f"; break; } case '\r': { escape = "\\r"; break; } case '"': { if ((options & 1) == 0) { goto default; } escape = "\\\""; break; } case '\'': { if ((options & 2) == 0) { goto default; } escape = "\\'"; break; } case '\\': { escape = "\\\\"; break; } default: { if (c < ' ') { int code = c; escape = "\\x" + code.ToString("x2", CultureInfo.InvariantCulture); break; } else { count++; continue; } } } if (builder == null) { builder = new StringBuilder(value.Length + 5); } if (count > 0) { builder.Append(value, startIndex, count); } builder.Append(escape); startIndex = i + 1; count = 0; } if (builder == null) { return value; } if (count > 0) { builder.Append(value, startIndex, count); } return builder.ToString(); } private class Part { public bool Expression { get; set; } public string Value { get; set; } } private static string Compile(string template, bool useWithBlock) { if (string.IsNullOrWhiteSpace(template)) { return null; } var statements = false; var encoded = false; var value = template; value = escapedCurlyRegExp.Replace(value, "__CURLY__"); var encodedValue = encodeRegExp.Replace(value, "#=e($1)#"); if (value != encodedValue) { value = encodedValue; encoded = true; } value = curlyRegExp.Replace(value, "}"); value = escapedSharpRegExp.Replace(value, "__SHARP__"); var prev = null as Part; var parts = value.Split('#'). Select( (part, i) => { if ((i % 2) == 0) { part = Escape(part, 2); return part.Length == 0 ? null : new Part { Expression = true, Value = "'" + part + "'" }; } else { switch(part.Length > 0 ? part[0] : 0) { case '=': { part = part.Substring(1, part.Length - 1).Trim(); return new Part { Expression = true, Value = "(" + part + ")" }; } case ':': { encoded = true; part = part.Substring(1, part.Length - 1).Trim(); return new Part { Expression = true, Value = "e(" + part + ")" }; } default: { statements = true; part = part.TrimEnd(); return part.Length == 0 ? null : new Part { Expression = false, Value = part }; } } } }). Where(part => part != null). Select( part => { var partValue = part.Value; if (prev != null) { if (part.Expression) { if (prev.Expression) { partValue = "+" + partValue; } else { partValue = "\no+=" + partValue; } } else { partValue = "\n" + partValue; } } else { if (part.Expression) { if (statements) { partValue = "o=" + partValue; } else { partValue = "return " + partValue; } } else { partValue = "o=''\n" + partValue; } } prev = part; partValue = sharpRegExp.Replace(partValue, "#"); return partValue; }); value = string.Join("", parts); value = "function(data)\n{\n" + (encoded || statements ? "var " : "") + (encoded ? "e=kendo.htmlEncode" : "") + (encoded && statements ? "," : "") + (encoded && !statements ? "\n" : "") + (statements && useWithBlock ? "o\n" : "") + (useWithBlock ? "with(data)\n{\n" : "") + value + (useWithBlock ? "\n}\n" : "\n") + (statements ? "return o\n}" : "}"); return value; } private static readonly XPathExpression IdSelector = XPathExpression.Compile(".//*[@data-template-id]"); private static readonly XPathExpression BodySelector = XPathExpression.Compile(".//body"); private static readonly XPathExpression NodeSelector = XPathExpression.Compile(".//node()"); private static readonly char[] spaces = { ' ', '\r', '\n', '\t' }; private static readonly Regex escapePattern = new Regex("&#([0-9A-Fa-f]+);"); private static readonly Regex linePattern = new Regex("[\\r\\n]+"); private static readonly Regex encodeRegExp = new Regex("\\$\\{([^}]*)\\}"); private static readonly Regex escapedCurlyRegExp = new Regex("\\\\\\}"); private static readonly Regex curlyRegExp = new Regex("__CURLY__"); private static readonly Regex escapedSharpRegExp = new Regex("\\\\#"); private static readonly Regex sharpRegExp = new Regex("__SHARP__"); #>