/** * Copyright (C) 2013 by Raphael Michel under the MIT license: * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the Software * is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ package de.geeksfactory.opacclient.apis; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.utils.URLEncodedUtils; import org.apache.http.message.BasicNameValuePair; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.jsoup.Jsoup; import org.jsoup.nodes.Comment; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.nodes.TextNode; import org.jsoup.select.Elements; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.net.URLEncoder; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.regex.Pattern; import de.geeksfactory.opacclient.i18n.StringProvider; import de.geeksfactory.opacclient.networking.HttpClientFactory; import de.geeksfactory.opacclient.networking.HttpUtils; import de.geeksfactory.opacclient.networking.NotReachableException; import de.geeksfactory.opacclient.objects.Account; import de.geeksfactory.opacclient.objects.AccountData; import de.geeksfactory.opacclient.objects.Copy; 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.LentItem; import de.geeksfactory.opacclient.objects.Library; import de.geeksfactory.opacclient.objects.ReservedItem; import de.geeksfactory.opacclient.objects.SearchRequestResult; import de.geeksfactory.opacclient.objects.SearchResult; import de.geeksfactory.opacclient.objects.SearchResult.MediaType; import de.geeksfactory.opacclient.objects.Volume; import de.geeksfactory.opacclient.reporting.Report; import de.geeksfactory.opacclient.reporting.ReportHandler; import de.geeksfactory.opacclient.searchfields.DropdownSearchField; 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.JsonKeyIterator; /** * OpacApi implementation for Bibliotheca Web Opacs, originally developed by BOND, now owned by * OCLC. Known to work well with Web Opac versions from 2.6, maybe older, to 2.8 */ public class Bibliotheca extends BaseApi { protected static HashMap<String, MediaType> defaulttypes = new HashMap<>(); static { defaulttypes.put("mbuchs", MediaType.BOOK); defaulttypes.put("cdkl", MediaType.CD); defaulttypes.put("cd", MediaType.CD); defaulttypes.put("cdromkl", MediaType.CD_SOFTWARE); defaulttypes.put("mcdroms", MediaType.CD); defaulttypes.put("ekl", MediaType.EBOOK); defaulttypes.put("emedium", MediaType.EBOOK); defaulttypes.put("monleihe", MediaType.EBOOK); defaulttypes.put("mdivis", MediaType.EBOOK); defaulttypes.put("mbmonos", MediaType.PACKAGE_BOOKS); defaulttypes.put("mbuechers", MediaType.PACKAGE_BOOKS); defaulttypes.put("mdvds", MediaType.DVD); defaulttypes.put("mdvd", MediaType.DVD); defaulttypes.put("blu-ray--disc_s_35x35", MediaType.BLURAY); defaulttypes.put("mblurays", MediaType.BLURAY); defaulttypes.put("mfilms", MediaType.MOVIE); defaulttypes.put("mvideos", MediaType.MOVIE); defaulttypes.put("mhoerbuchs", MediaType.AUDIOBOOK); defaulttypes.put("mmusikcds", MediaType.CD_MUSIC); defaulttypes.put("mcdns", MediaType.CD_MUSIC); defaulttypes.put("mnoten1s", MediaType.SCORE_MUSIC); defaulttypes.put("munselbs", MediaType.UNKNOWN); defaulttypes.put("mztgs", MediaType.NEWSPAPER); defaulttypes.put("zeitung", MediaType.NEWSPAPER); defaulttypes.put("spielekl", MediaType.BOARDGAME); defaulttypes.put("mspiels", MediaType.BOARDGAME); defaulttypes.put("tafelkl", MediaType.SCHOOL_VERSION); defaulttypes.put("spiel_konsol", MediaType.GAME_CONSOLE); defaulttypes.put("wii", MediaType.GAME_CONSOLE); } protected final long SESSION_LIFETIME = 1000 * 60 * 3; protected String opac_url = ""; protected JSONObject data; protected Library library; protected long logged_in; protected Account logged_in_as; protected String _res_target = "vorbesttranskonto"; protected String branch_inputfield = "zstauswahl"; @Override public List<SearchField> parseSearchFields() throws IOException, JSONException { if (!initialised) { start(); } List<SearchField> fields = new ArrayList<>(); // Read branches and media types List<NameValuePair> nameValuePairs = new ArrayList<>(2); nameValuePairs.add(new BasicNameValuePair("link_profis.x", "0")); nameValuePairs.add(new BasicNameValuePair("link_profis.y", "1")); String html = httpPost(opac_url + "/index.asp", new UrlEncodedFormEntity(nameValuePairs), getDefaultEncoding()); Document doc = Jsoup.parse(html); Elements fieldElems = doc.select(".suchfeldinhalt"); for (Element fieldElem : fieldElems) { String name = fieldElem.select(".suchfeld_inhalt_titel label") .text(); String hint = ""; if (fieldElem.select(".suchfeld_inhalt_input").size() > 0) { List<TextNode> textNodes = fieldElem .select(".suchfeld_inhalt_input").first().textNodes(); if (textNodes.size() > 0) { for (TextNode node : textNodes) { String text = node.getWholeText().replace("\n", ""); if (!text.equals("")) { hint = node.getWholeText().replace("\n", ""); break; } } } } Elements inputs = fieldElem .select(".suchfeld_inhalt_input input[type=text], " + ".suchfeld_inhalt_input select"); if (inputs.size() == 1) { SearchField field = createSearchField(name, hint, inputs.get(0)); Elements radios = fieldElem.select("input[type=radio]"); if (field instanceof TextSearchField && radios.size() > 0) { TextSearchField tf = (TextSearchField) field; if (radios.get(0).attr("value").equals("stich")) { tf.setFreeSearch(true); if (fieldElem.select("label[for=stichtit_sich]").size() > 0) { tf.setHint(fieldElem.select("label[for=stichtit_sich]").text().trim()); } JSONObject addData = new JSONObject(); JSONObject params = new JSONObject(); params.put("stichtit", "stich"); addData.put("additional_params", params); tf.setData(addData); } if (radios.size() == 2 && radios.get(1).attr("value").equals("titel")) { TextSearchField tf2 = new TextSearchField(); tf2.setId(tf.getId()); if (fieldElem.select("label[for=stichtit_titel]").size() > 0) { tf2.setDisplayName( fieldElem.select("label[for=stichtit_titel]").text().trim()); } JSONObject addData = new JSONObject(); JSONObject params = new JSONObject(); params.put("stichtit", "titel"); addData.put("additional_params", params); tf2.setData(addData); fields.add(tf2); } } fields.add(field); } else if (inputs.size() == 2 && inputs.select("input[type=text]").size() == 2) { // Two text fields, e.g. year from/to or two keywords fields.add(createSearchField(name, hint, inputs.get(0))); TextSearchField secondField = (TextSearchField) createSearchField( name, hint, inputs.get(1)); secondField.setHalfWidth(true); fields.add(secondField); } else if (inputs.size() == 2 && inputs.get(0).tagName().equals("select") && inputs.get(1).tagName().equals("input") && inputs.get(0).attr("name").equals("feld1")) { // A dropdown to select from different search field types. // Break it down into single text fields. for (Element option : inputs.get(0).select("option")) { TextSearchField field = new TextSearchField(); field.setHint(hint); field.setDisplayName(option.text()); field.setId(inputs.get(1).attr("name") + "$" + option.attr("value")); JSONObject data = new JSONObject(); JSONObject params = new JSONObject(); params.put(inputs.get(0).attr("name"), option.attr("value")); data.put("additional_params", params); field.setData(data); fields.add(field); } } } DropdownSearchField orderField = new DropdownSearchField("orderselect", stringProvider.getString(StringProvider.ORDER), false, null); orderField.addDropdownValue("1", stringProvider.getString(StringProvider.ORDER_DEFAULT)); orderField.addDropdownValue("2:desc", stringProvider.getString(StringProvider.ORDER_YEAR_DESC)); orderField.addDropdownValue("2:asc", stringProvider.getString(StringProvider.ORDER_YEAR_ASC)); orderField.addDropdownValue("3:desc", stringProvider.getString(StringProvider.ORDER_CATEGORY_DESC)); orderField.addDropdownValue("3:asc", stringProvider.getString(StringProvider.ORDER_CATEGORY_ASC)); orderField.setMeaning(Meaning.ORDER); fields.add(orderField); return fields; } private SearchField createSearchField(String name, String hint, Element input) { if (input.tagName().equals("input") && input.attr("type").equals("text")) { TextSearchField field = new TextSearchField(); field.setDisplayName(name); field.setHint(hint); field.setId(input.attr("name")); return field; } else if (input.tagName().equals("select")) { DropdownSearchField field = new DropdownSearchField(); field.setDisplayName(name); field.setId(input.attr("name")); for (Element option : input.select("option")) { field.addDropdownValue(option.attr("value"), option.text()); } return field; } else { return null; } } @Override public void start() throws IOException { String db = ""; if (data.has("db")) { try { db = "&db=" + data.getString("db"); } catch (JSONException e) { e.printStackTrace(); } } httpGet(opac_url + "/woload.asp?lkz=1&nextpage=" + db, getDefaultEncoding()); super.start(); } @Override public void init(Library lib, HttpClientFactory httpClientFactory) { super.init(lib, httpClientFactory); this.library = lib; this.data = lib.getData(); try { this.opac_url = data.getString("baseurl"); } catch (JSONException e) { throw new RuntimeException(e); } } @Override public SearchRequestResult search(List<SearchQuery> queries) throws IOException, JSONException, OpacErrorException { if (!initialised) { start(); } List<NameValuePair> nameValuePairs = new ArrayList<>(2); boolean stichtitSet = false; int ifeldCount = 0; String order = "asc"; for (SearchQuery query : queries) { if (query.getValue().equals("")) { continue; } String key = query.getKey(); if (key.contains("$")) { key = key.substring(0, key.indexOf("$")); } if (key.contains("ifeld")) { ifeldCount++; if (ifeldCount > 1) { throw new OpacErrorException( stringProvider .getString(StringProvider.COMBINATION_NOT_SUPPORTED)); } } if (key.equals("orderselect") && query.getValue().contains(":")) { nameValuePairs.add(new BasicNameValuePair("orderselect", query .getValue().split(":")[0])); order = query.getValue().split(":")[1]; } else { nameValuePairs .add(new BasicNameValuePair(key, query.getValue())); } if (query.getSearchField().getData() != null) { JSONObject data = query.getSearchField().getData(); if (data.has("additional_params")) { JSONObject params = data.getJSONObject("additional_params"); Iterator<String> keys = new JsonKeyIterator(params); while (keys.hasNext()) { String additionalKey = keys.next(); if (additionalKey.equals("stichtit")) { if (stichtitSet) { throw new OpacErrorException( stringProvider.getString( StringProvider.COMBINATION_NOT_SUPPORTED)); } stichtitSet = true; } nameValuePairs .add(new BasicNameValuePair(additionalKey, params.getString(additionalKey))); } } } } if (!stichtitSet) { nameValuePairs.add(new BasicNameValuePair("stichtit", "stich")); } nameValuePairs.add(new BasicNameValuePair("suche_starten.x", "1")); nameValuePairs.add(new BasicNameValuePair("suche_starten.y", "1")); if (data.has("db")) { try { nameValuePairs.add(new BasicNameValuePair("dbase", data .getString("db"))); } catch (JSONException e) { e.printStackTrace(); } } String html = httpPost(opac_url + "/index.asp", new UrlEncodedFormEntity(nameValuePairs), getDefaultEncoding()); if (html.contains("<a href=\"index.asp?order=" + order + "\">")) { html = httpGet(opac_url + "/index.asp?order=" + order, getDefaultEncoding()); } return parseSearch(html, 1, data); } @Override public SearchRequestResult searchGetPage(int page) throws IOException { if (!initialised) { start(); } String html = httpGet(opac_url + "/index.asp?scrollAction=" + page, getDefaultEncoding()); return parseSearch(html, page, data); } public static SearchRequestResult parseSearch(String html, int page, JSONObject data) { Document doc = Jsoup.parse(html); doc.setBaseUri(data.optString("baseurl")); Elements table = doc .select(".resulttab tr.result_trefferX, .resulttab tr.result_treffer"); List<SearchResult> results = new ArrayList<>(); for (int i = 0; i < table.size(); i++) { Element tr = table.get(i); SearchResult sr = new SearchResult(); int contentindex = 1; if (tr.select("td a img").size() > 0) { String[] fparts = tr.select("td a img").get(0).attr("src") .split("/"); String fname = fparts[fparts.length - 1]; if (data.has("mediatypes")) { try { sr.setType(MediaType.valueOf(data.getJSONObject( "mediatypes").getString(fname))); } catch (JSONException | IllegalArgumentException e) { sr.setType(defaulttypes.get(fname .toLowerCase(Locale.GERMAN).replace(".jpg", "") .replace(".gif", "").replace(".png", ""))); } } else { sr.setType(defaulttypes.get(fname .toLowerCase(Locale.GERMAN).replace(".jpg", "") .replace(".gif", "").replace(".png", ""))); } } else { if (tr.children().size() == 3) { contentindex = 2; } } sr.setInnerhtml(tr.child(contentindex).child(0).html()); sr.setNr(i); Element link = tr.child(contentindex).select("a").first(); try { if (link != null && link.attr("href").contains("detmediennr")) { Map<String, String> params = getQueryParamsFirst(link .attr("abs:href")); String nr = params.get("detmediennr"); if (Integer.parseInt(nr) > i + 1) { // Seems to be an ID… if (params.get("detDB") != null) { sr.setId("&detmediennr=" + nr + "&detDB=" + params.get("detDB")); } else { sr.setId("&detmediennr=" + nr); } } } } catch (Exception e) { } try { if (tr.child(1).childNode(0) instanceof Comment) { Comment c = (Comment) tr.child(1).childNode(0); String comment = c.getData().trim(); String id = comment.split(": ")[1]; sr.setId(id); } } catch (Exception e) { e.printStackTrace(); } results.add(sr); } int results_total = -1; if (doc.select(".result_gefunden").size() > 0) { try { results_total = Integer.parseInt(doc.select(".result_gefunden") .text().trim() .replaceAll(".*[^0-9]+([0-9]+).*", "$1")); } catch (NumberFormatException e) { e.printStackTrace(); results_total = -1; } } return new SearchRequestResult(results, results_total, page); } @Override public DetailedItem getResultById(String a, String homebranch) throws IOException { if (!initialised) { start(); } String html = httpGet(opac_url + "/index.asp?MedienNr=" + a, getDefaultEncoding()); DetailedItem result = parseResult(html, data); if (result.getId() == null) { result.setId(a); } return result; } @Override public DetailedItem getResult(int nr) throws IOException { String html = httpGet(opac_url + "/index.asp?detmediennr=" + nr, getDefaultEncoding()); return parseResult(html, data); } static DetailedItem parseResult(String html, JSONObject data) { Document doc = Jsoup.parse(html); doc.setBaseUri(data.optString("baseurl")); DetailedItem result = new DetailedItem(); if (doc.select(".detail_cover img").size() == 1) { result.setCover(doc.select(".detail_cover img").get(0).attr("src")); } result.setTitle(doc.select(".detail_titel").text()); Elements detailtrs = doc.select(".detailzeile table tr"); for (int i = 0; i < detailtrs.size(); i++) { Element tr = detailtrs.get(i); if (tr.child(0).hasClass("detail_feld")) { String title = tr.child(0).text(); String content = tr.child(1).text(); if (title.equals("Gesamtwerk:") || title.equals("Erschienen in:")) { try { if (tr.child(1).select("a").size() > 0) { Element link = tr.child(1).select("a").first(); List<NameValuePair> query = URLEncodedUtils.parse( new URI(link.absUrl("href")), "UTF-8"); for (NameValuePair q : query) { if (q.getName().equals("MedienNr")) { result.setCollectionId(q.getValue()); } } } } catch (URISyntaxException e) { } } else { if (content.contains("hier klicken") && tr.child(1).select("a").size() > 0) { content += " " + tr.child(1).select("a").first().attr("href"); } result.addDetail(new Detail(title, content)); } } } Elements detailcenterlinks = doc .select(".detailzeile_center a.detail_link"); for (int i = 0; i < detailcenterlinks.size(); i++) { Element a = detailcenterlinks.get(i); result.addDetail(new Detail(a.text().trim(), a.absUrl("href"))); } try { JSONObject copymap = new JSONObject(); if (data.has("copiestable")) { copymap = data.getJSONObject("copiestable"); } else { Elements ths = doc.select(".exemplartab .exemplarmenubar th"); for (int i = 0; i < ths.size(); i++) { Element th = ths.get(i); String head = th.text().trim(); if (head.equals("Zweigstelle")) { copymap.put("branch", i); } else if (head.equals("Abteilung")) { copymap.put("department", i); } else if (head.equals("Bereich") || head.equals("Standort")) { copymap.put("location", i); } else if (head.equals("Signatur")) { copymap.put("signature", i); } else if (head.equals("Barcode") || head.equals("Medien-Nummer")) { copymap.put("barcode", i); } else if (head.equals("Status")) { copymap.put("status", i); } else if (head.equals("Frist") || head.matches("Verf.+gbar")) { copymap.put("returndate", i); } else if (head.equals("Vorbestellungen") || head.equals("Reservierungen")) { copymap.put("reservations", i); } } } Elements exemplartrs = doc .select(".exemplartab .tabExemplar, .exemplartab .tabExemplar_"); DateTimeFormatter fmt = DateTimeFormat.forPattern("dd.MM.yyyy").withLocale(Locale.GERMAN); for (int i = 0; i < exemplartrs.size(); i++) { Element tr = exemplartrs.get(i); Copy copy = new Copy(); Iterator<?> keys = copymap.keys(); while (keys.hasNext()) { String key = (String) keys.next(); int index; try { index = copymap.has(key) ? copymap.getInt(key) : -1; } catch (JSONException e1) { index = -1; } if (index >= 0) { try { copy.set(key, tr.child(index).text(), fmt); } catch (IllegalArgumentException e) { e.printStackTrace(); } } } result.addCopy(copy); } } catch (Exception e) { e.printStackTrace(); } try { Elements bandtrs = doc.select("table .tabBand a"); for (int i = 0; i < bandtrs.size(); i++) { Element tr = bandtrs.get(i); Volume volume = new Volume(); volume.setId(tr.attr("href").split("=")[1]); volume.setTitle(tr.text()); result.addVolume(volume); } } catch (Exception e) { e.printStackTrace(); } if (doc.select(".detail_vorbest a").size() == 1) { result.setReservable(true); result.setReservation_info(doc.select(".detail_vorbest a").attr( "href")); } return result; } @Override public ReservationResult reservation(DetailedItem item, Account acc, int useraction, String selection) throws IOException { String reservation_info = item.getReservation_info(); Document doc = null; if (useraction == MultiStepResult.ACTION_CONFIRMATION) { List<NameValuePair> nameValuePairs = new ArrayList<>(2); nameValuePairs.add(new BasicNameValuePair("make_allvl", "Bestaetigung")); nameValuePairs.add(new BasicNameValuePair("target", "makevorbest")); httpPost(opac_url + "/index.asp", new UrlEncodedFormEntity( nameValuePairs), getDefaultEncoding()); return new ReservationResult(MultiStepResult.Status.OK); } else if (selection == null || useraction == 0) { String html = httpGet(opac_url + "/" + reservation_info, getDefaultEncoding()); doc = Jsoup.parse(html); if (doc.select("input[name=AUSWEIS]").size() > 0) { // Needs login List<NameValuePair> nameValuePairs = new ArrayList<>( 2); nameValuePairs.add(new BasicNameValuePair("AUSWEIS", acc .getName())); nameValuePairs.add(new BasicNameValuePair("PWD", acc .getPassword())); if (data.has("db")) { try { nameValuePairs.add(new BasicNameValuePair("vkontodb", data.getString("db"))); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } } nameValuePairs.add(new BasicNameValuePair("B1", "weiter")); nameValuePairs.add(new BasicNameValuePair("target", doc.select( "input[name=target]").val())); nameValuePairs.add(new BasicNameValuePair("type", "VT2")); html = httpPost(opac_url + "/index.asp", new UrlEncodedFormEntity(nameValuePairs), getDefaultEncoding()); doc = Jsoup.parse(html); } if (doc.select("select[name=" + branch_inputfield + "]").size() == 0) { if (doc.select("select[name=VZST]").size() > 0) { branch_inputfield = "VZST"; } } if (doc.select("select[name=" + branch_inputfield + "]").size() > 0) { List<Map<String, String>> branches = new ArrayList<>(); for (Element option : doc .select("select[name=" + branch_inputfield + "]") .first().children()) { String value = option.text().trim(); String key; if (option.hasAttr("value")) { key = option.attr("value"); } else { key = value; } Map<String, String> selopt = new HashMap<>(); selopt.put("key", key); selopt.put("value", value); branches.add(selopt); } _res_target = doc.select("input[name=target]").attr("value"); ReservationResult result = new ReservationResult( MultiStepResult.Status.SELECTION_NEEDED); result.setActionIdentifier(ReservationResult.ACTION_BRANCH); result.setSelection(branches); return result; } } else if (useraction == ReservationResult.ACTION_BRANCH) { List<NameValuePair> nameValuePairs = new ArrayList<>(2); nameValuePairs.add(new BasicNameValuePair(branch_inputfield, selection)); nameValuePairs.add(new BasicNameValuePair("button2", "weiter")); nameValuePairs.add(new BasicNameValuePair("target", _res_target)); String html = httpPost(opac_url + "/index.asp", new UrlEncodedFormEntity(nameValuePairs), getDefaultEncoding()); doc = Jsoup.parse(html); } if (doc == null) { return new ReservationResult(MultiStepResult.Status.ERROR); } if (doc.select("input[name=target]").size() > 0) { if (doc.select("input[name=target]").attr("value") .equals("makevorbest")) { List<String[]> details = new ArrayList<>(); if (doc.getElementsByClass("kontomeldung").size() == 1) { details.add(new String[]{doc .getElementsByClass("kontomeldung").get(0).text() .trim()}); } Pattern p = Pattern.compile("geb.hr", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); for (Element div : doc.select(".kontozeile_center")) { for (String text : Jsoup .parse(div.html().replaceAll("(?i)<br[^>]*>", "br2n")).text().split("br2n")) { if (p.matcher(text).find() && !text.contains("usstehend") && text.contains("orbestellung")) { details.add(new String[]{text.trim()}); } } } if (doc.select("#vorbest").size() > 0 && doc.select("#vorbest").val().contains("(")) { // Erlangen uses "Kostenpflichtige Vorbestellung (1 Euro)" // as the label of its reservation button details.add(new String[]{doc.select("#vorbest").val().trim()}); } for (Element row : doc.select(".kontozeile_center table tr")) { if (row.select(".konto_feld").size() == 1 && row.select(".konto_feldinhalt").size() == 1) { details.add(new String[]{ row.select(".konto_feld").text().trim(), row.select(".konto_feldinhalt").text().trim()}); } } ReservationResult result = new ReservationResult( MultiStepResult.Status.CONFIRMATION_NEEDED); result.setDetails(details); return result; } } if (doc.getElementsByClass("kontomeldung").size() == 1) { return new ReservationResult(MultiStepResult.Status.ERROR, doc .getElementsByClass("kontomeldung").get(0).text()); } return new ReservationResult(MultiStepResult.Status.ERROR, stringProvider.getString(StringProvider.UNKNOWN_ERROR)); } @Override public ProlongResult prolong(String a, Account account, int useraction, String selection) throws IOException { if (!initialised) { start(); } if (System.currentTimeMillis() - logged_in > SESSION_LIFETIME || logged_in_as == null) { try { account(account); } catch (JSONException e) { e.printStackTrace(); return new ProlongResult( MultiStepResult.Status.ERROR, stringProvider .getString(StringProvider.COULD_NOT_LOAD_ACCOUNT)); } catch (OpacErrorException e) { return new ProlongResult(MultiStepResult.Status.ERROR, e.getMessage()); } } else if (logged_in_as.getId() != account.getId()) { try { account(account); } catch (JSONException e) { e.printStackTrace(); return new ProlongResult( MultiStepResult.Status.ERROR, stringProvider .getString(StringProvider.COULD_NOT_LOAD_ACCOUNT)); } catch (OpacErrorException e) { return new ProlongResult(MultiStepResult.Status.ERROR, e.getMessage()); } } if (useraction == MultiStepResult.ACTION_CONFIRMATION) { List<NameValuePair> nameValuePairs = new ArrayList<>(2); nameValuePairs.add(new BasicNameValuePair("target", "make_vl")); nameValuePairs.add(new BasicNameValuePair("verlaengern", "Bestätigung")); httpPost(opac_url + "/index.asp", new UrlEncodedFormEntity( nameValuePairs), getDefaultEncoding()); return new ProlongResult(MultiStepResult.Status.OK); } else { String html = httpGet(opac_url + "/" + a, getDefaultEncoding()); Document doc = Jsoup.parse(html); if (doc.getElementsByClass("kontomeldung").size() == 1) { return new ProlongResult(MultiStepResult.Status.ERROR, doc .getElementsByClass("kontomeldung").get(0).text()); } if (doc.select("#verlaengern").size() == 1) { if (doc.select(".kontozeile_center table").size() == 1) { Element table = doc.select(".kontozeile_center table") .first(); ProlongResult res = new ProlongResult( MultiStepResult.Status.CONFIRMATION_NEEDED); List<String[]> details = new ArrayList<>(); for (Element row : table.select("tr")) { if (row.select(".konto_feld").size() == 1 && row.select(".konto_feldinhalt").size() == 1) { details.add(new String[]{ row.select(".konto_feld").text().trim(), row.select(".konto_feldinhalt").text() .trim()}); } } res.setDetails(details); return res; } else { List<NameValuePair> nameValuePairs = new ArrayList<>( 2); nameValuePairs.add(new BasicNameValuePair("target", "make_vl")); nameValuePairs.add(new BasicNameValuePair("verlaengern", "Bestätigung")); httpPost(opac_url + "/index.asp", new UrlEncodedFormEntity( nameValuePairs), getDefaultEncoding()); return new ProlongResult(MultiStepResult.Status.OK); } } } return new ProlongResult(MultiStepResult.Status.ERROR, "??"); } @Override public ProlongAllResult prolongAll(Account account, int useraction, String selection) throws IOException { if (!initialised) { start(); } if (System.currentTimeMillis() - logged_in > SESSION_LIFETIME || logged_in_as == null) { try { account(account); } catch (JSONException e) { e.printStackTrace(); return new ProlongAllResult(MultiStepResult.Status.ERROR, stringProvider .getString(StringProvider.CONNECTION_ERROR)); } catch (OpacErrorException e) { return new ProlongAllResult(MultiStepResult.Status.ERROR, e.getMessage()); } } else if (logged_in_as.getId() != account.getId()) { try { account(account); } catch (JSONException e) { e.printStackTrace(); return new ProlongAllResult(MultiStepResult.Status.ERROR, stringProvider .getString(StringProvider.CONNECTION_ERROR)); } catch (OpacErrorException e) { return new ProlongAllResult(MultiStepResult.Status.ERROR, e.getMessage()); } } String html = httpGet(opac_url + "/index.asp?target=alleverl", getDefaultEncoding()); Document doc = Jsoup.parse(html); if (doc.getElementsByClass("kontomeldung").size() == 1) { String err = doc.getElementsByClass("kontomeldung").get(0).text(); return new ProlongAllResult(MultiStepResult.Status.ERROR, err); } if (doc.select(".kontozeile table").size() == 1) { Map<Integer, String> colmap = new HashMap<>(); List<Map<String, String>> result = new ArrayList<>(); for (Element tr : doc.select(".kontozeile table tr")) { if (tr.select(".tabHeaderKonto").size() > 0) { int i = 0; for (Element th : tr.select("th")) { if (th.text().contains("Verfasser")) { colmap.put(i, OpacApi.ProlongAllResult.KEY_LINE_AUTHOR); } else if (th.text().contains("Titel")) { colmap.put(i, OpacApi.ProlongAllResult.KEY_LINE_TITLE); } else if (th.text().contains("Neue")) { colmap.put( i, OpacApi.ProlongAllResult.KEY_LINE_NEW_RETURNDATE); } else if (th.text().contains("Frist")) { colmap.put( i, OpacApi.ProlongAllResult.KEY_LINE_OLD_RETURNDATE); } else if (th.text().contains("Status")) { colmap.put(i, OpacApi.ProlongAllResult.KEY_LINE_MESSAGE); } i++; } } else { Map<String, String> line = new HashMap<>(); for (Entry<Integer, String> entry : colmap.entrySet()) { line.put(entry.getValue(), tr.child(entry.getKey()) .text().trim()); } result.add(line); } } if (doc.select("input#make_allvl").size() > 0) { List<NameValuePair> nameValuePairs = new ArrayList<>( 2); nameValuePairs.add(new BasicNameValuePair("target", "make_allvl_flag")); nameValuePairs.add(new BasicNameValuePair("make_allvl", "Bestaetigung")); httpPost(opac_url + "/index.asp", new UrlEncodedFormEntity(nameValuePairs), getDefaultEncoding()); } return new ProlongAllResult(MultiStepResult.Status.OK, result); } return new ProlongAllResult(MultiStepResult.Status.ERROR, stringProvider.getString(StringProvider.INTERNAL_ERROR)); } @Override public CancelResult cancel(String media, Account account, int useraction, String selection) throws IOException, OpacErrorException { if (!initialised) { start(); } if (System.currentTimeMillis() - logged_in > SESSION_LIFETIME || logged_in_as == null) { try { account(account); } catch (JSONException e) { return new CancelResult( MultiStepResult.Status.ERROR, stringProvider .getString(StringProvider.COULD_NOT_LOAD_ACCOUNT)); } } else if (logged_in_as.getId() != account.getId()) { try { account(account); } catch (JSONException e) { return new CancelResult( MultiStepResult.Status.ERROR, stringProvider .getString(StringProvider.COULD_NOT_LOAD_ACCOUNT)); } } httpGet(opac_url + "/" + media, getDefaultEncoding()); List<NameValuePair> nameValuePairs = new ArrayList<>(2); nameValuePairs.add(new BasicNameValuePair("target", "delvorbest")); nameValuePairs .add(new BasicNameValuePair("vorbdelbest", "Bestätigung")); httpPost(opac_url + "/index.asp", new UrlEncodedFormEntity( nameValuePairs), getDefaultEncoding()); return new CancelResult(MultiStepResult.Status.OK); } @Override public AccountData account(Account acc) throws IOException, JSONException, OpacErrorException { if (!initialised) { start(); } List<NameValuePair> nameValuePairs; String html = httpGet(opac_url + "/index.asp?kontofenster=start", "ISO-8859-1"); Document doc = Jsoup.parse(html); if (doc.select("input[name=AUSWEIS]").size() > 0) { // Login vonnöten nameValuePairs = new ArrayList<>(); nameValuePairs .add(new BasicNameValuePair("AUSWEIS", acc.getName())); nameValuePairs .add(new BasicNameValuePair("PWD", acc.getPassword())); if (data.has("db")) { nameValuePairs.add(new BasicNameValuePair("vkontodb", data .getString("db"))); } nameValuePairs.add(new BasicNameValuePair("B1", "weiter")); nameValuePairs.add(new BasicNameValuePair("kontofenster", "true")); nameValuePairs.add(new BasicNameValuePair("target", "konto")); nameValuePairs.add(new BasicNameValuePair("type", "K")); html = httpPost(opac_url + "/index.asp", new UrlEncodedFormEntity( nameValuePairs), "ISO-8859-1", true); doc = Jsoup.parse(html); } if (doc.getElementsByClass("kontomeldung").size() == 1) { throw new OpacErrorException(doc.getElementsByClass("kontomeldung") .get(0).text()); } logged_in_as = acc; logged_in = System.currentTimeMillis(); return parse_account(acc, doc, data, reportHandler, loadJsonResource("/bibliotheca/headers_lent.json"), loadJsonResource("/bibliotheca/headers_reservations.json")); } public static AccountData parse_account(Account acc, Document doc, JSONObject data, ReportHandler reportHandler, JSONObject headers_lent, JSONObject headers_reservations) throws JSONException, NotReachableException { if (doc.select(".kontozeile_center table").size() == 0) { throw new NotReachableException(); } Map<String, Integer> copymap = new HashMap<>(); Elements headerCells = doc.select(".kontozeile_center table").get(0) .select("tr.exemplarmenubar").get(0).children(); JSONArray headersList = new JSONArray(); JSONArray unknownHeaders = new JSONArray(); int i = 0; for (Element headerCell : headerCells) { String header = headerCell.text(); headersList.put(header); if (headers_lent.has(header)) { if (!headers_lent.isNull(header)) copymap.put(headers_lent.getString(header), i); } else { unknownHeaders.put(header); } i++; } if (unknownHeaders.length() > 0) { // send report JSONObject reportData = new JSONObject(); reportData.put("headers", headersList); reportData.put("unknown_headers", unknownHeaders); Report report = new Report(acc.getLibrary(), "bibliotheca", "unknown header - lent", DateTime.now(), reportData); reportHandler.sendReport(report); // fallback to JSON JSONObject accounttable = data.getJSONObject("accounttable"); copymap = jsonToMap(accounttable); } List<LentItem> media = new ArrayList<>(); Elements exemplartrs = doc.select(".kontozeile_center table").get(0) .select("tr.tabKonto"); DateTimeFormatter fmt = DateTimeFormat.forPattern("dd.MM.yyyy").withLocale(Locale.GERMAN); DateTimeFormatter fmt2 = DateTimeFormat.forPattern("d/M/yyyy").withLocale(Locale.GERMAN); for (Element tr : exemplartrs) { LentItem item = new LentItem(); for (Entry<String, Integer> entry : copymap.entrySet()) { String key = entry.getKey(); int index = entry.getValue(); if (key.equals("prolongurl")) { if (tr.child(index).children().size() > 0) { item.setProlongData(tr.child(index).child(0).attr("href")); item.setRenewable( !tr.child(index).child(0).attr("href").contains("vermsg")); } } else if (key.equals("returndate")) { try { item.setDeadline(fmt.parseLocalDate(tr.child(index).text())); } catch (IllegalArgumentException e1) { try { item.setDeadline(fmt2.parseLocalDate(tr.child(index).text())); } catch (IllegalArgumentException e2) { e2.printStackTrace(); } } } else { item.set(key, tr.child(index).text()); } } media.add(item); } copymap = new HashMap<>(); headerCells = doc.select(".kontozeile_center table").get(1) .select("tr.exemplarmenubar").get(0).children(); headersList = new JSONArray(); unknownHeaders = new JSONArray(); i = 0; for (Element headerCell : headerCells) { String header = headerCell.text(); headersList.put(header); if (headers_reservations.has(header)) { if (!headers_reservations.isNull(header)) { copymap.put(headers_reservations.getString(header), i); } } else { unknownHeaders.put(header); } i++; } if (unknownHeaders.length() > 0) { // send report JSONObject reportData = new JSONObject(); reportData.put("headers", headersList); reportData.put("unknown_headers", unknownHeaders); Report report = new Report(acc.getLibrary(), "bibliotheca", "unknown header - reservations", DateTime.now(), reportData); reportHandler.sendReport(report); // fallback to JSON JSONObject reservationtable = data.getJSONObject("reservationtable"); copymap = jsonToMap(reservationtable); } List<ReservedItem> reservations = new ArrayList<>(); exemplartrs = doc.select(".kontozeile_center table").get(1) .select("tr.tabKonto"); for (Element tr : exemplartrs) { ReservedItem item = new ReservedItem(); for (Entry<String, Integer> entry : copymap.entrySet()) { String key = entry.getKey(); int index = entry.getValue(); if (key.equals("cancelurl")) { if (tr.child(index).children().size() > 0) { item.setCancelData(tr.child(index).child(0).attr("href")); } } else if (key.equals("availability")) { try { item.setReadyDate(fmt.parseLocalDate(tr.child(index).text())); } catch (IllegalArgumentException e1) { try { item.setReadyDate(fmt2.parseLocalDate(tr.child(index).text())); } catch (IllegalArgumentException e2) { e2.printStackTrace(); } } } else if (key.equals("expirationdate")) { try { item.setExpirationDate(fmt.parseLocalDate(tr.child(index).text())); } catch (IllegalArgumentException e1) { try { item.setExpirationDate(fmt2.parseLocalDate(tr.child(index).text())); } catch (IllegalArgumentException e2) { item.setStatus(tr.child(index).text()); } } } else { item.set(key, tr.child(index).text()); } } reservations.add(item); } AccountData res = new AccountData(acc.getId()); for (Element row : doc.select(".kontozeile_center, div[align=center]")) { String text = row.text().trim(); if (text.matches( ".*Ausstehende Geb.+hren:[^0-9]+([0-9.,]+)[^0-9€A-Z]*(€|EUR|CHF|Fr.).*")) { text = text .replaceAll( ".*Ausstehende Geb.+hren:[^0-9]+([0-9.," + "]+)[^0-9€A-Z]*(€|EUR|CHF|Fr.).*", "$1 $2"); res.setPendingFees(text); } if (text.matches("Ihr Ausweis ist g.ltig bis:.*")) { text = text.replaceAll( "Ihr Ausweis ist g.ltig bis:[^A-Za-z0-9]+", ""); res.setValidUntil(text); } else if (text.matches("Ausweis g.ltig bis:.*")) { text = text.replaceAll("Ausweis g.ltig bis:[^A-Za-z0-9]+", ""); res.setValidUntil(text); } } res.setLent(media); res.setReservations(reservations); return res; } @Override public String getShareUrl(String id, String title) { try { // our server does not like getting a "+" as the encoding for a space, so we replace it // with %20. return "http://opacapp.de/:" + URLEncoder.encode(library.getIdent(), "UTF-8").replace("+", "%20") + ":" + id + ":" + URLEncoder.encode(title, "UTF-8"); } catch (UnsupportedEncodingException ignored) { return null; } } @Override public int getSupportFlags() { int flags = SUPPORT_FLAG_CHANGE_ACCOUNT | SUPPORT_FLAG_WARN_RESERVATION_FEES; flags |= SUPPORT_FLAG_ENDLESS_SCROLLING; if (!data.has("disableProlongAll")) { flags |= SUPPORT_FLAG_ACCOUNT_PROLONG_ALL; } return flags; } @Override public SearchRequestResult filterResults(Filter filter, Option option) { // TODO Auto-generated method stub return null; } @Override public void checkAccountData(Account acc) throws IOException, JSONException, OpacErrorException { start(); List<NameValuePair> nameValuePairs = new ArrayList<>(); nameValuePairs.add(new BasicNameValuePair("AUSWEIS", acc.getName())); nameValuePairs.add(new BasicNameValuePair("PWD", acc.getPassword())); if (data.has("db")) { nameValuePairs.add(new BasicNameValuePair("vkontodb", data .getString("db"))); } nameValuePairs.add(new BasicNameValuePair("B1", "weiter")); nameValuePairs.add(new BasicNameValuePair("target", "konto")); nameValuePairs.add(new BasicNameValuePair("type", "K")); String html = httpPostWithRedirect(opac_url + "/index.asp", new UrlEncodedFormEntity(nameValuePairs), "ISO-8859-1"); Document doc = Jsoup.parse(html); if (doc.select(".kontomeldung").size() > 0) { throw new OpacErrorException(doc.select(".kontomeldung").text()); } } private String httpPostWithRedirect(String url, UrlEncodedFormEntity data, String encoding) throws IOException { HttpPost httppost = new HttpPost(url); httppost.setEntity(data); HttpResponse response = http_client.execute(httppost); if (response.getStatusLine().getStatusCode() == 302) { HttpGet httpget = new HttpGet(url.substring(0, url.lastIndexOf("/") + 1) + response.getFirstHeader("Location").getValue()); HttpUtils.consume(response.getEntity()); response = http_client.execute(httpget); } return convertStreamToString(response.getEntity().getContent(), encoding); } @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; } }