diff --git a/Examples/ConsoleApp/Program.cs b/Examples/ConsoleApp/Program.cs index ae9b669..66faaf4 100644 --- a/Examples/ConsoleApp/Program.cs +++ b/Examples/ConsoleApp/Program.cs @@ -22,6 +22,7 @@ public class Program { public static string root = @"..\..\"; public static bool debug = true; + public static HandleFailAction modelEntryFailAction = HandleFailAction.exception; static void Main(string[] args) { @@ -40,7 +41,7 @@ static void Generate(string name, object model) Console.WriteLine("Generating " + name); - var doc = Templ.Load(file).Build(model, debug).SaveAs(output); + var doc = Templ.Load(file).Build(model, debug, modelEntryFailAction).SaveAs(output); if (debug) doc.Debugger.SaveAs(output); } } diff --git a/Templ.NET/ModelEntry.cs b/Templ.NET/ModelEntry.cs index 3d68728..8c35923 100644 --- a/Templ.NET/ModelEntry.cs +++ b/Templ.NET/ModelEntry.cs @@ -7,6 +7,22 @@ namespace TemplNET { + public class ModelEntryException : Exception + { + public ModelEntryException(string message, Exception innerException) : base(message, innerException) + { + } + } + + /// + /// List of ways in which an exception in a module handler can be handled + /// + /// Example: {txt:user.name} // user.name is not a property in the model + public enum HandleFailAction + { + exception, ignore, remove + }; + /// /// Represents a reference-by-reflection to a Entry (a field/property/member) within an object. /// The entry's nested Path is defined with a string. @@ -189,23 +205,28 @@ private static MemberValue GetPrimitive(object model, string path) /// public static TemplModelEntry Get(object model, string path) { - MemberValue propVal; - var primitive = GetPrimitive(model, path); - if (primitive == null) - { - propVal = MemberValue.FindPath(model, path.Trim()); + try { + MemberValue propVal; + var primitive = GetPrimitive(model, path); + if (primitive == null) + { + propVal = MemberValue.FindPath(model, path.Trim()); + } + else + { + propVal = primitive; + } + return new TemplModelEntry() + { + Model = model, + Path = path.Trim(), + Info = propVal.Info, + Value = propVal.Value + }; } - else - { - propVal = primitive; + catch (Exception ex) { + throw new ModelEntryException($"Failed to retrieve entry from the model at path '{path}'", ex); } - return new TemplModelEntry() - { - Model = model, - Path = path.Trim(), - Info = propVal.Info, - Value = propVal.Value - }; } /// diff --git a/Templ.NET/Module.cs b/Templ.NET/Module.cs index c355346..d917623 100644 --- a/Templ.NET/Module.cs +++ b/Templ.NET/Module.cs @@ -65,7 +65,7 @@ protected TemplModule AddPrefix(string prefix) /// Applies all changes to the document. /// automatically executes it for all . /// - public abstract void Build(DocX doc, object model); + public abstract void Build(DocX doc, object model, HandleFailAction modelEntryFailAction); } /// @@ -127,7 +127,6 @@ private bool RemoveExpired(T m) /// Verifies the number of fields in the supplied Match's placeholder is within the min/max expected for this Module. /// Throws exception if problems are found. /// - /// private T CheckFieldCount(T m) { var l = m.Fields.Length; @@ -141,10 +140,11 @@ private T CheckFieldCount(T m) } return m; } + /// /// Handle and/or remove a collection of Matches from the document /// - public void BuildFromScope(DocX doc, object model, IEnumerable scope) + public void BuildFromScope(DocX doc, object model, IEnumerable scope, HandleFailAction modelEntryFailAction) { var watch = Stopwatch.StartNew(); // Mark module instance as "used" if any matches are being processed @@ -154,35 +154,51 @@ public void BuildFromScope(DocX doc, object model, IEnumerable scope) // Note how we are constantly "committing" the changes using ToList(). // This ensures order is preserved (e.g. all "finds" happen before all "handler"s) scope.Select( m => CheckFieldCount(m) ).ToList() - .Select( m => Handler(doc, model, m)).ToList() + .Select( m => TryHandler(doc, model, m, modelEntryFailAction)).ToList() .Where( m =>!RemoveExpired(m)).ToList() .Select( m => CustomHandler(doc, model, m)).ToList() .ForEach(m => RemoveExpired(m)); Statistics.millis = watch.ElapsedMilliseconds; } + + private T TryHandler(DocX doc, object model, T m, HandleFailAction modelEntryFailAction) + { + try + { + return Handler(doc, model, m); + } + catch (ModelEntryException) + { + switch (modelEntryFailAction) + { + case HandleFailAction.exception: + throw; + case HandleFailAction.remove: + m.Expired = true; + break; + } + return m; + } + } + /// /// Find and build all content from the document /// - public override void Build(DocX doc, object model) + public override void Build(DocX doc, object model, HandleFailAction modelEntryFailAction) { - BuildFromScope(doc, model, Regexes.SelectMany(rxp => FindAll(doc, rxp))); + BuildFromScope(doc, model, Regexes.SelectMany(rxp => FindAll(doc, rxp)), modelEntryFailAction); } /// /// Module-specific Match handler. /// Implementations should modify the Matched content, or mark expired to delete /// - /// - /// - /// public abstract T Handler(DocX doc, object model, T m); /// /// Module-specific finder. /// Implementations should retrieve all regex-matching content from the document. /// - /// - /// public abstract IEnumerable FindAll(DocX doc, TemplRegex rxp); } diff --git a/Templ.NET/Modules.cs b/Templ.NET/Modules.cs index 8ea33f3..da67c30 100644 --- a/Templ.NET/Modules.cs +++ b/Templ.NET/Modules.cs @@ -64,7 +64,7 @@ public void BuildFromScope(DocX doc, object model, IEnumerable paragr { Path = path; Paragraphs = paragraphs; - Build(doc, model); + Build(doc, model, HandleFailAction.exception); Path = ""; paragraphs = new List(); } diff --git a/Templ.NET/Templ.cs b/Templ.NET/Templ.cs index 465e168..d326bb1 100644 --- a/Templ.NET/Templ.cs +++ b/Templ.NET/Templ.cs @@ -127,7 +127,7 @@ public static Templ Load(string templatePath, bool useDefaultModules = true) /// /// /// - public Templ Build(object model, bool debug = TemplConst.Debug) + public Templ Build(object model, bool debug = TemplConst.Debug, HandleFailAction modelEntryFailAction = HandleFailAction.exception) { if (debug) { @@ -135,7 +135,7 @@ public Templ Build(object model, bool debug = TemplConst.Debug) } ActiveModules.ForEach(module => { - module.Build(Document.Docx, model); + module.Build(Document.Docx, model, modelEntryFailAction); if (debug && module.Used) { Debugger.AddState(Document, module.Name);