| // Copyright (C) 2009 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.google.gwtexpui.safehtml.client; |
| |
| import static java.util.Comparator.comparing; |
| import static java.util.stream.Collectors.toList; |
| |
| import com.google.gwt.user.client.ui.SuggestOracle; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| |
| /** |
| * A suggestion oracle that tries to highlight the matched text. |
| * |
| * <p>Suggestions supplied by the implementation of {@link #onRequestSuggestions(Request, Callback)} |
| * are modified to wrap all occurrences of the {@link |
| * com.google.gwt.user.client.ui.SuggestOracle.Request#getQuery()} substring in HTML {@code |
| * <strong>} tags, so they can be emphasized to the user. |
| */ |
| public abstract class HighlightSuggestOracle extends SuggestOracle { |
| private static String escape(String ds) { |
| return new SafeHtmlBuilder().append(ds).asString(); |
| } |
| |
| @Override |
| public final boolean isDisplayStringHTML() { |
| return true; |
| } |
| |
| @Override |
| public final void requestSuggestions(Request request, Callback cb) { |
| onRequestSuggestions( |
| request, |
| new Callback() { |
| @Override |
| public void onSuggestionsReady(Request request, Response response) { |
| final String qpat = getQueryPattern(request.getQuery()); |
| final boolean html = isHTML(); |
| final ArrayList<Suggestion> r = new ArrayList<>(); |
| for (Suggestion s : response.getSuggestions()) { |
| r.add(new BoldSuggestion(qpat, s, html)); |
| } |
| cb.onSuggestionsReady(request, new Response(r)); |
| } |
| }); |
| } |
| |
| protected String getQueryPattern(String query) { |
| return query; |
| } |
| |
| /** |
| * @return true if {@link |
| * com.google.gwt.user.client.ui.SuggestOracle.Suggestion#getDisplayString()} returns HTML; |
| * false if the text must be escaped before evaluating in an HTML like context. |
| */ |
| protected boolean isHTML() { |
| return false; |
| } |
| |
| /** Compute the suggestions and return them for display. */ |
| protected abstract void onRequestSuggestions(Request request, Callback done); |
| |
| private static class BoldSuggestion implements Suggestion { |
| private final Suggestion suggestion; |
| private final String displayString; |
| |
| BoldSuggestion(String qstr, Suggestion s, boolean html) { |
| suggestion = s; |
| |
| String ds = s.getDisplayString(); |
| if (!html) { |
| ds = escape(ds); |
| } |
| |
| if (qstr != null && !qstr.isEmpty()) { |
| StringBuilder pattern = new StringBuilder(); |
| for (String qterm : splitQuery(qstr)) { |
| qterm = escape(qterm); |
| // We now surround qstr by <strong>. But the chosen approach is not too |
| // smooth, if qstr is small (e.g.: "t") and this small qstr may occur in |
| // escapes (e.g.: "Tim <email@example.org>"). Those escapes will |
| // get <strong>-ed as well (e.g.: "<" -> "&<strong>l</strong>t;"). But |
| // as repairing those mangled escapes is easier than not mangling them in |
| // the first place, we repair them afterwards. |
| if (pattern.length() > 0) { |
| pattern.append("|"); |
| } |
| pattern.append(qterm); |
| } |
| |
| ds = sgi(ds, "(" + pattern.toString() + ")", "<strong>$1</strong>"); |
| |
| // Repairing <strong>-ed escapes. |
| ds = sgi(ds, "(&[a-z]*)<strong>([a-z]*)</strong>([a-z]*;)", "$1$2$3"); |
| } |
| |
| displayString = ds; |
| } |
| |
| /** |
| * Split the query by whitespace and filter out query terms which are substrings of other query |
| * terms. |
| */ |
| private static List<String> splitQuery(String query) { |
| List<String> queryTerms = |
| Arrays.stream(query.split("\\s+")).sorted(comparing(String::length)).collect(toList()); |
| |
| List<String> result = new ArrayList<>(); |
| for (String s : queryTerms) { |
| boolean add = true; |
| for (String queryTerm : result) { |
| if (queryTerm.toLowerCase().contains(s.toLowerCase())) { |
| add = false; |
| break; |
| } |
| } |
| if (add) { |
| result.add(s); |
| } |
| } |
| return result; |
| } |
| |
| private static native String sgi(String inString, String pat, String newHtml) |
| /*-{ return inString.replace(RegExp(pat, 'gi'), newHtml); }-*/ ; |
| |
| @Override |
| public String getDisplayString() { |
| return displayString; |
| } |
| |
| @Override |
| public String getReplacementString() { |
| return suggestion.getReplacementString(); |
| } |
| } |
| } |