package org.wikipedia.search;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.mediawiki.api.json.Api;
import org.mediawiki.api.json.ApiException;
import org.mediawiki.api.json.ApiResult;
import org.mediawiki.api.json.RequestBuilder;
import org.wikipedia.Constants;
import org.wikipedia.dataclient.ApiTask;
import org.wikipedia.dataclient.WikiSite;
import org.wikipedia.page.PageTitle;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
public class TitleSearchTask extends ApiTask<SearchResults> {
private final String prefix;
private final WikiSite wiki;
private static final String NUM_RESULTS_PER_QUERY = "20";
public TitleSearchTask(Api api, WikiSite wiki, String prefix) {
super(api);
this.prefix = prefix;
this.wiki = wiki;
}
@Override
public RequestBuilder buildRequest(Api api) {
return api.action("query")
.param("generator", "prefixsearch")
.param("redirects", "true")
.param("gpssearch", prefix)
.param("gpsnamespace", "0")
.param("gpslimit", NUM_RESULTS_PER_QUERY)
// -- Parameters causing prefix search to return suggestion.
.param("list", "search")
.param("srsearch", prefix)
.param("srnamespace", "0")
.param("srwhat", "text")
.param("srinfo", "suggestion")
.param("srprop", "")
.param("sroffset", "0")
.param("srlimit", "1")
// --
.param("prop", "pageterms|pageimages")
.param("wbptterms", "description") // only interested in Wikidata description
.param("piprop", "thumbnail")
.param("pilicense", "any")
.param("pithumbsize", Integer.toString(Constants.PREFERRED_THUMB_SIZE))
.param("pilimit", NUM_RESULTS_PER_QUERY)
.param("continue", ""); // to avoid warning about new continuation syntax
}
@Override
public SearchResults processResult(final ApiResult result) throws Throwable {
JSONObject data;
try {
data = result.asObject();
} catch (ApiException e) {
if (e.getCause() instanceof JSONException) {
// the only reason for a JSONException is if the response is an empty array.
return new SearchResults();
} else {
throw e;
}
}
// The search results arrive unordered, but they do have an "index" property, which we'll
// use to sort the results ourselves.
JSONObject queryResult = data.optJSONObject("query");
if (queryResult == null) {
return new SearchResults();
}
String suggestion = "";
JSONObject searchinfo = queryResult.optJSONObject("searchinfo");
if (searchinfo != null) {
if (searchinfo.has("suggestion")) {
suggestion = searchinfo.getString("suggestion");
}
}
List<SearchResult> resultList = new ArrayList<>();
JSONObject pages = queryResult.optJSONObject("pages");
if (pages == null) {
return new SearchResults(resultList, null, suggestion);
}
// First, put all the page objects into an array
JSONObject[] pageArray = new JSONObject[pages.length()];
int pageIndex = 0;
Iterator<String> pageIter = pages.keys();
while (pageIter.hasNext()) {
JSONObject page = (JSONObject)pages.get(pageIter.next());
pageArray[pageIndex++] = page;
}
// Go through the redirects array (if any), and transfer any "tofragment" items to
// the original results. "tofragment", if it exists, contains a link to a particular
// section of the page, which is important to preserve so the user gets redirected to the
// correct section.
if (queryResult.has("redirects")) {
JSONArray redirs = queryResult.getJSONArray("redirects");
for (int i = 0; i < redirs.length(); i++) {
JSONObject redirect = (JSONObject) redirs.get(i);
for (JSONObject page : pageArray) {
if (page.getString("title").equals(redirect.getString("to"))) {
page.put("redirectFrom", redirect.optString("from"));
if (redirect.has("tofragment") && !page.has("tofragment")) {
page.put("tofragment", redirect.getString("tofragment"));
}
}
}
}
}
// now sort the array based on the "index" property
Arrays.sort(pageArray, new Comparator<JSONObject>() {
@Override
public int compare(JSONObject lhs, JSONObject rhs) {
return ((Integer) lhs.optInt("index")).compareTo(rhs.optInt("index"));
}
});
// and create our list of PageTitles from the now-sorted array
for (JSONObject item : pageArray) {
String thumbUrl = null;
if (item.has("thumbnail")) {
thumbUrl = item.getJSONObject("thumbnail").optString("source", null);
}
String description = null;
if (item.has("terms")) {
JSONArray arr = item.getJSONObject("terms").optJSONArray("description");
if (arr != null && arr.length() > 0) {
description = arr.getString(0);
}
}
String titleText = item.getString("title");
if (item.has("tofragment")) {
titleText += "#" + item.getString("tofragment");
}
resultList.add(new SearchResult(new PageTitle(titleText, wiki, thumbUrl, description),
item.optString("redirectFrom")));
}
return new SearchResults(resultList, null, suggestion);
}
}