using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using CTA.WebForms.Helpers.TagConversion; using CTA.WebForms.Services; using CTA.WebForms.TagCodeBehindHandlers; using CTA.WebForms.TagConverters.TagTemplateConditions; using CTA.WebForms.TagConverters.TagTemplateInvokables; using HtmlAgilityPack; namespace CTA.WebForms.TagConverters { /// /// A converter that utilizes a series of templates denoting /// optional alternate configurations to port view layer tags /// of a given type. /// public class TemplateTagConverter : TagConverter { public IEnumerable Conditions { get; set; } /// /// The set of actions to be taken outside of normal view or code /// behind conversion. /// public IEnumerable Invocations { get; set; } /// /// The set of temlate options to be used for source tags of the /// given type. /// public IDictionary Templates { get; set; } /// public override void Initialize( TaskManagerService taskManagerService, CodeBehindReferenceLinkerService codeBehindLinkerService, ViewImportService viewImportService) { base.Initialize(taskManagerService, codeBehindLinkerService, viewImportService); foreach (var invokable in Invocations ?? Enumerable.Empty()) { invokable.Initialize(viewImportService); } } /// public override void Validate() { if (string.IsNullOrEmpty(TagName)) { throw new ConfigValidationException($"{Rules.Config.Constants.WebFormsErrorTag}Failed to validate template tag converter, " + $"expected TagName to have a value but was null or empty"); } if (CodeBehindHandler != null && GetCodeBehindHandlerType() == null) { throw new ConfigValidationException($"{Rules.Config.Constants.WebFormsErrorTag}Failed to validate template tag converter, " + $"CodeBehindHandler type {CodeBehindHandler} could not be found"); } foreach (var condition in Conditions ?? Enumerable.Empty()) { condition.Validate(true); } foreach (var invocation in Invocations ?? Enumerable.Empty()) { invocation.Validate(); } } /// public override async Task MigrateTagAsync(HtmlNode node, string viewFilePath, TagCodeBehindHandler handler, int taskId) { var template = SelectTemplate(node); if (template == null) { // No suitable template was found for replacing this node // so we do nothing here return; } var templateParser = new TagTemplateParser(_taskManagerService, _codeBehindLinkerService); var replacementText = await templateParser.ParseTemplateAsync(template, node, viewFilePath, handler, taskId); ReplaceNode(node, replacementText); var validInvocations = Invocations?.Where(invokable => invokable.ShouldInvoke(template)); foreach (var invocation in validInvocations ?? Enumerable.Empty()) { invocation.Invoke(); } } /// /// Uses condition collection to determine which template should be /// used to convert the given node, then returns it. /// /// The node to be replaced. /// The template that should be used for conversion. private string SelectTemplate(HtmlNode node) { foreach (var kvp in Templates) { var shouldUseTemplate = Conditions? .Where(condition => condition.ShouldCheckCondition(kvp.Key)) .All(condition => condition.ConditionIsMet(node)) ?? true; if (shouldUseTemplate) { return kvp.Value; } } return null; } /// /// Replaces with the contents of . /// /// The node to be replaced. /// The text to replace the node with private void ReplaceNode(HtmlNode node, string nodeReplacementText) { var parent = node.ParentNode; var current = node; // Can't create multiple nodes at once, so instead we load the replacement // text into a new temporary html document and let HtmlAgilityPack generate // the nodes as children. var tempDoc = new HtmlDocument(); tempDoc.OptionOutputOriginalCase = true; tempDoc.LoadHtml(nodeReplacementText); foreach (var child in tempDoc.DocumentNode.ChildNodes) { child.OwnerDocument.OptionOutputOriginalCase = true; parent.InsertAfter(child, current); current = child; } parent.RemoveChild(node); } } }