1. Introduction
The RIS file format is one of the common formats used to exchange date between citation managers. It was initially developed by the commercial company Research Information Systems (RIS). It is used by numerous citation managers and digital libraries, some of which are e.g. Zotero or EndNote. For more details about the RIS file format, and it’s history, refer to the respective Wikipedia article.
KRis is a kotlin implementation that aims to be used as a library for Kotlin and Java projects. It is available on GitHub and can be used via gradle or maven dependency management. KRis started as a fork of JRis, which was developed by Gianluca Colaianni (fastluca).
KRis requires Java Platform 17 or later.
2. Usage
KRis
can be used
-
to export instances of
RisRecord
to a text file conforming with the RIS format -
to import RIS records from a text file and convert each of them to a
RisRecord
.
KRis
offers an API that should be usable both from Kotlin and from Java.
It offers a number of utility functions that allow you to easily handle your use case.
KRis
offers methods to process RIS records in a non-blocking or blocking way.
3. Using KRis
from Kotlin
Using KRis from kotlin allows us to profit from syntactic sugar provided by kotlin like data classes with default arguments for creating RisRecords or extension functions with receivers for calling the actual export/import methods.
3.1. Export: RIS records → text file
Let’s assume we have a number of bibliographic references we want to export them into a file in RIS format: We first have to convert each reference into a [RisRecord].
RisRecord
[kotlin]val record1 = RisRecord(
type = RisType.JOUR,
authors = mutableListOf("Shannon, Claude E."),
publicationYear = "1948/07//",
title = "A Mathematical Theory of Communication",
secondaryTitle = "Bell System Technical Journal",
startPage = "379",
endPage = "423",
volumeNumber = "27"
)
val record2 = RisRecord(
type = RisType.JOUR,
authors = mutableListOf("Turing, Alan Mathison"),
publicationYear = "1948/07//",
periodicalNameFullFormatJO = "Proc. of London Mathematical Society",
title = "On computable numbers, with an application to the Entscheidungsproblem",
secondaryTitle = "Bell System Technical Journal",
startPage = "230",
endPage = "265",
volumeNumber = "47",
issue = "1",
primaryDate = "1937"
)
3.1.1. List: Blocking conversion
We can convert the records as a list into a list of strings. This happens in a blocking manner, and it is up to us what we want to do with the list of lines.
RisRecords
in a blocking manner into a list of RIS lines [kotlin] val lines: List<String> = listOf(record1, record2).toRisLines()
We may want to influence how the RIS tags are sorted in the file. We can do that by providing a list of RIS tag names (as string) in the order we’d like to have them in the file.
RisRecords
applying custom sort in a blocking manner into a list of RIS lines [kotlin] val sort: List<String> = listOf("SP", "EP", "T1")
val lines: List<String> = listOf(record1, record2).toRisLines(sort)
All the subsequent method calls optionally accept a list of RIS tag names, specifying the tag sort order. We will not explicitly mention this option in the subsequent examples. |
You can get a complete list of the RisTags available:
// import ch.difty.kris.risTags
val namesOfAllRisTags = risTagNames
If we want to write the list of lines into a file, we can do that manually, e.g.
val lines: List<String> = listOf(record1, record2).toRisLines()
// writing the lines to file, one of many possibilities
val content: String = lines.joinToString(separator = "")
File("export.ris").writeText(content)
To make this process easier, KRis offers some extension functions providing some syntactic sugar.
We can use the accept
extension function with a writer, a file, an output stream or a path as receiver respectively:
RisRecords
in a blocking manner into a buffered writer [kotlin] val writer = File.createTempFile("export.ris", null, null).bufferedWriter()
writer.accept(listOf(record1, record2))
RisRecords
in a blocking manner into a file [kotlin] val file = File.createTempFile("export.ris", null, null)
file.accept(listOf(record1, record2))
RisRecords
in a blocking manner into an output stream [kotlin] val outputStream = File.createTempFile("export.ris", null, null).outputStream()
outputStream.accept(listOf(record1, record2))
RisRecords
in a blocking manner into a path [kotlin] "export.ris".accept(listOf(record1, record2))
3.1.2. Flow: Non-Blocking conversion
If you want to work with non-blocking reactive streams, you can work with flows:
RisRecords
in a non-blocking manner into a flow of Strings [kotlin] val flow = flowOf(record1, record2)
val risLines: Flow<String> = flow.toRisLines()
val risLines: Flow<String> = flowOf(record1, record2).toRisLines()
runBlocking {
val content: String = risLines.toList().joinToString(separator = "")
File("export.ris").writeText(content)
}
3.2. Import: Text file → RIS records
Assume we have a text file import.ris
with one or more RIS records similar to the following example:
TY - JOUR AU - Hjortebjerg,D. AU - Nybo Andersen,AM. AU - Ketzel,M. AU - Raaschou-Nielsen,O. AU - Sørensen,M. PY - 2018 TI - Exposure to traffic noise and air pollution and risk for febrile seizure: a cohort study. JO - Scand J Work Environ Health. 2018 Mar 25. pii: 3724. doi. ID - 29574476 DO - 10.5271/sjweh.3724 M1 - 9300 L2 - https://www.ncbi.nlm.nih.gov/pubmed/29574476 ER -
3.2.1. Reading from File
If we have a reader, file, input stream or simply the path of the file, we can use the method process
on the receiver.
We will receive a list of [RisRecord] instances.
val bufferedReader: BufferedReader = File("import.ris").bufferedReader()
val records: List<RisRecord> = bufferedReader.process()
val file: File = File("import.ris")
val records: List<RisRecord> = file.process()
val inputStream: FileInputStream = File("import.ris").inputStream()
val records: List<RisRecord> = inputStream.process()
val records: List<RisRecord> = "import.ris".process()
3.2.2. RIS file lines in memory
Lists/Sequences: blocking conversion
In case we have the file content already in memory, we can pass it as a list or sequence.
val lines: List<String> = TODO()
val records: List<RisRecord> = lines.toRisRecords()
val lineSequence: Sequence<String> = TODO()
val records: Sequence<RisRecord> = lineSequence.toRisRecords(GlobalScope)
4. Using KRis
from Java
Building RisRecords in Java is more cumbersome than in kotlin, as we cannot simply use the default arguments of the data class or comfortably use extension functions.
4.1. Export: RIS records → text file
We can however use the builder to only specify the fields we actually need.
RisRecord
[Java] private final RisRecord record1 = new RisRecord.Builder()
.type(RisType.JOUR)
.authors(List.of("Shannon, Claude E."))
.publicationYear("1948/07//")
.title("A Mathematical Theory of Communication")
.secondaryTitle("Bell System Technical Journal")
.startPage("379")
.endPage("423")
.volumeNumber("27")
.build();
private final RisRecord record2 = new RisRecord.Builder()
.type(RisType.JOUR)
.authors(List.of("Turing, Alan Mathison"))
.publicationYear("1948/07//")
.periodicalNameFullFormatJO("Proc. of London Mathematical Society")
.title("On computable numbers, with an application to the Entscheidungsproblem")
.secondaryTitle("Bell System Technical Journal")
.startPage("230")
.endPage("265")
.volumeNumber("47")
.issue("1")
.primaryDate("1937")
.build();
4.1.1. List: Blocking conversion
We can convert the records as a list into a list of strings. This happens in a blocking manner, and it is up to us what we want to do with the list of lines.
RisRecords
in a blocking manner into a list of RIS lines [Java] final List<RisRecord> records = List.of(record1, record2);
List<String> lines = KRis.buildFromList(records);
We may want to influence how the RIS tags are sorted in the file. We can do that by providing a list of RIS tag names (as string) in the order we’d like to have them in the file.
RisRecords
applying custom sort in a blocking manner into a list of RIS lines [Java] final List<RisRecord> records = List.of(record1, record2);
final List<String> sort = List.of("SP", "EP", "T1");
List<String> lines = KRis.buildFromList(records, sort);
All the subsequent method calls optionally accept a list of RIS tag names, specifying the tag sort order. We will not explicitly mention this option in the subsequent examples. |
You can get a complete list of the RisTags available:
List<String> namesOfAllRisTags = KRis.risTagNames();
4.1.2. Observable: Non-blocking conversion
Working with KRis in non-blocking manner in Java involves RxJava2.
RisRecords
in non-blocking manner into a file [Java] void fromObservable() throws IOException {
final List<RisRecord> records = List.of(record1, record2);
final Observable<RisRecord> observable = Observable.fromIterable(records);
final BufferedWriter writer = new BufferedWriter(new FileWriter("export.ris"));
KRis
.exportObservable(observable)
.doFinally(() -> closeWriter(writer))
.blockingSubscribe(writer::append);
}
private void closeWriter(final BufferedWriter writer) {
try {
writer.close();
} catch (Exception ex) {
}
}
To make the process of exporting to files easier, KRis offers some dedicated functions in the utility class KRisIO
.
We can use the export
functions with a writer, a file, an output stream or a path as receiver respectively:
RisRecords
in a blocking manner into a buffered writer [Java] final List<RisRecord> records = List.of(record1, record2);
try (final BufferedWriter writer = new BufferedWriter(new FileWriter("export.ris"))) {
KRisIO.export(records, writer);
}
RisRecords
in a blocking manner into a file [Java] final List<RisRecord> records = List.of(record1, record2);
final File file = new File("export.ris");
KRisIO.export(records, file);
RisRecords
in a blocking manner into an output stream [Java] final List<RisRecord> records = List.of(record1, record2);
try (OutputStream s = new BufferedOutputStream(new FileOutputStream("export.ris"))) {
KRisIO.export(records, s);
}
RisRecords
in a blocking manner into a path [Java] final List<RisRecord> records = List.of(record1, record2);
KRisIO.export(records, "export.ris");
4.2. Import: Text file → RIS records
Assume we have a text file import.ris
with one or more RIS records similar to the following example:
TY - JOUR AU - Hjortebjerg,D. AU - Nybo Andersen,AM. AU - Ketzel,M. AU - Raaschou-Nielsen,O. AU - Sørensen,M. PY - 2018 TI - Exposure to traffic noise and air pollution and risk for febrile seizure: a cohort study. JO - Scand J Work Environ Health. 2018 Mar 25. pii: 3724. doi. ID - 29574476 DO - 10.5271/sjweh.3724 M1 - 9300 L2 - https://www.ncbi.nlm.nih.gov/pubmed/29574476 ER -
4.2.1. Reading from File
If we have a reader, file, input stream or simply the path of the file, we can use the method process
on KRisIO
.
We will receive a list of [RisRecord] instances.
try (final BufferedReader reader = new BufferedReader(new FileReader("import.ris"))) {
final List<RisRecord> records = KRisIO.process(reader);
}
final File file = new File("import.ris");
final List<RisRecord> records = KRisIO.process(file);
try (InputStream s = new BufferedInputStream(new FileInputStream("import.ris"))) {
final List<RisRecord> records = KRisIO.process(s);
}
final List<RisRecord> records = KRisIO.process("import.ris");
While this is sufficient to process reasonably small files, you may prefer consuming streams for processing large files.
try (final BufferedReader reader = new BufferedReader(new FileReader("import.ris"))) {
final Stream<RisRecord> records = KRisIO.processToStream(reader);
}
final File file = new File("import.ris");
final Stream<RisRecord> records = KRisIO.processToStream(file);
try (InputStream s = new BufferedInputStream(new FileInputStream("import.ris"))) {
final Stream<RisRecord> records = KRisIO.processToStream(s);
}
final Stream<RisRecord> records = KRisIO.processToStream("import.ris");
4.2.2. RIS file lines in memory
Lists: Blocking conversion
In case we have the file content already in memory, we can pass it as a list.
final List<String> lines = List.of(); // TODO
List<RisRecord> records = KRis.processList(lines);
Observable : Non-blocking conversion
final List<RisRecord> risRecords = new ArrayList<>();
final List<String> lines = new ArrayList<>();
final Observable<String> observable = Observable.fromIterable(lines);
KRis
.processObservables(observable)
.blockingSubscribe(risRecords::add);