package org.jabref.logic.importer.util;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.jabref.logic.cleanup.Cleanups;
import org.jabref.logic.importer.ParseException;
import org.jabref.model.database.BibDatabaseMode;
import org.jabref.model.metadata.ContentSelectors;
import org.jabref.model.metadata.MetaData;
import org.jabref.model.metadata.SaveOrderConfig;
import org.jabref.model.strings.StringUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class MetaDataParser {
private static final Log LOGGER = LogFactory.getLog(MetaDataParser.class);
private MetaDataParser() {
}
/**
* Parses the given data map and returns a new resulting {@link MetaData} instance.
*/
public static MetaData parse(Map<String, String> data, Character keywordSeparator) throws ParseException {
return parse(new MetaData(), data, keywordSeparator);
}
/**
* Parses the data map and changes the given {@link MetaData} instance respectively.
*/
public static MetaData parse(MetaData metaData, Map<String, String> data, Character keywordSeparator) throws ParseException {
List<String> defaultCiteKeyPattern = new ArrayList<>();
Map<String, List<String>> nonDefaultCiteKeyPatterns = new HashMap<>();
for (Map.Entry<String, String> entry : data.entrySet()) {
List<String> value = getAsList(entry.getValue());
if (entry.getKey().startsWith(MetaData.PREFIX_KEYPATTERN)) {
String entryType = entry.getKey().substring(MetaData.PREFIX_KEYPATTERN.length());
nonDefaultCiteKeyPatterns.put(entryType, Collections.singletonList(getSingleItem(value)));
continue;
} else if (entry.getKey().startsWith(MetaData.FILE_DIRECTORY + '-')) {
// The user name comes directly after "FILE_DIRECTORY-"
String user = entry.getKey().substring(MetaData.FILE_DIRECTORY.length() + 1);
metaData.setUserFileDirectory(user, getSingleItem(value));
continue;
} else if (entry.getKey().startsWith(MetaData.SELECTOR_META_PREFIX)) {
metaData.addContentSelector(ContentSelectors.parse(entry.getKey().substring(MetaData.SELECTOR_META_PREFIX.length()), StringUtil.unquote(entry.getValue(), MetaData.ESCAPE_CHARACTER)));
continue;
}
switch (entry.getKey()) {
case MetaData.GROUPSTREE:
case MetaData.GROUPSTREE_LEGACY:
metaData.setGroups(GroupsParser.importGroups(value, keywordSeparator));
break;
case MetaData.SAVE_ACTIONS:
metaData.setSaveActions(Cleanups.parse(value));
break;
case MetaData.DATABASE_TYPE:
metaData.setMode(BibDatabaseMode.parse(getSingleItem(value)));
break;
case MetaData.KEYPATTERNDEFAULT:
defaultCiteKeyPattern = Collections.singletonList(getSingleItem(value));
break;
case MetaData.PROTECTED_FLAG_META:
if (Boolean.parseBoolean(getSingleItem(value))) {
metaData.markAsProtected();
} else {
metaData.markAsNotProtected();
}
break;
case MetaData.FILE_DIRECTORY:
metaData.setDefaultFileDirectory(getSingleItem(value));
break;
case MetaData.SAVE_ORDER_CONFIG:
metaData.setSaveOrderConfig(SaveOrderConfig.parse(value));
break;
default:
// Keep meta data items that we do not know in the file
metaData.putUnkownMetaDataItem(entry.getKey(), value);
}
}
if (!defaultCiteKeyPattern.isEmpty() || !nonDefaultCiteKeyPatterns.isEmpty()) {
metaData.setCiteKeyPattern(defaultCiteKeyPattern, nonDefaultCiteKeyPatterns);
}
return metaData;
}
/**
* Returns the first item in the list.
* If the specified list does not contain exactly one item, then a {@link ParseException} will be thrown.
* @param value
* @return
*/
private static String getSingleItem(List<String> value) throws ParseException {
if (value.size() == 1) {
return value.get(0);
} else {
throw new ParseException("Expected a single item but received " + value.toString());
}
}
private static List<String> getAsList(String value) throws ParseException {
StringReader valueReader = new StringReader(value);
List<String> orderedValue = new ArrayList<>();
// We must allow for ; and \ in escape sequences.
try {
Optional<String> unit;
while ((unit = getNextUnit(valueReader)).isPresent()) {
orderedValue.add(unit.get());
}
} catch (IOException ex) {
LOGGER.error("Weird error while parsing meta data.", ex);
throw new ParseException("Weird error while parsing meta data.", ex);
}
return orderedValue;
}
/**
* Reads the next unit. Units are delimited by ';' (MetaData.SEPARATOR_CHARACTER).
*/
private static Optional<String> getNextUnit(Reader reader) throws IOException {
int c;
boolean escape = false;
StringBuilder res = new StringBuilder();
while ((c = reader.read()) != -1) {
if (escape) {
res.append((char) c);
escape = false;
} else if (c == MetaData.ESCAPE_CHARACTER) {
escape = true;
} else if (c == MetaData.SEPARATOR_CHARACTER) {
break;
} else {
res.append((char) c);
}
}
if (res.length() > 0) {
return Optional.of(res.toString());
}
return Optional.empty();
}
}