1 module hunt.markdown.renderer.text.CoreTextContentNodeRenderer; 2 3 import hunt.markdown.node; 4 import hunt.markdown.node.Heading; 5 import hunt.markdown.node.AbstractVisitor; 6 import hunt.markdown.renderer.NodeRenderer; 7 import hunt.markdown.renderer.text.TextContentNodeRendererContext; 8 import hunt.markdown.renderer.text.TextContentWriter; 9 import hunt.markdown.internal.renderer.text.BulletListHolder; 10 import hunt.markdown.internal.renderer.text.ListHolder; 11 import hunt.markdown.internal.renderer.text.OrderedListHolder; 12 13 import hunt.collection.HashSet; 14 import hunt.collection.Set; 15 import hunt.Char; 16 import hunt.util.StringBuilder; 17 18 import std.conv; 19 20 alias Character = Char; 21 22 /** 23 * The node renderer that renders all the core nodes (comes last in the order of node renderers). 24 */ 25 class CoreTextContentNodeRenderer : AbstractVisitor, NodeRenderer { 26 27 protected TextContentNodeRendererContext context; 28 private TextContentWriter textContent; 29 30 private ListHolder listHolder; 31 32 public this(TextContentNodeRendererContext context) { 33 this.context = context; 34 this.textContent = context.getWriter(); 35 } 36 37 override public Set!TypeInfo_Class getNodeTypes() { 38 return new HashSet!TypeInfo_Class([ 39 typeid(Document), 40 typeid(Heading), 41 typeid(Paragraph), 42 typeid(BlockQuote), 43 typeid(BulletList), 44 typeid(FencedCodeBlock), 45 typeid(HtmlBlock), 46 typeid(ThematicBreak), 47 typeid(IndentedCodeBlock), 48 typeid(Link), 49 typeid(ListItem), 50 typeid(OrderedList), 51 typeid(Image), 52 typeid(Emphasis), 53 typeid(StrongEmphasis), 54 typeid(Text), 55 typeid(Code), 56 typeid(HtmlInline), 57 typeid(SoftLineBreak), 58 typeid(HardLineBreak) 59 ]); 60 } 61 62 public void render(Node node) { 63 node.accept(this); 64 } 65 66 override public void visit(Document document) { 67 // No rendering itself 68 visitChildren(document); 69 } 70 71 override public void visit(BlockQuote blockQuote) { 72 textContent.write("«"); 73 visitChildren(blockQuote); 74 textContent.write("»"); 75 76 writeEndOfLineIfNeeded(blockQuote, null); 77 } 78 79 override public void visit(BulletList bulletList) { 80 if (listHolder !is null) { 81 writeEndOfLine(); 82 } 83 listHolder = new BulletListHolder(listHolder, bulletList); 84 visitChildren(bulletList); 85 writeEndOfLineIfNeeded(bulletList, null); 86 if (listHolder.getParent() !is null) { 87 listHolder = listHolder.getParent(); 88 } else { 89 listHolder = null; 90 } 91 } 92 93 override public void visit(Code code) { 94 textContent.write('\"'); 95 textContent.write(code.getLiteral()); 96 textContent.write('\"'); 97 } 98 99 override public void visit(FencedCodeBlock fencedCodeBlock) { 100 if (context.stripNewlines()) { 101 textContent.writeStripped(fencedCodeBlock.getLiteral()); 102 writeEndOfLineIfNeeded(fencedCodeBlock, null); 103 } else { 104 textContent.write(fencedCodeBlock.getLiteral()); 105 } 106 } 107 108 override public void visit(HardLineBreak hardLineBreak) { 109 writeEndOfLineIfNeeded(hardLineBreak, null); 110 } 111 112 override public void visit(Heading heading) { 113 visitChildren(heading); 114 writeEndOfLineIfNeeded(heading, new Char(':')); 115 } 116 117 override public void visit(ThematicBreak thematicBreak) { 118 if (!context.stripNewlines()) { 119 textContent.write("***"); 120 } 121 writeEndOfLineIfNeeded(thematicBreak, null); 122 } 123 124 override public void visit(HtmlInline htmlInline) { 125 writeText(htmlInline.getLiteral()); 126 } 127 128 override public void visit(HtmlBlock htmlBlock) { 129 writeText(htmlBlock.getLiteral()); 130 } 131 132 override public void visit(Image image) { 133 writeLink(image, image.getTitle(), image.getDestination()); 134 } 135 136 override public void visit(IndentedCodeBlock indentedCodeBlock) { 137 if (context.stripNewlines()) { 138 textContent.writeStripped(indentedCodeBlock.getLiteral()); 139 writeEndOfLineIfNeeded(indentedCodeBlock, null); 140 } else { 141 textContent.write(indentedCodeBlock.getLiteral()); 142 } 143 } 144 145 override public void visit(Link link) { 146 writeLink(link, link.getTitle(), link.getDestination()); 147 } 148 149 override public void visit(ListItem listItem) { 150 if (listHolder !is null && cast(OrderedListHolder)listHolder !is null) { 151 OrderedListHolder orderedListHolder = cast(OrderedListHolder) listHolder; 152 string indent = context.stripNewlines() ? "" : orderedListHolder.getIndent(); 153 textContent.write(indent ~ orderedListHolder.getCounter().to!string ~ orderedListHolder.getDelimiter() ~ " "); 154 visitChildren(listItem); 155 writeEndOfLineIfNeeded(listItem, null); 156 orderedListHolder.increaseCounter(); 157 } else if (listHolder !is null && cast(BulletListHolder)listHolder !is null) { 158 BulletListHolder bulletListHolder = cast(BulletListHolder) listHolder; 159 if (!context.stripNewlines()) { 160 textContent.write(bulletListHolder.getIndent() ~ bulletListHolder.getMarker() ~ " "); 161 } 162 visitChildren(listItem); 163 writeEndOfLineIfNeeded(listItem, null); 164 } 165 } 166 167 override public void visit(OrderedList orderedList) { 168 if (listHolder !is null) { 169 writeEndOfLine(); 170 } 171 listHolder = new OrderedListHolder(listHolder, orderedList); 172 visitChildren(orderedList); 173 writeEndOfLineIfNeeded(orderedList, null); 174 if (listHolder.getParent() !is null) { 175 listHolder = listHolder.getParent(); 176 } else { 177 listHolder = null; 178 } 179 } 180 181 override public void visit(Paragraph paragraph) { 182 visitChildren(paragraph); 183 // Add "end of line" only if its "root paragraph. 184 if (paragraph.getParent() is null || cast(Document)paragraph.getParent() !is null) { 185 writeEndOfLineIfNeeded(paragraph, null); 186 } 187 } 188 189 override public void visit(SoftLineBreak softLineBreak) { 190 writeEndOfLineIfNeeded(softLineBreak, null); 191 } 192 193 override public void visit(Text text) { 194 writeText(text.getLiteral()); 195 } 196 197 override protected void visitChildren(Node parent) { 198 Node node = parent.getFirstChild(); 199 while (node !is null) { 200 Node next = node.getNext(); 201 context.render(node); 202 node = next; 203 } 204 } 205 206 private void writeText(string text) { 207 if (context.stripNewlines()) { 208 textContent.writeStripped(text); 209 } else { 210 textContent.write(text); 211 } 212 } 213 214 private void writeLink(Node node, string title, string destination) { 215 bool hasChild = node.getFirstChild() !is null; 216 bool hasTitle = title !is null && !(title == destination); 217 bool hasDestination = destination !is null && !(destination == ("")); 218 219 if (hasChild) { 220 textContent.write('"'); 221 visitChildren(node); 222 textContent.write('"'); 223 if (hasTitle || hasDestination) { 224 textContent.whitespace(); 225 textContent.write('('); 226 } 227 } 228 229 if (hasTitle) { 230 textContent.write(title); 231 if (hasDestination) { 232 textContent.colon(); 233 textContent.whitespace(); 234 } 235 } 236 237 if (hasDestination) { 238 textContent.write(destination); 239 } 240 241 if (hasChild && (hasTitle || hasDestination)) { 242 textContent.write(')'); 243 } 244 } 245 246 private void writeEndOfLineIfNeeded(Node node, Character c) { 247 if (context.stripNewlines()) { 248 if (c !is null) { 249 textContent.write(c.charValue); 250 } 251 if (node.getNext() !is null) { 252 textContent.whitespace(); 253 } 254 } else { 255 if (node.getNext() !is null) { 256 textContent.line(); 257 } 258 } 259 } 260 261 private void writeEndOfLine() { 262 if (context.stripNewlines()) { 263 textContent.whitespace(); 264 } else { 265 textContent.line(); 266 } 267 } 268 }