package org.jabref.logic.layout;
import java.io.IOException;
import java.io.PushbackReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
/**
* Helper class to get a Layout object.
*
* <code>
* LayoutHelper helper = new LayoutHelper(...a reader...);
* Layout layout = helper.getLayoutFromText();
* </code>
*
*/
public class LayoutHelper {
public static final int IS_LAYOUT_TEXT = 1;
public static final int IS_SIMPLE_FIELD = 2;
public static final int IS_FIELD_START = 3;
public static final int IS_FIELD_END = 4;
public static final int IS_OPTION_FIELD = 5;
public static final int IS_GROUP_START = 6;
public static final int IS_GROUP_END = 7;
public static final int IS_ENCODING_NAME = 8;
public static final int IS_FILENAME = 9;
public static final int IS_FILEPATH = 10;
private static String currentGroup;
private final PushbackReader in;
private final List<StringInt> parsedEntries = new ArrayList<>();
private final LayoutFormatterPreferences prefs;
private boolean endOfFile;
public LayoutHelper(Reader in, LayoutFormatterPreferences prefs) {
this.in = new PushbackReader(Objects.requireNonNull(in));
this.prefs = Objects.requireNonNull(prefs);
}
public Layout getLayoutFromText() throws IOException {
parse();
for (StringInt parsedEntry : parsedEntries) {
if ((parsedEntry.i == LayoutHelper.IS_SIMPLE_FIELD) || (parsedEntry.i == LayoutHelper.IS_FIELD_START)
|| (parsedEntry.i == LayoutHelper.IS_FIELD_END) || (parsedEntry.i == LayoutHelper.IS_GROUP_START)
|| (parsedEntry.i == LayoutHelper.IS_GROUP_END)) {
parsedEntry.s = parsedEntry.s.trim().toLowerCase(Locale.ROOT);
}
}
return new Layout(parsedEntries, prefs);
}
public static String getCurrentGroup() {
return LayoutHelper.currentGroup;
}
public static void setCurrentGroup(String newGroup) {
LayoutHelper.currentGroup = newGroup;
}
private void doBracketedField(final int field) throws IOException {
StringBuilder buffer = null;
int c;
boolean start = false;
while (!endOfFile) {
c = read();
if (c == -1) {
endOfFile = true;
if (buffer != null) {
parsedEntries.add(new StringInt(buffer.toString(), field));
}
return;
}
if ((c == '{') || (c == '}')) {
if (c == '}') {
if (buffer != null) {
parsedEntries.add(new StringInt(buffer.toString(), field));
return;
}
} else {
start = true;
}
} else {
if (buffer == null) {
buffer = new StringBuilder(100);
}
if (start && (c != '}')) {
buffer.append((char) c);
}
}
}
}
/**
*
*/
private void doBracketedOptionField() throws IOException {
StringBuilder buffer = null;
int c;
boolean start = false;
boolean inQuotes = false;
boolean doneWithOptions = false;
String option = null;
String tmp;
while (!endOfFile) {
c = read();
if (c == -1) {
endOfFile = true;
if (buffer != null) {
if (option == null) {
tmp = buffer.toString();
} else {
tmp = buffer.toString() + '\n' + option;
}
parsedEntries.add(new StringInt(tmp, LayoutHelper.IS_OPTION_FIELD));
}
return;
}
if (!inQuotes && ((c == ']') || (c == '[') || (doneWithOptions && ((c == '{') || (c == '}'))))) {
if ((c == ']') || (doneWithOptions && (c == '}'))) {
// changed section start - arudert
// buffer may be null for parameters
if ((c == ']') && (buffer != null)) {
// changed section end - arudert
option = buffer.toString();
buffer = null;
start = false;
doneWithOptions = true;
} else if (c == '}') {
// changed section begin - arudert
// bracketed option must be followed by an (optionally empty) parameter
// if empty, the parameter is set to " " (whitespace to avoid that the tokenizer that
// splits the string later on ignores the empty parameter)
String parameter = buffer == null ? " " : buffer.toString();
if (option == null) {
tmp = parameter;
} else {
tmp = parameter + '\n' + option;
}
parsedEntries.add(new StringInt(tmp, LayoutHelper.IS_OPTION_FIELD));
return;
}
// changed section end - arudert
// changed section start - arudert
// }
// changed section end - arudert
} else {
start = true;
}
} else if (c == '"') {
inQuotes = !inQuotes;
if (buffer == null) {
buffer = new StringBuilder(100);
}
buffer.append('"');
} else {
if (buffer == null) {
buffer = new StringBuilder(100);
}
if (start) {
// changed section begin - arudert
// keep the backslash so we know wether this is a fieldname or an ordinary parameter
//if (c != '\\')
//{
buffer.append((char) c);
//}
// changed section end - arudert
}
}
}
}
private void parse() throws IOException, StringIndexOutOfBoundsException {
skipWhitespace();
int c;
StringBuilder buffer = null;
boolean escaped = false;
while (!endOfFile) {
c = read();
if (c == -1) {
endOfFile = true;
/*
* CO 2006-11-11: Added check for null, otherwise a Layout that
* finishes with a curly brace throws a NPE
*/
if (buffer != null) {
parsedEntries.add(new StringInt(buffer.toString(), LayoutHelper.IS_LAYOUT_TEXT));
}
return;
}
if ((c == '\\') && (peek() != '\\') && !escaped) {
if (buffer != null) {
parsedEntries.add(new StringInt(buffer.toString(), LayoutHelper.IS_LAYOUT_TEXT));
buffer = null;
}
parseField();
// To make sure the next character, if it is a backslash,
// doesn't get ignored, since "previous" now holds a backslash:
escaped = false;
} else {
if (buffer == null) {
buffer = new StringBuilder(100);
}
if ((c != '\\') || escaped)// (previous == '\\')))
{
buffer.append((char) c);
}
escaped = (c == '\\') && !escaped;
}
}
}
private void parseField() throws IOException {
int c;
StringBuilder buffer = null;
String name;
while (!endOfFile) {
c = read();
if (c == -1) {
endOfFile = true;
}
if (!Character.isLetter((char) c) && (c != '_') && (c != '-')) {
unread(c);
name = buffer == null ? "" : buffer.toString();
if (name.isEmpty()) {
StringBuilder lastFive = new StringBuilder(10);
for (StringInt entry : parsedEntries.subList(Math.max(0, parsedEntries.size() - 6),
parsedEntries.size() - 1)) {
lastFive.append(entry.s);
}
throw new StringIndexOutOfBoundsException(
"Backslash parsing error near \'" + lastFive.toString().replace("\n", " ") + '\'');
}
if ("begin".equalsIgnoreCase(name)) {
// get field name
doBracketedField(LayoutHelper.IS_FIELD_START);
return;
} else if ("begingroup".equalsIgnoreCase(name)) {
// get field name
doBracketedField(LayoutHelper.IS_GROUP_START);
return;
} else if ("format".equalsIgnoreCase(name)) {
if (c == '[') {
// get format parameter
// get field name
doBracketedOptionField();
return;
} else {
// get field name
doBracketedField(LayoutHelper.IS_OPTION_FIELD);
return;
}
} else if ("filename".equalsIgnoreCase(name)) {
// Print the name of the database BIB file.
// This is only supported in begin/end layouts, not in
// entry layouts.
parsedEntries.add(new StringInt(name, LayoutHelper.IS_FILENAME));
return;
} else if ("filepath".equalsIgnoreCase(name)) {
// Print the full path of the database BIB file.
// This is only supported in begin/end layouts, not in
// entry layouts.
parsedEntries.add(new StringInt(name, LayoutHelper.IS_FILEPATH));
return;
} else if ("end".equalsIgnoreCase(name)) {
// get field name
doBracketedField(LayoutHelper.IS_FIELD_END);
return;
} else if ("endgroup".equalsIgnoreCase(name)) {
// get field name
doBracketedField(LayoutHelper.IS_GROUP_END);
return;
} else if ("encoding".equalsIgnoreCase(name)) {
// Print the name of the current encoding used for export.
// This is only supported in begin/end layouts, not in
// entry layouts.
parsedEntries.add(new StringInt(name, LayoutHelper.IS_ENCODING_NAME));
return;
}
// for all other cases
parsedEntries.add(new StringInt(name, LayoutHelper.IS_SIMPLE_FIELD));
return;
} else {
if (buffer == null) {
buffer = new StringBuilder(100);
}
buffer.append((char) c);
}
}
}
private int peek() throws IOException {
int c = read();
unread(c);
return c;
}
private int read() throws IOException {
return in.read();
}
private void skipWhitespace() throws IOException {
int c;
while (true) {
c = read();
if ((c == -1) || (c == 65535)) {
endOfFile = true;
return;
}
if (!Character.isWhitespace((char) c)) {
unread(c);
break;
}
}
}
private void unread(int c) throws IOException {
in.unread(c);
}
}