Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Converter Inheritance Support #385

Merged
merged 3 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions src/ReverseMarkdown.Test/ChildConverterTests.cs
Original file line number Diff line number Diff line change
@@ -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<string, IConverter>)propValRaw;

Assert.NotNull(propVal);

var converters = propVal.Select(e => e.Value.GetType()).ToArray();

Assert.DoesNotContain(typeof(A), converters);
Assert.Contains(typeof(IgnoreAWhenHasClass), converters);
}
}
}
28 changes: 28 additions & 0 deletions src/ReverseMarkdown.Test/Children/IgnoreAWhenHasClass.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
66 changes: 50 additions & 16 deletions src/ReverseMarkdown/Converter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,37 +10,71 @@ namespace ReverseMarkdown
{
public class Converter
{
private readonly IDictionary<string, IConverter> _converters = new Dictionary<string, IConverter>();
private readonly IConverter _passThroughTagsConverter;
private readonly IConverter _dropTagsConverter;
private readonly IConverter _byPassTagsConverter;
protected readonly IDictionary<string, IConverter> _converters = new Dictionary<string, IConverter>();
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<Assembly> assemblies = new List<Assembly>()
{
typeof(IConverter).GetTypeInfo().Assembly
};

if (!(additionalAssemblies is null))
assemblies.AddRange(additionalAssemblies);

List<Type> types = new List<Type>();
// 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);

Expand All @@ -63,12 +97,12 @@ public string Convert(string html)
return result.Trim();
}

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))
Expand Down