1 module hunt.markdown.renderer.text.TextContentRenderer; 2 3 import hunt.markdown.Extension; 4 import hunt.markdown.internal.renderer.NodeRendererMap; 5 import hunt.markdown.node.Node; 6 import hunt.markdown.renderer.NodeRenderer; 7 import hunt.markdown.renderer.Renderer; 8 import hunt.markdown.renderer.text.TextContentWriter; 9 import hunt.markdown.renderer.text.TextContentNodeRendererFactory; 10 import hunt.markdown.renderer.text.TextContentNodeRendererContext; 11 import hunt.markdown.renderer.text.CoreTextContentNodeRenderer; 12 13 import hunt.collection.ArrayList; 14 import hunt.collection.List; 15 16 import hunt.util.Appendable; 17 import hunt.util.Common; 18 import hunt.util.StringBuilder; 19 20 class TextContentRenderer : Renderer { 21 22 private bool _stripNewlines; 23 24 private List!(TextContentNodeRendererFactory) nodeRendererFactories; 25 26 private this(Builder builder) { 27 this._stripNewlines = builder._stripNewlines; 28 29 this.nodeRendererFactories = new ArrayList!TextContentNodeRendererFactory(builder.nodeRendererFactories.size() + 1); 30 this.nodeRendererFactories.addAll(builder.nodeRendererFactories); 31 // Add as last. This means clients can override the rendering of core nodes if they want. 32 this.nodeRendererFactories.add(new class TextContentNodeRendererFactory { 33 override public NodeRenderer create(TextContentNodeRendererContext context) { 34 return new CoreTextContentNodeRenderer(context); 35 } 36 }); 37 } 38 39 /** 40 * Create a new builder for configuring an {@link TextContentRenderer}. 41 * 42 * @return a builder 43 */ 44 public static Builder builder() { 45 return new Builder(); 46 } 47 48 public void render(Node node, Appendable output) { 49 RendererContext context = new RendererContext(new TextContentWriter(output)); 50 context.render(node); 51 } 52 53 override public string render(Node node) { 54 StringBuilder sb = new StringBuilder(); 55 render(node, sb); 56 return sb.toString(); 57 } 58 59 /** 60 * Builder for configuring an {@link TextContentRenderer}. See methods for default configuration. 61 */ 62 public static class Builder { 63 64 private bool _stripNewlines = false; 65 private List!(TextContentNodeRendererFactory) nodeRendererFactories; 66 67 this() 68 { 69 nodeRendererFactories = new ArrayList!TextContentNodeRendererFactory(); 70 } 71 72 /** 73 * @return the configured {@link TextContentRenderer} 74 */ 75 public TextContentRenderer build() { 76 return new TextContentRenderer(this); 77 } 78 79 /** 80 * Set the value of flag for stripping new lines. 81 * 82 * @param stripNewlines true for stripping new lines and render text as "single line", 83 * false for keeping all line breaks 84 * @return {@code this} 85 */ 86 public Builder stripNewlines(bool stripNewlines) { 87 this._stripNewlines = stripNewlines; 88 return this; 89 } 90 91 /** 92 * Add a factory for instantiating a node renderer (done when rendering). This allows to override the rendering 93 * of node types or define rendering for custom node types. 94 * <p> 95 * If multiple node renderers for the same node type are created, the one from the factory that was added first 96 * "wins". (This is how the rendering for core node types can be overridden; the default rendering comes last.) 97 * 98 * @param nodeRendererFactory the factory for creating a node renderer 99 * @return {@code this} 100 */ 101 public Builder nodeRendererFactory(TextContentNodeRendererFactory nodeRendererFactory) { 102 this.nodeRendererFactories.add(nodeRendererFactory); 103 return this; 104 } 105 106 /** 107 * @param extensions extensions to use on this text content renderer 108 * @return {@code this} 109 */ 110 public Builder extensions(Iterable!Extension extensions) { 111 foreach (Extension extension ; extensions) { 112 if (cast(TextContentRenderer.TextContentRendererExtension)extension !is null) { 113 TextContentRenderer.TextContentRendererExtension htmlRendererExtension = 114 cast(TextContentRenderer.TextContentRendererExtension) extension; 115 htmlRendererExtension.extend(this); 116 } 117 } 118 return this; 119 } 120 } 121 122 /** 123 * Extension for {@link TextContentRenderer}. 124 */ 125 public interface TextContentRendererExtension : Extension { 126 void extend(TextContentRenderer.Builder rendererBuilder); 127 } 128 129 private class RendererContext : TextContentNodeRendererContext { 130 private TextContentWriter textContentWriter; 131 private NodeRendererMap nodeRendererMap; 132 133 private this(TextContentWriter textContentWriter) { 134 nodeRendererMap = new NodeRendererMap(); 135 this.textContentWriter = textContentWriter; 136 137 // The first node renderer for a node type "wins". 138 for (int i = nodeRendererFactories.size() - 1; i >= 0; i--) { 139 TextContentNodeRendererFactory nodeRendererFactory = nodeRendererFactories.get(i); 140 NodeRenderer nodeRenderer = nodeRendererFactory.create(this); 141 nodeRendererMap.add(nodeRenderer); 142 } 143 } 144 145 override public bool stripNewlines() { 146 return _stripNewlines; 147 } 148 149 override public TextContentWriter getWriter() { 150 return textContentWriter; 151 } 152 153 public void render(Node node) { 154 nodeRendererMap.render(node); 155 } 156 } 157 }