package de.geeksfactory.opacclient.apis; import org.json.JSONException; import org.json.JSONObject; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.parser.Parser; import org.jsoup.select.Elements; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import de.geeksfactory.opacclient.i18n.StringProvider; import de.geeksfactory.opacclient.networking.HttpClientFactory; import de.geeksfactory.opacclient.networking.NotReachableException; import de.geeksfactory.opacclient.objects.Account; import de.geeksfactory.opacclient.objects.AccountData; import de.geeksfactory.opacclient.objects.Detail; import de.geeksfactory.opacclient.objects.DetailedItem; import de.geeksfactory.opacclient.objects.Filter; import de.geeksfactory.opacclient.objects.Filter.Option; import de.geeksfactory.opacclient.objects.Library; import de.geeksfactory.opacclient.objects.SearchRequestResult; import de.geeksfactory.opacclient.objects.SearchResult; import de.geeksfactory.opacclient.objects.SearchResult.MediaType; import de.geeksfactory.opacclient.searchfields.BarcodeSearchField; import de.geeksfactory.opacclient.searchfields.CheckboxSearchField; import de.geeksfactory.opacclient.searchfields.SearchField; import de.geeksfactory.opacclient.searchfields.SearchField.Meaning; import de.geeksfactory.opacclient.searchfields.SearchQuery; import de.geeksfactory.opacclient.searchfields.TextSearchField; import de.geeksfactory.opacclient.utils.ISBNTools; public class SRU extends BaseApi implements OpacApi { protected static HashMap<String, MediaType> defaulttypes = new HashMap<>(); static { defaulttypes.put("print", MediaType.BOOK); defaulttypes.put("large print", MediaType.BOOK); defaulttypes.put("braille", MediaType.UNKNOWN); defaulttypes.put("electronic", MediaType.EBOOK); defaulttypes.put("microfiche", MediaType.UNKNOWN); defaulttypes.put("microfilm", MediaType.UNKNOWN); defaulttypes.put("Tontraeger", MediaType.AUDIOBOOK); } protected String opac_url = ""; protected JSONObject data; protected int resultcount = 10; protected String shareUrl; private String currentSearchParams; private Document searchDoc; private HashMap<String, String> searchQueries = new HashMap<>(); private String idSearchQuery; @Override public void init(Library lib, HttpClientFactory httpClientFactory) { super.init(lib, httpClientFactory); this.data = lib.getData(); try { this.opac_url = data.getString("baseurl"); JSONObject searchQueriesJson = data.getJSONObject("searchqueries"); addSearchQueries(searchQueriesJson); if (data.has("sharelink")) { shareUrl = data.getString("sharelink"); } } catch (JSONException e) { throw new RuntimeException(e); } } private void addSearchQueries(JSONObject searchQueriesJson) { String[] queries = {KEY_SEARCH_QUERY_FREE, KEY_SEARCH_QUERY_TITLE, KEY_SEARCH_QUERY_AUTHOR, KEY_SEARCH_QUERY_KEYWORDA, KEY_SEARCH_QUERY_KEYWORDB, KEY_SEARCH_QUERY_BRANCH, KEY_SEARCH_QUERY_HOME_BRANCH, KEY_SEARCH_QUERY_ISBN, KEY_SEARCH_QUERY_YEAR, KEY_SEARCH_QUERY_YEAR_RANGE_START, KEY_SEARCH_QUERY_YEAR_RANGE_END, KEY_SEARCH_QUERY_SYSTEM, KEY_SEARCH_QUERY_AUDIENCE, KEY_SEARCH_QUERY_PUBLISHER, KEY_SEARCH_QUERY_CATEGORY, KEY_SEARCH_QUERY_BARCODE, KEY_SEARCH_QUERY_LOCATION, KEY_SEARCH_QUERY_DIGITAL}; for (String query : queries) { if (searchQueriesJson.has(query)) { try { searchQueries .put(query, searchQueriesJson.getString(query)); } catch (JSONException e) { e.printStackTrace(); } } } try { if (searchQueriesJson.has("id")) { idSearchQuery = searchQueriesJson.getString("id"); } } catch (JSONException e) { e.printStackTrace(); } } protected int addParameters(Map<String, String> query, String key, String searchkey, StringBuilder params, int index) { if (!query.containsKey(key) || query.get(key).equals("")) { return index; } if (index != 0) { params.append("%20and%20"); } params.append(searchkey).append("%3D").append(query.get(key)); return index + 1; } @Override public SearchRequestResult search(List<SearchQuery> queryList) throws IOException, OpacErrorException { Map<String, String> query = searchQueryListToMap(queryList); StringBuilder params = new StringBuilder(); int index = 0; start(); for (String parameter : searchQueries.keySet()) { index = addParameters(query, parameter, searchQueries.get(parameter), params, index); } if (index == 0) { throw new OpacErrorException( stringProvider.getString(StringProvider.NO_CRITERIA_INPUT)); } currentSearchParams = params.toString(); String xml = httpGet(opac_url + "?version=1.1&operation=searchRetrieve&maximumRecords=" + resultcount + "&recordSchema=mods&sortKeys=relevance,,1&query=" + currentSearchParams, getDefaultEncoding()); return parse_result(xml); } private SearchRequestResult parse_result(String xml) throws OpacErrorException { searchDoc = Jsoup.parse(xml, "", Parser.xmlParser()); if (searchDoc.select("diag|diagnostic").size() > 0) { throw new OpacErrorException(searchDoc.select("diag|message") .text()); } int resultcount; List<SearchResult> results = new ArrayList<>(); resultcount = Integer.valueOf(searchDoc.select("zs|numberOfRecords") .text()); Elements records = searchDoc.select("zs|records > zs|record"); int i = 0; for (Element record : records) { SearchResult sr = new SearchResult(); String title = getDetail(record, "titleInfo title"); String firstName = getDetail(record, "name > namePart[type=given]"); String lastName = getDetail(record, "name > namePart[type=family]"); String year = getDetail(record, "dateIssued"); String mType = getDetail(record, "physicalDescription > form"); String isbn = getDetail(record, "identifier[type=isbn]"); String coverUrl = getDetail(record, "url[displayLabel=C Cover]"); String additionalInfo = firstName + " " + lastName + ", " + year; sr.setInnerhtml("<b>" + title + "</b><br>" + additionalInfo); sr.setType(defaulttypes.get(mType)); sr.setNr(i); sr.setId(getDetail(record, "recordIdentifier")); if (coverUrl.equals("")) { sr.setCover(ISBNTools.getAmazonCoverURL(isbn, false)); } else { sr.setCover(coverUrl); } results.add(sr); i++; } return new SearchRequestResult(results, resultcount, 1); } private String getDetail(Element record, String selector) { if (record.select(selector).size() > 0) { return record.select(selector).first().text(); } else { return ""; } } @Override public SearchRequestResult filterResults(Filter filter, Option option) throws IOException { return null; } @Override public SearchRequestResult searchGetPage(int page) throws IOException, OpacErrorException { if (!initialised) { start(); } String xml = httpGet(opac_url + "?version=1.1&operation=searchRetrieve&maximumRecords=" + resultcount + "&recordSchema=mods&sortKeys=relevance,,1&startRecord=" + String.valueOf(page * resultcount + 1) + "&query=" + currentSearchParams, getDefaultEncoding()); return parse_result(xml); } @Override public DetailedItem getResultById(String id, String homebranch) throws IOException, OpacErrorException { if (idSearchQuery != null) { String xml = httpGet(opac_url + "?version=1.1&operation=searchRetrieve&maximumRecords=" + resultcount + "&recordSchema=mods&sortKeys=relevance,,1&query=" + idSearchQuery + "%3D" + id, getDefaultEncoding()); searchDoc = Jsoup.parse(xml, "", Parser.xmlParser()); if (searchDoc.select("diag|diagnostic").size() > 0) { throw new OpacErrorException(searchDoc.select("diag|message") .text()); } if (searchDoc.select("zs|record").size() != 1) { // should not // happen throw new OpacErrorException( stringProvider.getString(StringProvider.INTERNAL_ERROR)); } return parse_detail(searchDoc.select("zs|record").first()); } else { return null; } } private DetailedItem parse_detail(Element record) { String title = getDetail(record, "titleInfo title"); String firstName = getDetail(record, "name > namePart[type=given]"); String lastName = getDetail(record, "name > namePart[type=family]"); String year = getDetail(record, "dateIssued"); String desc = getDetail(record, "abstract"); String isbn = getDetail(record, "identifier[type=isbn]"); String coverUrl = getDetail(record, "url[displayLabel=C Cover]"); DetailedItem item = new DetailedItem(); item.setTitle(title); item.addDetail(new Detail("Autor", firstName + " " + lastName)); item.addDetail(new Detail("Jahr", year)); item.addDetail(new Detail("Beschreibung", desc)); if (coverUrl.equals("") && isbn.length() > 0) { item.setCover(ISBNTools.getAmazonCoverURL(isbn, true)); } else if (!coverUrl.equals("")) { item.setCover(coverUrl); } if (isbn.length() > 0) { item.addDetail(new Detail("ISBN", isbn)); } return item; } @Override public DetailedItem getResult(int position) throws IOException, OpacErrorException { return parse_detail(searchDoc.select("zs|records > zs|record").get( position)); } @Override public ReservationResult reservation(DetailedItem item, Account account, int useraction, String selection) throws IOException { return null; } @Override public ProlongResult prolong(String media, Account account, int useraction, String selection) throws IOException { return null; } @Override public ProlongAllResult prolongAll(Account account, int useraction, String selection) throws IOException { return null; } @Override public AccountData account(Account account) throws IOException, JSONException, OpacErrorException { return null; } @Override public List<SearchField> parseSearchFields() throws OpacErrorException, NotReachableException { List<SearchField> searchFields = new ArrayList<>(); Set<String> fieldsCompat = searchQueries.keySet(); if (fieldsCompat.contains(KEY_SEARCH_QUERY_FREE)) { SearchField field = new TextSearchField(KEY_SEARCH_QUERY_FREE, "", false, false, "Freie Suche", true, false); field.setMeaning(Meaning.FREE); searchFields.add(field); } if (fieldsCompat.contains(KEY_SEARCH_QUERY_TITLE)) { SearchField field = new TextSearchField(KEY_SEARCH_QUERY_TITLE, "Titel", false, false, "Stichwort", false, false); field.setMeaning(Meaning.TITLE); searchFields.add(field); } if (fieldsCompat.contains(KEY_SEARCH_QUERY_AUTHOR)) { SearchField field = new TextSearchField(KEY_SEARCH_QUERY_AUTHOR, "Verfasser", false, false, "Nachname, Vorname", false, false); field.setMeaning(Meaning.AUTHOR); searchFields.add(field); } if (fieldsCompat.contains(KEY_SEARCH_QUERY_PUBLISHER)) { SearchField field = new TextSearchField(KEY_SEARCH_QUERY_PUBLISHER, "Verlag", false, false, "", false, false); field.setMeaning(Meaning.PUBLISHER); searchFields.add(field); } if (fieldsCompat.contains(KEY_SEARCH_QUERY_DIGITAL)) { SearchField field = new CheckboxSearchField( KEY_SEARCH_QUERY_DIGITAL, "nur digitale Medien", false); field.setMeaning(Meaning.DIGITAL); searchFields.add(field); } if (fieldsCompat.contains(KEY_SEARCH_QUERY_AVAILABLE)) { SearchField field = new CheckboxSearchField( KEY_SEARCH_QUERY_AVAILABLE, "nur verfügbare Medien", false); field.setMeaning(Meaning.AVAILABLE); searchFields.add(field); } if (fieldsCompat.contains(KEY_SEARCH_QUERY_ISBN)) { SearchField field = new BarcodeSearchField(KEY_SEARCH_QUERY_ISBN, "Strichcode", false, false, "ISBN"); field.setMeaning(Meaning.ISBN); searchFields.add(field); } if (fieldsCompat.contains(KEY_SEARCH_QUERY_BARCODE)) { SearchField field = new BarcodeSearchField( KEY_SEARCH_QUERY_BARCODE, "Strichcode", false, true, "Buchungsnr."); field.setMeaning(Meaning.BARCODE); searchFields.add(field); } if (fieldsCompat.contains(KEY_SEARCH_QUERY_YEAR)) { SearchField field = new TextSearchField(KEY_SEARCH_QUERY_YEAR, "Jahr", false, false, "", false, true); field.setMeaning(Meaning.YEAR); searchFields.add(field); } if (fieldsCompat.contains(KEY_SEARCH_QUERY_YEAR_RANGE_START)) { SearchField field = new TextSearchField( KEY_SEARCH_QUERY_YEAR_RANGE_START, "Jahr", false, false, "von", false, true); field.setMeaning(Meaning.YEAR); searchFields.add(field); } if (fieldsCompat.contains(KEY_SEARCH_QUERY_YEAR_RANGE_END)) { SearchField field = new TextSearchField( KEY_SEARCH_QUERY_YEAR_RANGE_END, "Jahr", false, true, "bis", false, true); field.setMeaning(Meaning.YEAR); searchFields.add(field); } if (fieldsCompat.contains(KEY_SEARCH_QUERY_PUBLISHER)) { SearchField field = new TextSearchField(KEY_SEARCH_QUERY_PUBLISHER, "Verlag", false, false, "", false, false); field.setMeaning(Meaning.PUBLISHER); searchFields.add(field); } if (fieldsCompat.contains(KEY_SEARCH_QUERY_KEYWORDA)) { SearchField field = new TextSearchField(KEY_SEARCH_QUERY_KEYWORDA, "Schlagwort", true, false, "", false, false); field.setMeaning(Meaning.KEYWORD); searchFields.add(field); } if (fieldsCompat.contains(KEY_SEARCH_QUERY_KEYWORDB)) { SearchField field = new TextSearchField(KEY_SEARCH_QUERY_KEYWORDB, "Schlagwort", true, true, "", false, false); field.setMeaning(Meaning.KEYWORD); searchFields.add(field); } if (fieldsCompat.contains(KEY_SEARCH_QUERY_SYSTEM)) { SearchField field = new TextSearchField(KEY_SEARCH_QUERY_SYSTEM, "Systematik", true, false, "", false, false); field.setMeaning(Meaning.SYSTEM); searchFields.add(field); } if (fieldsCompat.contains(KEY_SEARCH_QUERY_AUDIENCE)) { SearchField field = new TextSearchField(KEY_SEARCH_QUERY_AUDIENCE, "Interessenkreis", true, false, "", false, false); field.setMeaning(Meaning.AUDIENCE); searchFields.add(field); } if (fieldsCompat.contains(KEY_SEARCH_QUERY_LOCATION)) { SearchField field = new TextSearchField(KEY_SEARCH_QUERY_LOCATION, "Ort", false, false, "", false, false); field.setMeaning(Meaning.LOCATION); searchFields.add(field); } //noinspection StatementWithEmptyBody if (fieldsCompat.contains(KEY_SEARCH_QUERY_ORDER)) { // TODO: Implement this (was this even usable before?) } return searchFields; } @Override public String getShareUrl(String id, String title) { if (shareUrl != null) { return String.format(shareUrl, id); } else { return null; } } @Override public int getSupportFlags() { return SUPPORT_FLAG_ENDLESS_SCROLLING; } @Override public CancelResult cancel(String media, Account account, int useraction, String selection) throws IOException, OpacErrorException { return null; } @Override protected String getDefaultEncoding() { return "UTF-8"; } @Override public void checkAccountData(Account account) throws IOException, JSONException, OpacErrorException { // TODO Auto-generated method stub } @Override public void setLanguage(String language) { // TODO Auto-generated method stub } @Override public Set<String> getSupportedLanguages() throws IOException { // TODO Auto-generated method stub return null; } }