diff --git a/opacclient/libopac/src/main/java/de/geeksfactory/opacclient/OpacApiFactory.java b/opacclient/libopac/src/main/java/de/geeksfactory/opacclient/OpacApiFactory.java index 68e9c9a5f..07e481137 100644 --- a/opacclient/libopac/src/main/java/de/geeksfactory/opacclient/OpacApiFactory.java +++ b/opacclient/libopac/src/main/java/de/geeksfactory/opacclient/OpacApiFactory.java @@ -26,6 +26,7 @@ import de.geeksfactory.opacclient.apis.Littera; import de.geeksfactory.opacclient.apis.OpacApi; import de.geeksfactory.opacclient.apis.Open; +import de.geeksfactory.opacclient.apis.Patron; import de.geeksfactory.opacclient.apis.PicaLBS; import de.geeksfactory.opacclient.apis.PicaOld; import de.geeksfactory.opacclient.apis.Primo; @@ -127,6 +128,8 @@ public static OpacApi create(Library lib, StringProvider sp, HttpClientFactory h newApiInstance = new TouchPoint(); } else if (lib.getApi().equals("open")) { newApiInstance = new Open(); + } else if (lib.getApi().equals("patron")) { + newApiInstance = new Patron(); } else if (lib.getApi().equals("test")) { newApiInstance = new TestApi(); } else { diff --git a/opacclient/libopac/src/main/java/de/geeksfactory/opacclient/apis/Patron.java b/opacclient/libopac/src/main/java/de/geeksfactory/opacclient/apis/Patron.java new file mode 100644 index 000000000..925edce6a --- /dev/null +++ b/opacclient/libopac/src/main/java/de/geeksfactory/opacclient/apis/Patron.java @@ -0,0 +1,375 @@ +package de.geeksfactory.opacclient.apis; + +import de.geeksfactory.opacclient.i18n.StringProvider; +import de.geeksfactory.opacclient.networking.HttpClientFactory; +import de.geeksfactory.opacclient.objects.*; +import de.geeksfactory.opacclient.searchfields.DropdownSearchField; +import de.geeksfactory.opacclient.searchfields.SearchField; +import de.geeksfactory.opacclient.searchfields.SearchQuery; +import de.geeksfactory.opacclient.searchfields.TextSearchField; + +import org.apache.http.HttpEntity; +import org.apache.http.NameValuePair; +import org.apache.http.client.CookieStore; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.impl.client.BasicCookieStore; +import org.apache.http.message.BasicNameValuePair; +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.select.Elements; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Patron extends BaseApi { + + private String base_url; + protected CookieStore cookieStore; + protected static String QUERY_URL = "faces/Szukaj.jsp"; + + protected List lastFormState; + + @Override + public void init(Library lib, HttpClientFactory httpClientFactory) { + super.init(lib, httpClientFactory); + cookieStore = new BasicCookieStore(); + try { + base_url = lib.getData().getString("baseurl"); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + + @Override + protected String getDefaultEncoding() { + return "UTF-8"; + } + + @Override + public SearchRequestResult search(List query) + throws IOException, OpacErrorException, JSONException { + + List sp = buildSearchParams(query); + sp.add(new BasicNameValuePair("form1:btnSzukajIndeks", "Szukaj")); + HttpEntity ent = buildHttpEntity(sp); + String html = httpPost(base_url + QUERY_URL, ent, getDefaultEncoding(), false, cookieStore); + +// ByteArrayOutputStream baos = new ByteArrayOutputStream(); +// ent.writeTo(baos); +// String s = new String(baos.toByteArray(), "utf-8"); + + Document doc = Jsoup.parse(html); + + return parseResults(doc, 1); + } + + private SearchResult parseListItem(Element item) { + SearchResult result = new SearchResult(); + + Element info = item.select(".info").first(); + info.select("script").remove(); + result.setInnerhtml(info.html()); + result.setId(item.id()); + + return result; + } + + + private int parseTotalResultCount(Element doc){ + Pattern p = Pattern.compile("Liczba rekordów:\\s*(\\d+)"); + + Matcher m = p.matcher(doc.select("#opisy").text()); + if(!m.find()){ + return 0; + } + return Integer.parseInt(m.group(1)); + } + private int parsePageCount(Element doc){ + return 1; + } + private int parsePageIndex(Element doc){ + return Integer.parseInt( doc.select("#opisy .linki").eq(1).select(".sel").first().text()); + } + + private SearchRequestResult parseResults(Document doc, int i) { + List results = new ArrayList<>(); + int total_result_count = parseTotalResultCount(doc); + int page_count = parsePageCount(doc); + int page_index = parsePageIndex(doc); + for (Element e : doc.select("#opisy .opis")) { + results.add(parseListItem(e)); + } + SearchRequestResult result = + new SearchRequestResult(results, total_result_count, page_count, page_index); + + return result; + } + + private HttpEntity buildHttpEntity(List nameValuePairs) throws IOException { + return new UrlEncodedFormEntity(nameValuePairs); + } + + protected List buildSearchParams(List query) + throws OpacErrorException { + if (lastFormState == null) { + try { + parseSearchFields(); + } catch (IOException e) { + e.printStackTrace(); + } catch (JSONException e) { + e.printStackTrace(); + } + } + List params = lastFormState; + + int n = 0; + for (SearchQuery term : query) { + if (term.getValue().isEmpty()) continue; + if (term.getKey().startsWith("@")) { + n++; + + if (n > 3) { + throw new OpacErrorException(stringProvider.getQuantityString( + StringProvider.LIMITED_NUM_OF_CRITERIA, 3, 3)); + } + + params.add(new BasicNameValuePair("form1:textField" + n, term.getValue())); + if (n > 1) { + params.add(new BasicNameValuePair("rbOper" + (n - 1), "a")); + } + continue; + } + if (term.getSearchField().getId().startsWith("#")) { + + params.add(new BasicNameValuePair(term.getKey().substring(1), term.getValue())); + } + } + params.add(new BasicNameValuePair("rbOperStem", "a")); + + return params; + } + + @Override + public SearchRequestResult filterResults(Filter filter, Filter.Option option) + throws IOException, OpacErrorException { + return null; + } + + @Override + public SearchRequestResult searchGetPage(int page) + throws IOException, OpacErrorException, JSONException { + return null; + } + + @Override + public DetailledItem getResultById(String id, String homebranch) + throws IOException, OpacErrorException { + return null; + } + + @Override + public DetailledItem getResult(int position) throws IOException, OpacErrorException { + return null; + } + + @Override + public ReservationResult reservation(DetailledItem 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 CancelResult cancel(String media, Account account, int useraction, String selection) + throws IOException, OpacErrorException { + return null; + } + + @Override + public AccountData account(Account account) + throws IOException, JSONException, OpacErrorException { + return null; + } + + @Override + public void checkAccountData(Account account) + throws IOException, JSONException, OpacErrorException { + + } + + protected FormParseResult parseForm(String html) { + return parseForm(Jsoup.parse(html)); + } + + protected FormParseResult parseForm(Document doc) { + List params = new ArrayList<>(); + Element form = doc.select("#form1").first(); + + Elements elements = form.select( + "input[type=\"text\"],input[type=\"hidden\"]," + + "input[type=\"radio\"][checked=\"checked\"],input[type=\"submit\"]" + ).not("[id=\"form1:btnCzyscForme\"]"); + + for (Element e : elements) { + params.add(new BasicNameValuePair(e.attr("name"), e.attr("value"))); + } + + for (Element select : form.select("select")) { + Elements selectedOptions = select.select("option[selected=\"selected\"]"); + if (!selectedOptions.isEmpty()) { + params.add(new BasicNameValuePair(select.attr("name"), + selectedOptions.first().attr("value"))); + } else { + Element firstOptions = select.select("option").first(); + params.add(new BasicNameValuePair(select.attr("name"), firstOptions.attr("value"))); + } + + } + + return new FormParseResult(form, params); + } + + @Override + public List parseSearchFields() + throws IOException, OpacErrorException, JSONException { + + String url = base_url + "faces/Szukaj.jsp"; + String html = httpGet(url, getDefaultEncoding(), false, cookieStore); + FormParseResult res = parseForm(html); + res.params.add(new BasicNameValuePair("form1:lnkZaaw", "form1:lnkZaaw")); + + url = res.form.attr("action"); + try { + URIBuilder ub = new URIBuilder(base_url); + ub.setPath(url); + url = ub.build().toString(); + } catch (URISyntaxException e) { + url = base_url + "faces/Szukaj.jsp"; + } + html = httpPost(url, new UrlEncodedFormEntity(res.params), + getDefaultEncoding(), false, cookieStore); + + res = parseForm(html); + lastFormState = res.params; + Element doc = res.form; + List fields = new LinkedList<>(); + + try { + Elements options = doc.select("[id=\"form1:dropdown1\"]").first().select("option"); + + for (Element option : options) { + + TextSearchField field = new TextSearchField(); + field.setDisplayName(option.text()); + field.setId("@term" + option.val()); + + field.setData(new JSONObject()); + field.getData().put("meaning", field.getId()); + field.setHint(""); + fields.add(field); + } + + + Elements txtFields = doc.select("[id=\"form1:textField4\"],[id=\"form1:textField5\"]"); + for (Element txtField : txtFields) { + TextSearchField field = new TextSearchField(); + String displayName = doc + .select(String.format("label[for=\"%s\"", txtField.id())) + .first() + .text() + .replaceAll(":$", ""); + field.setDisplayName(displayName); + field.setId("#" + txtField.id()); + + field.setData(new JSONObject()); + field.getData().put("meaning", field.getId()); + field.setHint(""); + fields.add(field); + } + + Elements selects = doc.select("[id=\"form1:dropdown4\"]," + + "[id=\"form1:dropdown5\"]," + + "[id=\"form1:dropdown6\"]," + + "[id=\"form1:dropdown7\"]" + ); + + for (Element select : selects) { + options = select.select("option"); + DropdownSearchField field = new DropdownSearchField(); + for (Element option : options) { + field.addDropdownValue(option.val(), option.text()); + } + field.setId("#" + select.id()); + field.setData(new JSONObject()); + field.getData().put("meaning", field.getId()); + + String displayName = doc + .select(String.format("label[for=\"%s\"", select.id())) + .first() + .text() + .replaceAll(":$", ""); + + field.setDisplayName(displayName); + fields.add(field); + } + + } catch (Exception e) { + System.err.print(e.getMessage()); + } + + return fields; + } + + @Override + public String getShareUrl(String id, String title) { + return null; + } + + @Override + public int getSupportFlags() { + return 0; + } + + @Override + public Set getSupportedLanguages() throws IOException { + return null; + } + + @Override + public void setLanguage(String language) { + + } + + private class FormParseResult { + public Element form; + public List params; + + public FormParseResult(Element form, List params) { + this.form = form; + this.params = params; + } + } +} \ No newline at end of file diff --git a/opacclient/libopac/src/main/java/de/geeksfactory/opacclient/objects/SearchResult.java b/opacclient/libopac/src/main/java/de/geeksfactory/opacclient/objects/SearchResult.java index 37a072f02..73b8e148a 100644 --- a/opacclient/libopac/src/main/java/de/geeksfactory/opacclient/objects/SearchResult.java +++ b/opacclient/libopac/src/main/java/de/geeksfactory/opacclient/objects/SearchResult.java @@ -233,7 +233,7 @@ public enum MediaType { GAME_CONSOLE, EBOOK, SCORE_MUSIC, PACKAGE_BOOKS, UNKNOWN, NEWSPAPER, BOARDGAME, SCHOOL_VERSION, MAP, BLURAY, AUDIO_CASSETTE, ART, MAGAZINE, GAME_CONSOLE_WII, GAME_CONSOLE_NINTENDO, GAME_CONSOLE_PLAYSTATION, - GAME_CONSOLE_XBOX, LP_RECORD, MP3, URL, EVIDEO, EDOC, EAUDIO + GAME_CONSOLE_XBOX, LP_RECORD, MP3, URL, EVIDEO, EDOC, EAUDIO, OLD_PRINT } /** diff --git a/opacclient/opacapp/src/main/assets/bibs/PL_BielskoBiala_Ksiaznica.json b/opacclient/opacapp/src/main/assets/bibs/PL_BielskoBiala_Ksiaznica.json new file mode 100644 index 000000000..ac0f34995 --- /dev/null +++ b/opacclient/opacapp/src/main/assets/bibs/PL_BielskoBiala_Ksiaznica.json @@ -0,0 +1,17 @@ +{ + "account_supported": false, + "api": "patron", + "city": "Bielsko-Biała", + "country": "Polska", + "data": { + "baseurl": "http://185.43.138.133:8080/Opac4/", + "wiki": "https://pl.wikipedia.org/wiki/Ksi%C4%85%C5%BCnica_Beskidzka" + }, + "geo": [ + 49.8240171, + 19.0405984 + ], + "information": "http://ksiaznica.bielsko.pl/", + "state": "śląskie", + "title": "Książnica Beskidzka" +} diff --git a/opacclient/opacapp/src/main/assets/bibs/PL_Tczew.json b/opacclient/opacapp/src/main/assets/bibs/PL_Tczew.json new file mode 100644 index 000000000..0375a8671 --- /dev/null +++ b/opacclient/opacapp/src/main/assets/bibs/PL_Tczew.json @@ -0,0 +1,15 @@ +{ + "account_supported": false, + "api": "patron", + "city": "Tczew", + "country": "Polska", + "data": { + "baseurl": "http://mbp.tczew.pl:8080/Opac4/" + }, + "geo": [ + 54.092192, 18.777331 + ], + "information": "http://mbp.tczew.pl", + "state": "pomorskie", + "title": "Miejska Biblioteka Publiczna w Tczewie" +} diff --git a/opacclient/opacapp/src/main/assets/bibs/PL_Wejherowo.json b/opacclient/opacapp/src/main/assets/bibs/PL_Wejherowo.json new file mode 100644 index 000000000..35312a31b --- /dev/null +++ b/opacclient/opacapp/src/main/assets/bibs/PL_Wejherowo.json @@ -0,0 +1,15 @@ +{ + "account_supported": false, + "api": "patron", + "city": "Wejherowo", + "country": "Polska", + "data": { + "baseurl": "http://biblioteka.wejherowo.pl:81/Opac4/" + }, + "geo": [ + 54.604167, 18.248889 + ], + "information": "http://biblioteka.wejherowo.pl/", + "state": "pomorskie", + "title": "Miejska Biblioteka Publiczna im. Aleksandra Majkowskiego w Wejherowie" +} diff --git a/opacclient/opacapp/src/main/assets/meanings/patron.json b/opacclient/opacapp/src/main/assets/meanings/patron.json new file mode 100644 index 000000000..38931b8dd --- /dev/null +++ b/opacclient/opacapp/src/main/assets/meanings/patron.json @@ -0,0 +1,7 @@ +{ + "@term1": "AUTHOR", + "@term2": "TITLE", + "@term7": "FREE", + "#form1:textField4": "YEAR", + "#form1:textField5": "YEAR" +} \ No newline at end of file diff --git a/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/frontend/ResultsAdapter.java b/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/frontend/ResultsAdapter.java index b65d1514c..fedbb91ca 100644 --- a/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/frontend/ResultsAdapter.java +++ b/opacclient/opacapp/src/main/java/de/geeksfactory/opacclient/frontend/ResultsAdapter.java @@ -109,6 +109,8 @@ public static int getResourceByMediaType(MediaType type) { return R.drawable.type_map; case LP_RECORD: return R.drawable.type_lp_record; + case OLD_PRINT: + return R.drawable.type_unknown; } return R.drawable.type_unknown;