/*
* Copyright 2015 Nokia Solutions and Networks
* Licensed under the Apache License, Version 2.0,
* see license.txt file for details.
*/
package org.robotframework.ide.eclipse.main.plugin.tableeditor.source;
import java.util.regex.Pattern;
import org.eclipse.jface.text.rules.ICharacterScanner;
import org.eclipse.jface.text.rules.IPredicateRule;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.Token;
import org.rf.ide.core.testdata.text.read.recognizer.ATokenRecognizer;
/**
* @author Michal Anglart
*/
class SectionPartitionRule implements IPredicateRule {
private final Section sectionType;
private final IToken token;
private final boolean isTsv;
SectionPartitionRule(final Section sectionType, final IToken token, final boolean isTsv) {
this.sectionType = sectionType;
this.token = token;
this.isTsv = isTsv;
}
@Override
public IToken evaluate(final ICharacterScanner scanner) {
return evaluate(scanner, false);
}
@Override
public IToken getSuccessToken() {
return token;
}
@Override
public IToken evaluate(final ICharacterScanner scanner, final boolean resume) {
if (resume) {
if (endDetected(scanner)) {
return token;
}
} else {
if (startDetected(scanner)) {
int next = 0;
while (!endDetected(scanner) && next != -1) {
next = scanner.read();
}
return token;
}
}
return Token.UNDEFINED;
}
private boolean startDetected(final ICharacterScanner scanner) {
if (scanner.getColumn() != 0) {
return false;
}
final int readAdditionally = readBeforeSection(scanner);
final String sectionHeader = getCell(scanner, isTsv);
if (sectionHeader.isEmpty()) {
return false;
}
return sectionIsOfExpectedType(scanner, sectionHeader, readAdditionally);
}
private boolean endDetected(final ICharacterScanner scanner) {
if (scanner.getColumn() != 0) {
return false;
}
final int readAdditionally = readBeforeSection(scanner);
final String sectionHeader = getCell(scanner, isTsv);
if (sectionHeader.isEmpty()) {
return false;
}
return !sectionIsOfExpectedType(scanner, sectionHeader, readAdditionally);
// because we want to have a single partition for sequence of same-type tables
}
private boolean sectionIsOfExpectedType(final ICharacterScanner scanner, final String sectionHeader,
final int readAdditionally) {
final boolean startedWithPipedSeparator = readAdditionally > 1;
if (sectionType.matches(sectionHeader, startedWithPipedSeparator)) {
return true;
} else {
for (int i = 0; i < sectionHeader.length() + readAdditionally; i++) {
scanner.unread();
}
return false;
}
}
private static int readBeforeSection(final ICharacterScanner scanner) {
int readAdditionally = 0;
if (lookAhead(scanner) == ' ') {
scanner.read();
readAdditionally++;
}
if (lookAhead(scanner, 2).equals("| ") || lookAhead(scanner, 2).equals("|\t")) {
readAdditionally += eatPipedLineStart(scanner);
}
// this is > 1 when pipe were read
return readAdditionally;
}
static int lookAhead(final ICharacterScanner scanner) {
final int ch = scanner.read();
scanner.unread();
return ch;
}
static String lookAhead(final ICharacterScanner scanner, final int n) {
boolean eofOccured = false;
final StringBuilder builder = new StringBuilder();
for (int i = 0; i < n; i++) {
final int ch = scanner.read();
if (ch == -1) {
eofOccured = true;
break;
} else {
builder.append((char) ch);
}
}
final int additional = eofOccured ? 1 : 0;
// maybe we've read less due to EOF
for (int i = 0; i < builder.length() + additional; i++) {
scanner.unread();
}
return builder.toString();
}
private static int eatPipedLineStart(final ICharacterScanner scanner) {
int readAdditionally = 0;
int ch = scanner.read();
if (ch == '|') {
readAdditionally++;
ch = scanner.read();
while (ch == ' ' || ch == '\t') {
readAdditionally++;
ch = scanner.read();
}
}
scanner.unread();
return readAdditionally;
}
private static String getCell(final ICharacterScanner scanner, final boolean isTsv) {
final int next = scanner.read();
if (next == '*') {
return isTsv ? getTsvCell(scanner) : getNormalCell(scanner);
} else {
scanner.unread();
return "";
}
}
private static String getNormalCell(final ICharacterScanner scanner) {
final StringBuilder header = new StringBuilder("*");
int next;
do {
next = scanner.read();
header.append((char) next);
} while (next != ICharacterScanner.EOF && next != '\r' && next != '\n' && next != '\t'
&& !isSeparator(scanner, next));
header.deleteCharAt(header.length() - 1);
scanner.unread();
return header.toString();
}
private static boolean isSeparator(final ICharacterScanner scanner, final int next) {
if (next == ' ') {
final String lookahead = lookAhead(scanner, 2);
return lookahead.startsWith(" ") || lookahead.startsWith("| ") || lookahead.startsWith("|\t")
|| lookahead.startsWith("|\n") || lookahead.startsWith("|\r");
}
return false;
}
private static String getTsvCell(final ICharacterScanner scanner) {
final StringBuilder header = new StringBuilder("*");
int next;
do {
next = scanner.read();
header.append((char) next);
} while (next != ICharacterScanner.EOF && next != '\r' && next != '\n' && next != '\t');
header.deleteCharAt(header.length() - 1);
scanner.unread();
return header.toString();
}
private static final String PIPED_SECTION_START = "^\\*(\\s*\\*)*\\s*";
private static final String PIPED_SECTION_END = "(\\s*\\*)*((( \\|).*)?|\\s*)$";
private static final String NORMAL_SECTION_START = "^\\*(\\s?\\*)*\\s*";
private static final String NORMAL_SECTION_END = "\\s?(\\*\\s?)*((( )|\\t).*)?$";
private static final Pattern SETTINGS_NORMAL = Pattern.compile(
NORMAL_SECTION_START + "(" + insensitiveWithSpace("Settings") + "|" + insensitiveWithSpace("Setting") + "|"
+ insensitiveWithSpace("Metadata") + ")" + NORMAL_SECTION_END);
private static final Pattern SETTINGS_PIPES = Pattern.compile(
PIPED_SECTION_START + "(" + insensitiveWithSpaces("Settings") + "|" + insensitiveWithSpaces("Setting") + "|"
+ insensitiveWithSpaces("Metadata") + ")" + PIPED_SECTION_END);
private static final Pattern VARIABLES_NORMAL = Pattern.compile(NORMAL_SECTION_START + "("
+ insensitiveWithSpace("Variables") + "|" + insensitiveWithSpace("Variable") + ")" + NORMAL_SECTION_END);
private static final Pattern VARIABLES_PIPES = Pattern.compile(PIPED_SECTION_START + "("
+ insensitiveWithSpace("Variables") + "|" + insensitiveWithSpace("Variable") + ")" + PIPED_SECTION_END);
private static final Pattern KEYWORDS_NORMAL = Pattern.compile(NORMAL_SECTION_START + "("
+ insensitiveWithSpace("User") + "[\\s]?)?" + "(" + insensitiveWithSpace("Keywords") + "|"
+ insensitiveWithSpace("Keyword") + ")" + NORMAL_SECTION_END);
private static final Pattern KEYWORDS_PIPES = Pattern.compile(PIPED_SECTION_START + "("
+ insensitiveWithSpaces("User") + "[\\s]*)?" + "(" + insensitiveWithSpaces("Keywords") + "|"
+ insensitiveWithSpaces("Keyword") + ")" + PIPED_SECTION_END);
private static final Pattern TEST_CASE_NORMAL = Pattern
.compile(NORMAL_SECTION_START + insensitiveWithSpace("Test") + "[\\s]?" + "("
+ insensitiveWithSpace("Cases") + "|" + insensitiveWithSpace("Case") + ")" + NORMAL_SECTION_END);
private static final Pattern TEST_CASE_PIPES = Pattern
.compile(PIPED_SECTION_START + insensitiveWithSpaces("Test") + "[\\s]*" + "("
+ insensitiveWithSpaces("Cases") + "|" + insensitiveWithSpaces("Case") + ")" + PIPED_SECTION_END);
private static String insensitiveWithSpaces(final String text) {
return ATokenRecognizer.createUpperLowerCaseWordWithSpacesInside(text);
}
private static String insensitiveWithSpace(final String text) {
return ATokenRecognizer.createUpperLowerCaseWordWithOptionalSpaceInside(text);
}
static enum Section {
TEST_CASES(TEST_CASE_NORMAL, TEST_CASE_PIPES),
KEYWORDS(KEYWORDS_NORMAL, KEYWORDS_PIPES),
SETTINGS(SETTINGS_NORMAL, SETTINGS_PIPES),
VARIABLES(VARIABLES_NORMAL, VARIABLES_PIPES);
private final Pattern normalPattern;
private final Pattern pipedStartPattern;
private Section(final Pattern normalPattern, final Pattern pipedStartPattern) {
this.normalPattern = normalPattern;
this.pipedStartPattern = pipedStartPattern;
}
boolean matches(final String header, final boolean startedWithPipedSeparator) {
return (startedWithPipedSeparator ? pipedStartPattern : normalPattern).matcher(header).find();
}
}
}