/// <summary>Process a string template</summary> public static string Process(Src template, string pattern, SubstituteFunction subst, int chunk_size = 4096) { using var tr = new TemplateReplacer(template, pattern, subst, chunk_size); return(tr.ReadToEnd()); }
/// <summary>Expands template mark-up files</summary> public static string Markup(Src src, string current_dir) { // Supported substitutions: // <!--#include file="filepath"--> // Substitutes the content of the file 'filepath' at the declared location. // 'filepath' can be relative to directory of the template file, or a full path // <!--#var name="count" file="filepath" value="const int Count = (?<value>\d+);"--> // Declares a variable for later use. Applies a regex match to the contents of 'filepath' to get a definition for 'value' // 'filepath' can be relative to directory of the template file, or a full path // <!--#value name="count"--> // Substitute a previously defined variable called 'count' // <!--#image file="filepath"--> // Substitute a base64 image file 'filepath' var variables = new Dictionary <string, string>(); // General form: [optional leading whitespace]<!--#command key="value" key="value"... --> // The pattern matches leading white space which is then inserted before every line in the substituted result. // This means includes on lines of the own are correctly tabbed, and <!--inline--> substitutions are also correct var result = TemplateReplacer.Process(src, @"(?<indent>[ \t]*)<!--#(?<cmd>\w+)\s+(?<kv>.*?)-->", (tr, match) => { var indent = match.Result("${indent}"); var cmd = match.Result("${cmd}"); var kv = match.Result("${kv}"); // list of key="value" pairs Match m; switch (cmd) { default: { throw new Exception(string.Format("Unknown template command: '{0}' ({1})", cmd, match.ToString())); } case "include": #region include { const string expected_form = "<!--#include file=\"filepath\"-->"; // Read the include filepath and check it exists m = Regex.Match(kv, @".*file=""(?<file>.*?)"".*"); if (!m.Success) { throw new Exception(string.Format("Invalid include: '{0}'.\nCould not match 'file' field.\nExpected form: {1}", match.ToString(), expected_form)); } var fpath = m.Result("${file}"); fpath = Path.IsPathRooted(fpath) ? fpath : Path.Combine(current_dir, fpath); if (!File.Exists(fpath)) { throw new FileNotFoundException(string.Format("File reference not found: {0}", fpath), fpath); } tr.PushSource(new AddIndents(new FileSrc(fpath), indent, true)); return(string.Empty); } #endregion case "var": #region var { const string expected_form = "<!--#var name=\"variable_name\" file=\"filepath\" value=\"regex_pattern_defining_value\"-->"; // Read the name of the variable m = Regex.Match(kv, @".*name=""(?<name>\w+)"".*"); if (!m.Success) { throw new Exception(string.Format("Invalid variable declaration: '{0}'.\nCould not match 'name' field.\nExpected form: {1}", match.ToString(), expected_form)); } var name = m.Result("${name}"); // Read the source filepath m = Regex.Match(kv, @".*file=""(?<file>.*?)"".*"); if (!m.Success) { throw new Exception(string.Format("Invalid variable declaration: '{0}'.\nCould not match 'file' field.\nExpected form: {1}", match.ToString(), expected_form)); } var filepath = m.Result("${file}"); filepath = Path.IsPathRooted(filepath) ? filepath : Path.Combine(current_dir, filepath); if (!File.Exists(filepath)) { throw new FileNotFoundException(string.Format("File reference not found: {0}", filepath), filepath); } // Read the regex pattern that defines 'value' m = Regex.Match(kv, @".*value=""(?<pattern>.*?)"".*"); if (!m.Success) { throw new Exception(string.Format("Invalid variable declaration: '{0}'.\nCould not match 'value' field.\nExpected form: {1}", match.ToString(), expected_form)); } var pat = m.Result("${pattern}"); Regex pattern; try { pattern = new Regex(pat, RegexOptions.Compiled); } catch (Exception ex) { throw new Exception(string.Format("Invalid variable declaration: '{0}'.\n'value' field is not a valid Regex expression.\nExpected form: {1}", match.ToString(), expected_form), ex); } // Use the pattern to get the value for the variable using (var sr = new StreamReader(filepath, Encoding.UTF8, true, 65536)) for (string?line; (line = sr.ReadLine()) != null && !(m = pattern.Match(line)).Success;) { } if (!m.Success) { throw new Exception(string.Format("Invalid variable declaration: '{0}'.\n'value' regex expression did not find a match in {2}.\nExpected form: {1}", match.ToString(), expected_form, filepath)); } var value = m.Result("${value}"); // Save the name/value pair variables[name] = value; return(string.Empty); } #endregion case "value": #region value { const string expected_form = "<!--#value name=\"variable_name\"-->"; // Read the name of the variable m = Regex.Match(kv, @".*name=""(?<name>\w+)"".*"); if (!m.Success) { throw new Exception(string.Format("Invalid value declaration: '{0}'.\nCould not match 'name' field.\nExpected form: {1}", match.ToString(), expected_form)); } var name = m.Result("${name}"); // Lookup the value for the variable if (!variables.TryGetValue(name, out var value)) { throw new Exception(string.Format("Invalid value declaration: '{0}'.\nVariable with 'name' {2} is not defined.\nExpected form: {1}", match.ToString(), expected_form, name)); } return(indent + value); } #endregion case "image": #region image { const string expected_form = "<!--#image file=\"filepath\"-->"; // Read the filename m = Regex.Match(kv, @".*file=""(?<file>.*?)"".*"); if (!m.Success) { throw new Exception(string.Format("Invalid image declaration: '{0}'.\nCould not match 'file' field.\nExpected form: {1}", match.ToString(), expected_form)); } var fpath = m.Result("${file}"); fpath = Path.IsPathRooted(fpath) ? fpath : Path.Combine(current_dir, fpath); if (!File.Exists(fpath)) { throw new FileNotFoundException(string.Format("File reference not found: {0}", fpath), fpath); } // Determine the image type var extn = (Path.GetExtension(fpath) ?? string.Empty).ToLowerInvariant(); if (string.IsNullOrEmpty(extn)) { throw new Exception(string.Format("Could not determine image file format from path '{0}'", fpath)); } // Read the image file var img = File.ReadAllBytes(fpath); switch (extn) { default: throw new Exception(string.Format("Unsupported image format: {0}", extn)); case ".png": return(string.Format("data:image/png;base64,{0}", Convert.ToBase64String(img))); case ".jpg": return(string.Format("data:image/jpg;base64,{0}", Convert.ToBase64String(img))); } } #endregion } }); return(result); }