diff --git a/src/main/java/org/jabref/gui/preview/PreviewViewer.java b/src/main/java/org/jabref/gui/preview/PreviewViewer.java index 7f154c6e78d..6a0c42b0837 100644 --- a/src/main/java/org/jabref/gui/preview/PreviewViewer.java +++ b/src/main/java/org/jabref/gui/preview/PreviewViewer.java @@ -85,7 +85,7 @@ public class PreviewViewer extends ScrollPane implements InvalidationListener { private boolean registered; private ChangeListener> listener = (queryObservable, queryOldValue, queryNewValue) -> { - searchHighlightPattern = queryNewValue.flatMap(SearchQuery::getPatternForWords); + searchHighlightPattern = queryNewValue.flatMap(SearchQuery::getJavaScriptPatternForWords); highlightSearchPattern(); }; @@ -131,7 +131,7 @@ public void setTheme(String theme) { private void highlightSearchPattern() { if (searchHighlightPattern.isPresent()) { - String pattern = searchHighlightPattern.get().pattern().replace("\\Q", "").replace("\\E", ""); + String pattern = searchHighlightPattern.get().pattern(); previewView.getEngine().executeScript( "var markInstance = new Mark(document.getElementById(\"content\"));" + diff --git a/src/main/java/org/jabref/logic/search/SearchQuery.java b/src/main/java/org/jabref/logic/search/SearchQuery.java index e6e35486767..6d89beb3c4b 100644 --- a/src/main/java/org/jabref/logic/search/SearchQuery.java +++ b/src/main/java/org/jabref/logic/search/SearchQuery.java @@ -18,6 +18,25 @@ public class SearchQuery implements SearchMatcher { + /** + * Regex pattern for escaping special characters in javascript regular expressions + */ + public static final Pattern JAVASCRIPT_ESCAPED_CHARS_PATTERN = Pattern.compile("[\\.\\*\\+\\?\\^\\$\\{\\}\\(\\)\\|\\[\\]\\\\/]"); + + /** + * The mode of escaping special characters in regular expressions + */ + private enum EscapeMode { + /** + * using \Q and \E marks + */ + JAVA, + /** + * escaping all javascript regex special characters separately + */ + JAVASCRIPT + } + private final String query; private final boolean caseSensitive; private final boolean regularExpression; @@ -124,6 +143,18 @@ public List getSearchWords() { // Returns a regular expression pattern in the form (w1)|(w2)| ... wi are escaped if no regular expression search is enabled public Optional getPatternForWords() { + return joinWordsToPattern(EscapeMode.JAVA); + } + + // Returns a regular expression pattern in the form (w1)|(w2)| ... wi are escaped for javascript if no regular expression search is enabled + public Optional getJavaScriptPatternForWords() { + return joinWordsToPattern(EscapeMode.JAVASCRIPT); + } + + /** Returns a regular expression pattern in the form (w1)|(w2)| ... wi are escaped if no regular expression search is enabled + * @param escapeMode the mode of escaping special characters in wi + */ + private Optional joinWordsToPattern(EscapeMode escapeMode) { List words = getSearchWords(); if ((words == null) || words.isEmpty() || words.get(0).isEmpty()) { @@ -133,7 +164,20 @@ public Optional getPatternForWords() { // compile the words to a regular expression in the form (w1)|(w2)|(w3) StringJoiner joiner = new StringJoiner(")|(", "(", ")"); for (String word : words) { - joiner.add(regularExpression ? word : Pattern.quote(word)); + if (regularExpression) { + joiner.add(word); + } else { + switch (escapeMode) { + case JAVA: + joiner.add(Pattern.quote(word)); + break; + case JAVASCRIPT: + joiner.add(JAVASCRIPT_ESCAPED_CHARS_PATTERN.matcher(word).replaceAll("\\\\$0")); + break; + default: + throw new IllegalArgumentException("Unknown special characters escape mode: " + escapeMode); + } + } } String searchPattern = joiner.toString(); diff --git a/src/test/java/org/jabref/logic/search/SearchQueryTest.java b/src/test/java/org/jabref/logic/search/SearchQueryTest.java index d430eab0e3f..e8630135ed7 100644 --- a/src/test/java/org/jabref/logic/search/SearchQueryTest.java +++ b/src/test/java/org/jabref/logic/search/SearchQueryTest.java @@ -203,4 +203,38 @@ public void testGetPattern() { //We can't directly compare the pattern objects assertEquals(Optional.of(pattern.toString()), result.getPatternForWords().map(Pattern::toString)); } + + @Test + public void testGetRegexpPattern() { + String queryText = "[a-c]\\d* \\d*"; + SearchQuery regexQuery = new SearchQuery(queryText, false, true); + Pattern pattern = Pattern.compile("([a-c]\\d* \\d*)"); + assertEquals(Optional.of(pattern.toString()), regexQuery.getPatternForWords().map(Pattern::toString)); + } + + @Test + public void testGetRegexpJavascriptPattern() { + String queryText = "[a-c]\\d* \\d*"; + SearchQuery regexQuery = new SearchQuery(queryText, false, true); + Pattern pattern = Pattern.compile("([a-c]\\d* \\d*)"); + assertEquals(Optional.of(pattern.toString()), regexQuery.getJavaScriptPatternForWords().map(Pattern::toString)); + } + + @Test + public void testEscapingInPattern() { + //first word contain all java special regex characters + String queryText = "<([{\\\\^-=$!|]})?*+.> word1 word2."; + SearchQuery textQueryWithSpecialChars = new SearchQuery(queryText, false, false); + String pattern = "(\\Q<([{\\^-=$!|]})?*+.>\\E)|(\\Qword1\\E)|(\\Qword2.\\E)"; + assertEquals(Optional.of(pattern), textQueryWithSpecialChars.getPatternForWords().map(Pattern::toString)); + } + + @Test + public void testEscapingInJavascriptPattern() { + //first word contain all javascript special regex characters that should be escaped individually in text based search + String queryText = "([{\\\\^$|]})?*+./ word1 word2."; + SearchQuery textQueryWithSpecialChars = new SearchQuery(queryText, false, false); + String pattern = "(\\(\\[\\{\\\\\\^\\$\\|\\]\\}\\)\\?\\*\\+\\.\\/)|(word1)|(word2\\.)"; + assertEquals(Optional.of(pattern), textQueryWithSpecialChars.getJavaScriptPatternForWords().map(Pattern::toString)); + } }