package org.jabref.logic.importer.util;
import java.util.List;
import org.jabref.logic.groups.DefaultGroupsFactory;
import org.jabref.logic.importer.ParseException;
import org.jabref.logic.l10n.Localization;
import org.jabref.logic.util.MetadataSerializationConfiguration;
import org.jabref.logic.util.strings.QuotedStringTokenizer;
import org.jabref.model.groups.AbstractGroup;
import org.jabref.model.groups.AutomaticKeywordGroup;
import org.jabref.model.groups.AutomaticPersonsGroup;
import org.jabref.model.groups.ExplicitGroup;
import org.jabref.model.groups.GroupHierarchyType;
import org.jabref.model.groups.GroupTreeNode;
import org.jabref.model.groups.KeywordGroup;
import org.jabref.model.groups.RegexKeywordGroup;
import org.jabref.model.groups.SearchGroup;
import org.jabref.model.groups.WordKeywordGroup;
import org.jabref.model.strings.StringUtil;
/**
* Converts string representation of groups to a parsed {@link GroupTreeNode}.
*/
public class GroupsParser {
private GroupsParser() {
}
public static GroupTreeNode importGroups(List<String> orderedData, Character keywordSeparator)
throws ParseException {
try {
GroupTreeNode cursor = null;
GroupTreeNode root = null;
for (String string : orderedData) {
// This allows to read databases that have been modified by, e.g., BibDesk
string = string.trim();
if (string.isEmpty()) {
continue;
}
int spaceIndex = string.indexOf(' ');
if (spaceIndex <= 0) {
throw new ParseException("Expected \"" + string + "\" to contain whitespace");
}
int level = Integer.parseInt(string.substring(0, spaceIndex));
AbstractGroup group = GroupsParser.fromString(string.substring(spaceIndex + 1), keywordSeparator);
GroupTreeNode newNode = GroupTreeNode.fromGroup(group);
if (cursor == null) {
// create new root
cursor = newNode;
root = cursor;
} else {
// insert at desired location
while ((level <= cursor.getLevel()) && (cursor.getParent().isPresent())) {
cursor = cursor.getParent().get();
}
cursor.addChild(newNode);
cursor = newNode;
}
}
return root;
} catch (ParseException e) {
throw new ParseException(Localization
.lang("Group tree could not be parsed. If you save the BibTeX library, all groups will be lost."),
e);
}
}
/**
* Re-create a group instance from a textual representation.
*
* @param s The result from the group's toString() method.
* @return New instance of the encoded group.
* @throws ParseException If an error occurred and a group could not be created,
* e.g. due to a malformed regular expression.
*/
public static AbstractGroup fromString(String s, Character keywordSeparator)
throws ParseException {
if (s.startsWith(MetadataSerializationConfiguration.KEYWORD_GROUP_ID)) {
return GroupsParser.keywordGroupFromString(s, keywordSeparator);
}
if (s.startsWith(MetadataSerializationConfiguration.ALL_ENTRIES_GROUP_ID)) {
return GroupsParser.allEntriesGroupFromString(s);
}
if (s.startsWith(MetadataSerializationConfiguration.SEARCH_GROUP_ID)) {
return GroupsParser.searchGroupFromString(s);
}
if (s.startsWith(MetadataSerializationConfiguration.EXPLICIT_GROUP_ID)) {
return GroupsParser.explicitGroupFromString(s, keywordSeparator);
}
if (s.startsWith(MetadataSerializationConfiguration.LEGACY_EXPLICIT_GROUP_ID)) {
return GroupsParser.legacyExplicitGroupFromString(s, keywordSeparator);
}
if (s.startsWith(MetadataSerializationConfiguration.AUTOMATIC_PERSONS_GROUP_ID)) {
return GroupsParser.automaticPersonsGroupFromString(s);
}
if (s.startsWith(MetadataSerializationConfiguration.AUTOMATIC_KEYWORD_GROUP_ID)) {
return GroupsParser.automaticKeywordGroupFromString(s);
}
throw new ParseException("Unknown group: " + s);
}
private static AbstractGroup automaticPersonsGroupFromString(String string) {
if (!string.startsWith(MetadataSerializationConfiguration.AUTOMATIC_PERSONS_GROUP_ID)) {
throw new IllegalArgumentException("KeywordGroup cannot be created from \"" + string + "\".");
}
QuotedStringTokenizer tok = new QuotedStringTokenizer(string.substring(MetadataSerializationConfiguration.AUTOMATIC_PERSONS_GROUP_ID
.length()), MetadataSerializationConfiguration.GROUP_UNIT_SEPARATOR, MetadataSerializationConfiguration.GROUP_QUOTE_CHAR);
String name = StringUtil.unquote(tok.nextToken(), MetadataSerializationConfiguration.GROUP_QUOTE_CHAR);
GroupHierarchyType context = GroupHierarchyType.getByNumberOrDefault(Integer.parseInt(tok.nextToken()));
String field = StringUtil.unquote(tok.nextToken(), MetadataSerializationConfiguration.GROUP_QUOTE_CHAR);
AutomaticPersonsGroup newGroup = new AutomaticPersonsGroup(name, context, field);
addGroupDetails(tok, newGroup);
return newGroup;
}
private static AbstractGroup automaticKeywordGroupFromString(String string) {
if (!string.startsWith(MetadataSerializationConfiguration.AUTOMATIC_KEYWORD_GROUP_ID)) {
throw new IllegalArgumentException("KeywordGroup cannot be created from \"" + string + "\".");
}
QuotedStringTokenizer tok = new QuotedStringTokenizer(string.substring(MetadataSerializationConfiguration.AUTOMATIC_KEYWORD_GROUP_ID
.length()), MetadataSerializationConfiguration.GROUP_UNIT_SEPARATOR, MetadataSerializationConfiguration.GROUP_QUOTE_CHAR);
String name = StringUtil.unquote(tok.nextToken(), MetadataSerializationConfiguration.GROUP_QUOTE_CHAR);
GroupHierarchyType context = GroupHierarchyType.getByNumberOrDefault(Integer.parseInt(tok.nextToken()));
String field = StringUtil.unquote(tok.nextToken(), MetadataSerializationConfiguration.GROUP_QUOTE_CHAR);
Character delimiter = tok.nextToken().charAt(0);
Character hierarchicalDelimiter = tok.nextToken().charAt(0);
AutomaticKeywordGroup newGroup = new AutomaticKeywordGroup(name, context, field, delimiter, hierarchicalDelimiter);
addGroupDetails(tok, newGroup);
return newGroup;
}
/**
* Parses s and recreates the KeywordGroup from it.
*
* @param s The String representation obtained from
* KeywordGroup.toString()
*/
private static KeywordGroup keywordGroupFromString(String s, Character keywordSeparator) throws ParseException {
if (!s.startsWith(MetadataSerializationConfiguration.KEYWORD_GROUP_ID)) {
throw new IllegalArgumentException("KeywordGroup cannot be created from \"" + s + "\".");
}
QuotedStringTokenizer tok = new QuotedStringTokenizer(s.substring(MetadataSerializationConfiguration.KEYWORD_GROUP_ID
.length()), MetadataSerializationConfiguration.GROUP_UNIT_SEPARATOR, MetadataSerializationConfiguration.GROUP_QUOTE_CHAR);
String name = StringUtil.unquote(tok.nextToken(), MetadataSerializationConfiguration.GROUP_QUOTE_CHAR);
GroupHierarchyType context = GroupHierarchyType.getByNumberOrDefault(Integer.parseInt(tok.nextToken()));
String field = StringUtil.unquote(tok.nextToken(), MetadataSerializationConfiguration.GROUP_QUOTE_CHAR);
String expression = StringUtil.unquote(tok.nextToken(), MetadataSerializationConfiguration.GROUP_QUOTE_CHAR);
boolean caseSensitive = Integer.parseInt(tok.nextToken()) == 1;
boolean regExp = Integer.parseInt(tok.nextToken()) == 1;
KeywordGroup newGroup;
if (regExp) {
newGroup = new RegexKeywordGroup(name, context, field, expression, caseSensitive);
} else {
newGroup = new WordKeywordGroup(name, context, field, expression, caseSensitive, keywordSeparator, false);
}
addGroupDetails(tok, newGroup);
return newGroup;
}
private static ExplicitGroup explicitGroupFromString(String input, Character keywordSeparator) throws ParseException {
if (!input.startsWith(MetadataSerializationConfiguration.EXPLICIT_GROUP_ID)) {
throw new IllegalArgumentException("ExplicitGroup cannot be created from \"" + input + "\".");
}
QuotedStringTokenizer tok = new QuotedStringTokenizer(input.substring(MetadataSerializationConfiguration.EXPLICIT_GROUP_ID.length()),
MetadataSerializationConfiguration.GROUP_UNIT_SEPARATOR, MetadataSerializationConfiguration.GROUP_QUOTE_CHAR);
String name = tok.nextToken();
try {
int context = Integer.parseInt(tok.nextToken());
ExplicitGroup newGroup = new ExplicitGroup(name, GroupHierarchyType.getByNumberOrDefault(context), keywordSeparator);
addGroupDetails(tok, newGroup);
return newGroup;
} catch (NumberFormatException exception) {
throw new ParseException("Could not parse context in " + input);
}
}
private static ExplicitGroup legacyExplicitGroupFromString(String input, Character keywordSeparator) throws ParseException {
if (!input.startsWith(MetadataSerializationConfiguration.LEGACY_EXPLICIT_GROUP_ID)) {
throw new IllegalArgumentException("ExplicitGroup cannot be created from \"" + input + "\".");
}
QuotedStringTokenizer tok = new QuotedStringTokenizer(input.substring(MetadataSerializationConfiguration.LEGACY_EXPLICIT_GROUP_ID.length()),
MetadataSerializationConfiguration.GROUP_UNIT_SEPARATOR, MetadataSerializationConfiguration.GROUP_QUOTE_CHAR);
String name = tok.nextToken();
try {
int context = Integer.parseInt(tok.nextToken());
ExplicitGroup newGroup = new ExplicitGroup(name, GroupHierarchyType.getByNumberOrDefault(context), keywordSeparator);
GroupsParser.addLegacyEntryKeys(tok, newGroup);
return newGroup;
} catch (NumberFormatException exception) {
throw new ParseException("Could not parse context in " + input);
}
}
/**
* Called only when created fromString.
* JabRef used to store the entries of an explicit group in the serialization, e.g.
* ExplicitGroup:GroupName\;0\;Key1\;Key2\;;
* This method exists for backwards compatibility.
*/
private static void addLegacyEntryKeys(QuotedStringTokenizer tok, ExplicitGroup group) {
while (tok.hasMoreTokens()) {
String key = StringUtil.unquote(tok.nextToken(), MetadataSerializationConfiguration.GROUP_QUOTE_CHAR);
group.addLegacyEntryKey(key);
}
}
private static AbstractGroup allEntriesGroupFromString(String s) {
if (!s.startsWith(MetadataSerializationConfiguration.ALL_ENTRIES_GROUP_ID)) {
throw new IllegalArgumentException("AllEntriesGroup cannot be created from \"" + s + "\".");
}
return DefaultGroupsFactory.getAllEntriesGroup();
}
/**
* Parses s and recreates the SearchGroup from it.
*
* @param s The String representation obtained from
* SearchGroup.toString(), or null if incompatible
*/
private static AbstractGroup searchGroupFromString(String s) {
if (!s.startsWith(MetadataSerializationConfiguration.SEARCH_GROUP_ID)) {
throw new IllegalArgumentException("SearchGroup cannot be created from \"" + s + "\".");
}
QuotedStringTokenizer tok = new QuotedStringTokenizer(s.substring(MetadataSerializationConfiguration.SEARCH_GROUP_ID.length()),
MetadataSerializationConfiguration.GROUP_UNIT_SEPARATOR, MetadataSerializationConfiguration.GROUP_QUOTE_CHAR);
String name = tok.nextToken();
int context = Integer.parseInt(tok.nextToken());
String expression = tok.nextToken();
boolean caseSensitive = Integer.parseInt(tok.nextToken()) == 1;
boolean regExp = Integer.parseInt(tok.nextToken()) == 1;
// version 0 contained 4 additional booleans to specify search
// fields; these are ignored now, all fields are always searched
return new SearchGroup(StringUtil.unquote(name, MetadataSerializationConfiguration.GROUP_QUOTE_CHAR),
GroupHierarchyType.getByNumberOrDefault(context), StringUtil.unquote(expression, MetadataSerializationConfiguration.GROUP_QUOTE_CHAR), caseSensitive, regExp
);
}
private static void addGroupDetails(QuotedStringTokenizer tokenizer, AbstractGroup group) {
if (tokenizer.hasMoreTokens()) {
group.setExpanded(Integer.parseInt(tokenizer.nextToken()) == 1);
group.setColor(tokenizer.nextToken());
group.setIconCode(tokenizer.nextToken());
group.setDescription(tokenizer.nextToken());
}
}
}