blob: fef3c23f27632979e15cb22c79e6166b934a339f [file] [log] [blame]
/*******************************************************************************
* 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.ImageAttributes;
import org.eclipse.mylyn.wikitext.parser.LinkAttributes;
import org.eclipse.mylyn.wikitext.parser.builder.AbstractMarkupDocumentBuilder;
import org.eclipse.mylyn.wikitext.parser.builder.EntityReferences;
import com.google.common.base.Strings;
/**
* 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;
}
}
private class LinkBlock extends ContentBlock {
private final LinkAttributes attributes;
LinkBlock(LinkAttributes attributes) {
super(null, "", "", 0, 0, true);
this.attributes = attributes;
}
@Override
protected void emitContent(String content) throws IOException {
// [[http://url.com|label]]
CreoleDocumentBuilder.this.emitContent("[[");
if (!Strings.isNullOrEmpty(attributes.getHref())) {
CreoleDocumentBuilder.this.emitContent(attributes.getHref());
}
if (!Strings.isNullOrEmpty(attributes.getHref()) && !Strings.isNullOrEmpty(content)) {
CreoleDocumentBuilder.this.emitContent('|');
}
if (!Strings.isNullOrEmpty(content)) {
CreoleDocumentBuilder.this.emitContent(content);
}
CreoleDocumentBuilder.this.emitContent("]]");
}
}
private class TableCellBlock extends ContentBlock {
public TableCellBlock(BlockType blockType) {
super(blockType, blockType == BlockType.TABLE_CELL_NORMAL ? "|" : "|=", "", 0, 0, true);
}
@Override
protected void emitContent(String content) throws IOException {
if (Strings.isNullOrEmpty(content) || content.trim().isEmpty()) {
content = " "; //$NON-NLS-1$
}
super.emitContent(content);
}
}
public CreoleDocumentBuilder(Writer out) {
super(out);
}
@Override
protected Block computeBlock(BlockType type, Attributes attributes) {
switch (type) {
case BULLETED_LIST:
case NUMERIC_LIST:
return new NewlineDelimitedBlock(type, doubleNewlineDelimiterCount(), 1);
case LIST_ITEM:
char prefixChar = computeCurrentListType() == BlockType.NUMERIC_LIST ? '#' : '*';
return new ContentBlock(type, computePrefix(prefixChar, computeListLevel()) + " ", "", 1, 1);
case PARAGRAPH:
return new ContentBlock(type, "", "", 2, 2); //$NON-NLS-1$ //$NON-NLS-2$
case PREFORMATTED:
case CODE:
return new ContentBlock(type, "{{{\n", "\n}}}", 2, 2); //$NON-NLS-1$ //$NON-NLS-2$
case TABLE:
return new SuffixBlock(type, "\n"); //$NON-NLS-1$
case TABLE_CELL_HEADER:
case TABLE_CELL_NORMAL:
return new TableCellBlock(type);
case TABLE_ROW:
return new SuffixBlock(type, "|\n"); //$NON-NLS-1$
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$
}
}
private int doubleNewlineDelimiterCount() {
if (currentBlock != null) {
BlockType currentBlockType = currentBlock.getBlockType();
if (currentBlockType == BlockType.LIST_ITEM || currentBlockType == BlockType.BULLETED_LIST
|| currentBlockType == BlockType.NUMERIC_LIST) {
return 1;
}
}
return 2;
}
@Override
protected Block computeSpan(SpanType type, Attributes attributes) {
switch (type) {
case LINK:
if (attributes instanceof LinkAttributes) {
return new LinkBlock((LinkAttributes) attributes);
}
return new ContentBlock("[[", "]]", 0, 0);
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 CODE:
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 != null ? text.replace("~", "˜") : null;
}
@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) {
if (url != null) {
assertOpenBlock();
try {
currentBlock.write("{{");
currentBlock.write(url);
writeImageAttributes(attributes);
currentBlock.write("}}");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
private void writeImageAttributes(Attributes attributes) throws IOException {
if (attributes instanceof ImageAttributes) {
if (!Strings.isNullOrEmpty(((ImageAttributes) attributes).getAlt())) {
currentBlock.write('|');
currentBlock.write(((ImageAttributes) attributes).getAlt());
} else if (!Strings.isNullOrEmpty(((ImageAttributes) attributes).getTitle())) {
currentBlock.write('|');
currentBlock.write(((ImageAttributes) attributes).getTitle());
}
}
}
@Override
public void link(Attributes attributes, String hrefOrHashName, String text) {
assertOpenBlock();
LinkAttributes linkAttributes = new LinkAttributes();
linkAttributes.setHref(hrefOrHashName);
beginSpan(SpanType.LINK, linkAttributes);
characters(text);
endSpan();
}
@Override
public void imageLink(Attributes linkAttributes, Attributes imageAttributes, String href, String imageUrl) {
String altText = "";
if (imageAttributes instanceof ImageAttributes
&& !Strings.isNullOrEmpty(((ImageAttributes) imageAttributes).getAlt())) {
altText = ((ImageAttributes) imageAttributes).getAlt();
}
link(linkAttributes, href, "{{" + Strings.nullToEmpty(imageUrl) + "}}" + altText);
}
@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();
}
}