~slub.team/goobi-indexserver/3.x

« back to all changes in this revision

Viewing changes to lucene/contrib/queryparser/src/java/org/apache/lucene/queryParser/standard/parser/EscapeQuerySyntaxImpl.java

  • Committer: Sebastian Meyer
  • Date: 2012-08-03 09:12:40 UTC
  • Revision ID: sebastian.meyer@slub-dresden.de-20120803091240-x6861b0vabq1xror
Remove Lucene and Solr source code and add patches instead
Fix Bug #985487: Auto-suggestion for the search interface

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
package org.apache.lucene.queryParser.standard.parser;
2
 
 
3
 
/**
4
 
 * Licensed to the Apache Software Foundation (ASF) under one or more
5
 
 * contributor license agreements.  See the NOTICE file distributed with
6
 
 * this work for additional information regarding copyright ownership.
7
 
 * The ASF licenses this file to You under the Apache License, Version 2.0
8
 
 * (the "License"); you may not use this file except in compliance with
9
 
 * the License.  You may obtain a copy of the License at
10
 
 *
11
 
 *     http://www.apache.org/licenses/LICENSE-2.0
12
 
 *
13
 
 * Unless required by applicable law or agreed to in writing, software
14
 
 * distributed under the License is distributed on an "AS IS" BASIS,
15
 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 
 * See the License for the specific language governing permissions and
17
 
 * limitations under the License.
18
 
 */
19
 
 
20
 
import java.util.Locale;
21
 
 
22
 
import org.apache.lucene.messages.MessageImpl;
23
 
import org.apache.lucene.queryParser.core.messages.QueryParserMessages;
24
 
import org.apache.lucene.queryParser.core.parser.EscapeQuerySyntax;
25
 
import org.apache.lucene.queryParser.core.util.UnescapedCharSequence;
26
 
 
27
 
/**
28
 
 */
29
 
public class EscapeQuerySyntaxImpl implements EscapeQuerySyntax {
30
 
 
31
 
  private static final char[] wildcardChars = { '*', '?' };
32
 
 
33
 
  private static final String[] escapableTermExtraFirstChars = { "+", "-", "@" };
34
 
 
35
 
  private static final String[] escapableTermChars = { "\"", "<", ">", "=",
36
 
      "!", "(", ")", "^", "[", "{", ":", "]", "}", "~" };
37
 
 
38
 
  // TODO: check what to do with these "*", "?", "\\"
39
 
  private static final String[] escapableQuotedChars = { "\"" };
40
 
  private static final String[] escapableWhiteChars = { " ", "\t", "\n", "\r",
41
 
      "\f", "\b", "\u3000" };
42
 
  private static final String[] escapableWordTokens = { "AND", "OR", "NOT",
43
 
      "TO", "WITHIN", "SENTENCE", "PARAGRAPH", "INORDER" };
44
 
 
45
 
  private static final CharSequence escapeChar(CharSequence str, Locale locale) {
46
 
    if (str == null || str.length() == 0)
47
 
      return str;
48
 
 
49
 
    CharSequence buffer = str;
50
 
 
51
 
    // regular escapable Char for terms
52
 
    for (int i = 0; i < escapableTermChars.length; i++) {
53
 
      buffer = replaceIgnoreCase(buffer, escapableTermChars[i].toLowerCase(),
54
 
          "\\", locale);
55
 
    }
56
 
 
57
 
    // First Character of a term as more escaping chars
58
 
    for (int i = 0; i < escapableTermExtraFirstChars.length; i++) {
59
 
      if (buffer.charAt(0) == escapableTermExtraFirstChars[i].charAt(0)) {
60
 
        buffer = "\\" + buffer.charAt(0)
61
 
            + buffer.subSequence(1, buffer.length());
62
 
        break;
63
 
      }
64
 
    }
65
 
 
66
 
    return buffer;
67
 
  }
68
 
 
69
 
  private final CharSequence escapeQuoted(CharSequence str, Locale locale) {
70
 
    if (str == null || str.length() == 0)
71
 
      return str;
72
 
 
73
 
    CharSequence buffer = str;
74
 
 
75
 
    for (int i = 0; i < escapableQuotedChars.length; i++) {
76
 
      buffer = replaceIgnoreCase(buffer, escapableTermChars[i].toLowerCase(),
77
 
          "\\", locale);
78
 
    }
79
 
    return buffer;
80
 
  }
81
 
 
82
 
  private static final CharSequence escapeTerm(CharSequence term, Locale locale) {
83
 
    if (term == null)
84
 
      return term;
85
 
 
86
 
    // Escape single Chars
87
 
    term = escapeChar(term, locale);
88
 
    term = escapeWhiteChar(term, locale);
89
 
 
90
 
    // Escape Parser Words
91
 
    for (int i = 0; i < escapableWordTokens.length; i++) {
92
 
      if (escapableWordTokens[i].equalsIgnoreCase(term.toString()))
93
 
        return "\\" + term;
94
 
    }
95
 
    return term;
96
 
  }
97
 
 
98
 
  /**
99
 
   * replace with ignore case
100
 
   * 
101
 
   * @param string
102
 
   *          string to get replaced
103
 
   * @param sequence1
104
 
   *          the old character sequence in lowercase
105
 
   * @param escapeChar
106
 
   *          the new character to prefix sequence1 in return string.
107
 
   * @return the new String
108
 
   */
109
 
  private static CharSequence replaceIgnoreCase(CharSequence string,
110
 
      CharSequence sequence1, CharSequence escapeChar, Locale locale) {
111
 
    if (escapeChar == null || sequence1 == null || string == null)
112
 
      throw new NullPointerException();
113
 
 
114
 
    // empty string case
115
 
    int count = string.length();
116
 
    int sequence1Length = sequence1.length();
117
 
    if (sequence1Length == 0) {
118
 
      StringBuilder result = new StringBuilder((count + 1)
119
 
          * escapeChar.length());
120
 
      result.append(escapeChar);
121
 
      for (int i = 0; i < count; i++) {
122
 
        result.append(string.charAt(i));
123
 
        result.append(escapeChar);
124
 
      }
125
 
      return result.toString();
126
 
    }
127
 
 
128
 
    // normal case
129
 
    StringBuilder result = new StringBuilder();
130
 
    char first = sequence1.charAt(0);
131
 
    int start = 0, copyStart = 0, firstIndex;
132
 
    while (start < count) {
133
 
      if ((firstIndex = string.toString().toLowerCase(locale).indexOf(first,
134
 
          start)) == -1)
135
 
        break;
136
 
      boolean found = true;
137
 
      if (sequence1.length() > 1) {
138
 
        if (firstIndex + sequence1Length > count)
139
 
          break;
140
 
        for (int i = 1; i < sequence1Length; i++) {
141
 
          if (string.toString().toLowerCase(locale).charAt(firstIndex + i) != sequence1
142
 
              .charAt(i)) {
143
 
            found = false;
144
 
            break;
145
 
          }
146
 
        }
147
 
      }
148
 
      if (found) {
149
 
        result.append(string.toString().substring(copyStart, firstIndex));
150
 
        result.append(escapeChar);
151
 
        result.append(string.toString().substring(firstIndex,
152
 
            firstIndex + sequence1Length));
153
 
        copyStart = start = firstIndex + sequence1Length;
154
 
      } else {
155
 
        start = firstIndex + 1;
156
 
      }
157
 
    }
158
 
    if (result.length() == 0 && copyStart == 0)
159
 
      return string;
160
 
    result.append(string.toString().substring(copyStart));
161
 
    return result.toString();
162
 
  }
163
 
 
164
 
  /**
165
 
   * escape all tokens that are part of the parser syntax on a given string
166
 
   * 
167
 
   * @param str
168
 
   *          string to get replaced
169
 
   * @param locale
170
 
   *          locale to be used when performing string compares
171
 
   * @return the new String
172
 
   */
173
 
  private static final CharSequence escapeWhiteChar(CharSequence str,
174
 
      Locale locale) {
175
 
    if (str == null || str.length() == 0)
176
 
      return str;
177
 
 
178
 
    CharSequence buffer = str;
179
 
 
180
 
    for (int i = 0; i < escapableWhiteChars.length; i++) {
181
 
      buffer = replaceIgnoreCase(buffer, escapableWhiteChars[i].toLowerCase(),
182
 
          "\\", locale);
183
 
    }
184
 
    return buffer;
185
 
  }
186
 
 
187
 
  public CharSequence escape(CharSequence text, Locale locale, Type type) {
188
 
    if (text == null || text.length() == 0)
189
 
      return text;
190
 
 
191
 
    // escape wildcards and the escape char (this has to be perform before
192
 
    // anything else)
193
 
    // since we need to preserve the UnescapedCharSequence and escape the
194
 
    // original escape chars
195
 
    if (text instanceof UnescapedCharSequence) {
196
 
      text = ((UnescapedCharSequence) text).toStringEscaped(wildcardChars);
197
 
    } else {
198
 
      text = new UnescapedCharSequence(text).toStringEscaped(wildcardChars);
199
 
    }
200
 
 
201
 
    if (type == Type.STRING) {
202
 
      return escapeQuoted(text, locale);
203
 
    } else {
204
 
      return escapeTerm(text, locale);
205
 
    }
206
 
  }
207
 
 
208
 
  /**
209
 
   * Returns a String where the escape char has been removed, or kept only once
210
 
   * if there was a double escape.
211
 
   * 
212
 
   * Supports escaped unicode characters, e. g. translates <code>A</code> to
213
 
   * <code>A</code>.
214
 
   * 
215
 
   */
216
 
  public static UnescapedCharSequence discardEscapeChar(CharSequence input)
217
 
      throws ParseException {
218
 
    // Create char array to hold unescaped char sequence
219
 
    char[] output = new char[input.length()];
220
 
    boolean[] wasEscaped = new boolean[input.length()];
221
 
 
222
 
    // The length of the output can be less than the input
223
 
    // due to discarded escape chars. This variable holds
224
 
    // the actual length of the output
225
 
    int length = 0;
226
 
 
227
 
    // We remember whether the last processed character was
228
 
    // an escape character
229
 
    boolean lastCharWasEscapeChar = false;
230
 
 
231
 
    // The multiplier the current unicode digit must be multiplied with.
232
 
    // E. g. the first digit must be multiplied with 16^3, the second with
233
 
    // 16^2...
234
 
    int codePointMultiplier = 0;
235
 
 
236
 
    // Used to calculate the codepoint of the escaped unicode character
237
 
    int codePoint = 0;
238
 
 
239
 
    for (int i = 0; i < input.length(); i++) {
240
 
      char curChar = input.charAt(i);
241
 
      if (codePointMultiplier > 0) {
242
 
        codePoint += hexToInt(curChar) * codePointMultiplier;
243
 
        codePointMultiplier >>>= 4;
244
 
        if (codePointMultiplier == 0) {
245
 
          output[length++] = (char) codePoint;
246
 
          codePoint = 0;
247
 
        }
248
 
      } else if (lastCharWasEscapeChar) {
249
 
        if (curChar == 'u') {
250
 
          // found an escaped unicode character
251
 
          codePointMultiplier = 16 * 16 * 16;
252
 
        } else {
253
 
          // this character was escaped
254
 
          output[length] = curChar;
255
 
          wasEscaped[length] = true;
256
 
          length++;
257
 
        }
258
 
        lastCharWasEscapeChar = false;
259
 
      } else {
260
 
        if (curChar == '\\') {
261
 
          lastCharWasEscapeChar = true;
262
 
        } else {
263
 
          output[length] = curChar;
264
 
          length++;
265
 
        }
266
 
      }
267
 
    }
268
 
 
269
 
    if (codePointMultiplier > 0) {
270
 
      throw new ParseException(new MessageImpl(
271
 
          QueryParserMessages.INVALID_SYNTAX_ESCAPE_UNICODE_TRUNCATION));
272
 
    }
273
 
 
274
 
    if (lastCharWasEscapeChar) {
275
 
      throw new ParseException(new MessageImpl(
276
 
          QueryParserMessages.INVALID_SYNTAX_ESCAPE_CHARACTER));
277
 
    }
278
 
 
279
 
    return new UnescapedCharSequence(output, wasEscaped, 0, length);
280
 
  }
281
 
 
282
 
  /** Returns the numeric value of the hexadecimal character */
283
 
  private static final int hexToInt(char c) throws ParseException {
284
 
    if ('0' <= c && c <= '9') {
285
 
      return c - '0';
286
 
    } else if ('a' <= c && c <= 'f') {
287
 
      return c - 'a' + 10;
288
 
    } else if ('A' <= c && c <= 'F') {
289
 
      return c - 'A' + 10;
290
 
    } else {
291
 
      throw new ParseException(new MessageImpl(
292
 
          QueryParserMessages.INVALID_SYNTAX_ESCAPE_NONE_HEX_UNICODE, c));
293
 
    }
294
 
  }
295
 
 
296
 
}