blob: 7a2dfc26f8e67eb7040959c979ee12c2cf45d04d [file] [log] [blame]
/*
* Copyright 2001-2005 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.servicemix.docs.confluence;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.mylyn.internal.wikitext.confluence.core.block.AbstractConfluenceDelimitedBlock;
import org.eclipse.mylyn.internal.wikitext.confluence.core.block.ColorBlock;
import org.eclipse.mylyn.internal.wikitext.confluence.core.block.ExtendedPreformattedBlock;
import org.eclipse.mylyn.internal.wikitext.confluence.core.block.ExtendedQuoteBlock;
import org.eclipse.mylyn.internal.wikitext.confluence.core.block.HeadingBlock;
import org.eclipse.mylyn.internal.wikitext.confluence.core.block.ListBlock;
import org.eclipse.mylyn.internal.wikitext.confluence.core.block.QuoteBlock;
import org.eclipse.mylyn.internal.wikitext.confluence.core.block.TableOfContentsBlock;
import org.eclipse.mylyn.internal.wikitext.confluence.core.block.TextBoxBlock;
import org.eclipse.mylyn.internal.wikitext.confluence.core.phrase.ConfluenceWrappedPhraseModifier;
import org.eclipse.mylyn.internal.wikitext.confluence.core.phrase.EmphasisPhraseModifier;
import org.eclipse.mylyn.internal.wikitext.confluence.core.phrase.ImagePhraseModifier;
import org.eclipse.mylyn.internal.wikitext.confluence.core.phrase.SimplePhraseModifier;
import org.eclipse.mylyn.internal.wikitext.confluence.core.phrase.SimpleWrappedPhraseModifier;
import org.eclipse.mylyn.internal.wikitext.confluence.core.token.EscapedCharacterReplacementToken;
import org.eclipse.mylyn.wikitext.core.parser.Attributes;
import org.eclipse.mylyn.wikitext.core.parser.DocumentBuilder;
import org.eclipse.mylyn.wikitext.core.parser.DocumentBuilder.BlockType;
import org.eclipse.mylyn.wikitext.core.parser.DocumentBuilder.SpanType;
import org.eclipse.mylyn.wikitext.core.parser.LinkAttributes;
import org.eclipse.mylyn.wikitext.core.parser.markup.Block;
import org.eclipse.mylyn.wikitext.core.parser.markup.ContentState;
import org.eclipse.mylyn.wikitext.core.parser.markup.PatternBasedElement;
import org.eclipse.mylyn.wikitext.core.parser.markup.PatternBasedElementProcessor;
import org.eclipse.mylyn.wikitext.core.parser.markup.token.EntityReferenceReplacementToken;
import org.eclipse.mylyn.wikitext.core.parser.markup.token.ImpliedHyperlinkReplacementToken;
import org.eclipse.mylyn.wikitext.core.parser.markup.token.PatternEntityReferenceReplacementToken;
import org.eclipse.mylyn.wikitext.core.parser.markup.token.PatternLineBreakReplacementToken;
import org.eclipse.mylyn.wikitext.core.parser.markup.token.PatternLiteralReplacementToken;
public class ConfluenceLanguage extends org.eclipse.mylyn.wikitext.confluence.core.ConfluenceLanguage {
private final List<Block> nestedBlocks = new ArrayList<Block>();
private final ContentState contentState = new ContentState();
@Override
protected void clearLanguageSyntax() {
super.clearLanguageSyntax();
nestedBlocks.clear();
}
public List<Block> getNestedBlocks() {
return nestedBlocks;
}
@Override
protected void addStandardBlocks(List<Block> blocks, List<Block> paragraphBreakingBlocks) {
// IMPORTANT NOTE: Most items below have order dependencies. DO NOT REORDER ITEMS BELOW!!
HeadingBlock headingBlock = new HeadingBlock();
blocks.add(headingBlock);
paragraphBreakingBlocks.add(headingBlock);
nestedBlocks.add(headingBlock);
ConfluenceGlossaryBlock glossaryBlock = new ConfluenceGlossaryBlock();
blocks.add(glossaryBlock);
paragraphBreakingBlocks.add(glossaryBlock);
nestedBlocks.add(glossaryBlock);
ListBlock listBlock = new ListBlock();
blocks.add(listBlock);
paragraphBreakingBlocks.add(listBlock);
nestedBlocks.add(listBlock);
blocks.add(new QuoteBlock());
TableBlock tableBlock = new TableBlock();
blocks.add(tableBlock);
paragraphBreakingBlocks.add(tableBlock);
nestedBlocks.add(tableBlock);
ExtendedQuoteBlock quoteBlock = new ExtendedQuoteBlock();
blocks.add(quoteBlock);
paragraphBreakingBlocks.add(quoteBlock);
ExtendedPreformattedBlock noformatBlock = new ExtendedPreformattedBlock();
blocks.add(noformatBlock);
paragraphBreakingBlocks.add(noformatBlock);
blocks.add(new TextBoxBlock(BlockType.PANEL, "panel")); //$NON-NLS-1$
blocks.add(new TextBoxBlock(BlockType.NOTE, "note")); //$NON-NLS-1$
blocks.add(new TextBoxBlock(BlockType.INFORMATION, "info")); //$NON-NLS-1$
blocks.add(new TextBoxBlock(BlockType.WARNING, "warning")); //$NON-NLS-1$
blocks.add(new TextBoxBlock(BlockType.TIP, "tip")); //$NON-NLS-1$
CodeBlock codeBlock = new CodeBlock();
blocks.add(codeBlock);
paragraphBreakingBlocks.add(codeBlock);
blocks.add(new TableOfContentsBlock());
ColorBlock colorBlock = new ColorBlock();
blocks.add(colorBlock);
paragraphBreakingBlocks.add(colorBlock);
}
@Override
protected void addStandardPhraseModifiers(PatternBasedSyntax phraseModifierSyntax) {
phraseModifierSyntax.beginGroup("(?:(?<=[\\s\\.,\\\"'?!;:\\)\\(\\[\\]])|^)(?:", 0); //$NON-NLS-1$
phraseModifierSyntax.add(new HyperlinkPhraseModifier());
phraseModifierSyntax.add(new SimplePhraseModifier("*", SpanType.STRONG, true)); //$NON-NLS-1$
phraseModifierSyntax.add(new EmphasisPhraseModifier());
phraseModifierSyntax.add(new SimplePhraseModifier("??", SpanType.CITATION, true)); //$NON-NLS-1$
phraseModifierSyntax.add(new SimplePhraseModifier("-", SpanType.DELETED, true)); //$NON-NLS-1$
phraseModifierSyntax.add(new SimplePhraseModifier("+", SpanType.UNDERLINED, true)); //$NON-NLS-1$
phraseModifierSyntax.add(new SimplePhraseModifier("^", SpanType.SUPERSCRIPT, false)); //$NON-NLS-1$
phraseModifierSyntax.add(new SimplePhraseModifier("~", SpanType.SUBSCRIPT, false)); //$NON-NLS-1$
phraseModifierSyntax.add(new SimpleWrappedPhraseModifier("{{", "}}", DocumentBuilder.SpanType.MONOSPACE, false)); //$NON-NLS-1$ //$NON-NLS-2$
phraseModifierSyntax.add(new ConfluenceWrappedPhraseModifier("{quote}", DocumentBuilder.SpanType.QUOTE, true)); //$NON-NLS-1$
phraseModifierSyntax.add(new ImagePhraseModifier());
phraseModifierSyntax.endGroup(")(?=\\W|$)", 0); //$NON-NLS-1$
}
@Override
protected void addStandardTokens(PatternBasedSyntax tokenSyntax) {
tokenSyntax.add(new PatternLineBreakReplacementToken("(\\\\\\\\)")); // line break //$NON-NLS-1$
tokenSyntax.add(new EscapedCharacterReplacementToken()); // ORDER DEPENDENCY must come after line break
tokenSyntax.add(new EntityReferenceReplacementToken("(tm)", "#8482")); //$NON-NLS-1$ //$NON-NLS-2$
tokenSyntax.add(new EntityReferenceReplacementToken("(TM)", "#8482")); //$NON-NLS-1$ //$NON-NLS-2$
tokenSyntax.add(new EntityReferenceReplacementToken("(c)", "#169")); //$NON-NLS-1$ //$NON-NLS-2$
tokenSyntax.add(new EntityReferenceReplacementToken("(C)", "#169")); //$NON-NLS-1$ //$NON-NLS-2$
tokenSyntax.add(new EntityReferenceReplacementToken("(r)", "#174")); //$NON-NLS-1$ //$NON-NLS-2$
tokenSyntax.add(new EntityReferenceReplacementToken("(R)", "#174")); //$NON-NLS-1$ //$NON-NLS-2$
tokenSyntax.add(new PatternEntityReferenceReplacementToken("(?:(?<=\\w\\s)(---)(?=\\s\\w))", "#8212")); // emdash //$NON-NLS-1$ //$NON-NLS-2$
tokenSyntax.add(new PatternEntityReferenceReplacementToken("(?:(?<=\\w\\s)(--)(?=\\s\\w))", "#8211")); // endash //$NON-NLS-1$ //$NON-NLS-2$
tokenSyntax.add(new PatternLiteralReplacementToken("(----)", "<hr/>")); // horizontal rule //$NON-NLS-1$ //$NON-NLS-2$
tokenSyntax.add(new ImpliedHyperlinkReplacementToken());
tokenSyntax.add(new AnchorReplacementToken());
}
@Override
protected ContentState createState() {
return contentState;
}
public static class CodeBlock extends AbstractConfluenceDelimitedBlock {
private String title;
private String language;
public CodeBlock() {
super("code"); //$NON-NLS-1$
}
@Override
protected void beginBlock() {
if (title != null) {
Attributes attributes = new Attributes();
attributes.setTitle(title);
builder.beginBlock(DocumentBuilder.BlockType.PANEL, attributes);
}
Attributes attributes = new Attributes();
Attributes preAttributes = new Attributes();
if (language != null) {
attributes.setLanguage(language); //$NON-NLS-1$
}
// builder.beginBlock(DocumentBuilder.BlockType.PREFORMATTED, preAttributes);
builder.beginBlock(DocumentBuilder.BlockType.CODE, attributes);
builder.charactersUnescaped("<![CDATA[");
}
@Override
protected void handleBlockContent(String content) {
builder.charactersUnescaped(content);
builder.charactersUnescaped("\n"); //$NON-NLS-1$
}
@Override
protected void endBlock() {
builder.charactersUnescaped("]]>");
if (title != null) {
builder.endBlock(); // panel
}
builder.endBlock(); // code
// builder.endBlock(); // pre
}
@Override
protected void resetState() {
super.resetState();
title = null;
}
@Override
protected void setOption(String key, String value) {
if (key.equals("title")) { //$NON-NLS-1$
title = value;
} else if (key.equals("lang")) {
language = value;
}
}
@Override
protected void setOption(String option) {
language = option.toLowerCase();
}
}
public static class ConfluenceGlossaryBlock extends Block {
static final Pattern startPattern = Pattern.compile("\\s*-\\s*+(.*?)\\s*+::\\s*+(.*?)\\s*+"); //$NON-NLS-1$
private Matcher matcher;
private int blockLineCount = 0;
@Override
public int processLineContent(String line, int offset) {
if (blockLineCount == 0) {
builder.beginBlock(BlockType.DEFINITION_LIST, new Attributes());
} else {
matcher = startPattern.matcher(line);
if (!matcher.matches()) {
setClosed(true);
return 0;
}
}
++blockLineCount;
String key = matcher.group(1);
String val = matcher.group(2);
builder.beginBlock(BlockType.DEFINITION_TERM, new Attributes());
markupLanguage.emitMarkupLine(getParser(), state, key, 0);
builder.endBlock();
builder.beginBlock(BlockType.DEFINITION_ITEM, new Attributes());
markupLanguage.emitMarkupLine(getParser(), state, val, 0);
builder.endBlock();
return -1;
}
@Override
public boolean canStart(String line, int lineOffset) {
if (lineOffset == 0 && !markupLanguage.isFilterGenerativeContents()) {
matcher = startPattern.matcher(line);
return matcher.matches();
} else {
matcher = null;
return false;
}
}
@Override
public void setClosed(boolean closed) {
if (closed && !isClosed()) {
builder.endBlock();
}
super.setClosed(closed);
}
}
public static class TableBlock extends Block {
static final Pattern startPattern = Pattern.compile("(\\|(.*)?(\\|\\s*$))"); //$NON-NLS-1$
static final Pattern TABLE_ROW_PATTERN = Pattern.compile("\\|(\\|)?" + "((?:(?:[^\\|\\[]*)(?:\\[[^\\]]*\\])?)*)" //$NON-NLS-1$ //$NON-NLS-2$
+ "(\\|\\|?\\s*$)?"); //$NON-NLS-1$
private int blockLineCount = 0;
private Matcher matcher;
public TableBlock() {
}
@Override
public int processLineContent(String line, int offset) {
if (blockLineCount == 0) {
Attributes attributes = new Attributes();
builder.beginBlock(BlockType.TABLE, attributes);
} else if (markupLanguage.isEmptyLine(line)) {
setClosed(true);
return 0;
}
++blockLineCount;
if (offset == line.length()) {
return -1;
}
String textileLine = offset == 0 ? line : line.substring(offset);
Matcher rowMatcher = TABLE_ROW_PATTERN.matcher(textileLine);
if (!rowMatcher.find()) {
setClosed(true);
return 0;
}
builder.beginBlock(BlockType.TABLE_ROW, new Attributes());
do {
int start = rowMatcher.start();
if (start == textileLine.length() - 1) {
break;
}
String headerIndicator = rowMatcher.group(1);
String text = rowMatcher.group(2).trim(); // MODIFIED: added trim()
int lineOffset = offset + rowMatcher.start(2);
boolean header = headerIndicator != null && "|".equals(headerIndicator); //$NON-NLS-1$
Attributes attributes = new Attributes();
builder.beginBlock(header ? BlockType.TABLE_CELL_HEADER : BlockType.TABLE_CELL_NORMAL, attributes);
markupLanguage.emitMarkupLine(getParser(), state, lineOffset, text, 0);
builder.endBlock(); // table cell
} while (rowMatcher.find());
builder.endBlock(); // table row
return -1;
}
@Override
public boolean canStart(String line, int lineOffset) {
blockLineCount = 0;
if (lineOffset == 0) {
matcher = startPattern.matcher(line);
return matcher.matches();
} else {
matcher = null;
return false;
}
}
@Override
public void setClosed(boolean closed) {
if (closed && !isClosed()) {
builder.endBlock();
}
super.setClosed(closed);
}
}
public static class HyperlinkPhraseModifier extends PatternBasedElement {
@Override
protected String getPattern(int groupOffset) {
return "\\[(?:\\s*([^\\]\\|]+)\\|)?([^\\]]+)\\]"; //$NON-NLS-1$
}
@Override
protected int getPatternGroupCount() {
return 2;
}
@Override
protected PatternBasedElementProcessor newProcessor() {
return new HyperlinkPhraseModifierProcessor();
}
private static class HyperlinkPhraseModifierProcessor extends PatternBasedElementProcessor {
@Override
public void emit() {
String text = group(1);
String linkComposite = group(2);
String[] parts = linkComposite.split("\\s*\\|\\s*"); //$NON-NLS-1$
if (parts.length == 0) {
// can happen if linkComposite is ' |', see bug 290434
} else {
if (text != null) {
text = text.trim();
}
String href = parts[0];
if (href != null) {
href = href.trim();
}
if (href.charAt(0) == '#') {
}
String tip = parts.length > 1 ? parts[1] : null;
if (tip != null) {
tip = tip.trim();
}
if (text == null || text.length() == 0) {
text = href;
if (text.length() > 0 && text.charAt(0) == '#') {
text = text.substring(1);
}
if (href.charAt(0) == '#') {
href = "#" + state.getIdGenerator().getGenerationStrategy().generateId(href.substring(1));
}
Attributes attributes = new LinkAttributes();
attributes.setTitle(tip);
getBuilder().link(attributes, href, text);
} else {
if (href.charAt(0) == '#') {
href = "#" + state.getIdGenerator().getGenerationStrategy().generateId(href.substring(1));
}
LinkAttributes attributes = new LinkAttributes();
attributes.setTitle(tip);
attributes.setHref(href);
getBuilder().beginSpan(SpanType.LINK, attributes);
getMarkupLanguage().emitMarkupLine(parser, state, start(1), text, 0);
getBuilder().endSpan();
}
}
}
}
}
public static class AnchorReplacementToken extends PatternBasedElement {
@Override
protected String getPattern(int groupOffset) {
return "\\{anchor:([^\\}]+)\\}"; //$NON-NLS-1$
}
@Override
protected int getPatternGroupCount() {
return 1;
}
@Override
protected PatternBasedElementProcessor newProcessor() {
return new AnchorReplacementTokenProcessor();
}
private static class AnchorReplacementTokenProcessor extends PatternBasedElementProcessor {
@Override
public void emit() {
String name = group(1);
name = state.getIdGenerator().getGenerationStrategy().generateId(name);
Attributes attributes = new Attributes();
attributes.setId(name);
getBuilder().beginSpan(SpanType.SPAN, attributes);
getBuilder().endSpan();
}
}
}
}