diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index 386145056778..63604ddb41d7 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -22,6 +22,7 @@ env: SpringerNatureAPIKey: ${{ secrets.SpringerNatureAPIKey }} AstrophysicsDataSystemAPIKey: ${{ secrets.AstrophysicsDataSystemAPIKey }} IEEEAPIKey: ${{ secrets.IEEEAPIKey }} + BiodiversityHeritageApiKey: ${{ secrets.BiodiversityHeritageApiKey}} OSXCERT: ${{ secrets.OSX_SIGNING_CERT }} GRADLE_OPTS: -Xmx4g -Dorg.gradle.daemon=false -Dorg.gradle.vfs.watch=false JAVA_OPTS: -Xmx4g diff --git a/.github/workflows/tests-fetchers.yml b/.github/workflows/tests-fetchers.yml index 5cf4d1fef3e5..fa8641a7926c 100644 --- a/.github/workflows/tests-fetchers.yml +++ b/.github/workflows/tests-fetchers.yml @@ -26,6 +26,7 @@ env: SpringerNatureAPIKey: ${{ secrets.SPRINGERNATUREAPIKEY_FOR_TESTS }} AstrophysicsDataSystemAPIKey: ${{ secrets.AstrophysicsDataSystemAPIKey_FOR_TESTS }} IEEEAPIKey: ${{ secrets.IEEEAPIKey_FOR_TESTS }} + BiodiversityHeritageApiKey: ${{ secrets.BiodiversityHeritageApiKey_FOR_TESTS}} concurrency: group: fetcher-tests-${{ github.head_ref }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 529acdf7c64a..2f55e8f8b065 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,6 +13,7 @@ env: SpringerNatureAPIKey: ${{ secrets.SpringerNatureAPIKey }} AstrophysicsDataSystemAPIKey: ${{ secrets.AstrophysicsDataSystemAPIKey }} IEEEAPIKey: ${{ secrets.IEEEAPIKey }} + BiodiversityHeritageApiKey: ${{ secrets.BiodiversityHeritageApiKey}} GRADLE_OPTS: -Xmx4g JAVA_OPTS: -Xmx4g diff --git a/build.gradle b/build.gradle index 28d703edc27f..97b44919784b 100644 --- a/build.gradle +++ b/build.gradle @@ -245,7 +245,8 @@ processResources { "springerNatureAPIKey": System.getenv('SpringerNatureAPIKey') ? System.getenv('SpringerNatureAPIKey') : '', "astrophysicsDataSystemAPIKey": System.getenv('AstrophysicsDataSystemAPIKey') ? System.getenv('AstrophysicsDataSystemAPIKey') : '', "ieeeAPIKey": System.getenv('IEEEAPIKey') ? System.getenv('IEEEAPIKey') : '', - "scienceDirectApiKey": System.getenv('SCIENCEDIRECTAPIKEY') ? System.getenv('SCIENCEDIRECTAPIKEY') : '' + "scienceDirectApiKey": System.getenv('SCIENCEDIRECTAPIKEY') ? System.getenv('SCIENCEDIRECTAPIKEY') : '', + "biodiversityHeritageApiKey": System.getenv('BiodiversityHeritageApiKey')?System.getenv('BiodiversityHeritageApiKey'):'' ) filteringCharset = 'UTF-8' } diff --git a/src/main/java/org/jabref/logic/importer/WebFetchers.java b/src/main/java/org/jabref/logic/importer/WebFetchers.java index fc520e428e17..a616c9dcf816 100644 --- a/src/main/java/org/jabref/logic/importer/WebFetchers.java +++ b/src/main/java/org/jabref/logic/importer/WebFetchers.java @@ -12,6 +12,7 @@ import org.jabref.logic.importer.fetcher.ApsFetcher; import org.jabref.logic.importer.fetcher.ArXiv; import org.jabref.logic.importer.fetcher.AstrophysicsDataSystem; +import org.jabref.logic.importer.fetcher.BiodiversityLibrary; import org.jabref.logic.importer.fetcher.CiteSeer; import org.jabref.logic.importer.fetcher.CollectionOfComputerScienceBibliographiesFetcher; import org.jabref.logic.importer.fetcher.CompositeSearchBasedFetcher; @@ -111,6 +112,7 @@ public static SortedSet getSearchBasedFetchers(ImportFormatP // set.add(new JstorFetcher(importFormatPreferences)); set.add(new SemanticScholar()); set.add(new ResearchGate(importFormatPreferences)); + set.add(new BiodiversityLibrary()); return set; } diff --git a/src/main/java/org/jabref/logic/importer/fetcher/BiodiversityLibrary.java b/src/main/java/org/jabref/logic/importer/fetcher/BiodiversityLibrary.java new file mode 100644 index 000000000000..7f58c78abe24 --- /dev/null +++ b/src/main/java/org/jabref/logic/importer/fetcher/BiodiversityLibrary.java @@ -0,0 +1,276 @@ +package org.jabref.logic.importer.fetcher; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.jabref.logic.importer.FetcherException; +import org.jabref.logic.importer.ParseException; +import org.jabref.logic.importer.Parser; +import org.jabref.logic.importer.SearchBasedParserFetcher; +import org.jabref.logic.importer.fetcher.transformers.BiodiversityLibraryTransformer; +import org.jabref.logic.importer.util.JsonReader; +import org.jabref.logic.net.URLDownload; +import org.jabref.logic.util.BuildInfo; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.types.StandardEntryType; + +import kong.unirest.json.JSONArray; +import kong.unirest.json.JSONException; +import kong.unirest.json.JSONObject; +import org.apache.http.client.utils.URIBuilder; +import org.apache.lucene.queryparser.flexible.core.nodes.QueryNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Fetches data from the Biodiversity Heritage Library + * + * @implNote API documentation + */ + +public class BiodiversityLibrary implements SearchBasedParserFetcher { + + private static final Logger LOGGER = LoggerFactory.getLogger(BiodiversityLibrary.class); + private static final String API_KEY = new BuildInfo().biodiversityHeritageApiKey; + private static final String BASE_URL = "https://www.biodiversitylibrary.org/api3"; + private static final String RESPONSE_FORMAT = "json"; + + public BiodiversityLibrary() { + } + + public URL getBaseURL() throws URISyntaxException, MalformedURLException { + URIBuilder baseURI = new URIBuilder(BASE_URL); + baseURI.addParameter("apikey", API_KEY); + baseURI.addParameter("format", RESPONSE_FORMAT); + + return baseURI.build().toURL(); + } + + @Override + public String getName() { + return "Biodiversity Heritage"; + } + + public URL getItemMetadataURL(String identifier) throws URISyntaxException, MalformedURLException { + URIBuilder uriBuilder = new URIBuilder(getBaseURL().toURI()); + uriBuilder.addParameter("op", "GetItemMetadata"); + uriBuilder.addParameter("pages", "f"); + uriBuilder.addParameter("ocr", "f"); + uriBuilder.addParameter("ocr", "f"); + uriBuilder.addParameter("id", identifier); + + return uriBuilder.build().toURL(); + + } + + public URL getPartMetadataURL(String identifier) throws URISyntaxException, MalformedURLException { + URIBuilder uriBuilder = new URIBuilder(getBaseURL().toURI()); + uriBuilder.addParameter("op", "GetPartMetadata"); + uriBuilder.addParameter("pages", "f"); + uriBuilder.addParameter("names", "f"); + uriBuilder.addParameter("id", identifier); + + return uriBuilder.build().toURL(); + + } + + public JSONObject getDetails(URL url) throws IOException { + + URLDownload download = new URLDownload(url); + String response = download.asString(); + return new JSONObject(response).getJSONArray("Result").getJSONObject(0); + } + + public BibEntry getMostDetails(JSONObject item, BibEntry entry) throws IOException, URISyntaxException { + + if (item.has("BHLType")) { + if (item.getString("BHLType").equals("Part")) { + URL url = getPartMetadataURL(item.getString("PartID")); + JSONObject itemsDetails = getDetails(url); + // Language + if (itemsDetails.has("Language")) { + entry.setField(StandardField.LANGUAGE, itemsDetails.getString("Language")); + } + // DOI + if (itemsDetails.has("Doi")) { + entry.setField(StandardField.DOI, itemsDetails.getString("Doi")); + } + // Publisher + if (itemsDetails.has("PublisherName")) { + entry.setField(StandardField.PUBLISHER, itemsDetails.getString("PublisherName")); + } + // Volume + if (itemsDetails.has("Volume") && !entry.hasField(StandardField.VOLUME)) { + entry.setField(StandardField.VOLUME, itemsDetails.getString("Volume")); + } + // Date + if (itemsDetails.has("Date") && !entry.hasField(StandardField.DATE) && !entry.hasField(StandardField.YEAR)) { + entry.setField(StandardField.DATE, itemsDetails.getString("Date")); + } + // Link + if (itemsDetails.has("PartUrl")) { + entry.setField(StandardField.URL, itemsDetails.getString("PartUrl")); + } + + } + + if (item.getString("BHLType").equals("Item")) { + URL url = getItemMetadataURL(item.getString("ItemID")); + JSONObject itemsDetails = getDetails(url); + // Editor + if (itemsDetails.has("Sponsor")) { + entry.setField(StandardField.EDITOR, itemsDetails.getString("Sponsor")); + } + // Publisher + if (itemsDetails.has("HoldingInstitution")) { + entry.setField(StandardField.PUBLISHER, itemsDetails.getString("HoldingInstitution")); + } + // Language + if (itemsDetails.has("Language")) { + entry.setField(StandardField.LANGUAGE, itemsDetails.getString("Language")); + } + // Link + if (itemsDetails.has("ItemUrl")) { + entry.setField(StandardField.URL, itemsDetails.getString("ItemUrl")); + } + // Date + if (itemsDetails.has("Date") && !entry.hasField(StandardField.DATE) && !entry.hasField(StandardField.YEAR)) { + entry.setField(StandardField.DATE, itemsDetails.getString("Date")); + } + + } + + } + return entry; + } + + public BibEntry jsonResultToBibEntry(JSONObject item) { + BibEntry entry = new BibEntry(); + + if ("Book".equals(item.optString("Genre"))) { + entry.setType(StandardEntryType.Book); + } else { + entry.setType(StandardEntryType.Article); + } + + // Title + if (item.has("Title")) { + entry.setField(StandardField.TITLE, item.optString("Title")); + } + // Authors + if (item.has("Authors")) { + JSONArray authors = item.getJSONArray("Authors"); + List authorList = new ArrayList<>(); + for (int i = 0; i < authors.length(); i++) { + if (authors.getJSONObject(i).has("Name")) { + authorList.add(authors.getJSONObject(i).getString("Name")); + } else { + LOGGER.debug("Empty author name."); + } + } + entry.setField(StandardField.AUTHOR, String.join(" and ", authorList)); + } else { + LOGGER.debug("Empty author name"); + } + // Pages + if (item.has("PageRange")) { + entry.setField(StandardField.PAGES, item.getString("PageRange")); + } else { + LOGGER.debug("Empty pages number"); + } + + // Publisher Place + if (item.has("PublisherPlace")) { + entry.setField(StandardField.PUBSTATE, item.getString("PublisherPlace")); + } else { + LOGGER.debug("Empty Publisher Place"); + } + // Publisher Name + if (item.has("PublisherName")) { + entry.setField(StandardField.PUBLISHER, item.getString("PublisherName")); + } else { + LOGGER.debug("Empty Publisher Name"); + } + + // Date + if (item.has("Date")) { + entry.setField(StandardField.DATE, item.getString("Date")); + } else if (item.has("PublicationDate")) { + entry.setField(StandardField.YEAR, item.getString("PublicationDate")); + } else { + LOGGER.debug("Empty date"); + } + + // Journal Title + if (item.has("ContainerTitle")) { + entry.setField(StandardField.JOURNALTITLE, item.getString("ContainerTitle")); + } else { + LOGGER.debug("Empty journal name"); + } + + // Volumes + if (item.has("Volume")) { + entry.setField(StandardField.VOLUME, item.getString("Volume")); + } else { + LOGGER.debug("Empty volume number"); + } + return entry; + + } + + @Override + public Parser getParser() { + return inputStream -> { + JSONObject response = JsonReader.toJsonObject(inputStream); + if (response.isEmpty()) { + return Collections.emptyList(); + } + String errorMessage = response.getString("ErrorMessage"); + if (!errorMessage.isBlank()) { + return Collections.emptyList(); + } + + JSONArray items = response.getJSONArray("Result"); + List entries = new ArrayList<>(items.length()); + for (int i = 0; i < items.length(); i++) { + JSONObject item = items.getJSONObject(i); + BibEntry entry = jsonResultToBibEntry(item); + try { + entry = getMostDetails(item, entry); + } catch (JSONException | IOException | URISyntaxException exception) { + throw new ParseException("Error when parse entry", exception); + } + entries.add(entry); + } + return entries; + }; + } + + @Override + public URL getURLForQuery(QueryNode luceneQuery) throws URISyntaxException, MalformedURLException, FetcherException { + URIBuilder uriBuilder = new URIBuilder(getBaseURL().toURI()); + BiodiversityLibraryTransformer transformer = new BiodiversityLibraryTransformer(); + transformer.transformLuceneQuery(luceneQuery).orElse(""); + uriBuilder.addParameter("op", "PublicationSearchAdvanced"); + + if (transformer.getAuthor().isPresent()) { + uriBuilder.addParameter("authorname", transformer.getAuthor().get()); + } + + if (transformer.getTitle().isPresent()) { + uriBuilder.addParameter("title", transformer.getTitle().get()); + uriBuilder.addParameter("titleop", "all"); + } + if (transformer.getTitle().isEmpty() && transformer.getAuthor().isEmpty()) { + throw new FetcherException("Must add author or title"); + } + return uriBuilder.build().toURL(); + + } +} diff --git a/src/main/java/org/jabref/logic/importer/fetcher/transformers/BiodiversityLibraryTransformer.java b/src/main/java/org/jabref/logic/importer/fetcher/transformers/BiodiversityLibraryTransformer.java new file mode 100644 index 000000000000..180d8be7ea16 --- /dev/null +++ b/src/main/java/org/jabref/logic/importer/fetcher/transformers/BiodiversityLibraryTransformer.java @@ -0,0 +1,97 @@ +package org.jabref.logic.importer.fetcher.transformers; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import org.jabref.model.strings.StringUtil; + +public class BiodiversityLibraryTransformer extends YearRangeByFilteringQueryTransformer { + + private static final List STOP_WORDS = List.of("a", "and", "for", "or", "with"); + + // These have to be integrated into the IEEE query URL as these are just supported as query parameters + // Journal is wrapped in quotes by the transformer + private String journal; + private String articleNumber; + private String author; + private String title; + + @Override + protected String getLogicalAndOperator() { + return " AND "; + } + + @Override + protected String getLogicalOrOperator() { + return " OR "; + } + + @Override + protected String getLogicalNotOperator() { + return " NOT "; + } + + @Override + protected String handleAuthor(String author) { + this.author = author; +// return createKeyValuePair("authorname", this.author); + return StringUtil.quoteStringIfSpaceIsContained(author); + } + + @Override + protected String handleTitle(String title) { + this.title = title; + return StringUtil.quoteStringIfSpaceIsContained(title); + } + + @Override + protected String handleJournal(String journal) { + this.journal = journal; + return StringUtil.quoteStringIfSpaceIsContained(journal); + } + + @Override + protected String handleYear(String year) { + startYear = Math.min(startYear, Integer.parseInt(year)); + endYear = Math.max(endYear, Integer.parseInt(year)); + return ""; + } + + @Override + protected Optional handleOtherField(String fieldAsString, String term) { + return switch (fieldAsString) { + case "article_number" -> handleArticleNumber(term); + default -> super.handleOtherField(fieldAsString, term); + }; + } + + @Override + protected Optional handleUnFieldedTerm(String term) { + if (STOP_WORDS.contains(term)) { + return Optional.empty(); + } + return super.handleUnFieldedTerm(term); + } + + private Optional handleArticleNumber(String term) { + articleNumber = term; + return Optional.empty(); + } + + public Optional getJournal() { + return Objects.isNull(journal) ? Optional.empty() : Optional.of(journal); + } + + public Optional getArticleNumber() { + return Objects.isNull(articleNumber) ? Optional.empty() : Optional.of(articleNumber); + } + + public Optional getTitle() { + return Objects.isNull(title) ? Optional.empty() : Optional.of(title); + } + + public Optional getAuthor() { + return Objects.isNull(author) ? Optional.empty() : Optional.of(author); + } +} diff --git a/src/main/java/org/jabref/logic/util/BuildInfo.java b/src/main/java/org/jabref/logic/util/BuildInfo.java index 47ef962f21a2..7a1cf33b9aff 100644 --- a/src/main/java/org/jabref/logic/util/BuildInfo.java +++ b/src/main/java/org/jabref/logic/util/BuildInfo.java @@ -29,6 +29,7 @@ public final class BuildInfo { public final String scienceDirectApiKey; public final String minRequiredJavaVersion; public final boolean allowJava9; + public final String biodiversityHeritageApiKey; public BuildInfo() { this("/build.properties"); @@ -57,6 +58,7 @@ public BuildInfo(String path) { scienceDirectApiKey = BuildInfo.getValue(properties, "scienceDirectApiKey", "fb82f2e692b3c72dafe5f4f1fa0ac00b"); minRequiredJavaVersion = properties.getProperty("minRequiredJavaVersion", "1.8"); allowJava9 = "true".equals(properties.getProperty("allowJava9", "true")); + biodiversityHeritageApiKey = BuildInfo.getValue(properties, "biodiversityHeritageApiKey", "36b910b6-2eb3-46f2-b64c-9abc149925ba"); } private static String getValue(Properties properties, String key, String defaultValue) { diff --git a/src/main/resources/build.properties b/src/main/resources/build.properties index ec86a4cab545..efe52c53aad6 100644 --- a/src/main/resources/build.properties +++ b/src/main/resources/build.properties @@ -5,3 +5,4 @@ azureInstrumentationKey=${azureInstrumentationKey} springerNatureAPIKey=${springerNatureAPIKey} astrophysicsDataSystemAPIKey=${astrophysicsDataSystemAPIKey} ieeeAPIKey=${ieeeAPIKey} +biodiversityHeritageApiKey=${biodiversityHeritageApiKey} diff --git a/src/test/java/org/jabref/logic/importer/fetcher/BiodiversityLibraryTest.java b/src/test/java/org/jabref/logic/importer/fetcher/BiodiversityLibraryTest.java new file mode 100644 index 000000000000..80851ee38142 --- /dev/null +++ b/src/test/java/org/jabref/logic/importer/fetcher/BiodiversityLibraryTest.java @@ -0,0 +1,169 @@ +package org.jabref.logic.importer.fetcher; + +import java.net.MalformedURLException; +import java.net.URISyntaxException; + +import org.jabref.logic.util.BuildInfo; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.entry.types.StandardEntryType; +import org.jabref.testutils.category.FetcherTest; + +import kong.unirest.json.JSONObject; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +@SuppressWarnings("checkstyle:EmptyLineSeparator") +@FetcherTest +public class BiodiversityLibraryTest { + private final BiodiversityLibrary fetcher = new BiodiversityLibrary(); + private final String BASE_URL = "https://www.biodiversitylibrary.org/api3?"; + private final String RESPONSE_FORMAT = "&format=json"; + private final BuildInfo buildInfo = new BuildInfo(); + + @Test + public void testGetName() { + assertEquals("Biodiversity Heritage", fetcher.getName()); + assertNotEquals("Biodiversity Heritage Library", fetcher.getName()); + assertNotEquals("Biodiversity Library", fetcher.getName()); + } + + @Test + public void biodiversityHeritageApiKeyIsNotEmpty() { + BuildInfo buildInfo = new BuildInfo(); + assertNotNull(buildInfo.biodiversityHeritageApiKey); + } + + @Test + public void baseURLConstruction() throws MalformedURLException, URISyntaxException { + + String baseURL = fetcher.getBaseURL().toString(); + assertEquals(BASE_URL + .concat("apikey=") + .concat(buildInfo.biodiversityHeritageApiKey) + .concat(RESPONSE_FORMAT), + baseURL); + + } + + @Test + public void getPartMetadaUrl() throws MalformedURLException, URISyntaxException { + String id = "1234"; + String expected_base = (BASE_URL + .concat("apikey=") + .concat(buildInfo.biodiversityHeritageApiKey) + .concat(RESPONSE_FORMAT) + .concat("&op=GetPartMetadata&pages=f&names=f") + .concat("&id=") + ); + String expected = expected_base.concat(id); + + assertEquals(expected, fetcher.getPartMetadataURL(id).toString()); + + id = "4321"; + expected = expected_base.concat(id); + assertEquals(expected, fetcher.getPartMetadataURL(id).toString()); + + id = "331"; + expected = expected_base.concat(id); + assertEquals(expected, fetcher.getPartMetadataURL(id).toString()); + + } + + @Test + public void getItemMetadaUrl() throws MalformedURLException, URISyntaxException { + String id = "1234"; + String expected_base = (BASE_URL + .concat("apikey=") + .concat(buildInfo.biodiversityHeritageApiKey) + .concat(RESPONSE_FORMAT) + .concat("&op=GetItemMetadata&pages=f&ocr=f&ocr=f") + .concat("&id=") + ); + String expected = expected_base.concat(id); + + assertEquals(expected, fetcher.getItemMetadataURL(id).toString()); + + id = "4321"; + expected = expected_base.concat(id); + assertEquals(expected, fetcher.getItemMetadataURL(id).toString()); + + id = "331"; + expected = expected_base.concat(id); + assertEquals(expected, fetcher.getItemMetadataURL(id).toString()); + + } + + @Test + public void jsonResultToBibEntry() { + JSONObject input = new JSONObject("{\n\"BHLType\": \"Part\",\n\"FoundIn\": \"Metadata\",\n\"Volume\": \"3\",\n\"Authors\": [\n{\n\"Name\": \"Dimmock, George,\"\n}\n],\n\"PartUrl\": \"https://www.biodiversitylibrary.org/part/181199\",\n\"PartID\": \"181199\",\n\"Genre\": \"Article\",\n\"Title\": \"The Cocoons of Cionus Scrophulariae\",\n\"ContainerTitle\": \"Psyche.\",\n\"Date\": \"1882\",\n\"PageRange\": \"411--413\"\n}"); + BibEntry expect = new BibEntry(StandardEntryType.Article) + .withField(StandardField.TITLE, "The Cocoons of Cionus Scrophulariae") + .withField(StandardField.AUTHOR, "Dimmock, George,") + .withField(StandardField.PAGES, "411--413") + .withField(StandardField.DATE, "1882") + .withField(StandardField.JOURNALTITLE, "Psyche.") + .withField(StandardField.VOLUME, "3"); + + assertEquals(expect, fetcher.jsonResultToBibEntry(input)); + + input = new JSONObject("{\n" + + " \"BHLType\": \"Item\",\n" + + " \"FoundIn\": \"Metadata\",\n" + + " \"ItemID\": \"174333\",\n" + + " \"TitleID\": \"96205\",\n" + + " \"ItemUrl\": \"https://www.biodiversitylibrary.org/item/174333\",\n" + + " \"TitleUrl\": \"https://www.biodiversitylibrary.org/bibliography/96205\",\n" + + " \"MaterialType\": \"Published material\",\n" + + " \"PublisherPlace\": \"Salisbury\",\n" + + " \"PublisherName\": \"Frederick A. Blake,\",\n" + + " \"PublicationDate\": \"1861\",\n" + + " \"Authors\": [\n" + + " {\n" + + " \"Name\": \"George, George\"\n" + + " }\n" + + " ],\n" + + " \"Genre\": \"Book\",\n" + + " \"Title\": \"Potatoes : the poor man's own crop : illustrated with plates, showing the decay and disease of the potatoe [sic] : with hints to improve the land and life of the poor man : published to aid the Industrial Marlborough Exhibition\"\n" + + " }"); + expect = new BibEntry(StandardEntryType.Book) + .withField(StandardField.TITLE, "Potatoes : the poor man's own crop : illustrated with plates, showing the decay and disease of the potatoe [sic] : with hints to improve the land and life of the poor man : published to aid the Industrial Marlborough Exhibition") + .withField(StandardField.AUTHOR, "George, George") + .withField(StandardField.YEAR, "1861") + .withField(StandardField.PUBSTATE, "Salisbury") + .withField(StandardField.PUBLISHER, "Frederick A. Blake,"); + assertEquals(expect, fetcher.jsonResultToBibEntry(input)); + + input = new JSONObject("{\n" + + " \"BHLType\": \"Item\",\n" + + " \"FoundIn\": \"Metadata\",\n" + + " \"ItemID\": \"200116\",\n" + + " \"TitleID\": \"115108\",\n" + + " \"ItemUrl\": \"https://www.biodiversitylibrary.org/item/200116\",\n" + + " \"TitleUrl\": \"https://www.biodiversitylibrary.org/bibliography/115108\",\n" + + " \"MaterialType\": \"Published material\",\n" + + " \"PublisherPlace\": \"Washington\",\n" + + " \"PublisherName\": \"Government Prining Office,\",\n" + + " \"PublicationDate\": \"1911\",\n" + + " \"Authors\": [\n" + + " {\n" + + " \"Name\": \"Whitaker, George M. (George Mason)\"\n" + + " }\n" + + " ],\n" + + " \"Genre\": \"Book\",\n" + + " \"Title\": \"The extra cost of producing clean milk.\"\n" + + " }"); + expect = new BibEntry(StandardEntryType.Book) + .withField(StandardField.TITLE, "The extra cost of producing clean milk.") + .withField(StandardField.AUTHOR, "Whitaker, George M. (George Mason)") + .withField(StandardField.YEAR, "1911") + .withField(StandardField.PUBSTATE, "Washington") + .withField(StandardField.PUBLISHER, "Government Prining Office,"); + assertEquals(expect, fetcher.jsonResultToBibEntry(input)); + + } + +}