blob: 9b8e50a8a39796f31ffe138aefa8a8e2d90f4be5 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2011 David Green and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* David Green - initial API and implementation
*******************************************************************************/
package org.eclipse.mylyn.internal.wikitext.twiki.core.block;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.mylyn.wikitext.core.parser.Attributes;
import org.eclipse.mylyn.wikitext.core.parser.DocumentBuilder.BlockType;
import org.eclipse.mylyn.wikitext.core.parser.markup.Block;
/**
* List block, matches blocks that start with <code> *</code>, <code> 1</code>, <code> a</code>, <code> A</code>
* <code> i</code> or <code> I</code>. Note that preceding spaces must be in multiples of 3.
*
* @author David Green
*/
public class ListBlock extends Block {
static final Pattern startPattern = Pattern.compile("((?: {3})+)(\\*|(?:1|a|A|i|I)\\.)\\s(.*+)"); //$NON-NLS-1$
static final Pattern continuationPattern = Pattern.compile(" {3}\\s*(.*+)"); //$NON-NLS-1$
private int blockLineCount = 0;
private Matcher matcher;
private Stack<ListState> listState;
public ListBlock() {
}
@Override
public int processLineContent(String line, int offset) {
boolean continuation = false;
if (blockLineCount == 0) {
listState = new Stack<>();
Attributes attributes = new Attributes();
String listSpec = matcher.group(1);
int level = calculateLevel(listSpec);
String typeSpec = matcher.group(2);
BlockType type = calculateType(typeSpec);
computeAttributes(attributes, type, typeSpec);
// 0-offset matches may start with the "*** " prefix.
offset = matcher.start(3);
listState.push(new ListState(1, type));
builder.beginBlock(type, attributes);
adjustLevel(type, listSpec, level);
} else {
Matcher matcher = startPattern.matcher(line);
if (!matcher.matches()) {
// FIXME: continuations not yet implemented
matcher = continuationPattern.matcher(line);
if (listState.isEmpty() || !matcher.matches()) {
setClosed(true);
return 0;
} else {
continuation = true;
offset = matcher.start(1) - 1; // use -1 to get one whitespace character
}
} else {
String listSpec = matcher.group(1);
int level = calculateLevel(listSpec);
String typeSpec = matcher.group(2);
BlockType type = calculateType(typeSpec);
offset = matcher.start(3);
adjustLevel(type, typeSpec, level);
}
}
++blockLineCount;
ListState listState = this.listState.peek();
if (!continuation && listState.openItem) {
builder.endBlock();
listState.openItem = false;
}
if (!listState.openItem) {
listState.openItem = true;
builder.beginBlock(BlockType.LIST_ITEM, new Attributes());
}
markupLanguage.emitMarkupLine(getParser(), state, line, offset);
return -1;
}
private void computeAttributes(Attributes attributes, BlockType type, String typeSpec) {
if (type == BlockType.NUMERIC_LIST) {
switch (typeSpec.charAt(0)) {
case 'a':
attributes.setCssStyle("list-style: lower-alpha;"); //$NON-NLS-1$
break;
case 'A':
attributes.setCssStyle("list-style: upper-alpha;"); //$NON-NLS-1$
break;
case 'i':
attributes.setCssStyle("list-style: lower-roman;"); //$NON-NLS-1$
break;
case 'I':
attributes.setCssStyle("list-style: upper-roman;"); //$NON-NLS-1$
break;
}
}
}
private void adjustLevel(BlockType type, String typeSpec, int level) {
for (ListState previousState = listState.peek(); level != previousState.level || previousState.type != type; previousState = listState.peek()) {
if (level > previousState.level) {
if (!previousState.openItem) {
builder.beginBlock(BlockType.LIST_ITEM, new Attributes());
previousState.openItem = true;
}
Attributes blockAttributes = new Attributes();
computeAttributes(blockAttributes, type, typeSpec);
listState.push(new ListState(previousState.level + 1, type));
builder.beginBlock(type, blockAttributes);
} else {
closeOne();
if (listState.isEmpty()) {
Attributes blockAttributes = new Attributes();
computeAttributes(blockAttributes, type, typeSpec);
listState.push(new ListState(1, type));
builder.beginBlock(type, blockAttributes);
}
}
}
}
private int calculateLevel(String listSpec) {
return listSpec.length() / 3;
}
private BlockType calculateType(String listSpec) {
return listSpec.charAt(listSpec.length() - 1) == '*' ? BlockType.BULLETED_LIST : BlockType.NUMERIC_LIST;
}
@Override
public boolean canStart(String line, int lineOffset) {
blockLineCount = 0;
listState = null;
if (lineOffset == 0) {
matcher = startPattern.matcher(line);
return matcher.matches();
} else {
matcher = null;
return false;
}
}
@Override
public void setClosed(boolean closed) {
if (closed && !isClosed()) {
while (listState != null && !listState.isEmpty()) {
closeOne();
}
listState = null;
}
super.setClosed(closed);
}
private void closeOne() {
ListState e = listState.pop();
if (e.openItem) {
builder.endBlock();
}
builder.endBlock();
}
private static class ListState {
int level;
BlockType type;
boolean openItem;
private ListState(int level, BlockType type) {
super();
this.level = level;
this.type = type;
}
}
}