From 2e3d52f7842365d236d79135aadd1347f89ccd55 Mon Sep 17 00:00:00 2001 From: Bounds Date: Tue, 18 Jun 2024 21:56:21 -0700 Subject: [PATCH] [Draft] Converter Inheritance Support (#385) * Updated converter for inheritance. * Added tests. * Broke the two constructors apart. --------- Co-authored-by: Aria Bounds --- .../ChildConverterTests.cs | 41 ++++++++++++ .../Children/IgnoreAWhenHasClass.cs | 28 ++++++++ src/ReverseMarkdown/Converter.cs | 66 ++++++++++++++----- 3 files changed, 119 insertions(+), 16 deletions(-) create mode 100644 src/ReverseMarkdown.Test/ChildConverterTests.cs create mode 100644 src/ReverseMarkdown.Test/Children/IgnoreAWhenHasClass.cs diff --git a/src/ReverseMarkdown.Test/ChildConverterTests.cs b/src/ReverseMarkdown.Test/ChildConverterTests.cs new file mode 100644 index 0000000..b43fcc8 --- /dev/null +++ b/src/ReverseMarkdown.Test/ChildConverterTests.cs @@ -0,0 +1,41 @@ +using ReverseMarkdown.Converters; +using ReverseMarkdown.Test.Children; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +using Xunit; + +namespace ReverseMarkdown.Test +{ + public class ChildConverterTests + { + [Fact] + public void WhenConverter_A_IsReplacedByConverter_IgnoreAWhenHasClass() + { + var converter = new ReverseMarkdown.Converter(new Config(), typeof(IgnoreAWhenHasClass).Assembly); + + var type = converter.GetType(); + var prop = type.GetField("_converters", BindingFlags.NonPublic | BindingFlags.Instance); + + Assert.NotNull(prop); + + var propValRaw = prop.GetValue(converter); + + Assert.NotNull(propValRaw); + + var propVal = (IDictionary)propValRaw; + + Assert.NotNull(propVal); + + var converters = propVal.Select(e => e.Value.GetType()).ToArray(); + + Assert.DoesNotContain(typeof(A), converters); + Assert.Contains(typeof(IgnoreAWhenHasClass), converters); + } + } +} diff --git a/src/ReverseMarkdown.Test/Children/IgnoreAWhenHasClass.cs b/src/ReverseMarkdown.Test/Children/IgnoreAWhenHasClass.cs new file mode 100644 index 0000000..f83597c --- /dev/null +++ b/src/ReverseMarkdown.Test/Children/IgnoreAWhenHasClass.cs @@ -0,0 +1,28 @@ +using HtmlAgilityPack; + +using ReverseMarkdown.Converters; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ReverseMarkdown.Test.Children +{ + internal class IgnoreAWhenHasClass : A + { + private readonly string _ignore = "ignore"; + + public IgnoreAWhenHasClass(Converter converter) : base(converter) + { } + + public override string Convert(HtmlNode node) + { + if (node.HasClass(_ignore)) + return ""; + + return base.Convert(node); + } + } +} diff --git a/src/ReverseMarkdown/Converter.cs b/src/ReverseMarkdown/Converter.cs index 78ced05..b31e247 100644 --- a/src/ReverseMarkdown/Converter.cs +++ b/src/ReverseMarkdown/Converter.cs @@ -10,37 +10,71 @@ namespace ReverseMarkdown { public class Converter { - private readonly IDictionary _converters = new Dictionary(); - private readonly IConverter _passThroughTagsConverter; - private readonly IConverter _dropTagsConverter; - private readonly IConverter _byPassTagsConverter; + protected readonly IDictionary _converters = new Dictionary(); + protected readonly IConverter _passThroughTagsConverter; + protected readonly IConverter _dropTagsConverter; + protected readonly IConverter _byPassTagsConverter; public Converter() : this(new Config()) {} - public Converter(Config config) + public Converter(Config config) : this(config, null) {} + + public Converter(Config config, params Assembly[] additionalAssemblies) { Config = config; + List assemblies = new List() + { + typeof(IConverter).GetTypeInfo().Assembly + }; + + if (!(additionalAssemblies is null)) + assemblies.AddRange(additionalAssemblies); + + List types = new List(); // instantiate all converters excluding the unknown tags converters - foreach (var ctype in typeof(IConverter).GetTypeInfo().Assembly.GetTypes() - .Where(t => t.GetTypeInfo().GetInterfaces().Contains(typeof(IConverter)) && - !t.GetTypeInfo().IsAbstract - && t != typeof(PassThrough) - && t != typeof(Drop) - && t != typeof(ByPass))) + foreach (var assembly in assemblies) { - Activator.CreateInstance(ctype, this); + foreach (var ctype in assembly.GetTypes() + .Where(t => t.GetTypeInfo().GetInterfaces().Contains(typeof(IConverter)) && + !t.GetTypeInfo().IsAbstract + && t != typeof(PassThrough) + && t != typeof(Drop) + && t != typeof(ByPass))) + { + // Check to see if any existing types are children/equal to + // the type to add. + if (types.Any(e => ctype.IsAssignableFrom(e))) + // If they are, ignore the type. + continue; + + // See if there is a type that is a parent of the + // current type. + Type toRemove = types.FirstOrDefault(e => e.IsAssignableFrom(ctype)); + // if there is ... + if (!(toRemove is null)) + // ... remove the parent. + types.Remove(toRemove); + + // finally, add the type. + types.Add(ctype); + } } + // For each type to register ... + foreach (var ctype in types) + // ... activate them + Activator.CreateInstance(ctype, this); + // register the unknown tags converters _passThroughTagsConverter = new PassThrough(this); _dropTagsConverter = new Drop(this); _byPassTagsConverter = new ByPass(this); } - public Config Config { get; } + public Config Config { get; protected set; } - public string Convert(string html) + public virtual string Convert(string html) { html = Cleaner.PreTidy(html, Config.RemoveComments); @@ -63,12 +97,12 @@ public string Convert(string html) return result.Trim().FixMultipleNewlines(); } - public void Register(string tagName, IConverter converter) + public virtual void Register(string tagName, IConverter converter) { _converters[tagName] = converter; } - public IConverter Lookup(string tagName) + public virtual IConverter Lookup(string tagName) { // if a tag is in the pass through list then use the pass through tags converter if (Config.PassThroughTags.Contains(tagName))