blob: 274d6598be23f2b0810d565ffca63aed08815325 [file] [log] [blame]
package com.atlassian.uwc.converters.moinmoin;
import java.util.Enumeration;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.log4j.Logger;
import com.atlassian.uwc.converters.BaseConverter;
import com.atlassian.uwc.ui.Page;
public class MoinListConverter extends BaseConverter {
private Logger log = Logger.getLogger(this.getClass());
protected static final String LSEP = System.getProperty("line.separator", "\n");
public void convert(Page page) {
log.debug("Converting list in Page: " + page.getName());
String cont = page.getOriginalText();
page.setConvertedText(this.convertList(cont));
}
private class Indent {
int their;
int mine;
String symbol;
// Indent(int their, int mine){
// this.their = their;
// this.mine = mine;
// }
Indent(int their, int mine, String symbol){
this.their = their;
this.mine = mine;
this.symbol = symbol;
}
Indent(){
}
Indent getNew(int theirnew, String symbol){
return new Indent(theirnew, mine + 1, symbol);
}
boolean hasNew(int theirnew){
return theirnew > this.their;
}
boolean isSame(int theirnew){
return theirnew == their;
}
}
private final Indent first = new Indent(-1,0," ");
// void appendX(StringBuilder sb, indent){
// for (int i = 0; i < count; i++){
// sb.append(s);
// }
// }
//
void appendX(StringBuilder sb, Stack<Indent> s){
for (Enumeration<Indent> e = s.elements(); e.hasMoreElements(); ){
sb.append( e.nextElement().symbol );
}
}
static Pattern startp = Pattern.compile("(\\s*)(([\\*\\.])|([AaIi1]\\.)) +.*", Pattern.MULTILINE);
static Pattern indented = Pattern.compile("(\\s+).*");
static Pattern startBlock = Pattern.compile("\\{{3}");
static Pattern endBlock = Pattern.compile("\\}{3}");
String convertList(String input){
StringBuilder output = new StringBuilder();
final Stack<Indent> intend = new Stack<Indent>();
intend.push(first);
// Flag to keep track of an end-of-line that still needs to be added. We don't immediately add eol's after
// each line. We postpone them to make it possible to join lines that are really a continuation.
// See below, at the comment "this line is a continuation of the previous indented line"
boolean doEol = false;
boolean inBlock = false;
for( String line : input.split("\\r?\\n") ){
// Track whether or not we're inside a {{{ }}} block (i.e. noformat -- in that case, we don't change anything)
if (startBlock.matcher(line).find()) {
if (doEol) {
output.append(LSEP);
doEol = false;
}
output.append(line);
output.append(LSEP);
inBlock = true;
continue;
}
if (endBlock.matcher(line).find()) {
if (doEol) {
output.append(LSEP);
doEol = false;
}
output.append(line);
output.append(LSEP);
inBlock = false;
continue;
}
if (inBlock) {
// Inside a {{{ }}} block (= noformat), so simply output the line without any processing
output.append(line);
output.append(LSEP);
} else {
Matcher mat = startp.matcher(line);
Matcher indentMat = indented.matcher(line);
if( mat.matches() ) {
//log.debug("LINE 1 " + line);
int intendation = mat.group(1) != null ? mat.group(1).length() : 0;
// find the different type
final int symbolLength;
final String symbol;
if (mat.group(4) != null) {
symbol = "#";
symbolLength = 2;
} else if (mat.group(3) != null) {
symbol = "*";
symbolLength = 1;
} else { // somehow the line matches but the enum items are not there
if (doEol) {
output.append(LSEP);
doEol = false;
}
output.append(line);
doEol = true;
// reset the intendation
intend.clear();
intend.push(first);
continue;
}
Indent i = intend.peek();
if (i.hasNew(intendation)) {
i = i.getNew(intendation, symbol);
intend.push(i);
} else if (i.isSame(intendation)) {
} else {
// we have lower indentation -> pop until we're at the same level (or lower)
intend.pop();
i = intend.peek();
while (!(i.isSame(intendation) || i.hasNew(intendation)) && i != first) {
intend.pop();
i = intend.peek();
}
if (i.equals(first)) {
// Moin Moin has a silly behavior of something like this
// * first (level one)
// * second level one (this has to be level one but only if they dont have a previous thing
output.append(LSEP);
i = i.getNew(1, symbol);
intend.push(i);
}
}
i.symbol = symbol; //actual current symbol
// output.append(" ");
if (doEol) {
output.append(LSEP);
doEol = false;
}
appendX(output, intend);
output.append(line.substring(intendation + symbolLength));
//output.append(LSEP);
doEol = true;
} else if ("".equals(line) && !first.equals(intend.peek())) {
// empty line, but we're still inside indentation -> \\ (otherwise the list gets broken in Confluence)
if (doEol) {
output.append(LSEP);
doEol = false;
}
output.append("\\\\");
output.append(LSEP);
} else if (indentMat.matches()) {
int indentation = indentMat.group(1).length();
if (intend.peek().isSame(indentation)) {
// this line is a continuation of the previous indented line, so append with space separator.
output.append(" " + line.substring(indentation));
doEol = true;
} else {
if (doEol) {
output.append(LSEP);
doEol = false;
}
output.append(line);
doEol = true;
}
} else {
if (doEol) {
output.append(LSEP);
doEol = false;
}
//log.debug("LINE 0 " + line);
// If we're exiting indentation, insert linebreak, otherwise content immediately following the list
// in MoinMoin might be indented and appear part of the list in Confluence
if (!first.equals(intend.peek())) {
output.append(LSEP);
}
output.append(line);
output.append(LSEP);
// reset the intendation
intend.clear();
intend.push(first);
}
}
}
return output.toString();
}
}