542496: Add a DocumentBuilder for Creole markup
Includes support for:
- Paragraph / Implicit Paragraph blocks
- Basic span types (Italic/Emphasis/Mark/Bold/Strong/Deleted/Underlined)
- Headings
- LineBreaks
- EntityReferences
Change-Id: Ib8e82a6a7385b3f1c132adc881d096ee15dc6de8
Task-Url: https://bugs.eclipse.org/bugs/show_bug.cgi?id=542496
Signed-off-by: Kevin de Vlaming <kevin.devlaming@tasktop.com>
diff --git a/wikitext/core/org.eclipse.mylyn.wikitext.creole/src/main/java/org/eclipse/mylyn/wikitext/creole/internal/CreoleDocumentBuilder.java b/wikitext/core/org.eclipse.mylyn.wikitext.creole/src/main/java/org/eclipse/mylyn/wikitext/creole/internal/CreoleDocumentBuilder.java
new file mode 100644
index 0000000..6e10d72
--- /dev/null
+++ b/wikitext/core/org.eclipse.mylyn.wikitext.creole/src/main/java/org/eclipse/mylyn/wikitext/creole/internal/CreoleDocumentBuilder.java
@@ -0,0 +1,240 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Tasktop Technologies.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Kevin de Vlaming - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.mylyn.wikitext.creole.internal;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.logging.Logger;
+
+import org.eclipse.mylyn.wikitext.creole.CreoleLanguage;
+import org.eclipse.mylyn.wikitext.parser.Attributes;
+import org.eclipse.mylyn.wikitext.parser.builder.AbstractMarkupDocumentBuilder;
+import org.eclipse.mylyn.wikitext.parser.builder.EntityReferences;
+
+/**
+ * a document builder that emits Creole markup
+ *
+ * @author Kevin de Vlaming
+ * @see CreoleLanguageLanguage
+ * @see CreoleLanguage#createDocumentBuilder(Writer)
+ */
+public class CreoleDocumentBuilder extends AbstractMarkupDocumentBuilder {
+
+ private interface CreoleBlock {
+
+ void lineBreak() throws IOException;
+
+ }
+
+ private class ContentBlock extends NewlineDelimitedBlock implements CreoleBlock {
+
+ protected String prefix;
+
+ protected String suffix;
+
+ private final boolean emitWhenEmpty;
+
+ public ContentBlock(BlockType blockType, String prefix, String suffix, int precedingNewlineCount,
+ int trailingNewlineCount, boolean emitWhenEmpty) {
+ super(blockType, precedingNewlineCount, trailingNewlineCount);
+ this.prefix = prefix;
+ this.suffix = suffix;
+ this.emitWhenEmpty = emitWhenEmpty;
+ }
+
+ public ContentBlock(BlockType blockType, String prefix, String suffix, int precedingNewlineCount,
+ int trailingNewlineCount) {
+ this(blockType, prefix, suffix, precedingNewlineCount, trailingNewlineCount, false);
+ }
+
+ public ContentBlock(String prefix, String suffix, int precedingNewlineCount, int trailingNewlineCount) {
+ this(null, prefix, suffix, precedingNewlineCount, trailingNewlineCount);
+ }
+
+ public ContentBlock(String prefix, String suffix) {
+ this(null, prefix, suffix, 0, 0);
+ }
+
+ @Override
+ public void write(int c) throws IOException {
+ CreoleDocumentBuilder.this.emitContent(c);
+ }
+
+ @Override
+ public void write(String s) throws IOException {
+ CreoleDocumentBuilder.this.emitContent(s);
+ }
+
+ @Override
+ public void open() throws IOException {
+ super.open();
+ pushWriter(new StringWriter());
+ }
+
+ @Override
+ public void close() throws IOException {
+ Writer thisContent = popWriter();
+ String content = thisContent.toString();
+ if (emitWhenEmpty || content.length() > 0) {
+ emitContent(content);
+ }
+ super.close();
+ }
+
+ protected void emitContent(final String content) throws IOException {
+ CreoleDocumentBuilder.this.emitContent(prefix);
+ CreoleDocumentBuilder.this.emitContent(content);
+ CreoleDocumentBuilder.this.emitContent(suffix);
+ }
+
+ @Override
+ public void lineBreak() throws IOException {
+ write("\\\\"); //$NON-NLS-1$
+ }
+
+ }
+
+ private class ImplicitParagraphBlock extends ContentBlock {
+
+ ImplicitParagraphBlock() {
+ super(BlockType.PARAGRAPH, "", "", 2, 2); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ @Override
+ protected boolean isImplicitBlock() {
+ return true;
+ }
+
+ }
+
+ public CreoleDocumentBuilder(Writer out) {
+ super(out);
+ }
+
+ @Override
+ protected Block computeBlock(BlockType type, Attributes attributes) {
+ switch (type) {
+ case PARAGRAPH:
+ return new ContentBlock(type, "", "", 2, 2); //$NON-NLS-1$ //$NON-NLS-2$
+ default:
+ Logger.getLogger(getClass().getName()).warning("Unexpected block type: " + type); //$NON-NLS-1$
+ return new ContentBlock(type, "", "", 2, 2); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ }
+
+ @Override
+ protected Block computeSpan(SpanType type, Attributes attributes) {
+ switch (type) {
+ case ITALIC:
+ case EMPHASIS:
+ case MARK:
+ return new ContentBlock("//", "//"); //$NON-NLS-1$ //$NON-NLS-2$
+ case BOLD:
+ case STRONG:
+ return new ContentBlock("**", "**"); //$NON-NLS-1$ //$NON-NLS-2$
+ case DELETED:
+ return new ContentBlock("--", "--"); //$NON-NLS-1$ //$NON-NLS-2$
+ case UNDERLINED:
+ return new ContentBlock("__", "__"); //$NON-NLS-1$ //$NON-NLS-2$
+ default:
+ Logger.getLogger(getClass().getName()).warning("Unexpected span type: " + type); //$NON-NLS-1$
+ return new ContentBlock("", "", 2, 2); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ }
+
+ @Override
+ protected Block computeHeading(int level, Attributes attributes) {
+ return new ContentBlock(computePrefix('=', level) + " ", "", 1, 2); //$NON-NLS-1$
+
+ }
+
+ @Override
+ public void characters(String text) {
+ String escapedText = escapeTilde(text);
+ assertOpenBlock();
+ try {
+ currentBlock.write(escapedText);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private String escapeTilde(String text) {
+ return text.replace("~", "˜");
+ }
+
+ @Override
+ public void entityReference(String entity) {
+ assertOpenBlock();
+ String literal = EntityReferences.instance().equivalentString(entity);
+ if (literal == null) {
+ literal = "&" + entity + ";"; //$NON-NLS-1$//$NON-NLS-2$
+ }
+ try {
+ currentBlock.write(literal);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void image(Attributes attributes, String url) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void link(Attributes attributes, String hrefOrHashName, String text) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void imageLink(Attributes linkAttributes, Attributes imageAttributes, String href, String imageUrl) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void acronym(String text, String definition) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void lineBreak() {
+ assertOpenBlock();
+ try {
+ if (currentBlock instanceof CreoleBlock) {
+ ((CreoleBlock) currentBlock).lineBreak();
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void horizontalRule() {
+ assertOpenBlock();
+ String horizontalRule = "\n----\n"; //$NON-NLS-1$
+ try {
+ currentBlock.write(horizontalRule);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ protected Block createImplicitParagraphBlock() {
+ return new ImplicitParagraphBlock();
+ }
+
+}
diff --git a/wikitext/core/org.eclipse.mylyn.wikitext.creole/src/test/java/org/eclipse/mylyn/internal/wikitext/creole/tests/documentbuilder/AbstractCreoleDocumentBuilderTest.java b/wikitext/core/org.eclipse.mylyn.wikitext.creole/src/test/java/org/eclipse/mylyn/internal/wikitext/creole/tests/documentbuilder/AbstractCreoleDocumentBuilderTest.java
new file mode 100644
index 0000000..42212d6
--- /dev/null
+++ b/wikitext/core/org.eclipse.mylyn.wikitext.creole/src/test/java/org/eclipse/mylyn/internal/wikitext/creole/tests/documentbuilder/AbstractCreoleDocumentBuilderTest.java
@@ -0,0 +1,44 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Tasktop Technologies.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Kevin de Vlaming - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.mylyn.internal.wikitext.creole.tests.documentbuilder;
+
+import java.io.StringWriter;
+
+import org.eclipse.mylyn.wikitext.creole.internal.CreoleDocumentBuilder;
+import org.eclipse.mylyn.wikitext.parser.DocumentBuilder;
+
+import junit.framework.TestCase;
+
+/**
+ * @author Kevin de Vlaming
+ */
+public abstract class AbstractCreoleDocumentBuilderTest extends TestCase {
+
+ protected DocumentBuilder builder;
+
+ protected StringWriter out;
+
+ @Override
+ protected void setUp() throws Exception {
+ out = new StringWriter();
+ builder = new CreoleDocumentBuilder(out);
+ }
+
+ protected void assertMarkup(String expected) {
+ String markup = out.toString();
+
+ assertEquals(expected, markup);
+ }
+
+}
\ No newline at end of file
diff --git a/wikitext/core/org.eclipse.mylyn.wikitext.creole/src/test/java/org/eclipse/mylyn/internal/wikitext/creole/tests/documentbuilder/CreoleDocumentBuilderBlockTest.java b/wikitext/core/org.eclipse.mylyn.wikitext.creole/src/test/java/org/eclipse/mylyn/internal/wikitext/creole/tests/documentbuilder/CreoleDocumentBuilderBlockTest.java
new file mode 100644
index 0000000..4ac6299
--- /dev/null
+++ b/wikitext/core/org.eclipse.mylyn.wikitext.creole/src/test/java/org/eclipse/mylyn/internal/wikitext/creole/tests/documentbuilder/CreoleDocumentBuilderBlockTest.java
@@ -0,0 +1,129 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Tasktop Technologies.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Kevin de Vlaming - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.mylyn.internal.wikitext.creole.tests.documentbuilder;
+
+import org.eclipse.mylyn.wikitext.parser.Attributes;
+import org.eclipse.mylyn.wikitext.parser.DocumentBuilder.BlockType;
+import org.eclipse.mylyn.wikitext.parser.DocumentBuilder.SpanType;
+
+/**
+ * @see http://www.wikicreole.org/wiki/Elements
+ * @author Kevin de Vlaming
+ */
+public class CreoleDocumentBuilderBlockTest extends AbstractCreoleDocumentBuilderTest {
+
+ public void testParagraph() {
+ builder.beginDocument();
+ builder.beginBlock(BlockType.PARAGRAPH, new Attributes());
+ builder.characters("A paragraph ends when a blank line begins!");
+ builder.endBlock();
+ builder.endDocument();
+ assertMarkup("A paragraph ends when a blank line begins!\n\n");
+ }
+
+ public void testParagraphConsecutive() {
+ builder.beginDocument();
+ builder.beginBlock(BlockType.PARAGRAPH, new Attributes());
+ builder.characters("Paragraph 1");
+ builder.endBlock();
+ builder.beginBlock(BlockType.PARAGRAPH, new Attributes());
+ builder.characters("Paragraph 2");
+ builder.endBlock();
+ builder.endDocument();
+ assertMarkup("Paragraph 1\n\nParagraph 2\n\n");
+ }
+
+ public void testParagraphWithStrongEmphasis() {
+ builder.beginDocument();
+ builder.beginBlock(BlockType.PARAGRAPH, new Attributes());
+ builder.characters("some ");
+ builder.beginSpan(SpanType.STRONG, new Attributes());
+ builder.characters("strong");
+ builder.endSpan();
+ builder.characters(" and ");
+ builder.beginSpan(SpanType.EMPHASIS, new Attributes());
+ builder.characters("emphasis");
+ builder.endSpan();
+ builder.endBlock();
+ builder.endDocument();
+ assertMarkup("some **strong** and //emphasis//\n\n");
+ }
+
+ public void testImplicitParagraph() {
+ builder.beginDocument();
+ builder.characters("text1");
+ builder.beginBlock(BlockType.PARAGRAPH, new Attributes());
+ builder.characters("text2");
+ builder.endBlock();
+ builder.characters("text3");
+ builder.endDocument();
+ assertMarkup("text1\n\ntext2\n\ntext3\n\n");
+ }
+
+ public void testSpanImplicitParagraph() {
+ builder.beginDocument();
+ builder.beginBlock(BlockType.PARAGRAPH, new Attributes());
+ builder.characters("Paragraph");
+ builder.endBlock();
+ builder.beginSpan(SpanType.ITALIC, new Attributes());
+ builder.characters("Implicit");
+ builder.endSpan();
+ builder.characters(" paragraph");
+ builder.endDocument();
+ assertMarkup("Paragraph\n\n//Implicit// paragraph\n\n");
+ }
+
+ public void testImplicitParagraphWithSpan() {
+ builder.beginDocument();
+ builder.beginSpan(SpanType.BOLD, new Attributes());
+ builder.characters("text1");
+ builder.endSpan();
+ builder.beginBlock(BlockType.PARAGRAPH, new Attributes());
+ builder.characters("text2");
+ builder.endBlock();
+ builder.endDocument();
+ assertMarkup("**text1**\n\ntext2\n\n");
+ }
+
+ public void testSpanOpensImplicitParagraph() {
+ builder.beginDocument();
+ builder.beginBlock(BlockType.PARAGRAPH, new Attributes());
+ builder.characters("text");
+ builder.endBlock();
+ builder.beginSpan(SpanType.BOLD, new Attributes());
+ builder.characters("bold");
+ builder.endSpan();
+ builder.characters(" text");
+ builder.endDocument();
+ assertMarkup("text\n\n**bold** text\n\n");
+ }
+
+ public void testEmptyBlock() {
+ builder.beginDocument();
+ builder.beginBlock(BlockType.PARAGRAPH, new Attributes());
+ builder.endBlock();
+ builder.endDocument();
+ assertMarkup("");
+ }
+
+ public void testUnsupportedBlock() {
+ builder.beginDocument();
+ builder.beginBlock(BlockType.FOOTNOTE, new Attributes());
+ builder.characters("unsupported");
+ builder.endBlock();
+ builder.endDocument();
+ assertMarkup("unsupported\n\n");
+ }
+
+}
\ No newline at end of file
diff --git a/wikitext/core/org.eclipse.mylyn.wikitext.creole/src/test/java/org/eclipse/mylyn/internal/wikitext/creole/tests/documentbuilder/CreoleDocumentBuilderSpanTest.java b/wikitext/core/org.eclipse.mylyn.wikitext.creole/src/test/java/org/eclipse/mylyn/internal/wikitext/creole/tests/documentbuilder/CreoleDocumentBuilderSpanTest.java
new file mode 100644
index 0000000..554b9b4
--- /dev/null
+++ b/wikitext/core/org.eclipse.mylyn.wikitext.creole/src/test/java/org/eclipse/mylyn/internal/wikitext/creole/tests/documentbuilder/CreoleDocumentBuilderSpanTest.java
@@ -0,0 +1,107 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Tasktop Technologies.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Kevin de Vlaming - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.mylyn.internal.wikitext.creole.tests.documentbuilder;
+
+import org.eclipse.mylyn.wikitext.parser.Attributes;
+import org.eclipse.mylyn.wikitext.parser.DocumentBuilder.SpanType;
+
+/**
+ * @see http://www.wikicreole.org/wiki/Elements
+ * @author Kevin de Vlaming
+ */
+public class CreoleDocumentBuilderSpanTest extends AbstractCreoleDocumentBuilderTest {
+
+ public void testItalic() {
+ builder.beginDocument();
+ builder.beginSpan(SpanType.ITALIC, new Attributes());
+ builder.characters("italic");
+ builder.endSpan();
+ builder.endDocument();
+ assertMarkup("//italic//\n\n");
+ }
+
+ public void testEmphasis() {
+ builder.beginDocument();
+ builder.beginSpan(SpanType.EMPHASIS, new Attributes());
+ builder.characters("emphasis");
+ builder.endSpan();
+ builder.endDocument();
+ assertMarkup("//emphasis//\n\n");
+ }
+
+ public void testMark() {
+ builder.beginDocument();
+ builder.beginSpan(SpanType.MARK, new Attributes());
+ builder.characters("mark");
+ builder.endSpan();
+ builder.endDocument();
+ assertMarkup("//mark//\n\n");
+ }
+
+ public void testBold() {
+ builder.beginDocument();
+ builder.beginSpan(SpanType.BOLD, new Attributes());
+ builder.characters("bold");
+ builder.endSpan();
+ builder.endDocument();
+ assertMarkup("**bold**\n\n");
+ }
+
+ public void testStrong() {
+ builder.beginDocument();
+ builder.beginSpan(SpanType.STRONG, new Attributes());
+ builder.characters("strong");
+ builder.endSpan();
+ builder.endDocument();
+ assertMarkup("**strong**\n\n");
+ }
+
+ public void testDeleted() {
+ builder.beginDocument();
+ builder.beginSpan(SpanType.DELETED, new Attributes());
+ builder.characters("deleted");
+ builder.endSpan();
+ builder.endDocument();
+ assertMarkup("--deleted--\n\n");
+ }
+
+ public void testUnderlined() {
+ builder.beginDocument();
+ builder.beginSpan(SpanType.UNDERLINED, new Attributes());
+ builder.characters("underlined");
+ builder.endSpan();
+ builder.endDocument();
+ assertMarkup("__underlined__\n\n");
+ }
+
+ public void testEmptySpan() {
+ builder.beginDocument();
+ builder.characters("prefix");
+ builder.beginSpan(SpanType.BOLD, new Attributes());
+ builder.endSpan();
+ builder.characters(" suffix");
+ builder.endDocument();
+ assertMarkup("prefix suffix\n\n");
+ }
+
+ public void testUnsupportedSpan() {
+ builder.beginDocument();
+ builder.beginSpan(SpanType.INSERTED, new Attributes());
+ builder.characters("unsupported");
+ builder.endSpan();
+ builder.endDocument();
+ assertMarkup("unsupported\n\n");
+ }
+
+}
\ No newline at end of file
diff --git a/wikitext/core/org.eclipse.mylyn.wikitext.creole/src/test/java/org/eclipse/mylyn/internal/wikitext/creole/tests/documentbuilder/CreoleDocumentBuilderTest.java b/wikitext/core/org.eclipse.mylyn.wikitext.creole/src/test/java/org/eclipse/mylyn/internal/wikitext/creole/tests/documentbuilder/CreoleDocumentBuilderTest.java
new file mode 100644
index 0000000..752ce4c
--- /dev/null
+++ b/wikitext/core/org.eclipse.mylyn.wikitext.creole/src/test/java/org/eclipse/mylyn/internal/wikitext/creole/tests/documentbuilder/CreoleDocumentBuilderTest.java
@@ -0,0 +1,108 @@
+/*******************************************************************************
+ * Copyright (c) 2018 Tasktop Technologies.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Kevin de Vlaming - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.mylyn.internal.wikitext.creole.tests.documentbuilder;
+
+import org.eclipse.mylyn.wikitext.parser.Attributes;
+import org.eclipse.mylyn.wikitext.parser.DocumentBuilder.BlockType;
+
+/**
+ * @see http://www.wikicreole.org/wiki/Elements
+ * @author Kevin de Vlaming
+ */
+public class CreoleDocumentBuilderTest extends AbstractCreoleDocumentBuilderTest {
+
+ public void testLineBreak() {
+ builder.beginDocument();
+ builder.beginBlock(BlockType.PARAGRAPH, new Attributes());
+ builder.characters("line");
+ builder.lineBreak();
+ builder.characters("break");
+ builder.endBlock();
+ builder.endDocument();
+ assertMarkup("line\\\\break\n\n");
+ }
+
+ public void testLineBreakImplicitParagraph() {
+ builder.beginDocument();
+ builder.characters("line");
+ builder.lineBreak();
+ builder.characters("break");
+ builder.endDocument();
+ assertMarkup("line\\\\break\n\n");
+ }
+
+ public void testHeadings() {
+ builder.beginDocument();
+ builder.beginHeading(1, new Attributes());
+ builder.characters("This is an H1");
+ builder.endHeading();
+ builder.beginHeading(2, new Attributes());
+ builder.characters("This is an H2");
+ builder.endHeading();
+ builder.beginHeading(6, new Attributes());
+ builder.characters("This is an H6");
+ builder.endHeading();
+ builder.endDocument();
+ assertMarkup("= This is an H1\n\n== This is an H2\n\n====== This is an H6\n\n");
+ }
+
+ public void testHorizontalRule() {
+ builder.beginDocument();
+ builder.beginBlock(BlockType.PARAGRAPH, new Attributes());
+ builder.characters("horizontal");
+ builder.horizontalRule();
+ builder.characters("rule");
+ builder.endBlock();
+ builder.endDocument();
+ assertMarkup("horizontal\n----\nrule\n\n");
+ }
+
+ public void testEscapedTilde() {
+ builder.beginDocument();
+ builder.beginBlock(BlockType.PARAGRAPH, new Attributes());
+ builder.characters("this ~ is interpreted as an escape");
+ builder.endBlock();
+ builder.endDocument();
+ assertMarkup("this ˜ is interpreted as an escape\n\n");
+ }
+
+ public void testEntityReference() {
+ builder.beginDocument();
+ builder.beginBlock(BlockType.PARAGRAPH, new Attributes());
+ builder.characters("5 ");
+ builder.entityReference("gt");
+ builder.characters(" 4");
+ builder.endBlock();
+ builder.endDocument();
+ assertMarkup("5 > 4\n\n");
+ }
+
+ public void testEntityReferenceImplicitParagraph() {
+ builder.beginDocument();
+ builder.characters("4 ");
+ builder.entityReference("lt");
+ builder.characters(" 5");
+ builder.endDocument();
+ assertMarkup("4 < 5\n\n");
+ }
+
+ public void testUnknownEntityReference() {
+ builder.beginDocument();
+ builder.entityReference("unknown");
+ builder.characters(" reference");
+ builder.endDocument();
+ assertMarkup("&unknown; reference\n\n");
+ }
+
+}
\ No newline at end of file