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 }