1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.doxia.module.markdown;
20
21 import javax.inject.Inject;
22
23 import java.io.IOException;
24 import java.io.Reader;
25 import java.io.StringReader;
26 import java.io.StringWriter;
27 import java.io.Writer;
28
29 import org.apache.maven.doxia.module.xhtml5.Xhtml5Parser;
30 import org.apache.maven.doxia.parser.ParseException;
31 import org.apache.maven.doxia.parser.Parser;
32 import org.apache.maven.doxia.sink.Sink;
33 import org.apache.maven.doxia.sink.impl.AbstractSinkTest;
34 import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
35 import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet.Semantics;
36 import org.apache.maven.doxia.sink.impl.SinkEventTestingSink;
37 import org.apache.maven.doxia.util.DoxiaStringUtils;
38 import org.junit.jupiter.api.Test;
39
40 import static org.junit.jupiter.api.Assertions.assertEquals;
41 import static org.junit.jupiter.api.Assertions.assertIterableEquals;
42
43
44
45
46 class MarkdownSinkTest extends AbstractSinkTest {
47 @Inject
48 protected MarkdownParser parser;
49
50 @Inject
51 protected Xhtml5Parser htmlParser;
52
53 protected String outputExtension() {
54 return "md";
55 }
56
57 protected Sink createSink(Writer writer) {
58 return MarkdownSink.newInstance(writer);
59 }
60
61 protected boolean isXmlSink() {
62 return false;
63 }
64
65 protected String getTitleBlock(String title) {
66
67 return "";
68 }
69
70 protected String getAuthorBlock(String author) {
71
72 return "";
73 }
74
75 protected String getDateBlock(String date) {
76
77 return "";
78 }
79
80 protected String getHeadBlock() {
81 return "";
82 }
83
84 protected String getBodyBlock() {
85 return "";
86 }
87
88 protected String getArticleBlock() {
89 return "<article></article>";
90 }
91
92 protected String getNavigationBlock() {
93 return "<nav></nav>";
94 }
95
96 protected String getSidebarBlock() {
97 return "<aside></aside>";
98 }
99
100 protected String getSectionBlock(String title, int level) {
101 return DoxiaStringUtils.repeat(MarkdownMarkup.SECTION_TITLE_START_MARKUP, level) + SPACE + title + EOL + EOL;
102 }
103
104 protected String getSection1Block(String title) {
105 return getSectionBlock(title, 1);
106 }
107
108 protected String getSection2Block(String title) {
109 return getSectionBlock(title, 2);
110 }
111
112 protected String getSection3Block(String title) {
113 return getSectionBlock(title, 3);
114 }
115
116 protected String getSection4Block(String title) {
117 return getSectionBlock(title, 4);
118 }
119
120 protected String getSection5Block(String title) {
121 return getSectionBlock(title, 5);
122 }
123
124 protected String getSection6Block(String title) {
125 return getSectionBlock(title, 6);
126 }
127
128 protected String getHeaderBlock() {
129
130 return "";
131 }
132
133 protected String getContentBlock() {
134 return "<main><div class=\"content\"></div></main>";
135 }
136
137 protected String getFooterBlock() {
138 return "<footer></footer>";
139 }
140
141 protected String getListBlock(String item) {
142 return MarkdownMarkup.LIST_UNORDERED_ITEM_START_MARKUP + getEscapedText(item) + EOL;
143 }
144
145 protected String getNumberedListBlock(String item) {
146 return MarkdownMarkup.LIST_ORDERED_ITEM_START_MARKUP + getEscapedText(item) + EOL;
147 }
148
149 protected String getDefinitionListBlock(String definum, String definition) {
150
151 return "<dl>" + EOL + "<dt>" + getEscapedText(definum) + "</dt>" + EOL + "<dd>" + getEscapedText(definition)
152 + "</dd>" + EOL + "</dl>";
153 }
154
155 protected String getFigureBlock(String source, String caption) {
156 return "";
157 }
158
159 protected String getTableBlock(String cell, String caption) {
160 return MarkdownMarkup.TABLE_ROW_PREFIX + " " + MarkdownMarkup.TABLE_CELL_SEPARATOR_MARKUP + " "
161 + MarkdownMarkup.TABLE_CELL_SEPARATOR_MARKUP + " " + MarkdownMarkup.TABLE_CELL_SEPARATOR_MARKUP + EOL
162 + MarkdownMarkup.TABLE_ROW_PREFIX
163 + ":---:" + MarkdownMarkup.TABLE_CELL_SEPARATOR_MARKUP + "---"
164 + MarkdownMarkup.TABLE_CELL_SEPARATOR_MARKUP + "---" + MarkdownMarkup.TABLE_CELL_SEPARATOR_MARKUP + EOL
165 + MarkdownMarkup.TABLE_ROW_PREFIX
166 + cell + MarkdownMarkup.TABLE_CELL_SEPARATOR_MARKUP + cell + MarkdownMarkup.TABLE_CELL_SEPARATOR_MARKUP
167 + cell + MarkdownMarkup.TABLE_CELL_SEPARATOR_MARKUP + EOL + EOL;
168 }
169
170 @Override
171 protected String getTableWithHeaderBlock(String... rowPrefixes) {
172 StringBuilder expectedMarkup = new StringBuilder();
173 expectedMarkup
174 .append(MarkdownMarkup.TABLE_ROW_PREFIX)
175 .append(getEscapedText(rowPrefixes[0]))
176 .append("0|")
177 .append(getEscapedText(rowPrefixes[0]))
178 .append("1|")
179 .append(getEscapedText(rowPrefixes[0]))
180 .append("2|")
181 .append(EOL);
182 expectedMarkup
183 .append(MarkdownMarkup.TABLE_ROW_PREFIX)
184 .append(":---|---:|:---:|")
185 .append(EOL);
186 for (int n = 1; n < rowPrefixes.length; n++) {
187 expectedMarkup
188 .append(MarkdownMarkup.TABLE_ROW_PREFIX)
189 .append(getEscapedText(rowPrefixes[n]))
190 .append("0|")
191 .append(getEscapedText(rowPrefixes[n]))
192 .append("1|")
193 .append(getEscapedText(rowPrefixes[n]))
194 .append("2|")
195 .append(EOL);
196 }
197 expectedMarkup.append(EOL);
198 return expectedMarkup.toString();
199 }
200
201 protected String getParagraphBlock(String text) {
202 return text + EOL + EOL;
203 }
204
205 protected String getDataBlock(String value, String text) {
206 return "<data value=\"" + value + "\">" + text + "</data>";
207 }
208
209 protected String getTimeBlock(String datetime, String text) {
210 return "<time datetime=\"" + datetime + "\">" + text + "</time>";
211 }
212
213 protected String getAddressBlock(String text) {
214 return "<address>" + text + "</address>";
215 }
216
217 protected String getBlockquoteBlock(String text) {
218 return "> " + text + EOL;
219 }
220
221 protected String getDivisionBlock(String text) {
222 return "<div>" + text + "</div>";
223 }
224
225 protected String getVerbatimBlock(String text) {
226 return MarkdownMarkup.VERBATIM_START_MARKUP + EOL + text + EOL + MarkdownMarkup.VERBATIM_END_MARKUP + EOL + EOL;
227 }
228
229 protected String getVerbatimSourceBlock(String text) {
230 return MarkdownMarkup.VERBATIM_START_MARKUP + "unknown" + EOL + text + EOL + MarkdownMarkup.VERBATIM_END_MARKUP
231 + EOL + EOL;
232 }
233
234 protected String getHorizontalRuleBlock() {
235 return MarkdownMarkup.HORIZONTAL_RULE_MARKUP + EOL + EOL;
236 }
237
238 protected String getPageBreakBlock() {
239 return "";
240 }
241
242 protected String getAnchorBlock(String anchor) {
243 return "<a id=\"" + anchor + "\"></a>" + anchor;
244 }
245
246 protected String getLinkBlock(String link, String text) {
247 return MarkdownMarkup.LINK_START_1_MARKUP
248 + text
249 + MarkdownMarkup.LINK_START_2_MARKUP
250 + link
251 + MarkdownMarkup.LINK_END_MARKUP;
252 }
253
254 protected String getInlineBlock(String text) {
255 return text;
256 }
257
258 protected String getInlineItalicBlock(String text) {
259 return MarkdownMarkup.ITALIC_START_MARKUP + text + MarkdownMarkup.ITALIC_END_MARKUP;
260 }
261
262 protected String getInlineBoldBlock(String text) {
263 return MarkdownMarkup.BOLD_START_MARKUP + text + MarkdownMarkup.BOLD_END_MARKUP;
264 }
265
266 protected String getInlineCodeBlock(String text) {
267 return MarkdownMarkup.MONOSPACED_START_MARKUP + text + MarkdownMarkup.MONOSPACED_END_MARKUP;
268 }
269
270 protected String getInlineDeleteBlock(String text) {
271 return MarkdownMarkup.STRIKETHROUGH_START_MARKUP + text + MarkdownMarkup.STRIKETHROUGH_END_MARKUP;
272 }
273
274 protected String getMonospacedBlock(String text) {
275 return text;
276 }
277
278 protected String getLineBreakBlock() {
279 return "" + SPACE + SPACE + EOL;
280 }
281
282 protected String getLineBreakOpportunityBlock() {
283 return "<wbr />";
284 }
285
286 protected String getNonBreakingSpaceBlock() {
287 return MarkdownMarkup.NON_BREAKING_SPACE_MARKUP;
288 }
289
290 protected String getTextBlock(String text) {
291 if (text.equals("~,_=,_-,_+,_*,_[,_],_<,_>,_{,_},_\\")) {
292
293
294 return "~,\\_=,\\_-,\\_+,\\_*,\\_\\[,\\_\\],\\_<,\\_>,\\_{,\\_},\\_\\\\";
295 } else {
296
297 return getEscapedText(text);
298 }
299 }
300
301 protected String getRawTextBlock(String text) {
302 return text;
303 }
304
305
306
307
308
309
310 private String getEscapedText(String text) {
311 return MarkdownSink.ElementContext.BODY.escape(new LastTwoLinesAwareWriter(new StringWriter()), text);
312 }
313
314 @Override
315 protected String getCommentBlock(String text) {
316 return "<!--" + toXmlComment(text) + "-->";
317 }
318
319 @Override
320 protected String getCommentBlockFollowedByParagraph(String comment, String paragraph) {
321 return getCommentBlock(comment) + EOL + EOL + getParagraphBlock(paragraph);
322 }
323
324 @Test
325 void multipleAuthors() {
326 final Sink sink = getSink();
327 sink.head();
328 sink.author();
329 sink.text("first author");
330 sink.author_();
331 sink.author();
332 sink.text("second author");
333 sink.author_();
334 sink.head_();
335 sink.flush();
336 sink.close();
337
338 String expected = MarkdownMarkup.METADATA_MARKUP + EOL
339 + "author: " + EOL
340 + " - first author" + EOL
341 + " - second author" + EOL
342 + MarkdownMarkup.METADATA_MARKUP + EOL + EOL;
343
344 assertEquals(expected, getSinkContent(), "Wrong metadata section");
345 }
346
347
348 @Test
349 void roundtrip() throws Exception {
350 parseFile(parser, "test", getSink());
351
352 final SinkEventTestingSink regeneratedSink = new SinkEventTestingSink();
353 try (Reader reader = new StringReader(getSinkContent())) {
354 parser.parse(reader, regeneratedSink);
355 }
356
357 final SinkEventTestingSink originalSink = new SinkEventTestingSink();
358 parseFile(parser, "test", originalSink);
359
360
361
362 try {
363 assertIterableEquals(originalSink.getEventList(), regeneratedSink.getEventList());
364 } catch (AssertionError e) {
365
366 System.err.println(getSinkContent());
367 throw e;
368 }
369 }
370
371 private void parseFile(Parser parser, String file, Sink sink) throws ParseException, IOException {
372 parseFile(parser, file, outputExtension(), sink);
373 }
374
375 private void parseFile(Parser parser, String file, String extension, Sink sink) throws ParseException, IOException {
376 try (Reader reader = getTestReader(file, extension)) {
377 parser.parse(reader, sink);
378 }
379 }
380
381 @Test
382 void linksParagraphsAndStylesInTableCells() {
383 final String linkTarget = "target";
384 final String linkText = "link";
385 final String paragraphText = "paragraph text with |";
386 final Sink sink = getSink();
387 sink.table();
388 sink.tableRows();
389 sink.tableRow();
390 sink.tableCell();
391 sink.link(linkTarget);
392 sink.text(linkText);
393 sink.link_();
394 sink.tableCell_();
395 sink.tableCell();
396 sink.paragraph();
397 sink.text(paragraphText);
398 sink.bold();
399 sink.text("bold");
400 sink.bold_();
401 sink.paragraph_();
402 sink.tableCell_();
403 sink.tableRow_();
404 sink.tableRows_();
405 sink.table_();
406 sink.flush();
407 sink.close();
408
409 String expected =
410 "| | |" + EOL + "|---|---|" + EOL + "|[link](target)|paragraph text with \\|**bold**|" + EOL + EOL;
411
412 assertEquals(expected, getSinkContent(), "Wrong link or paragraph markup in table cell");
413 }
414
415 @Test
416 void inlineCodeWithSpecialCharacters() {
417 String text = "Test&<>*_";
418 final Sink sink = getSink();
419 sink.inline(SinkEventAttributeSet.Semantics.CODE);
420 sink.text(text);
421 sink.inline_();
422 sink.flush();
423 sink.close();
424
425 String expected = "`" + text + "`";
426
427 assertEquals(expected, getSinkContent(), "Wrong inline code!");
428 }
429
430 @Test
431 void nestedListWithBlockquotesParagraphsAndCode() {
432 final Sink sink = getSink();
433 sink.list();
434
435 sink.listItem();
436 sink.text("item1");
437 sink.list();
438 sink.listItem();
439 sink.text("item1a");
440 sink.listItem_();
441 sink.list_();
442 sink.listItem_();
443
444 sink.listItem();
445 sink.blockquote();
446 sink.text("blockquote");
447 sink.blockquote_();
448 sink.listItem_();
449
450 sink.listItem();
451 sink.text("item3");
452 sink.paragraph();
453 sink.text("item3paragraph2");
454 sink.paragraph_();
455 sink.listItem_();
456
457 sink.listItem();
458 sink.text("item4");
459 sink.verbatim();
460 sink.text("item4verbatim");
461 sink.lineBreak();
462 sink.text("item4verbatimline2");
463 sink.verbatim_();
464 sink.listItem_();
465
466 sink.list_();
467 sink.flush();
468 sink.close();
469
470 String expected = "- item1" + EOL
471 + " - item1a" + EOL
472 + "- " + EOL + EOL
473 + " > blockquote" + EOL + EOL
474 + "- item3" + EOL
475 + EOL
476 + " item3paragraph2" + EOL
477 + EOL
478 + "- item4" + EOL + EOL
479 + " ```" + EOL
480 + " item4verbatim" + EOL
481 + " item4verbatimline2" + EOL
482 + " ```" + EOL
483 + EOL;
484
485 assertEquals(expected, getSinkContent(), "Wrong inline code!");
486 }
487
488 @Test
489 void headingAfterInlineElement() {
490 try (Sink sink = getSink()) {
491 sink.text("Text");
492 sink.section1();
493 sink.sectionTitle1();
494 sink.text("Section1");
495 sink.sectionTitle1_();
496 sink.section1_();
497 }
498
499 String expected = "Text" + EOL + "# Section1" + EOL + EOL;
500 assertEquals(expected, getSinkContent(), "Wrong heading after inline element!");
501 }
502
503 @Test
504 void codeLink() {
505 try (Sink sink = getSink()) {
506 sink.inline(Semantics.CODE);
507 sink.link("http://example.com");
508 sink.text("label");
509 sink.link_();
510 sink.inline_();
511 }
512
513 String expected = "[`label`](http://example.com)";
514 assertEquals(expected, getSinkContent(), "Wrong link on code!");
515 }
516
517 @Test
518 void multilineVerbatimSourceAfterListItem() {
519 try (Sink sink = getSink()) {
520 sink.list();
521 sink.listItem();
522 sink.text("Before");
523 sink.verbatim(SinkEventAttributeSet.SOURCE);
524 sink.text("codeline1" + EOL + "codeline2");
525 sink.verbatim_();
526 sink.text("After");
527 sink.listItem_();
528 sink.list_();
529 }
530
531 String expected =
532 "- Before" + EOL + EOL + MarkdownMarkup.INDENT + MarkdownMarkup.VERBATIM_START_MARKUP + "unknown" + EOL
533 + MarkdownMarkup.INDENT + "codeline1" + EOL + MarkdownMarkup.INDENT + "codeline2" + EOL
534 + MarkdownMarkup.INDENT + MarkdownMarkup.VERBATIM_END_MARKUP + EOL + EOL + "After" + EOL;
535 assertEquals(expected, getSinkContent(), "Wrong verbatim!");
536 }
537
538 @Test
539 void definitionListWithInlineStyles() {
540 try (Sink sink = getSink()) {
541 sink.definitionList();
542 sink.definedTerm();
543 sink.text("term");
544 sink.definedTerm_();
545 sink.definition();
546 sink.text("prefix ");
547 sink.text("code", SinkEventAttributeSet.Semantics.CODE);
548 sink.text("suffix<a>test</a>", SinkEventAttributeSet.Semantics.EMPHASIS);
549 sink.definition_();
550 sink.definitionList_();
551 }
552 String expected = "<dl>" + EOL + "<dt>term</dt>" + EOL
553 + "<dd>prefix <code>code</code><em>suffix<a>test</a></em></dd>" + EOL + "</dl>";
554 assertEquals(expected, getSinkContent(), "Wrong heading after inline element!");
555 }
556
557 @Test
558 void nestedListBeingTight() {
559 try (Sink sink = getSink()) {
560 sink.list();
561 sink.listItem();
562 sink.text("item 1");
563 sink.listItem_();
564 sink.listItem();
565 sink.text("item 2");
566 sink.list();
567 sink.listItem();
568 sink.text("item 2 a");
569 sink.listItem_();
570 sink.listItem();
571 sink.text("item 2 b");
572 sink.listItem_();
573 sink.list_();
574 sink.listItem_();
575 sink.list_();
576 sink.listItem();
577 sink.text("item 3");
578 sink.listItem_();
579 sink.list();
580 }
581 String expected = "- item 1" + EOL
582 + "- item 2" + EOL
583 + " - item 2 a" + EOL
584 + " - item 2 b" + EOL
585 + "- item 3" + EOL;
586 assertEquals(expected, getSinkContent());
587 }
588
589 @Test
590 void linkInsideHtmlSection() {
591 try (Sink sink = getSink()) {
592 sink.definitionList();
593 sink.definedTerm();
594 sink.text("question1");
595 sink.definedTerm_();
596 sink.definition();
597 sink.link("#top");
598 sink.text("[top]");
599 sink.link_();
600 sink.definition_();
601 sink.definitionList_();
602 }
603 String expected =
604 "<dl>" + EOL + "<dt>question1</dt>" + EOL + "<dd><a href=\"#top\">[top]</a></dd>" + EOL + "</dl>";
605 assertEquals(expected, getSinkContent());
606 }
607
608 @Test
609 void commentPriorHead() {
610 try (Sink sink = getSink()) {
611 sink.comment("This is a comment");
612 sink.head();
613 sink.title();
614 sink.text("Title");
615 sink.title_();
616 sink.author();
617 sink.text("author1");
618 sink.author_();
619 sink.head_();
620 }
621 String expected = "---" + EOL
622 + "title: Title" + EOL
623 + "author: " + EOL
624 + " - author1" + EOL
625 + "---" + EOL
626 + EOL
627 + "<!--This is a comment-->";
628 assertEquals(expected, getSinkContent(), "Wrong metadata section");
629 }
630
631 @Test
632 void insignificantWhitespaceAfterBlockElement() {
633 try (Sink sink = getSink()) {
634 sink.paragraph();
635 sink.text("paragraph");
636 sink.paragraph_();
637 sink.markupLineBreak(
638 4);
639 sink.text("text after insignificant whitespace");
640 }
641 String expected = "paragraph" + EOL + EOL + "text after insignificant whitespace";
642 assertEquals(expected, getSinkContent());
643 }
644
645 @Test
646 void listItemsContainingInsignificantWhitespace() {
647 try (Sink sink = getSink()) {
648 sink.list();
649 sink.listItem();
650 sink.markupLineBreak(4);
651 sink.text("item 1");
652 sink.listItem_();
653 sink.listItem();
654 sink.markupLineBreak(4);
655 sink.text("item 2");
656 sink.listItem_();
657 sink.list_();
658 }
659 String expected = "- item 1" + EOL + "- item 2" + EOL;
660 assertEquals(expected, getSinkContent());
661 }
662
663 @Test
664 void tableWithInsignificantNewLines() throws ParseException, IOException {
665 try (Sink sink = getSink()) {
666 parseFile(htmlParser, "table", "html", sink);
667 }
668 String expected = "|Format<br />Newline|Short description|Doxia Module|" + EOL
669 + "|---|---|---|" + EOL
670 + "|[iText](../modules/index.html#iText)|iText PDF Library|[`doxia-module-itext`](../doxia/doxia-modules/doxia-module-itext/)|"
671 + EOL
672 + "|[FO](../modules/index.html#FO)<sup>*</sup>|XSL formatting objects \\(XSL-FO\\)|[`doxia-module-fo`](../doxia/doxia-modules/doxia-module-fo/)|"
673 + EOL
674 + "|[LaTeX](../modules/index.html#LaTeX)|LaTeX typesetting system|[`doxia-module-latex`](../doxia/doxia-modules/doxia-module-latex/)|"
675 + EOL
676 + "|[RTF](../modules/index.html#RTF)|Microsoft Rich Text Format|[`doxia-module-rtf`](../doxia/doxia-modules/doxia-module-rtf/)|"
677 + EOL + EOL;
678 assertEquals(expected, getSinkContent());
679 }
680
681 @Test
682 void linkFromHtml() throws ParseException, IOException {
683 try (Sink sink = getSink()) {
684 parseFile(htmlParser, "link", "html", getSink());
685 }
686 String expected = "[plugin](http://maven.apache.org/maven-1.x/plugins/xdoc/reference/xdocs.html) documentation."
687 + EOL + EOL;
688 assertEquals(expected, getSinkContent());
689 }
690
691 @Test
692 void verbatimInTableCell() throws ParseException, IOException {
693 try (Sink sink = getSink()) {
694 sink.table();
695 sink.tableRows();
696 sink.tableRow();
697 sink.tableCell();
698 sink.verbatim(SinkEventAttributeSet.SOURCE);
699 sink.text("code with | and ` inside");
700 sink.verbatim_();
701 sink.tableCell_();
702 sink.tableRow_();
703 sink.tableRow();
704 sink.tableCell();
705 sink.verbatim(SinkEventAttributeSet.SOURCE);
706 sink.text("code with | and ` inside");
707 sink.verbatim_();
708 sink.tableCell_();
709 sink.tableRow_();
710 sink.tableRows_();
711 sink.table_();
712 }
713 String expected = "| |" + EOL + "|---|" + EOL
714 + "|<pre><code>code with | and ` inside</code></pre>|" + EOL
715 + "|<pre><code>code with | and ` inside</code></pre>|" + EOL + EOL;
716 assertEquals(expected, getSinkContent());
717 }
718 }