// Copyright 2013 Michel Kraemer
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package de.undercouch.citeproc;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import de.undercouch.citeproc.csl.CSLCitation;
import de.undercouch.citeproc.csl.CSLCitationItem;
import de.undercouch.citeproc.csl.CSLItemData;
import de.undercouch.citeproc.csl.CitationIDIndexPair;
import de.undercouch.citeproc.helper.CSLUtils;
import de.undercouch.citeproc.helper.json.JsonBuilder;
import de.undercouch.citeproc.helper.json.MapJsonBuilderFactory;
import de.undercouch.citeproc.output.Bibliography;
import de.undercouch.citeproc.output.Citation;
import de.undercouch.citeproc.output.SecondFieldAlign;
import de.undercouch.citeproc.script.ScriptRunner;
import de.undercouch.citeproc.script.ScriptRunnerException;
import de.undercouch.citeproc.script.ScriptRunnerFactory;
/**
* <p>The citation processor.</p>
*
* <p>In order to use the processor in your application you first have to
* create an {@link ItemDataProvider} that provides citation item data. For
* example, the following dummy provider returns always the same data:</p>
*
* <blockquote><pre>
* public class MyItemProvider implements ItemDataProvider {
* @Override
* public CSLItemData retrieveItem(String id) {
* return new CSLItemDataBuilder()
* .id(id)
* .type(CSLType.ARTICLE_JOURNAL)
* .title("A dummy journal article")
* .author("John", "Smith")
* .issued(2013, 9, 6)
* .containerTitle("Dummy journal")
* .build();
* }
*
* @Override
* public String[] getIds() {
* String ids[] = {"ID-0", "ID-1", "ID-2"};
* return ids;
* }
* }</pre></blockquote>
*
* Now you can instantiate the CSL processor.
*
* <blockquote><pre>
* CSL citeproc = new CSL(new MyItemProvider(), "ieee");
* citeproc.setOutputFormat("html");</pre></blockquote>
*
* <h3>Ad-hoc usage</h3>
*
* <p>You may also use {@link #makeAdhocBibliography(String, CSLItemData...)} or
* {@link #makeAdhocBibliography(String, String, CSLItemData...)} to create
* ad-hoc bibliographies from CSL items.</p>
*
* <blockquote><pre>
* CSLItemData item = new CSLItemDataBuilder()
* .type(CSLType.WEBPAGE)
* .title("citeproc-java: A Citation Style Language (CSL) processor for Java")
* .author("Michel", "Kraemer")
* .issued(2014, 7, 13)
* .URL("http://michel-kraemer.github.io/citeproc-java/")
* .accessed(2014, 7, 13)
* .build();
*
* String bibl = CSL.makeAdhocBibliography("ieee", item).makeString();</pre></blockquote>
*
* <h3>Thread-safety</h3>
*
* <p>Please note that this class is not thread-safe. However, shared resources
* are held in memory, so constructing new instances is generally rather cheap.
* If your settings do not change you may cache instances of this class in a
* <code>ThreadLocal</code>.</p>
*
* <blockquote><pre>
* ThreadLocal<CSL> csl = new ThreadLocal<CSL>() {
* @Override
* protected CSL initialValue() {
* return new CSL(itemDataProvider, style, lang);
* }
* };</pre></blockquote>
*
* Please remember to reset the <code>ThreadLocal</code> if you don't need
* the <code>CSL</code> instance anymore to avoid memory leaks.
*
* @author Michel Kraemer
*/
public class CSL {
/**
* A thread-local holding a JavaScript runner that can
* be shared amongst multiple instances of this class
*/
private static ThreadLocal<ScriptRunner> sharedRunner = new ThreadLocal<>();
/**
* A JavaScript runner used to execute citeproc-js
*/
private final ScriptRunner runner;
/**
* The underlying citeproc-js engine
*/
private final Object engine;
/**
* The output format
* @see #setOutputFormat(String)
*/
private String outputFormat = "html";
/**
* Constructs a new citation processor
* @param itemDataProvider an object that provides citation item data
* @param style the citation style to use. May either be a serialized
* XML representation of the style or a style's name such as <code>ieee</code>.
* In the latter case, the processor loads the style from the classpath (e.g.
* <code>/ieee.csl</code>)
* @throws IOException if the underlying JavaScript files or the CSL style
* could not be loaded
*/
public CSL(ItemDataProvider itemDataProvider, String style) throws IOException {
this(itemDataProvider, style, "en-US");
}
/**
* Constructs a new citation processor
* @param itemDataProvider an object that provides citation item data
* @param abbreviationProvider an object that provides abbreviations
* @param style the citation style to use. May either be a serialized
* XML representation of the style or a style's name such as <code>ieee</code>.
* In the latter case, the processor loads the style from the classpath (e.g.
* <code>/ieee.csl</code>)
* @throws IOException if the underlying JavaScript files or the CSL style
* could not be loaded
*/
public CSL(ItemDataProvider itemDataProvider, AbbreviationProvider abbreviationProvider,
String style) throws IOException {
this(itemDataProvider, abbreviationProvider, style, "en-US");
}
/**
* Constructs a new citation processor
* @param itemDataProvider an object that provides citation item data
* @param style the citation style to use. May either be a serialized
* XML representation of the style or a style's name such as <code>ieee</code>.
* In the latter case, the processor loads the style from the classpath (e.g.
* <code>/ieee.csl</code>)
* @param lang an RFC 4646 identifier for the citation locale (e.g. <code>en-US</code>)
* @throws IOException if the underlying JavaScript files or the CSL style
* could not be loaded
*/
public CSL(ItemDataProvider itemDataProvider, String style, String lang) throws IOException {
this(itemDataProvider, new DefaultLocaleProvider(), style, lang, false);
}
/**
* Constructs a new citation processor
* @param itemDataProvider an object that provides citation item data
* @param abbreviationProvider an object that provides abbreviations
* @param style the citation style to use. May either be a serialized
* XML representation of the style or a style's name such as <code>ieee</code>.
* In the latter case, the processor loads the style from the classpath (e.g.
* <code>/ieee.csl</code>)
* @param lang an RFC 4646 identifier for the citation locale (e.g. <code>en-US</code>)
* @throws IOException if the underlying JavaScript files or the CSL style
* could not be loaded
*/
public CSL(ItemDataProvider itemDataProvider, AbbreviationProvider abbreviationProvider,
String style, String lang) throws IOException {
this(itemDataProvider, new DefaultLocaleProvider(), abbreviationProvider,
style, lang, false);
}
/**
* Constructs a new citation processor
* @param itemDataProvider an object that provides citation item data
* @param localeProvider an object that provides CSL locales
* @param style the citation style to use. May either be a serialized
* XML representation of the style or a style's name such as <code>ieee</code>.
* In the latter case, the processor loads the style from the classpath (e.g.
* <code>/ieee.csl</code>)
* @param lang an RFC 4646 identifier for the citation locale (e.g. <code>en-US</code>)
* @param forceLang true if the given locale should overwrite any default locale
* @throws IOException if the underlying JavaScript files or the CSL style
* could not be loaded
*/
public CSL(ItemDataProvider itemDataProvider, LocaleProvider localeProvider,
String style, String lang, boolean forceLang) throws IOException {
this(itemDataProvider, localeProvider, new DefaultAbbreviationProvider(),
style, lang, forceLang);
}
/**
* Constructs a new citation processor
* @param itemDataProvider an object that provides citation item data
* @param localeProvider an object that provides CSL locales
* @param abbreviationProvider an object that provides abbreviations
* @param style the citation style to use. May either be a serialized
* XML representation of the style or a style's name such as <code>ieee</code>.
* In the latter case, the processor loads the style from the classpath (e.g.
* <code>/ieee.csl</code>)
* @param lang an RFC 4646 identifier for the citation locale (e.g. <code>en-US</code>)
* @param forceLang true if the given locale should overwrite any default locale
* @throws IOException if the underlying JavaScript files or the CSL style
* could not be loaded
*/
public CSL(ItemDataProvider itemDataProvider, LocaleProvider localeProvider,
AbbreviationProvider abbreviationProvider, String style,
String lang, boolean forceLang) throws IOException {
this(itemDataProvider, localeProvider, abbreviationProvider, null,
style, lang, forceLang);
}
/**
* Constructs a new citation processor
* @param itemDataProvider an object that provides citation item data
* @param localeProvider an object that provides CSL locales
* @param abbreviationProvider an object that provides abbreviations
* @param variableWrapper an object that decorates rendered items
* @param style the citation style to use. May either be a serialized
* XML representation of the style or a style's name such as <code>ieee</code>.
* In the latter case, the processor loads the style from the classpath (e.g.
* <code>/ieee.csl</code>)
* @param lang an RFC 4646 identifier for the citation locale (e.g. <code>en-US</code>)
* @param forceLang true if the given locale should overwrite any default locale
* @throws IOException if the underlying JavaScript files or the CSL style
* could not be loaded
*/
public CSL(ItemDataProvider itemDataProvider, LocaleProvider localeProvider,
AbbreviationProvider abbreviationProvider, VariableWrapper variableWrapper,
String style, String lang, boolean forceLang) throws IOException {
runner = getRunner();
//load style if needed
if (!isStyle(style)) {
style = loadStyle(style);
}
//initialize engine
try {
engine = runner.callMethod("makeCsl", Object.class,
style, lang, forceLang, runner, itemDataProvider,
localeProvider, abbreviationProvider, variableWrapper);
} catch (ScriptRunnerException e) {
throw new IllegalArgumentException("Could not parse arguments", e);
}
}
/**
* Calculates a list of supported output formats
* @return the formats
* @throws IOException if the underlying JavaScript files could not be loaded
*/
public static List<String> getSupportedOutputFormats() throws IOException {
ScriptRunner runner = getRunner();
try {
return runner.callMethod("getSupportedFormats", List.class);
} catch (ScriptRunnerException e) {
throw new IllegalStateException("Could not get supported formats", e);
}
}
private static Set<String> getAvailableFiles(String prefix,
String knownName, String extension) throws IOException {
Set<String> result = new LinkedHashSet<>();
//first load a file that is known to exist
String name = prefix + knownName + "." + extension;
URL knownUrl = CSL.class.getResource("/" + name);
if (knownUrl != null) {
String path = knownUrl.getPath();
//get the jar file containing the file
if (path.endsWith(".jar!/" + name)) {
String jarPath = path.substring(0, path.length() - name.length() - 2);
URI jarUri;
try {
jarUri = new URI(jarPath);
} catch (URISyntaxException e) {
//ignore
return result;
}
try (ZipFile zip = new ZipFile(new File(jarUri))) {
Enumeration<? extends ZipEntry> entries = zip.entries();
while (entries.hasMoreElements()) {
ZipEntry e = entries.nextElement();
if (e.getName().endsWith("." + extension) &&
(prefix.isEmpty() || e.getName().startsWith(prefix))) {
result.add(e.getName().substring(
prefix.length(), e.getName().length() - 4));
}
}
}
}
}
return result;
}
/**
* Calculates a list of available citation styles
* @return the list
* @throws IOException if the citation styles could not be loaded
*/
public static Set<String> getSupportedStyles() throws IOException {
return getAvailableFiles("", "ieee", "csl");
}
/**
* Checks if a given citation style is supported
* @param style the citation style's name
* @return true if the style is supported, false otherwise
*/
public static boolean supportsStyle(String style) {
String styleFileName = style;
if (!styleFileName.endsWith(".csl")) {
styleFileName = styleFileName + ".csl";
}
if (!styleFileName.startsWith("/")) {
styleFileName = "/" + styleFileName;
}
URL url = CSL.class.getResource(styleFileName);
return (url != null);
}
/**
* Calculates a list of available citation locales
* @return the list
* @throws IOException if the citation locales could not be loaded
*/
public static Set<String> getSupportedLocales() throws IOException {
Set<String> locales = getAvailableFiles("locales-", "en-US", "xml");
try {
List<String> baseLocales = getRunner().callMethod(
"getBaseLocales", List.class);
locales.addAll(baseLocales);
} catch (ScriptRunnerException e) {
//ignore. don't add base locales
}
return locales;
}
/**
* Gets or initializes the shared script runner {@link #sharedRunner}
* @return the runner
* @throws IOException if bundles scripts could not be loaded
*/
private static ScriptRunner getRunner() throws IOException {
if (sharedRunner.get() == null) {
//create JavaScript runner
ScriptRunner runner = ScriptRunnerFactory.createRunner();
//load bundled scripts
try {
runner.loadScript(CSL.class.getResource("dump.js"));
runner.loadScript(CSL.class.getResource("citeproc.js"));
runner.loadScript(CSL.class.getResource("formats.js"));
runner.loadScript(CSL.class.getResource("loadsys.js"));
} catch (ScriptRunnerException e) {
//should never happen because bundled JavaScript files
//should be OK indeed
throw new RuntimeException("Invalid bundled javascript file", e);
}
sharedRunner.set(runner);
}
return sharedRunner.get();
}
/**
* @return the JavaScript runner used to execute citeproc-js
*/
protected ScriptRunner getScriptRunner() {
return runner;
}
/**
* @return the underlying citeproc-js engine
*/
protected Object getEngine() {
return engine;
}
/**
* Checks if the given String contains the serialized XML representation
* of a style
* @param style the string to examine
* @return true if the String is XML, false otherwise
*/
private boolean isStyle(String style) {
for (int i = 0; i < style.length(); ++i) {
char c = style.charAt(i);
if (!Character.isWhitespace(c)) {
return (c == '<');
}
}
return false;
}
/**
* Loads a CSL style from the classpath. For example, if the given name
* is <code>ieee</code> this method will load the file <code>/ieee.csl</code>
* @param styleName the style's name
* @return the serialized XML representation of the style
* @throws IOException if the style could not be loaded
*/
private String loadStyle(String styleName) throws IOException {
if (!styleName.endsWith(".csl")) {
styleName = styleName + ".csl";
}
if (!styleName.startsWith("/")) {
styleName = "/" + styleName;
}
URL url = getClass().getResource(styleName);
if (url == null) {
throw new FileNotFoundException("Could not find style in classpath: " + styleName);
}
return CSLUtils.readURLToString(url, "UTF-8");
}
/**
* Sets the processor's output format
* @param format the format (one of <code>"html"</code>,
* <code>"text"</code>, <code>"asciidoc"</code>, <code>"fo"</code>,
* or <code>"rtf"</code>)
*/
public void setOutputFormat(String format) {
try {
runner.callMethod(engine, "setOutputFormat", format);
outputFormat = format;
} catch (ScriptRunnerException e) {
throw new IllegalArgumentException("Could not set output format", e);
}
}
/**
* Specifies if the processor should convert URLs and DOIs in the output
* to links. How links are created depends on the output format that has
* been set with {@link #setOutputFormat(String)}
* @param convert true if URLs and DOIs should be converted to links
*/
public void setConvertLinks(boolean convert) {
try {
runner.callMethod("setConvertLinks", engine, convert);
} catch (ScriptRunnerException e) {
throw new IllegalArgumentException("Could not set option", e);
}
}
/**
* Enables the abbreviation list with the given name. The processor will
* call {@link AbbreviationProvider#getAbbreviations(String)} with the
* given String to get the abbreviations that should be used from here on.
* @param name the name of the abbreviation list to enable
*/
public void setAbbreviations(String name) {
try {
runner.callMethod(engine, "setAbbreviations", name);
} catch (ScriptRunnerException e) {
throw new IllegalArgumentException("Could not set abbreviations", e);
}
}
/**
* Introduces the given citation IDs to the processor. The processor will
* call {@link ItemDataProvider#retrieveItem(String)} for each ID to get
* the respective citation item. The retrieved items will be added to the
* bibliography, so you don't have to call {@link #makeCitation(String...)}
* for each of them anymore.
* @param ids the IDs to register
* @throws IllegalArgumentException if one of the given IDs refers to
* citation item data that does not exist
*/
public void registerCitationItems(String... ids) {
try {
runner.callMethod(engine, "updateItems", new Object[] { ids });
} catch (ScriptRunnerException e) {
throw new IllegalArgumentException("Could not update items", e);
}
}
/**
* Introduces the given citation IDs to the processor. The processor will
* call {@link ItemDataProvider#retrieveItem(String)} for each ID to get
* the respective citation item. The retrieved items will be added to the
* bibliography, so you don't have to call {@link #makeCitation(String...)}
* for each of them anymore.
* @param ids the IDs to register
* @param unsorted true if items should not be sorted in the bibliography
* @throws IllegalArgumentException if one of the given IDs refers to
* citation item data that does not exist
*/
public void registerCitationItems(String[] ids, boolean unsorted) {
try {
runner.callMethod(engine, "updateItems", ids, unsorted);
} catch (ScriptRunnerException e) {
throw new IllegalArgumentException("Could not update items", e);
}
}
/**
* Generates citation strings that can be inserted into the text. The
* method calls {@link ItemDataProvider#retrieveItem(String)} for each of the given
* IDs to request the corresponding citation item. Additionally, it saves
* the IDs, so {@link #makeBibliography()} will generate a bibliography
* that only consists of the retrieved citation items.
* @param ids IDs of citation items for which strings should be generated
* @return citations strings that can be inserted into the text
* @throws IllegalArgumentException if one of the given IDs refers to
* citation item data that does not exist
*/
public List<Citation> makeCitation(String... ids) {
CSLCitationItem[] items = new CSLCitationItem[ids.length];
for (int i = 0; i < ids.length; ++i) {
items[i] = new CSLCitationItem(ids[i]);
}
return makeCitation(new CSLCitation(items));
}
/**
* Generates citation strings that can be inserted into the text. The
* method calls {@link ItemDataProvider#retrieveItem(String)} for each item in the
* given set to request the corresponding citation item data. Additionally,
* it saves the requested citation IDs, so {@link #makeBibliography()} will
* generate a bibliography that only consists of the retrieved items.
* @param citation a set of citation items for which strings should be generated
* @return citations strings that can be inserted into the text
* @throws IllegalArgumentException if the given set of citation items
* refers to citation item data that does not exist
*/
public List<Citation> makeCitation(CSLCitation citation) {
return makeCitation(citation, null, null);
}
/**
* Generates citation strings that can be inserted into the text. The
* method calls {@link ItemDataProvider#retrieveItem(String)} for each item in the
* given set to request the corresponding citation item data. Additionally,
* it saves the requested citation IDs, so {@link #makeBibliography()} will
* generate a bibliography that only consists of the retrieved items.
* @param citation a set of citation items for which strings should be generated
* @param citationsPre citations that precede <code>citation</code>
* @param citationsPost citations that come after <code>citation</code>
* @return citations strings that can be inserted into the text
* @throws IllegalArgumentException if the given set of citation items
* refers to citation item data that does not exist
*/
public List<Citation> makeCitation(CSLCitation citation,
List<CitationIDIndexPair> citationsPre,
List<CitationIDIndexPair> citationsPost) {
List<?> r;
try {
if (citationsPre == null && citationsPost == null) {
r = runner.callMethod(engine, "appendCitationCluster",
List.class, citation);
} else {
r = runner.callMethod(engine, "processCitationCluster",
List.class, citation, citationsPre, citationsPost);
r = runner.convert(r.get(1), List.class);
}
} catch (ScriptRunnerException e) {
throw new IllegalArgumentException("Could not make citation", e);
}
List<Citation> result = new ArrayList<>();
for (Object o : r) {
if (o instanceof Map) {
o = runner.convert(o, List.class);
}
if (o instanceof List) {
@SuppressWarnings("unchecked")
List<Object> i = (List<Object>)o;
if (i.get(0) instanceof Number && i.get(1) instanceof CharSequence) {
int index = ((Number)i.get(0)).intValue();
String text = i.get(1).toString();
result.add(new Citation(index, text));
}
}
}
return result;
}
/**
* Generates a bibliography for the registered citations
* @return the bibliography
*/
public Bibliography makeBibliography() {
return makeBibliography(null);
}
/**
* Generates a bibliography for the registered citations. Depending
* on the selection mode selects, includes, or excludes bibliography
* items whose fields and field values match the fields and field values
* from the given example item data objects.
* @param mode the selection mode
* @param selection the example item data objects that contain
* the fields and field values to match
* @return the bibliography
*/
public Bibliography makeBibliography(SelectionMode mode, CSLItemData... selection) {
return makeBibliography(mode, selection, null);
}
/**
* Generates a bibliography for the registered citations. Depending
* on the selection mode selects, includes, or excludes bibliography
* items whose fields and field values match the fields and field values
* from the given example item data objects.
* @param mode the selection mode
* @param selection the example item data objects that contain
* the fields and field values to match
* @param quash regardless of the item data in <code>selection</code>
* skip items if all fields/values from this list match
* @return the bibliography
*/
public Bibliography makeBibliography(SelectionMode mode,
CSLItemData[] selection, CSLItemData[] quash) {
List<?> r;
try {
if ((selection == null || mode == null) && quash == null) {
r = runner.callMethod(engine, "makeBibliography", List.class);
} else {
Map<String, Object> args = new HashMap<>();
if (selection != null && mode != null) {
args.put(mode.toString(), selectionToList(selection));
}
if (quash != null) {
args.put("quash", selectionToList(quash));
}
r = runner.callMethod(engine, "makeBibliography",
List.class, args);
}
} catch (ScriptRunnerException e) {
throw new IllegalArgumentException("Could not make bibliography", e);
}
@SuppressWarnings("unchecked")
Map<String, Object> fpm = (Map<String, Object>)r.get(0);
List<CharSequence> entriesList = runner.convert(r.get(1), List.class);
String[] entries = new String[entriesList.size()];
for (int i = 0; i < entries.length; ++i) {
entries[i] = entriesList.get(i).toString();
}
int maxOffset = getFromMap(fpm, "maxoffset", 0);
int entrySpacing = getFromMap(fpm, "entryspacing", 0);
int lineSpacing = getFromMap(fpm, "linespacing", 0);
int hangingIndent = getFromMap(fpm, "hangingindent", 0);
boolean done = getFromMap(fpm, "done", false);
List<?> srcEntryIds = runner.convert(fpm.get("entry_ids"), List.class);
List<String> dstEntryIds = new ArrayList<>();
for (Object o : srcEntryIds) {
if (o instanceof Map) {
o = runner.convert(o, List.class);
}
if (o instanceof Collection) {
Collection<?> oc = (Collection<?>)o;
for (Object oco : oc) {
dstEntryIds.add(oco.toString());
}
} else {
dstEntryIds.add(o.toString());
}
}
String[] entryIds = dstEntryIds.toArray(new String[dstEntryIds.size()]);
SecondFieldAlign secondFieldAlign = SecondFieldAlign.FALSE;
Object sfa = fpm.get("second-field-align");
if (sfa != null) {
secondFieldAlign = SecondFieldAlign.fromString(sfa.toString());
}
String bibStart = getFromMap(fpm, "bibstart", "");
String bibEnd = getFromMap(fpm, "bibend", "");
//special treatment for some output formats
if (outputFormat.equals("fo")) {
//make reasonable margin for an average character width
String em = Math.max(2.5, maxOffset * 0.6) + "em";
for (int i = 0; i < entries.length; ++i) {
entries[i] = entries[i].replace("$$$__COLUMN_WIDTH_1__$$$", em);
}
}
return new Bibliography(entries, bibStart, bibEnd, entryIds,
maxOffset, entrySpacing, lineSpacing, hangingIndent,
done, secondFieldAlign);
}
/**
* Converts the given CSLItemData objects to a list of field/value pairs
* that can be used to filter bibliography items. Only those fields will
* be included that are actually set in the given objects.
* @param selection the CSLItemData objects
* @return the list of field/value pairs
*/
private List<Map<String, Object>> selectionToList(CSLItemData[] selection) {
MapJsonBuilderFactory mjbf = new MapJsonBuilderFactory();
List<Map<String, Object>> sl = new ArrayList<>();
for (CSLItemData item : selection) {
JsonBuilder jb = mjbf.createJsonBuilder();
@SuppressWarnings("unchecked")
Map<String, Object> mi = (Map<String, Object>)item.toJson(jb);
for (Map.Entry<String, Object> e : mi.entrySet()) {
Object v = e.getValue();
if (e.getKey().equals("id") && v instanceof String &&
((String)v).startsWith("-GEN-")) {
//skip generated ids
continue;
}
if (v instanceof Collection) {
Collection<?> coll = (Collection<?>)v;
if (coll.isEmpty()) {
putSelectionFieldValue(sl, e, "");
} else {
for (Object ao : coll) {
putSelectionFieldValue(sl, e, ao);
}
}
} else if (v instanceof Map && ((Map<?, ?>)v).isEmpty()) {
putSelectionFieldValue(sl, e, "");
} else {
putSelectionFieldValue(sl, e, v);
}
}
}
return sl;
}
private void putSelectionFieldValue(List<Map<String, Object>> sl,
Map.Entry<String, Object> e, Object v) {
Map<String, Object> sf = new HashMap<>(2);
sf.put("field", e.getKey());
sf.put("value", v);
sl.add(sf);
}
private int getFromMap(Map<String, Object> m, String key, int def) {
Number r = (Number)m.get(key);
if (r == null) {
return def;
}
return r.intValue();
}
private boolean getFromMap(Map<String, Object> m, String key, boolean def) {
Object r = m.get(key);
if (r == null) {
return def;
}
if (r instanceof CharSequence) {
return Boolean.parseBoolean(r.toString());
}
return (Boolean)r;
}
private String getFromMap(Map<String, Object> m, String key, String def) {
String r = (String)m.get(key);
if (r == null) {
r = def;
}
return r;
}
/**
* Resets the processor's state
*/
public void reset() {
try {
runner.callMethod(engine, "restoreProcessorState");
} catch (ScriptRunnerException e) {
throw new IllegalArgumentException("Could not reset processor state", e);
}
}
/**
* Creates an ad hoc bibliography from the given citation items using the
* <code>"html"</code> output format. Calling this method is rather
* expensive as it initializes the CSL processor. If you need to create
* bibliographies multiple times in your application you should create
* the processor yourself and cache it if necessary.
* @param style the citation style to use. May either be a serialized
* XML representation of the style or a style's name such as <code>ieee</code>.
* In the latter case, the processor loads the style from the classpath (e.g.
* <code>/ieee.csl</code>)
* @param items the citation items to add to the bibliography
* @return the bibliography
* @throws IOException if the underlying JavaScript files or the CSL style
* could not be loaded
* @see #makeAdhocBibliography(String, String, CSLItemData...)
*/
public static Bibliography makeAdhocBibliography(String style, CSLItemData... items)
throws IOException {
return makeAdhocBibliography(style, "html", items);
}
/**
* Creates an ad hoc bibliography from the given citation items. Calling
* this method is rather expensive as it initializes the CSL processor.
* If you need to create bibliographies multiple times in your application
* you should create the processor yourself and cache it if necessary.
* @param style the citation style to use. May either be a serialized
* XML representation of the style or a style's name such as <code>ieee</code>.
* In the latter case, the processor loads the style from the classpath (e.g.
* <code>/ieee.csl</code>)
* @param outputFormat the processor's output format (one of
* <code>"html"</code>, <code>"text"</code>, <code>"asciidoc"</code>,
* <code>"fo"</code>, or <code>"rtf"</code>)
* @param items the citation items to add to the bibliography
* @return the bibliography
* @throws IOException if the underlying JavaScript files or the CSL style
* could not be loaded
*/
public static Bibliography makeAdhocBibliography(String style, String outputFormat,
CSLItemData... items) throws IOException {
ItemDataProvider provider = new ListItemDataProvider(items);
CSL csl = new CSL(provider, style);
csl.setOutputFormat(outputFormat);
String[] ids = new String[items.length];
for (int i = 0; i < items.length; ++i) {
ids[i] = items[i].getId();
}
csl.registerCitationItems(ids);
return csl.makeBibliography();
}
}