Skip to main content
Code Review

Return to Question

replaced http://codereview.stackexchange.com/ with https://codereview.stackexchange.com/
Source Link

I came to the conclusion that the previous previous framework was too complicated and not easily extendable so I tried it again but this time with dynamics.

I came to the conclusion that the previous framework was too complicated and not easily extendable so I tried it again but this time with dynamics.

I came to the conclusion that the previous framework was too complicated and not easily extendable so I tried it again but this time with dynamics.

added 605 characters in body
Source Link
t3chb0t
  • 44.6k
  • 9
  • 84
  • 190

It's much much shorter and I think it's much easier to extend it now. Now it's also much easier to write another renderer that creates i.e. a XDocument/XElement.

public class MarkupBuilder : DynamicObject, IEnumerable<object>
{
 private readonly List<IMarkupBuilderExtension> _extensions = new List<UserQuery.IMarkupBuilderExtension>();
 private MarkupBuilder(MarkupBuilderSettingsMarkupBuilder settingsmarkupBuilder, string tag)
 {
 Settings_extensions = newmarkupBuilder._extensions;
 MarkupBuilderSettings(settings ?? MarkupSchema = new MarkupBuilderSettingsMarkupSchema()markupBuilder.MarkupSchema);
 Tag = tag;
 Attributes = new List<MarkupAttribute>();
 Content = new List<object>();
 }
 public MarkupBuilder(MarkupBuilderSettingsMarkupSchema settingsmarkupSchema = null)
 :{
 this MarkupSchema = new MarkupSchema(settings,markupSchema string.Empty?? new MarkupSchema());
 { Attributes = new List<MarkupAttribute>();
 Content = new List<object>();
 }
 public MarkupBuilderSettingsIEnumerable<IMarkupBuilderExtension> SettingsExtensions => _extensions.AsReadOnly();
 public MarkupSchema MarkupSchema { get; }
 public string Tag { get; protected set; }
 public List<MarkupAttribute> Attributes { get; private set; }
 public List<object> Content { get; private set; }
 public MarkupBuilder Parent { get; private set; }
 private int Depth
 {
 get
 {
 var depth = 0;
 var parent = Parent;
 while (parent != null)
 {
 depth++;
 parent = parent.Parent;
 }
 return depth;
 }
 }
 public MarkupBuilder Register<T>() where T : IMarkupBuilderExtension, new()
 {
 _extensions.Add(new T());
 return this;
  }
 // supports object initializer
 public void Add(object content)
 {
 if (content != null)
 {
 Content.Add(content);
 var htmlElement = content as MarkupBuilder;
 if (htmlElement != null)
 {
 htmlElement.Parent = this;
 }
 }
 }
 public MarkupBuilder AddRange(params object[] content)
 {
 foreach (var item in content)
 {
 Add(item);
 }
 return this;
 }
 public IEnumerator<object> GetEnumerator()
 {
 return Content.GetEnumerator();
 }
 IEnumerator IEnumerable.GetEnumerator()
 {
 return this.GetEnumerator();
 }
 // --- DynamicObject members
 public override bool TryGetMember(GetMemberBinder binder, out object result)
 {
 foreach (var extension in Settings.Extensions)
 {
 if (extension.TryGetMember(this, binder, out result))
 {
 return true;
 }
 }
 result = new MarkupBuilder(Settingsthis, binder.Name);
 return true;
 }
 public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
 {
 foreach (var extension in Settings.Extensions)
 {
 if (extension.TryInvokeMember(this, binder, args, out result))
 {
 return true;
 }
 }
 if (SettingsMarkupSchema.AllowedTagsTags.Any() && !SettingsMarkupSchema.AllowedTagsTags.ContainsContainsKey(binder.Name))
 {
 result = null;
  throw new NotSupportedException($"Method '{binder.Name}' is returnnot false;supported.");
 }

 result = new MarkupBuilder(Settingsthis, binder.Name).AddRange(args);
 return true;
 }
}

It's supported byIt can be customized with the MarkupBuilderSettingsMarkupSchema a very simple schema that defines which tags/attributes are allowed and how to format the markup later:

public class MarkupBuilderSettingsMarkupSchema
{
 public MarkupBuilderSettingsMarkupSchema() {
 Tags = new Dictionary<string, MarkupFormattingOptions>();
 GlobalAttributes = new HashSet<string>();
 IndentWidth = 4;
 }
 publicinternal MarkupBuilderSettingsMarkupSchema(MarkupBuilderSettingsMarkupSchema settingsother)
 {
 AllowedTagsTags = settings.AllowedTags ?? new List<string>Dictionary<string, MarkupFormattingOptions>(other.Tags);
 AllowerAttributesGlobalAttributes = settings.AllowerAttributes ?? new List<string>HashSet<string>(other.GlobalAttributes);
 ExtensionsIndentWidth = settingsother.ExtensionsIndentWidth;
 ?? new List<IMarkupBuilderExtension>(); }

 public Dictionary<string, MarkupFormattingOptions> Tags { get; set; }
 public IReadOnlyCollection<string>HashSet<string> AllowedTagsGlobalAttributes { get; set; }
 public IReadOnlyCollection<string>int AllowerAttributesIndentWidth { get; set; }
 // Creates a Html schema.
 public IReadOnlyCollection<IMarkupBuilderExtension>static ExtensionsMarkupSchema Html => new MarkupSchema
  { get; set; Tags =
 {
 ["body"] = MarkupFormattingOptions.PlaceClosingTagOnNewLine,
 ["br"] = MarkupFormattingOptions.IsVoid,
 ["span"] = MarkupFormattingOptions.None,
 ["p"] = MarkupFormattingOptions.PlaceOpeningTagOnNewLine,
 ["h1"] = MarkupFormattingOptions.PlaceOpeningTagOnNewLine,
 ["h2"] = MarkupFormattingOptions.PlaceOpeningTagOnNewLine,
 ["h3"] = MarkupFormattingOptions.PlaceOpeningTagOnNewLine,
 ["h4"] = MarkupFormattingOptions.PlaceOpeningTagOnNewLine,
 ["h5"] = MarkupFormattingOptions.PlaceOpeningTagOnNewLine,
 ["h6"] = MarkupFormattingOptions.PlaceOpeningTagOnNewLine, 
 ["ul"] = MarkupFormattingOptions.PlaceBothTagsOnNewLine,
 ["ol"] = MarkupFormattingOptions.PlaceBothTagsOnNewLine,
 ["li"] = MarkupFormattingOptions.PlaceOpeningTagOnNewLine,
 // ...
 },
 GlobalAttributes = { "style" }
 };
 public bool TagHasFormattingOptions(string tagName, MarkupFormattingOptions options)
 {
 var tagFormattingOptions = MarkupFormattingOptions.None;
 return Tags.TryGetValue(tagName, out tagFormattingOptions) ? tagFormattingOptions.HasFlag(options) : false;
  }
}
public class MarkupRenderer
{
 public static string RenderMarkup(object value, IMarkupFormatting formatting)
 {
 var element = value as MarkupBuilder;
 if (element == null)
 {
 return value == null ? string.Empty : (string)value;
 }
 var content = element.Content.Aggregate(
 new StringBuilder(),
 (builder, next) => builder.Append(MarkupRenderer.RenderMarkup(next, formatting))).ToString();
 var isEmpty = string.IsNullOrEmpty(content);
 var html = new StringBuilder();
 if (element.Parent != null && formatting[element.Tag].HasFlag(MarkupFormattingOptions.PlaceOpeningTagOnNewLine))
 {
 html.AppendLine().Append(IndentString(formatting.IndentWidth));
 }
 html.Append(CreateOpeningElement(element, formatting));
 // if (IsVoid)
 // {
 // return html.ToString();
 // }
 if (!isEmpty)
 {
 html.Append(content);
 }
 if (!isEmpty && formatting[element.Tag].HasFlag(MarkupFormattingOptions.PlaceClosingTagOnNewLine))
 {
 html.AppendLine();
 if (element.Parent != null) { html.Append(IndentString(formatting.IndentWidth)); }
 }
 html.Append(CreateClosingElement(element, formatting));
 return html.ToString();
 }
 private static string IndentString(int indentWidth)
 {
 return new string(' ', indentWidth);
 }
 private static string CreateOpeningElement(MarkupBuilder element, IMarkupFormatting formatting)
 {
 var attributes = CreateAttributesString(element, formatting);
 var html = new StringBuilder()
 .Append("<").Append(element.Tag)
 .Append(string.IsNullOrEmpty(attributes) ? string.Empty : " ")
 .Append(attributes)
 //.Append(IsVoid ? "/" : string.Empty)
 .Append(">")
 .ToString();
 return html;
 }
 private static string CreateAttributesString(MarkupBuilder element, IMarkupFormatting formatting)
 {
 return string.Join(" ", element.Attributes);
 }
 private static string CreateClosingElement(MarkupBuilder element, IMarkupFormatting formatting)
 {
 return formatting[element.Tag].HasFlag(MarkupFormattingOptions.IsVoid)
 ? string.Empty
 : new StringBuilder()
 .Append("</")
 .Append(element.Tag)
 .Append(">")
 .ToString();
 }
}

The rendering can be customized with the IMarkupFormatting interface:

public interface IMarkupFormatting
{
 MarkupFormattingOptions this[string tag] { get; }
 int IndentWidth { get; }
}

For html it looks like this:

public class HtmlFormattingMarkupRenderer
{
 : IMarkupFormatting public static string RenderMarkup(MarkupBuilder builder) 
{
 public HtmlFormatting return RenderMarkup(builder, builder.MarkupSchema);
 }
 
 private static string RenderMarkup(object value, MarkupSchema markupSchema)
 {
 Optionsvar markupBuilder = newvalue Dictionary<string,as MarkupFormattingOptions>MarkupBuilder;
 if (markupBuilder == null)
 {
 ["body"]return =value MarkupFormattingOptions== null ? string.PlaceClosingTagOnNewLine,Empty : (string)value;
 }
 ["br"] var content = MarkupFormattingOptionsmarkupBuilder.IsVoidContent.Aggregate(
 new StringBuilder(),
 ["span"] = MarkupFormattingOptions (sb, next) => sb.NoneAppend(MarkupRenderer.RenderMarkup(next, markupSchema))).ToString();

 var isEmpty = string.IsNullOrEmpty(content);
 ["p"] var html = new StringBuilder();
  if (markupBuilder.Parent != null && markupSchema.TagHasFormattingOptions(markupBuilder.Tag, MarkupFormattingOptions.PlaceOpeningTagOnNewLine,))
 {
 ["h1"] = MarkupFormattingOptions html.PlaceOpeningTagOnNewLine,AppendLine().Append(IndentString(markupSchema.IndentWidth));
 }
 html.Append(CreateOpeningElement(markupBuilder));
 IndentWidth// = 3; if (IsVoid)
 // {
 // return html.ToString();
 //  }
 public MarkupFormattingOptions this[string tag] if (!isEmpty)
 {
 get html.Append(content);
 }
  if (!isEmpty && markupSchema.TagHasFormattingOptions(markupBuilder.Tag, MarkupFormattingOptions.PlaceClosingTagOnNewLine))
 {
 varhtml.AppendLine();
 options if (markupBuilder.Parent != MarkupFormattingOptionsnull) { html.None;Append(IndentString(markupSchema.IndentWidth)); }
 }

 html.Append(CreateClosingElement(markupBuilder));
  return Optionshtml.TryGetValueToString(tag);
 }
 private static string IndentString(int indentWidth)
 {
 return new string(' ', outindentWidth);
 options }
 private static string CreateOpeningElement(MarkupBuilder markupBuilder) ? options : MarkupFormattingOptions{
 var attributes = CreateAttributesString(markupBuilder);
 var html = new StringBuilder()
 .None;Append("<").Append(markupBuilder.Tag)
 } .Append(string.IsNullOrEmpty(attributes) ? string.Empty : " ")
 set .Append(attributes)
 { //.Append(IsVoid ? "/" : string.Empty)
 Options[tag].Append(">")
 = value; .ToString();
 }return html;
 }
 publicprivate intstatic IndentWidthstring CreateAttributesString(MarkupBuilder markupBuilder)
 { get; set; return string.Join(" ", markupBuilder.Attributes);
 }
 publicprivate Dictionary<string,static MarkupFormattingOptions>string OptionsCreateClosingElement(MarkupBuilder markupBuilder)
  { get; set; return markupBuilder.MarkupSchema.TagHasFormattingOptions(markupBuilder.Tag, MarkupFormattingOptions.IsVoid)
 ? string.Empty
 : new StringBuilder()
 .Append("</")
 .Append(markupBuilder.Tag)
 .Append(">")
 .ToString();
  }
}

dynamic html = new MarkupBuilder(new MarkupBuilderSettings
{
 // if none are specified then all are allowed
 AllowedTags = new[] { "body", "p", "br", "span" },
 AllowerAttributes = new[] { "style" },
 Extensions = new IMarkupBuilderExtension[]
  {MarkupBuilder(MarkupSchema.Html)
 new cssExtension.Register<cssExtension>(),
 new attrExtension.Register<attrExtension>()
 }
});
var body = html.body
(
 html.p("foo"),
 html.p(
 "bar",
 html.span("quux").css("blah"),
 html.br,
 "baz"
 )
);
var reslt = MarkupRenderer.RenderMarkup(body as MarkupBuilder, new HtmlFormatting().Dump();

I think now it should be possible to create a different renderer for xml or even XDocument/XElement.

It's much much shorter and I think it's much easier to extend it now.

public class MarkupBuilder : DynamicObject, IEnumerable<object>
{
 private MarkupBuilder(MarkupBuilderSettings settings, string tag)
 {
 Settings = new MarkupBuilderSettings(settings ?? new MarkupBuilderSettings());
 Tag = tag;
 Attributes = new List<MarkupAttribute>();
 Content = new List<object>();
 }
 public MarkupBuilder(MarkupBuilderSettings settings = null)
 : this(settings, string.Empty)
 {
 }
 public MarkupBuilderSettings Settings { get; }
 public string Tag { get; protected set; }
 public List<MarkupAttribute> Attributes { get; private set; }
 public List<object> Content { get; private set; }
 public MarkupBuilder Parent { get; set; }
 private int Depth
 {
 get
 {
 var depth = 0;
 var parent = Parent;
 while (parent != null)
 {
 depth++;
 parent = parent.Parent;
 }
 return depth;
 }
 }
 // supports object initializer
 public void Add(object content)
 {
 if (content != null)
 {
 Content.Add(content);
 var htmlElement = content as MarkupBuilder;
 if (htmlElement != null)
 {
 htmlElement.Parent = this;
 }
 }
 }
 public MarkupBuilder AddRange(params object[] content)
 {
 foreach (var item in content)
 {
 Add(item);
 }
 return this;
 }
 public IEnumerator<object> GetEnumerator()
 {
 return Content.GetEnumerator();
 }
 IEnumerator IEnumerable.GetEnumerator()
 {
 return this.GetEnumerator();
 }
 // --- DynamicObject members
 public override bool TryGetMember(GetMemberBinder binder, out object result)
 {
 foreach (var extension in Settings.Extensions)
 {
 if (extension.TryGetMember(this, binder, out result))
 {
 return true;
 }
 }
 result = new MarkupBuilder(Settings, binder.Name);
 return true;
 }
 public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
 {
 foreach (var extension in Settings.Extensions)
 {
 if (extension.TryInvokeMember(this, binder, args, out result))
 {
 return true;
 }
 }
 if (Settings.AllowedTags.Any() && !Settings.AllowedTags.Contains(binder.Name))
 {
 result = null;
  return false;
 }

 result = new MarkupBuilder(Settings, binder.Name).AddRange(args);
 return true;
 }
}

It's supported by MarkupBuilderSettings:

public class MarkupBuilderSettings
{
 public MarkupBuilderSettings() { }
 public MarkupBuilderSettings(MarkupBuilderSettings settings)
 {
 AllowedTags = settings.AllowedTags ?? new List<string>();
 AllowerAttributes = settings.AllowerAttributes ?? new List<string>();
 Extensions = settings.Extensions ?? new List<IMarkupBuilderExtension>();
 }
 public IReadOnlyCollection<string> AllowedTags { get; set; }
 public IReadOnlyCollection<string> AllowerAttributes { get; set; }
 public IReadOnlyCollection<IMarkupBuilderExtension> Extensions { get; set; }
}
public class MarkupRenderer
{
 public static string RenderMarkup(object value, IMarkupFormatting formatting)
 {
 var element = value as MarkupBuilder;
 if (element == null)
 {
 return value == null ? string.Empty : (string)value;
 }
 var content = element.Content.Aggregate(
 new StringBuilder(),
 (builder, next) => builder.Append(MarkupRenderer.RenderMarkup(next, formatting))).ToString();
 var isEmpty = string.IsNullOrEmpty(content);
 var html = new StringBuilder();
 if (element.Parent != null && formatting[element.Tag].HasFlag(MarkupFormattingOptions.PlaceOpeningTagOnNewLine))
 {
 html.AppendLine().Append(IndentString(formatting.IndentWidth));
 }
 html.Append(CreateOpeningElement(element, formatting));
 // if (IsVoid)
 // {
 // return html.ToString();
 // }
 if (!isEmpty)
 {
 html.Append(content);
 }
 if (!isEmpty && formatting[element.Tag].HasFlag(MarkupFormattingOptions.PlaceClosingTagOnNewLine))
 {
 html.AppendLine();
 if (element.Parent != null) { html.Append(IndentString(formatting.IndentWidth)); }
 }
 html.Append(CreateClosingElement(element, formatting));
 return html.ToString();
 }
 private static string IndentString(int indentWidth)
 {
 return new string(' ', indentWidth);
 }
 private static string CreateOpeningElement(MarkupBuilder element, IMarkupFormatting formatting)
 {
 var attributes = CreateAttributesString(element, formatting);
 var html = new StringBuilder()
 .Append("<").Append(element.Tag)
 .Append(string.IsNullOrEmpty(attributes) ? string.Empty : " ")
 .Append(attributes)
 //.Append(IsVoid ? "/" : string.Empty)
 .Append(">")
 .ToString();
 return html;
 }
 private static string CreateAttributesString(MarkupBuilder element, IMarkupFormatting formatting)
 {
 return string.Join(" ", element.Attributes);
 }
 private static string CreateClosingElement(MarkupBuilder element, IMarkupFormatting formatting)
 {
 return formatting[element.Tag].HasFlag(MarkupFormattingOptions.IsVoid)
 ? string.Empty
 : new StringBuilder()
 .Append("</")
 .Append(element.Tag)
 .Append(">")
 .ToString();
 }
}

The rendering can be customized with the IMarkupFormatting interface:

public interface IMarkupFormatting
{
 MarkupFormattingOptions this[string tag] { get; }
 int IndentWidth { get; }
}

For html it looks like this:

public class HtmlFormatting : IMarkupFormatting
{
 public HtmlFormatting()
 {
 Options = new Dictionary<string, MarkupFormattingOptions>
 {
 ["body"] = MarkupFormattingOptions.PlaceClosingTagOnNewLine,
 ["br"] = MarkupFormattingOptions.IsVoid,
 ["span"] = MarkupFormattingOptions.None,
 ["p"] = MarkupFormattingOptions.PlaceOpeningTagOnNewLine,
 ["h1"] = MarkupFormattingOptions.PlaceOpeningTagOnNewLine,
 };
 IndentWidth = 3;
 }
 public MarkupFormattingOptions this[string tag]
 {
 get
 {
 var options = MarkupFormattingOptions.None;
 return Options.TryGetValue(tag, out options) ? options : MarkupFormattingOptions.None;
 }
 set
 {
 Options[tag] = value;
 }
 }
 public int IndentWidth { get; set; }
 public Dictionary<string, MarkupFormattingOptions> Options { get; set; }
}
dynamic html = new MarkupBuilder(new MarkupBuilderSettings
{
 // if none are specified then all are allowed
 AllowedTags = new[] { "body", "p", "br", "span" },
 AllowerAttributes = new[] { "style" },
 Extensions = new IMarkupBuilderExtension[]
  {
 new cssExtension(),
 new attrExtension()
 }
});
var body = html.body
(
 html.p("foo"),
 html.p(
 "bar",
 html.span("quux").css("blah"),
 html.br,
 "baz"
 )
);
var reslt = MarkupRenderer.RenderMarkup(body as MarkupBuilder, new HtmlFormatting());

I think now it should be possible to create a different renderer for xml or even XDocument/XElement.

It's much much shorter and I think it's much easier to extend it now. Now it's also much easier to write another renderer that creates i.e. a XDocument/XElement.

public class MarkupBuilder : DynamicObject, IEnumerable<object>
{
 private readonly List<IMarkupBuilderExtension> _extensions = new List<UserQuery.IMarkupBuilderExtension>();
 private MarkupBuilder(MarkupBuilder markupBuilder, string tag)
 {
 _extensions = markupBuilder._extensions;
  MarkupSchema = new MarkupSchema(markupBuilder.MarkupSchema);
 Tag = tag;
 Attributes = new List<MarkupAttribute>();
 Content = new List<object>();
 }
 public MarkupBuilder(MarkupSchema markupSchema = null)
 {
  MarkupSchema = new MarkupSchema(markupSchema ?? new MarkupSchema());
  Attributes = new List<MarkupAttribute>();
 Content = new List<object>();
 }
 public IEnumerable<IMarkupBuilderExtension> Extensions => _extensions.AsReadOnly();
 public MarkupSchema MarkupSchema { get; }
 public string Tag { get; }
 public List<MarkupAttribute> Attributes { get; }
 public List<object> Content { get; }
 public MarkupBuilder Parent { get; private set; }
 private int Depth
 {
 get
 {
 var depth = 0;
 var parent = Parent;
 while (parent != null)
 {
 depth++;
 parent = parent.Parent;
 }
 return depth;
 }
 }
 public MarkupBuilder Register<T>() where T : IMarkupBuilderExtension, new()
 {
 _extensions.Add(new T());
 return this;
  }
 // supports object initializer
 public void Add(object content)
 {
 if (content != null)
 {
 Content.Add(content);
 var htmlElement = content as MarkupBuilder;
 if (htmlElement != null)
 {
 htmlElement.Parent = this;
 }
 }
 }
 public MarkupBuilder AddRange(params object[] content)
 {
 foreach (var item in content)
 {
 Add(item);
 }
 return this;
 }
 public IEnumerator<object> GetEnumerator()
 {
 return Content.GetEnumerator();
 }
 IEnumerator IEnumerable.GetEnumerator()
 {
 return this.GetEnumerator();
 }
 // --- DynamicObject members
 public override bool TryGetMember(GetMemberBinder binder, out object result)
 {
 foreach (var extension in Extensions)
 {
 if (extension.TryGetMember(this, binder, out result))
 {
 return true;
 }
 }
 result = new MarkupBuilder(this, binder.Name);
 return true;
 }
 public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
 {
 foreach (var extension in Extensions)
 {
 if (extension.TryInvokeMember(this, binder, args, out result))
 {
 return true;
 }
 }
 if (MarkupSchema.Tags.Any() && !MarkupSchema.Tags.ContainsKey(binder.Name))
 {
 throw new NotSupportedException($"Method '{binder.Name}' is not supported.");
 }
 result = new MarkupBuilder(this, binder.Name).AddRange(args);
 return true;
 }
}

It can be customized with the MarkupSchema a very simple schema that defines which tags/attributes are allowed and how to format the markup later:

public class MarkupSchema
{
 public MarkupSchema() {
 Tags = new Dictionary<string, MarkupFormattingOptions>();
 GlobalAttributes = new HashSet<string>();
 IndentWidth = 4;
 }
 internal MarkupSchema(MarkupSchema other)
 {
 Tags = new Dictionary<string, MarkupFormattingOptions>(other.Tags);
 GlobalAttributes = new HashSet<string>(other.GlobalAttributes);
 IndentWidth = other.IndentWidth;
  }

 public Dictionary<string, MarkupFormattingOptions> Tags { get; set; }
 public HashSet<string> GlobalAttributes { get; set; }
 public int IndentWidth { get; set; }
 // Creates a Html schema.
 public static MarkupSchema Html => new MarkupSchema
  {  Tags =
 {
 ["body"] = MarkupFormattingOptions.PlaceClosingTagOnNewLine,
 ["br"] = MarkupFormattingOptions.IsVoid,
 ["span"] = MarkupFormattingOptions.None,
 ["p"] = MarkupFormattingOptions.PlaceOpeningTagOnNewLine,
 ["h1"] = MarkupFormattingOptions.PlaceOpeningTagOnNewLine,
 ["h2"] = MarkupFormattingOptions.PlaceOpeningTagOnNewLine,
 ["h3"] = MarkupFormattingOptions.PlaceOpeningTagOnNewLine,
 ["h4"] = MarkupFormattingOptions.PlaceOpeningTagOnNewLine,
 ["h5"] = MarkupFormattingOptions.PlaceOpeningTagOnNewLine,
 ["h6"] = MarkupFormattingOptions.PlaceOpeningTagOnNewLine, 
 ["ul"] = MarkupFormattingOptions.PlaceBothTagsOnNewLine,
 ["ol"] = MarkupFormattingOptions.PlaceBothTagsOnNewLine,
 ["li"] = MarkupFormattingOptions.PlaceOpeningTagOnNewLine,
 // ...
 },
 GlobalAttributes = { "style" }
 };
 public bool TagHasFormattingOptions(string tagName, MarkupFormattingOptions options)
 {
 var tagFormattingOptions = MarkupFormattingOptions.None;
 return Tags.TryGetValue(tagName, out tagFormattingOptions) ? tagFormattingOptions.HasFlag(options) : false;
  }
}
public class MarkupRenderer
{
  public static string RenderMarkup(MarkupBuilder builder) 
{
  return RenderMarkup(builder, builder.MarkupSchema);
 }
 
 private static string RenderMarkup(object value, MarkupSchema markupSchema)
 {
 var markupBuilder = value as MarkupBuilder;
 if (markupBuilder == null)
 {
 return value == null ? string.Empty : (string)value;
 }
  var content = markupBuilder.Content.Aggregate(
 new StringBuilder(),
  (sb, next) => sb.Append(MarkupRenderer.RenderMarkup(next, markupSchema))).ToString();

 var isEmpty = string.IsNullOrEmpty(content);
  var html = new StringBuilder();
  if (markupBuilder.Parent != null && markupSchema.TagHasFormattingOptions(markupBuilder.Tag, MarkupFormattingOptions.PlaceOpeningTagOnNewLine))
 {
  html.AppendLine().Append(IndentString(markupSchema.IndentWidth));
 }
 html.Append(CreateOpeningElement(markupBuilder));
 //  if (IsVoid)
 // {
 // return html.ToString();
 //  }
  if (!isEmpty)
 {
  html.Append(content);
 }
  if (!isEmpty && markupSchema.TagHasFormattingOptions(markupBuilder.Tag, MarkupFormattingOptions.PlaceClosingTagOnNewLine))
 {
 html.AppendLine();
 if (markupBuilder.Parent != null) { html.Append(IndentString(markupSchema.IndentWidth)); }
 }

 html.Append(CreateClosingElement(markupBuilder));
  return html.ToString();
 }
 private static string IndentString(int indentWidth)
 {
 return new string(' ', indentWidth);
  }
 private static string CreateOpeningElement(MarkupBuilder markupBuilder) {
 var attributes = CreateAttributesString(markupBuilder);
 var html = new StringBuilder()
 .Append("<").Append(markupBuilder.Tag)
  .Append(string.IsNullOrEmpty(attributes) ? string.Empty : " ")
  .Append(attributes)
  //.Append(IsVoid ? "/" : string.Empty)
 .Append(">")
  .ToString();
 return html;
 }
 private static string CreateAttributesString(MarkupBuilder markupBuilder)
 { return string.Join(" ", markupBuilder.Attributes);
 }
 private static string CreateClosingElement(MarkupBuilder markupBuilder)
  {  return markupBuilder.MarkupSchema.TagHasFormattingOptions(markupBuilder.Tag, MarkupFormattingOptions.IsVoid)
 ? string.Empty
 : new StringBuilder()
 .Append("</")
 .Append(markupBuilder.Tag)
 .Append(">")
 .ToString();
  }
}

dynamic html = 
 new MarkupBuilder(MarkupSchema.Html)
 .Register<cssExtension>()
 .Register<attrExtension>();
var body = html.body
(
 html.p("foo"),
 html.p(
 "bar",
 html.span("quux").css("blah"),
 html.br,
 "baz"
 )
);
MarkupRenderer.RenderMarkup(body as MarkupBuilder).Dump();
added 111 characters in body
Source Link
t3chb0t
  • 44.6k
  • 9
  • 84
  • 190
dynamic html = new MarkupBuilder(new MarkupBuilderSettings
{
 // if none are specified then all are allowed
 AllowedTags = new[] { "body", "p", "br", "span" },
 AllowerAttributes = new[] { "style" },
 Extensions = new IMarkupBuilderExtension[]
 {
 new cssExtension(),
 new attrExtension()
 }
});
var body = html.body
(
 html.p("foo"), 
 html.p(
 "bar", 
 html.span("quux").css("blah"), 
 html.br, 
 "baz"
 )
);
var reslt = MarkupRenderer.RenderMarkup(body as MarkupBuilder, new HtmlFormatting());

I think now it should be possible to create a different renderer for xml or even XDocument/XElement.

dynamic html = new MarkupBuilder(new MarkupBuilderSettings
{
 // if none are specified then all are allowed
 AllowedTags = new[] { "body", "p", "br", "span" },
 AllowerAttributes = new[] { "style" },
 Extensions = new IMarkupBuilderExtension[]
 {
 new cssExtension(),
 new attrExtension()
 }
});
var body = html.body
(
 html.p("foo"), 
 html.p(
 "bar", 
 html.span("quux").css("blah"), 
 html.br, 
 "baz"
 )
);
var reslt = MarkupRenderer.RenderMarkup(body as MarkupBuilder, new HtmlFormatting());
dynamic html = new MarkupBuilder(new MarkupBuilderSettings
{
 // if none are specified then all are allowed
 AllowedTags = new[] { "body", "p", "br", "span" },
 AllowerAttributes = new[] { "style" },
 Extensions = new IMarkupBuilderExtension[]
 {
 new cssExtension(),
 new attrExtension()
 }
});
var body = html.body
(
 html.p("foo"), 
 html.p(
 "bar", 
 html.span("quux").css("blah"), 
 html.br, 
 "baz"
 )
);
var reslt = MarkupRenderer.RenderMarkup(body as MarkupBuilder, new HtmlFormatting());

I think now it should be possible to create a different renderer for xml or even XDocument/XElement.

Source Link
t3chb0t
  • 44.6k
  • 9
  • 84
  • 190
Loading
lang-cs

AltStyle によって変換されたページ (->オリジナル) /