File Coverage

blib/lib/ElasticSearch/QueryParser.pm
Criterion Covered Total %
statement 237 247 95.9
branch 153 160 95.6
condition 52 63 82.5
subroutine 28 29 96.5
pod 3 3 100.0
total 473 502 94.2


line stmt bran cond sub pod time code
1             package ElasticSearch::QueryParser;
2             $ElasticSearch::QueryParser::VERSION = '0.68';
3 1     1   24188 use strict;
  1         3  
  1         48  
4 1     1   5 use warnings FATAL => 'all';
  1         2  
  1         45  
5 1     1   571 use ElasticSearch::Util qw(parse_params throw build_error);
  1         3  
  1         98  
6 1     1   6 use Scalar::Util qw(weaken);
  1         2  
  1         4361  
7              
8             =head1 NAME
9              
10             ElasticSearch::QueryParser - Check or filter query strings
11              
12             =head1 DESCRIPTION
13              
14             Passing an illegal query string to ElasticSearch, the request will fail.
15             When using a query string from an external source, eg the keywords field
16             from a web search form, it is important to filter it to avoid these
17             failures.
18              
19             You may also want to allow or disallow certain query string features, eg
20             the ability to search on a particular field.
21              
22             The L takes care of this for you.
23              
24             See L
25             for more information about the Lucene Query String syntax, and
26             L
27             for custom ElasticSearch extensions to the query string syntax.
28              
29             =head1 SYNOPSIS
30              
31             use ElasticSearch;
32             my $es = ElasticSearch->new(servers=>'127.0.0.1:9200');
33             my $qp = $es->query_parser(%opts);
34              
35             my $filtered_query_string = $qp->filter($unchecked_query_string)
36              
37             my $results = $es->search( query=> {
38             query_string=>{ query => $filtered_query_string }
39             });
40              
41             For example:
42              
43             my $qs = 'foo NOT AND -bar - baz * foo* secret_field:SIKRIT "quote';
44              
45             print $qp->filter($qs);
46             # foo AND -bar baz foo* "quote"
47              
48             =head1 METHODS
49              
50             =head2 new()
51              
52             my $qp = ElasticSearch::QueryParser->new(%opts);
53             my $qp = $es->query_parser(%opts);
54              
55             Creates a new L object, and sets the passed in
56             options (see L).
57              
58             =head2 filter()
59              
60             $filtered_query_string = $qp->filter($unchecked_query_string, %opts)
61              
62             Checks a passed in query string and returns a filtered version which is
63             suitable to pass to ElasticSearch.
64              
65             Note: C can still return an empty string, which is not considered
66             a valid query string, so you should still check for that before passing
67             to ElasticSearch.
68              
69             If any C<%opts> are passed in to C, these are added to the default
70             C<%opts> as set by L, and apply only for the current run.
71              
72             L does not promise to parse the query string in exactly
73             the same way as Lucene, just to clear it up so that it won't throw an
74             error when passed to ElasticSearch.
75              
76             =head2 check()
77              
78             $filtered_query_string = $qp->check($unchecked_query_string, %opts)
79              
80             Checks a passed in query string and throws an error if it is not valid.
81             This is useful for debugging your own query strings.
82              
83             If any C<%opts> are passed in to C, these are added to the default
84             C<%opts> as set by L, and apply only for the current run.
85              
86              
87             =head1 OPTIONS
88              
89             You can set various options to control how your query strings are filtered.
90              
91             The defaults (if no options are passed in) are:
92              
93             escape_reserved => 0
94             fields => 0
95             boost => 1
96             allow_bool => 1
97             allow_boost => 1
98             allow_fuzzy => 1
99             allow_slop => 1
100             allow_ranges => 0
101             wildcard_prefix => 1
102              
103             Any options passed in to L are merged with these defaults. These
104             options apply for the life of the QueryParser instance.
105              
106             Any options passed in to L or L are merged with
107             the options set in L and apply only for the current run.
108              
109             For instance:
110              
111             $qp = ElasticSearch::QueryParser->new(allow_fuzzy => 0);
112              
113             $qs = "foo~0.5 bar^2 foo:baz";
114              
115             print $qp->filter($qs, allow_fuzzy => 1, allow_boost => 0);
116             # foo~0.5 bar baz
117              
118             print $qp->filter($qs, fields => 1 );
119             # foo bar^2 foo:baz
120              
121             =head2 escape_reserved
122              
123             Reserved characters must be escaped to be used in the query string. By default,
124             L will remove these characters. Set C to true
125             if you want them to be escaped instead.
126              
127             Reserved characters: C< + - && || ! ( ) { } [ ] ^ " ~ * ? : \>
128              
129              
130             =head2 fields
131              
132             Normally, you don't want to allow your users to specify which fields to
133             search. By default, L removes any field prefixes, eg:
134              
135             $qp->filter('foo:bar secret_field:SIKRIT')
136             # bar SIKRIT
137              
138             You can set C to C<1> to allow all fields, or pass in a hashref
139             with a list of approved fieldnames, eg:
140              
141             $qp->filter('foo:bar secret_field:SIKRIT', fields => 1);
142             # foo:bar secret_field:SIKRIT
143              
144             $qp->filter('foo:bar secret_field:SIKRIT', fields => {foo => 1});
145             # foo:bar SIKRIT
146              
147             ElasticSearch extends the standard Lucene syntax to include:
148              
149             _exists_:fieldname
150             and
151             _missing_:fieldname
152              
153             The C option applies to these fieldnames as well.
154              
155             =head2 allow_bool
156              
157             Query strings can use boolean operators like:
158              
159             foo AND bar NOT baz OR ! (foo && bar)
160              
161             By default, boolean operators are allowed. Set C to C
162             to disable them.
163              
164             Note: This doesn't affect the C<+> or C<-> operators, which are always
165             allowed. eg:
166              
167             +apple -crab
168              
169             =head2 allow_boost
170              
171             Boost allows you to give a more importance to a particular word, group
172             of words or phrase, eg:
173              
174             foo^2 (bar baz)^3 "this exact phrase"^5
175              
176             By default, boost is enabled. Setting C to C would convert
177             the above example to:
178              
179             foo (bar baz) "this exact phrase"
180              
181             =head2 allow_fuzzy
182              
183             Lucene supports fuzzy searches based on the Levenshtein Distance, eg:
184              
185             supercalifragilisticexpialidocious~0.5
186              
187             To disable these, set C to false.
188              
189             =head2 allow_slop
190              
191             While a C (eg C<"this exact phrase">) looks for the exact
192             phrase, in the same order, you can use phrase slop to find all the words in
193             the phrase, in any order, within a certain number of words, eg:
194              
195             For the phrase: "The quick brown fox jumped over the lazy dog."
196              
197             Query string: Matches:
198             "quick brown" Yes
199             "brown quick" No
200             "quick fox" No
201             "brown quick"~2 Yes # within 2 words of each other
202             "fox dog"~6 Yes # within 6 words of each other
203              
204             To disable this "phrase slop", set C to C
205              
206             =head2 allow_ranges
207              
208             Lucene can accept ranges, eg:
209              
210             date:[2001 TO 2010] name:[alan TO john]
211              
212             To enable these, set C to C.
213              
214             =head2 wildcard_prefix
215              
216             Lucene can accept wildcard searches such as:
217              
218             jo*n smith?
219              
220             Lucene takes these wildcards and expands the search to include all matching
221             terms, eg C could be expanded to C, C, C etc
222              
223             This can result in a huge number of terms, so it is advisable to require
224             that the first C<$min> characters of the word are not wildcards.
225              
226             By default, the C requires that at least the first character
227             is not a wildcard, ie C<*> is not acceptable, but C is.
228              
229             You can change the minimum length of the non-wildcard prefix by setting
230             C, eg:
231              
232             $qp->filter("foo* foobar*", wildcard_prefix=>4)
233             # "foo foobar*"
234              
235             =cut
236              
237             #===================================
238             sub new {
239             #===================================
240 2     2 1 1284 my ( $proto, $params ) = parse_params(@_);
241 2   66     14 my $class = ref $proto || $proto;
242 2         25 $params = {
243             escape_reserved => 0,
244             fields => 0,
245             wildcard_prefix => 1,
246             allow_bool => 1,
247             allow_boost => 1,
248             allow_fuzzy => 1,
249             allow_slop => 1,
250             allow_ranges => 0,
251             %$params,
252             };
253 2         17 return bless {
254             _default_opts => $params,
255             _opts => $params,
256             }, $class;
257             }
258              
259             #===================================
260             sub filter {
261             #===================================
262 97     97 1 53572 my $self = shift;
263 97         146 my $text = shift;
264 97         295 my ( undef, $opts ) = parse_params( $self, @_ );
265 97         244 $opts->{fix} = 1;
266 97         220 return $self->_parse( $text, $opts );
267             }
268              
269             #===================================
270             sub check {
271             #===================================
272 97     97 1 125 my $self = shift;
273 97         114 my $text = shift;
274 97         295 my ( undef, $opts ) = parse_params( $self, @_ );
275 97         234 $opts->{fix} = 0;
276 97         197 return $self->_parse( $text, $opts );
277             }
278              
279             #===================================
280             sub _parse {
281             #===================================
282 194     194   280 my $self = shift;
283 194         246 my $text = shift;
284 194 50       448 $text = '' unless defined $text;
285 194         394 utf8::upgrade($text);
286              
287 194         212 my $opts = shift;
288 194         194 $self->{_opts} = { %{ $self->{_default_opts} }, %$opts };
  194         1555  
289              
290 194         771 $self->{_tokeniser} = $self->_init_tokeniser($text);
291 194         1923 $self->{_tokens} = [];
292 194         479 $self->{_stack} = [ {} ];
293 194         436 $self->{_start_pos} = 0;
294 194         239 $self->{_done} = 0;
295              
296 194         197 my $phrase;
297 194         254 eval {
298 194         501 $phrase = $self->_multi_clauses;
299 144 100 66     507 die "Syntax error\n"
300             unless $self->{_done} || $opts->{fix};
301             };
302 194 100       461 if ($@) {
303 58         325 $@ =~ s/\n$/:\n/;
304 58         231 substr( $text, $self->{_start_pos}, 0, ' ' );
305 58         306 $self->throw( 'QueryParser', "$@$text" );
306             }
307 136         733 return $phrase;
308             }
309              
310             #===================================
311             sub _dump_tokens {
312             #===================================
313 0     0   0 my $self = shift;
314 0         0 my $text = shift;
315 0 0       0 $text = '' unless defined $text;
316 0         0 utf8::upgrade($text);
317              
318 0         0 my $tokeniser = $self->_init_tokeniser($text);
319              
320 0         0 while ( my $next = $tokeniser->() ) {
321 0         0 printf "TOKEN: %-15s VARS: %s\n", shift @$next,
322 0         0 join( ', ', grep { defined $_ } @$next );
323             }
324             }
325              
326             #===================================
327             sub _next_token {
328             #===================================
329 1140     1140   1266 my $self = shift;
330 1140   100     1096 my $next = shift @{ $self->{_tokens} }
331             || $self->{_tokeniser}->();
332 1140 100       4179 return $next if $next;
333 257         370 $self->{_done} = 1;
334 257         1005 return;
335             }
336              
337             #===================================
338             sub _return_token {
339             #===================================
340 174     174   190 my $self = shift;
341 174         182 push @{ $self->{_tokens} }, shift;
  174         317  
342 174         804 $self->{_done} = 0;
343             }
344              
345             # 1 = Can follow
346             # 0 = Cannot follow, drop token and try next token
347             # undef = Cannot follow, stop looking
348              
349             my %Clauses = (
350             _LPAREN => 1,
351             _PLUS_MINUS => 1,
352             _EXISTS => 1,
353             _FIELD => 1,
354             _TERM => 1,
355             _PHRASE => 1,
356             _WILDTERM => 1,
357             _RANGE => 1,
358             _NOT => 1,
359             _AND_OR => 1,
360             _SPACE => 1,
361             _RESERVED => 1,
362             _ESCAPE => 1,
363             );
364              
365             my %Boost = ( _BOOST => 1 );
366              
367             my %Allowed = (
368             _CLAUSE => \%Clauses,
369             _LPAREN => { %Clauses, _RPAREN => 1 },
370             _AND_OR => { %Clauses, _AND_OR => 0 },
371             _NOT => { %Clauses, _NOT => 0, _AND_OR => 0 },
372             _PLUS_MINUS => {
373             %Clauses,
374             _NOT => 0,
375             _AND_OR => 0,
376             _PLUS_MINUS => 0,
377             _SPACE => undef,
378             },
379             _FIELD => {
380             _LPAREN => 1,
381             _TERM => 1,
382             _WILDTERM => 1,
383             _PHRASE => 1,
384             _RANGE => 1,
385             },
386             _PHRASE => { _BOOST => 1, _FUZZY => 1 },
387             _TERM => { _BOOST => 1, _FUZZY => 1 },
388             _WILDTERM => \%Boost,
389             _RANGE => \%Boost,
390             _FUZZY => \%Boost,
391             _RPAREN => \%Boost,
392             _EXISTS => \%Boost,
393             _BOOST => {},
394             _SPACE => {},
395             _RESERVED => {},
396             _ESCAPE => {},
397             );
398              
399             #===================================
400             sub _parse_context {
401             #===================================
402 942     942   1205 my $self = shift;
403 942         990 my $context = shift;
404 942         1373 my $allowed = $Allowed{$context};
405              
406 1111 100       2510 TOKEN: {
407 942         1057 my $token = $self->_next_token or return;
408              
409 856         1703 my ( $type, @args ) = @$token;
410 856 100       2206 if ( $allowed->{$type} ) {
    100          
411 666 100       1602 redo TOKEN if $type eq '_SPACE';
412 506         1287 return $self->$type(@args);
413             }
414             elsif ( defined $allowed->{$type} ) {
415 17 100       100 die "Syntax error\n" unless $self->{_opts}{fix};
416 9         23 redo TOKEN;
417             }
418             else {
419 173         571 $self->_return_token($token);
420 173         893 return undef;
421             }
422             }
423             }
424              
425             #===================================
426             sub _multi_clauses {
427             #===================================
428 214     214   255 my $self = shift;
429 214         217 my @clauses;
430 214         206 while (1) {
431 515         931 my $clause = $self->_parse_context('_CLAUSE');
432 465 100       1114 if ( !defined $clause ) {
433             last
434 173 100 100     178 if @{ $self->{_stack} } > 1
  173   100     1158  
435             || !$self->{_opts}{fix}
436             || $self->{_done};
437 9         19 $self->_next_token;
438 9         13 next;
439             }
440 292 100       658 next unless length $clause;
441 264         386 push @clauses, $clause;
442 264         883 $self->{_stack}[-1]{clauses}++;
443             }
444 164         603 return join( ' ', @clauses );
445             }
446              
447             #===================================
448             sub _AND_OR {
449             #===================================
450 45     45   63 my $self = shift;
451 45         58 my $op = shift;
452 45         66 my $opts = $self->{_opts};
453              
454 45 100       125 unless ( $self->{_stack}[-1]{clauses} ) {
455 8 100       34 return '' if $opts->{fix};
456 4         28 die "$op must be preceded by another clause\n";
457             }
458 37 100       79 unless ( $opts->{allow_bool} ) {
459 3 100       21 die qq("$op" not allowed) unless $opts->{fix};
460 2         10 return '';
461             }
462              
463 34         64 my $next = $self->_parse_context('_AND_OR');
464 30 100 66     238 return "$op $next"
465             if defined $next && length $next;
466              
467 4 100       18 return '' if $opts->{fix};
468 2         15 die "$op must be followed by a clause\n";
469             }
470              
471             #===================================
472             sub _NOT {
473             #===================================
474 27     27   36 my $self = shift;
475 27         43 my $op = shift;
476              
477 27         36 my $opts = $self->{_opts};
478 27 100       66 unless ( $opts->{allow_bool} ) {
479 3 100       21 die qq("$op" not allowed) unless $opts->{fix};
480 2         9 return '';
481             }
482              
483 24         52 my $next = $self->_parse_context('_NOT');
484 21 100       51 $next = '' unless defined $next;
485              
486 21 100 100     99 die "$op cannot be followed by + or -"
487             if $next =~ s/^[+-]+// && !$opts->{fix};
488              
489 20 100       118 return "$op $next"
490             if length $next;
491              
492 4 100       17 return '' if $opts->{fix};
493 2         16 die "$op must be followed by a clause\n";
494             }
495              
496             #===================================
497             sub _PLUS_MINUS {
498             #===================================
499 21     21   27 my $self = shift;
500 21         23 my $op = shift;
501 21         46 my $next = $self->_parse_context('_PLUS_MINUS');
502              
503 20 100 66     142 return "$op$next" if defined $next && length $next;
504              
505 3 100       14 return '' if $self->{_opts}{fix};
506 1         12 die "$op must be followed by a clause";
507             }
508              
509             #===================================
510             sub _LPAREN {
511             #===================================
512 20     20   28 my $self = shift;
513 20         22 push @{ $self->{_stack} }, {};
  20         46  
514 20         51 my $clause = $self->_multi_clauses;
515              
516 20         24 my $close = ')';
517 20         36 my $rparen = $self->_next_token;
518 20 100 66     89 if ( $rparen && $rparen->[0] eq '_RPAREN' ) {
    100          
519 18   100     33 my $next = $self->_parse_context('_RPAREN') || '';
520 18 100       35 $close .= $next if $next;
521 18         18 pop @{ $self->{_stack} };
  18         33  
522             }
523             elsif ( $self->{_opts}{fix} ) {
524 1         4 $self->_return_token($rparen);
525             }
526             else {
527 1         7 die "Missing closing parenthesis\n";
528             }
529 19 50       104 return $clause ? "(${clause}${close}" : '';
530             }
531              
532             #===================================
533             sub _BOOST {
534             #===================================
535 46     46   56 my $self = shift;
536 46 100       113 unless ( $self->{_opts}{allow_boost} ) {
537 7 100       47 die "Boost not allowed" unless $self->{_opts}{fix};
538 4         23 return '';
539             }
540 39         49 my $val = shift;
541 39 100 66     162 unless ( defined $val && length $val ) {
542 4 100       18 return '' if $self->{_opts}{fix};
543 2         15 die "Missing boost value\n";
544             }
545 35         203 return "^$val";
546             }
547              
548             #===================================
549             sub _FUZZY {
550             #===================================
551 40     40   43 my $self = shift;
552 40         107 my $fuzzy = shift;
553 40         52 my $opts = $self->{_opts};
554 40         50 my $fix = $opts->{fix};
555              
556 40 100       74 if ( $self->{current} eq '_PHRASE' ) {
557              
558             # phrase slop
559 21 100       31 if ( $opts->{allow_slop} ) {
560 17   100     53 $fuzzy = int( $fuzzy || 0 );
561 17 100       41 $fuzzy = $fuzzy ? "~$fuzzy" : '';
562             }
563             else {
564 4 100       21 die "Phrase slop not allowed\n" unless $fix;
565 2         3 $fuzzy = '';
566             }
567             }
568             else {
569              
570             # fuzzy
571 19 100       35 if ( $opts->{allow_fuzzy} ) {
572 15 100       37 if ( defined $fuzzy ) {
573 11 100       27 if ( $fuzzy <= 1 ) {
574 9         20 $fuzzy = "~$fuzzy";
575             }
576             else {
577 2 100       13 die "Fuzzy value must be between 0.0 and 1.0\n"
578             unless $fix;
579 1         2 $fuzzy = '';
580             }
581             }
582             else {
583 4         6 $fuzzy = '~';
584             }
585             }
586             else {
587 4 100       21 die "Fuzzy not allowed\n"
588             unless $fix;
589 2         4 $fuzzy = '';
590             }
591             }
592              
593 35   100     61 my $next = $self->_parse_context('_FUZZY') || '';
594 33         162 return "$fuzzy$next";
595             }
596              
597             #===================================
598             sub _PHRASE {
599             #===================================
600 28     28   35 my $self = shift;
601 28         34 my $string = shift;
602              
603 28         60 local $self->{current} = '_PHRASE';
604 28   100     56 my $next = $self->_parse_context('_PHRASE') || '';
605              
606 25         134 return qq("$string"$next);
607             }
608              
609             #===================================
610             sub _EXISTS {
611             #===================================
612 14     14   20 my $self = shift;
613 14         18 my $prefix = shift;
614 14         19 my $field = shift;
615              
616 14         24 my $opts = $self->{_opts};
617 14   100     30 my $next = $self->_parse_context('_EXISTS') || '';
618 14 100 100     82 unless ( $opts->{fields}
      66        
619             and ( !ref $opts->{fields} || $opts->{fields}{$field} ) )
620             {
621 5 100       56 return '' if $opts->{fix};
622 2         30 die qq("Field "$field" not allowed);
623             }
624              
625 9 100       42 return "$prefix:$field$next"
626             if $field;
627 3 100       20 return '' if $self->{_opts}{fix};
628 1         13 die "Missing field name for $prefix\n";
629             }
630              
631             #===================================
632             sub _FIELD {
633             #===================================
634 18     18   23 my $self = shift;
635 18         24 my $field = shift;
636              
637 18         27 my $opts = $self->{_opts};
638 18         33 my $next = $self->_parse_context('_FIELD');
639              
640 18 50 33     88 unless ( defined $next && length $next ) {
641 0 0       0 die "Missing clause after field $field\n"
642             unless $opts->{fix};
643 0         0 return '';
644             }
645              
646 18 100 100     137 return "$field:$next"
      66        
647             if $opts->{fields}
648             and !ref $opts->{fields} || $opts->{fields}{$field};
649              
650 10 100       68 die qq("Field "$field" not allowed)
651             unless $opts->{fix};
652              
653 5         18 return $next;
654             }
655              
656             #===================================
657             sub _TERM {
658             #===================================
659 196     196   242 my $self = shift;
660 196         614 local $self->{current} = '_TERM';
661 196   100     522 my $next = $self->_parse_context('_TERM') || '';
662 189         1413 return shift(@_) . $next;
663             }
664              
665             #===================================
666             sub _WILDTERM {
667             #===================================
668 13     13   15 my $self = shift;
669 13         20 my $term = shift;
670 13         26 my $min = $self->{_opts}{wildcard_prefix};
671 13   50     23 my $next = $self->_parse_context('_WILDTERM') || '';
672 13 100       143 if ( $term !~ /^[^*?]{$min}/ ) {
673 5 100       42 die "Wildcard cannot have * or ? "
    100          
674             . (
675             $min == 1 ? 'as first character' : "in first $min characters" )
676             unless $self->{_opts}{fix};
677 3         16 $term =~ s/[*?].*//;
678 3 100       15 return '' unless length $term;
679             }
680 10         61 return "$term$next";
681             }
682              
683             #===================================
684             sub _RANGE {
685             #===================================
686 26     26   37 my $self = shift;
687 26         54 my ( $open, $close, $from, $to ) = @_;
688 26         49 my $opts = $self->{_opts};
689 26   100     61 my $next = $self->_parse_context('_RANGE') || '';
690 26 100       61 unless ( $opts->{allow_ranges} ) {
691 2 100       17 die "Ranges not allowed\n"
692             unless $opts->{fix};
693 1         6 return '';
694             }
695 24 100       54 unless ( defined $to ) {
696 4 100       28 die "Malformed range\n" unless $opts->{fix};
697 2         12 return '';
698             }
699 20         149 return "$open$from TO $to$close$next";
700             }
701              
702             #===================================
703             sub _RESERVED {
704             #===================================
705 10     10   14 my $self = shift;
706 10         12 my $char = shift;
707 10 100       72 die "Reserved character $char\n"
708             unless $self->{_opts}{fix};
709 5 100       30 return $self->{_opts}{escape_reserved}
710             ? "\\$char"
711             : '';
712             }
713              
714             #===================================
715             sub _ESCAPE {
716             #===================================
717 2     2   3 my $self = shift;
718 2 100       12 die qq(Cannot end with "\\"\n)
719             unless $self->{_opts}{fix};
720 1         4 return '';
721             }
722              
723             my $DECIMAL = qr/[0-9]+(?:[.][0-9]+)?/;
724             my $NUM_CHAR = qr/[0-9]/;
725             my $ESC_CHAR = qr/\\./;
726             my $WS = qr/[ \t\n\r\x{3000}]/;
727             my $TERM_START_CHAR
728             = qr/[^ \t\n\r\x{3000}+\-!():^[\]"{}~*?\\&|] | $ESC_CHAR/x;
729             my $TERM_CHAR = qr/$TERM_START_CHAR |$ESC_CHAR | [-+]/x;
730             my $QUOTE_RANGE = qr/(?: " (?: \\" | [^"] )* ")/x;
731             my $RANGE_SEP = qr/ \s+ (?: TO \s+)?/x;
732              
733             #===================================
734             sub _init_tokeniser {
735             #===================================
736 207     207   9740 my $self = shift;
737 207         334 my $text = shift;
738              
739 207         236 my $weak_self = $self;
740 207         515 Scalar::Util::weaken($weak_self);
741             return sub {
742 1046   100     3281 TOKEN: {
743 1046     1046   1403 $weak_self->{_start_pos} = pos($text) || 0;
744 1046 100       5800 return ['_SPACE']
745             if $text =~ m/\G$WS/gc;
746 866 100       2646 return [ '_AND_OR', $1 ]
747             if $text =~ m/\G(AND\b | && | OR\b | \|{2})/gcx;
748 805 100       1834 return [ '_NOT', $1 ]
749             if $text =~ m/\G(NOT\b | !)/gcx;
750 774 100       1750 return [ '_PLUS_MINUS', $1 ]
751             if $text =~ m/\G([-+])/gc;
752 748 100       1551 return ['_LPAREN']
753             if $text =~ m/\G[(]/gc;
754 727 100       1455 return ['_RPAREN']
755             if $text =~ m/\G[)]/gc;
756 706 100       2980 return [ '_BOOST', $1 ]
757             if $text =~ m/\G\^($DECIMAL)?/gc;
758 654 100       2395 return [ '_FUZZY', $1 ]
759             if $text =~ m/\G[~]($DECIMAL)?/gc;
760 603 100       2318 return [ '_PHRASE', $1, $2 ]
761             if $text =~ m/\G " ( (?: $ESC_CHAR | [^"\\])*) "/gcx;
762 574 100       3035 return [ '_EXISTS', $1, $2 ]
763             if $text =~ m/\G
764             (_exists_|_missing_):
765             ((?:$TERM_START_CHAR $TERM_CHAR*)?)
766             /gcx;
767 558 100       2505 return [ '_FIELD', $1 ]
768             if $text =~ m/\G ($TERM_START_CHAR $TERM_CHAR*):/gcx;
769 539 100       7373 return [ '_TERM', $1 ]
770             if $text =~ m/\G
771             ( $TERM_START_CHAR $TERM_CHAR*)
772             (?!$TERM_CHAR | [*?])
773             /gcx;
774 338 100       2083 return [ '_WILDTERM', $1 ]
775             if $text =~ m/\G (
776             (?:$TERM_START_CHAR | [*?])
777             (?:$TERM_CHAR | [*?])*
778             )/gcx;
779 321 100       4138 return [ '_RANGE', '[', ']', $1, $2 ]
780             if $text =~ m/\G \[
781             ( $QUOTE_RANGE | [^ \]]+ )
782             (?: $RANGE_SEP
783             ( $QUOTE_RANGE | [^ \]]* )
784             )?
785             \]
786             /gcx;
787 293 100       1570 return [ '_RANGE', '{', '}', $1, $2 ]
788             if $text =~ m/\G \{
789             ( $QUOTE_RANGE | [^ }]+ )
790             (?:
791             $RANGE_SEP
792             ( $QUOTE_RANGE | [^ }]* )
793             )?
794             \}
795             /gcx;
796              
797 291 100       672 return [ '_RESERVED', $1 ]
798             if $text =~ m/\G ( ["&|!(){}[\]~^:+\-] )/gcx;
799              
800 273 100       1053 return ['_ESCAPE']
801             if $text =~ m/\G\\$/gc;
802             }
803 270         804 return;
804              
805 207         1454 };
806             }
807              
808             =head1 BUGS
809              
810             This is a new module, so it is likely that there will be bugs, and the list
811             of options and how L cleans up the query string may well change.
812              
813             If you have any suggestions for improvements, or find any bugs, please report
814             them to L.
815              
816             Patches welcome!
817              
818             =head1 LICENSE AND COPYRIGHT
819              
820             Copyright 2010 Clinton Gormley.
821              
822             This program is free software; you can redistribute it and/or modify it
823             under the terms of either: the GNU General Public License as published
824             by the Free Software Foundation; or the Artistic License.
825              
826             See http://dev.perl.org/licenses/ for more information.
827              
828              
829             =cut
830              
831             1;
832