View | Details | Raw Unified | Return to bug 24735
Collapse All | Expand All

(-)a/C4/AuthoritiesMarc.pm (-31 / +3 lines)
Lines 113-120 sub SearchAuthorities { Link Here
113
    $sortby="" unless $sortby;
113
    $sortby="" unless $sortby;
114
    my $query;
114
    my $query;
115
    my $qpquery = '';
115
    my $qpquery = '';
116
    my $QParser;
117
    $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser'));
118
    my $attr = '';
116
    my $attr = '';
119
        # the marclist may contain "mainentry". In this case, search the tag_to_report, that depends on
117
        # the marclist may contain "mainentry". In this case, search the tag_to_report, that depends on
120
        # the authtypecode. Then, search on $a of this tag_to_report
118
        # the authtypecode. Then, search on $a of this tag_to_report
Lines 133-141 sub SearchAuthorities { Link Here
133
        if ($n>1){
131
        if ($n>1){
134
            while ($n>1){$query= "\@or ".$query;$n--;}
132
            while ($n>1){$query= "\@or ".$query;$n--;}
135
        }
133
        }
136
        if ($QParser) {
137
            $qpquery .= '(authtype:' . join('|| authtype:', @auths) . ')';
138
        }
139
    }
134
    }
140
135
141
    my $dosearch;
136
    my $dosearch;
Lines 203-211 sub SearchAuthorities { Link Here
203
            $q2 .= $attr;
198
            $q2 .= $attr;
204
            $dosearch = 1;
199
            $dosearch = 1;
205
            ++$attr_cnt;
200
            ++$attr_cnt;
206
            if ($QParser) {
207
                $qpquery .= " $tags->[$i]:\"$value->[$i]\"";
208
            }
209
        }    #if value
201
        }    #if value
210
    }
202
    }
211
    ##Add how many queries generated
203
    ##Add how many queries generated
Lines 226-246 sub SearchAuthorities { Link Here
226
    } elsif ($sortby eq 'AuthidDsc') {
218
    } elsif ($sortby eq 'AuthidDsc') {
227
        $orderstring = '@attr 7=2 @attr 4=109 @attr 1=Local-Number 0';
219
        $orderstring = '@attr 7=2 @attr 4=109 @attr 1=Local-Number 0';
228
    }
220
    }
229
    if ($QParser) {
221
    $query=($query?$query:"\@attr 1=_ALLRECORDS \@attr 2=103 ''");
230
        $qpquery .= ' all:all' unless $value->[0];
222
    $query="\@or $orderstring $query" if $orderstring;
231
232
        if ( $value->[0] =~ m/^qp=(.*)$/ ) {
233
            $qpquery = $1;
234
        }
235
236
        $qpquery .= " #$sortby" unless $sortby eq '';
237
238
        $QParser->parse( $qpquery );
239
        $query = $QParser->target_syntax('authorityserver');
240
    } else {
241
        $query=($query?$query:"\@attr 1=_ALLRECORDS \@attr 2=103 ''");
242
        $query="\@or $orderstring $query" if $orderstring;
243
    }
244
223
245
    $offset = 0 if not defined $offset or $offset < 0;
224
    $offset = 0 if not defined $offset or $offset < 0;
246
    my $counter = $offset;
225
    my $counter = $offset;
Lines 758-771 sub FindDuplicateAuthority { Link Here
758
    my $auth_tag_to_report = Koha::Authority::Types->find($authtypecode)->auth_tag_to_report;
737
    my $auth_tag_to_report = Koha::Authority::Types->find($authtypecode)->auth_tag_to_report;
759
#     warn "record :".$record->as_formatted."  auth_tag_to_report :$auth_tag_to_report";
738
#     warn "record :".$record->as_formatted."  auth_tag_to_report :$auth_tag_to_report";
760
    # build a request for SearchAuthorities
739
    # build a request for SearchAuthorities
761
    my $QParser;
740
    my $op = 'AND';
762
    $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser'));
763
    my $op;
764
    if ($QParser) {
765
        $op = '&&';
766
    } else {
767
        $op = 'AND';
768
    }
769
    $authtypecode =~ s#/#\\/#; # GENRE/FORM contains forward slash which is a reserved character
741
    $authtypecode =~ s#/#\\/#; # GENRE/FORM contains forward slash which is a reserved character
770
    my $query='at:'.$authtypecode.' ';
742
    my $query='at:'.$authtypecode.' ';
771
    my $filtervalues=qr([\001-\040\Q!'"`#$%&*+,-./:;<=>?@(){[}_|~\E\]]);
743
    my $filtervalues=qr([\001-\040\Q!'"`#$%&*+,-./:;<=>?@(){[}_|~\E\]]);
(-)a/C4/Context.pm (-47 lines)
Lines 783-835 sub restore_dbh Link Here
783
    # return something, then this function should, too.
783
    # return something, then this function should, too.
784
}
784
}
785
785
786
=head2 queryparser
787
788
  $queryparser = C4::Context->queryparser
789
790
Returns a handle to an initialized Koha::QueryParser::Driver::PQF object.
791
792
=cut
793
794
sub queryparser {
795
    my $self = shift;
796
    unless (defined $context->{"queryparser"}) {
797
        $context->{"queryparser"} = &_new_queryparser();
798
    }
799
800
    return
801
      defined( $context->{"queryparser"} )
802
      ? $context->{"queryparser"}->new
803
      : undef;
804
}
805
806
=head2 _new_queryparser
807
808
Internal helper function to create a new QueryParser object. QueryParser
809
is loaded dynamically so as to keep the lack of the QueryParser library from
810
getting in anyone's way.
811
812
=cut
813
814
sub _new_queryparser {
815
    my $qpmodules = {
816
        'OpenILS::QueryParser'           => undef,
817
        'Koha::QueryParser::Driver::PQF' => undef
818
    };
819
    if ( can_load( 'modules' => $qpmodules ) ) {
820
        my $QParser     = Koha::QueryParser::Driver::PQF->new();
821
        my $config_file = $context->config('queryparser_config');
822
        $config_file ||= '/etc/koha/searchengine/queryparser.yaml';
823
        if ( $QParser->load_config($config_file) ) {
824
            # Set 'keyword' as the default search class
825
            $QParser->default_search_class('keyword');
826
            # TODO: allow indexes to be configured in the database
827
            return $QParser;
828
        }
829
    }
830
    return;
831
}
832
833
=head2 userenv
786
=head2 userenv
834
787
835
  C4::Context->userenv;
788
  C4::Context->userenv;
(-)a/C4/Matcher.pm (-22 / +6 lines)
Lines 622-647 sub get_matches { Link Here
622
622
623
    my $matches = {};
623
    my $matches = {};
624
624
625
    my $QParser;
626
    $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser'));
627
    foreach my $matchpoint ( @{ $self->{'matchpoints'} } ) {
625
    foreach my $matchpoint ( @{ $self->{'matchpoints'} } ) {
628
        my @source_keys = _get_match_keys( $source_record, $matchpoint );
626
        my @source_keys = _get_match_keys( $source_record, $matchpoint );
629
627
630
        next if scalar(@source_keys) == 0;
628
        next if scalar(@source_keys) == 0;
631
629
632
        # FIXME - because of a bug in QueryParser, an expression ofthe
633
        # format 'isbn:"isbn1" || isbn:"isbn2" || isbn"isbn3"...'
634
        # does not get parsed correctly, so we will not
635
        # do AggressiveMatchOnISBN if UseQueryParser is on
636
        @source_keys = C4::Koha::GetVariationsOfISBNs(@source_keys)
630
        @source_keys = C4::Koha::GetVariationsOfISBNs(@source_keys)
637
          if ( $matchpoint->{index} =~ /^isbn$/i
631
          if ( $matchpoint->{index} =~ /^isbn$/i
638
            && C4::Context->preference('AggressiveMatchOnISBN') )
632
            && C4::Context->preference('AggressiveMatchOnISBN') );
639
            && !C4::Context->preference('UseQueryParser');
640
633
641
        @source_keys = C4::Koha::GetVariationsOfISSNs(@source_keys)
634
        @source_keys = C4::Koha::GetVariationsOfISSNs(@source_keys)
642
          if ( $matchpoint->{index} =~ /^issn$/i
635
          if ( $matchpoint->{index} =~ /^issn$/i
643
            && C4::Context->preference('AggressiveMatchOnISSN') )
636
            && C4::Context->preference('AggressiveMatchOnISSN') );
644
            && !C4::Context->preference('UseQueryParser');
645
637
646
        # build query
638
        # build query
647
        my $query;
639
        my $query;
Lines 650-667 sub get_matches { Link Here
650
        my $total_hits;
642
        my $total_hits;
651
        if ( $self->{'record_type'} eq 'biblio' ) {
643
        if ( $self->{'record_type'} eq 'biblio' ) {
652
644
653
            #NOTE: The QueryParser can't handle the CCL syntax of 'qualifier','qualifier', so fallback to non-QueryParser.
645
            my $phr = ( C4::Context->preference('AggressiveMatchOnISBN') || C4::Context->preference('AggressiveMatchOnISSN') )  ? ',phr' : q{};
654
            #NOTE: You can see this in C4::Search::SimpleSearch() as well in a different way.
646
            $query = join( " OR ",
655
            if ($QParser && $matchpoint->{'index'} !~ m/\w,\w/) {
647
                map { "$matchpoint->{'index'}$phr=\"$_\"" } @source_keys );
656
                $query = join( " || ",
648
                #NOTE: double-quote the values so you don't get a "Embedded truncation not supported" error when a term has a ? in it.
657
                    map { "$matchpoint->{'index'}:$_" } @source_keys );
658
            }
659
            else {
660
                my $phr = ( C4::Context->preference('AggressiveMatchOnISBN') || C4::Context->preference('AggressiveMatchOnISSN') )  ? ',phr' : q{};
661
                $query = join( " OR ",
662
                    map { "$matchpoint->{'index'}$phr=\"$_\"" } @source_keys );
663
                    #NOTE: double-quote the values so you don't get a "Embedded truncation not supported" error when a term has a ? in it.
664
            }
665
649
666
            # Use state variables to avoid recreating the objects every time.
650
            # Use state variables to avoid recreating the objects every time.
667
            # With Elasticsearch this also avoids creating a massive amount of
651
            # With Elasticsearch this also avoids creating a massive amount of
(-)a/C4/Search.pm (-166 / +8 lines)
Lines 99-120 sub FindDuplicate { Link Here
99
        $query = "isbn:$result->{isbn}";
99
        $query = "isbn:$result->{isbn}";
100
    }
100
    }
101
    else {
101
    else {
102
        my $QParser;
102
103
        $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser'));
103
        my $titleindex = 'ti,ext';
104
        my $titleindex;
104
        my $authorindex = 'au,ext';
105
        my $authorindex;
105
        my $op = 'and';
106
        my $op;
107
108
        if ($QParser) {
109
            $titleindex = 'title|exact';
110
            $authorindex = 'author|exact';
111
            $op = '&&';
112
            $QParser->custom_data->{'QueryAutoTruncate'} = C4::Context->preference('QueryAutoTruncate');
113
        } else {
114
            $titleindex = 'ti,ext';
115
            $authorindex = 'au,ext';
116
            $op = 'and';
117
        }
118
106
119
        $result->{title} =~ s /\\//g;
107
        $result->{title} =~ s /\\//g;
120
        $result->{title} =~ s /\"//g;
108
        $result->{title} =~ s /\"//g;
Lines 233-257 sub SimpleSearch { Link Here
233
    my $results = [];
221
    my $results = [];
234
    my $total_hits = 0;
222
    my $total_hits = 0;
235
223
236
    my $QParser;
237
    $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser') && ! ($query =~ m/\w,\w|\w=\w/));
238
    if ($QParser) {
239
        $QParser->custom_data->{'QueryAutoTruncate'} = C4::Context->preference('QueryAutoTruncate');
240
    }
241
242
    # Initialize & Search Zebra
224
    # Initialize & Search Zebra
243
    for ( my $i = 0 ; $i < @servers ; $i++ ) {
225
    for ( my $i = 0 ; $i < @servers ; $i++ ) {
244
        eval {
226
        eval {
245
            $zconns[$i] = C4::Context->Zconn( $servers[$i], 1 );
227
            $zconns[$i] = C4::Context->Zconn( $servers[$i], 1 );
246
            if ($QParser) {
228
            $query =~ s/:/=/g unless $options{skip_normalize};
247
                $query =~ s/=/:/g unless $options{skip_normalize};
229
            $zoom_queries[$i] = new ZOOM::Query::CCL2RPN( $query, $zconns[$i]);
248
                $QParser->parse( $query );
249
                $query = $QParser->target_syntax($servers[$i]);
250
                $zoom_queries[$i] = new ZOOM::Query::PQF( $query, $zconns[$i]);
251
            } else {
252
                $query =~ s/:/=/g unless $options{skip_normalize};
253
                $zoom_queries[$i] = new ZOOM::Query::CCL2RPN( $query, $zconns[$i]);
254
            }
255
            $tmpresults[$i] = $zconns[$i]->search( $zoom_queries[$i] );
230
            $tmpresults[$i] = $zconns[$i]->search( $zoom_queries[$i] );
256
231
257
            # error handling
232
            # error handling
Lines 1277-1410 sub getIndexes{ Link Here
1277
    return \@indexes;
1252
    return \@indexes;
1278
}
1253
}
1279
1254
1280
=head2 _handle_exploding_index
1281
1282
    my $query = _handle_exploding_index($index, $term)
1283
1284
Callback routine to generate the search for "exploding" indexes (i.e.
1285
those indexes which are turned into multiple or-connected searches based
1286
on authority data).
1287
1288
=cut
1289
1290
sub _handle_exploding_index {
1291
    my ($QParser, $filter, $params, $negate, $server) = @_;
1292
    my $index = $filter;
1293
    my $term = join(' ', @$params);
1294
1295
    return unless ($index =~ m/(su-br|su-na|su-rl)/ && $term);
1296
1297
    my $marcflavour = C4::Context->preference('marcflavour');
1298
1299
    my $codesubfield = $marcflavour eq 'UNIMARC' ? '5' : 'w';
1300
    my $wantedcodes = '';
1301
    my @subqueries = ( "\@attr 1=Subject \@attr 4=1 \"$term\"");
1302
    my ($error, $results, $total_hits) = SimpleSearch( "he:$term", undef, undef, [ "authorityserver" ] );
1303
    foreach my $auth (@$results) {
1304
        my $record = MARC::Record->new_from_usmarc($auth);
1305
        my @references = $record->field('5..');
1306
        if (@references) {
1307
            if ($index eq 'su-br') {
1308
                $wantedcodes = 'g';
1309
            } elsif ($index eq 'su-na') {
1310
                $wantedcodes = 'h';
1311
            } elsif ($index eq 'su-rl') {
1312
                $wantedcodes = '';
1313
            }
1314
            foreach my $reference (@references) {
1315
                my $codes = $reference->subfield($codesubfield);
1316
                push @subqueries, '@attr 1=Subject @attr 4=1 "' . $reference->as_string('abcdefghijlmnopqrstuvxyz') . '"' if (($codes && $codes eq $wantedcodes) || !$wantedcodes);
1317
            }
1318
        }
1319
    }
1320
    my $query = ' @or ' x (scalar(@subqueries) - 1) . join(' ', @subqueries);
1321
    return $query;
1322
}
1323
1324
=head2 parseQuery
1325
1326
    ( $operators, $operands, $indexes, $limits,
1327
      $sort_by, $scan, $lang ) =
1328
            parseQuery ( $operators, $operands, $indexes, $limits, $sort_by, $scan, $lang);
1329
1330
Shim function to ease the transition from buildQuery to a new QueryParser.
1331
This function is called at the beginning of buildQuery, and modifies
1332
buildQuery's input. If it can handle the input, it returns a query that
1333
buildQuery will not try to parse.
1334
1335
=cut
1336
1337
sub parseQuery {
1338
    my ( $operators, $operands, $indexes, $limits, $sort_by, $scan, $lang) = @_;
1339
1340
    my @operators = $operators ? @$operators : ();
1341
    my @indexes   = $indexes   ? @$indexes   : ();
1342
    my @operands  = $operands  ? @$operands  : ();
1343
    my @limits    = $limits    ? @$limits    : ();
1344
    my @sort_by   = $sort_by   ? @$sort_by   : ();
1345
1346
    my $query = $operands[0];
1347
    my $index;
1348
    my $term;
1349
    my $query_desc;
1350
1351
    my $QParser;
1352
    $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser') || $query =~ s/^qp=//);
1353
    undef $QParser if ($query =~ m/^(ccl=|pqf=|cql=)/ || grep (/\w,\w|\w=\w/, @operands, @indexes) );
1354
    undef $QParser if (scalar @limits > 0);
1355
1356
    if ($QParser)
1357
    {
1358
        $QParser->custom_data->{'QueryAutoTruncate'} = C4::Context->preference('QueryAutoTruncate');
1359
        $query = '';
1360
        for ( my $ii = 0 ; $ii <= @operands ; $ii++ ) {
1361
            next unless $operands[$ii];
1362
            $query .= $operators[$ii-1] && $operators[ $ii - 1 ] eq 'or' ? ' || ' : ' && '
1363
              if ($query);
1364
            if ( $operands[$ii] =~ /^[^"]\W*[-|_\w]*:\w.*[^"]$/ ) {
1365
                $query .= $operands[$ii];
1366
            }
1367
            elsif ( $indexes[$ii] && $indexes[$ii] =~ m/su-/ ) {
1368
                $query .= $indexes[$ii] . '(' . $operands[$ii] . ')';
1369
            }
1370
            else {
1371
                $query .=
1372
                  ( $indexes[$ii] ? "$indexes[$ii]:" : '' ) . $operands[$ii];
1373
            }
1374
        }
1375
        foreach my $limit (@limits) {
1376
        }
1377
        if ( scalar(@sort_by) > 0 ) {
1378
            my $modifier_re =
1379
              '#(' . join( '|', @{ $QParser->modifiers } ) . ')';
1380
            $query =~ s/$modifier_re//g;
1381
            foreach my $modifier (@sort_by) {
1382
                $query .= " #$modifier";
1383
            }
1384
        }
1385
1386
        $query_desc = $query;
1387
        $query_desc =~ s/\s+/ /g;
1388
        if ( C4::Context->preference("QueryWeightFields") ) {
1389
        }
1390
        $QParser->add_bib1_filter_map( 'su-br' => 'biblioserver' =>
1391
              { 'target_syntax_callback' => \&_handle_exploding_index } );
1392
        $QParser->add_bib1_filter_map( 'su-na' => 'biblioserver' =>
1393
              { 'target_syntax_callback' => \&_handle_exploding_index } );
1394
        $QParser->add_bib1_filter_map( 'su-rl' => 'biblioserver' =>
1395
              { 'target_syntax_callback' => \&_handle_exploding_index } );
1396
        $QParser->parse($query);
1397
        $operands[0] = "pqf=" . $QParser->target_syntax('biblioserver');
1398
    }
1399
    else {
1400
        require Koha::QueryParser::Driver::PQF;
1401
        my $modifier_re = '#(' . join( '|', @{Koha::QueryParser::Driver::PQF->modifiers}) . ')';
1402
        s/$modifier_re//g for @operands;
1403
    }
1404
1405
    return ( $operators, \@operands, $indexes, $limits, $sort_by, $scan, $lang, $query_desc);
1406
}
1407
1408
=head2 buildQuery
1255
=head2 buildQuery
1409
1256
1410
( $error, $query,
1257
( $error, $query,
Lines 1427-1433 sub buildQuery { Link Here
1427
    warn "---------\nEnter buildQuery\n---------" if $DEBUG;
1274
    warn "---------\nEnter buildQuery\n---------" if $DEBUG;
1428
1275
1429
    my $query_desc;
1276
    my $query_desc;
1430
    ( $operators, $operands, $indexes, $limits, $sort_by, $scan, $lang, $query_desc) = parseQuery($operators, $operands, $indexes, $limits, $sort_by, $scan, $lang);
1431
1277
1432
    # dereference
1278
    # dereference
1433
    my @operators = $operators ? @$operators : ();
1279
    my @operators = $operators ? @$operators : ();
Lines 1487-1498 sub buildQuery { Link Here
1487
        return ( undef, $', $', "q=cql=".uri_escape_utf8($'), $', '', '', '', 'cql' );
1333
        return ( undef, $', $', "q=cql=".uri_escape_utf8($'), $', '', '', '', 'cql' );
1488
    }
1334
    }
1489
    if ( $query =~ /^pqf=/ ) {
1335
    if ( $query =~ /^pqf=/ ) {
1490
        if ($query_desc) {
1336
        $query_desc = $';
1491
            $query_cgi = "q=".uri_escape_utf8($query_desc);
1337
        $query_cgi = "q=pqf=".uri_escape_utf8($');
1492
        } else {
1493
            $query_desc = $';
1494
            $query_cgi = "q=pqf=".uri_escape_utf8($');
1495
        }
1496
        return ( undef, $', $', $query_cgi, $query_desc, '', '', '', 'pqf' );
1338
        return ( undef, $', $', $query_cgi, $query_desc, '', '', '', 'pqf' );
1497
    }
1339
    }
1498
1340
(-)a/C4/UsageStats.pm (-1 lines)
Lines 304-310 sub BuildReport { Link Here
304
        TraceCompleteSubfields
304
        TraceCompleteSubfields
305
        TraceSubjectSubdivisions
305
        TraceSubjectSubdivisions
306
        UseICU
306
        UseICU
307
        UseQueryParser
308
        defaultSortField
307
        defaultSortField
309
        displayFacetCount
308
        displayFacetCount
310
        OPACdefaultSortField
309
        OPACdefaultSortField
(-)a/Koha/QueryParser/Driver/PQF.pm (-925 lines)
Lines 1-925 Link Here
1
package Koha::QueryParser::Driver::PQF;
2
3
# This file is part of Koha.
4
#
5
# Copyright 2012 C & P Bibliography Services
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
use base qw(OpenILS::QueryParser Class::Accessor);
21
22
use strict;
23
use warnings;
24
25
use Module::Load::Conditional qw(can_load);
26
use Koha::QueryParser::Driver::PQF::Util;
27
use Koha::QueryParser::Driver::PQF::query_plan;
28
use Koha::QueryParser::Driver::PQF::query_plan::facet;
29
use Koha::QueryParser::Driver::PQF::query_plan::filter;
30
use Koha::QueryParser::Driver::PQF::query_plan::modifier;
31
use Koha::QueryParser::Driver::PQF::query_plan::node;
32
use Koha::QueryParser::Driver::PQF::query_plan::node::atom;
33
use Koha::QueryParser::Driver::PQF::query_plan::node::atom;
34
35
36
=head1 NAME
37
38
Koha::QueryParser::Driver::PQF - QueryParser driver for PQF
39
40
=head1 SYNOPSIS
41
42
    use Koha::QueryParser::Driver::PQF;
43
    my $QParser = Koha::QueryParser::Driver::PQF->new(%args);
44
45
=head1 DESCRIPTION
46
47
Main entrypoint into the QueryParser PQF driver. PQF is the Prefix Query
48
Language, the syntax used to serialize Z39.50 queries.
49
50
=head1 ACCESSORS
51
52
In order to simplify Bib-1 attribute mapping, this driver uses Class::Accessor
53
for accessing the following maps:
54
55
=over 4
56
57
=item B<bib1_field_map> - search class/field Bib-1 mappings
58
59
=item B<bib1_modifier_map> - search modifier mappings
60
61
=item B<bib1_filter_map> - search filter mappings
62
63
=item B<bib1_relevance_bump_map> - relevance bump mappings
64
65
=back
66
67
=cut
68
69
__PACKAGE__->mk_accessors(qw(bib1_field_map bib1_modifier_map bib1_filter_map bib1_relevance_bump_map));
70
71
=head1 FUNCTIONS
72
73
=cut
74
75
=head2 get
76
77
Overridden accessor method for Class::Accessor. (Do not call directly)
78
79
=cut
80
81
sub get {
82
    my $self = shift;
83
    return $self->_map(@_);
84
}
85
86
=head2 set
87
88
Overridden mutator method for Class::Accessor. (Do not call directly)
89
90
=cut
91
92
sub set {
93
    my $self = shift;
94
    return $self->_map(@_);
95
}
96
97
=head2 add_bib1_field_map
98
99
    $QParser->add_bib1_field_map($class => $field => $server => \%attributes);
100
101
    $QParser->add_bib1_field_map('author' => 'personal' => 'biblioserver' =>
102
                                    { '1' => '1003' });
103
104
Adds a search field<->bib1 attribute mapping for the specified server. The
105
%attributes hash contains maps Bib-1 Attributes to the appropropriate
106
values. Not all attributes must be specified.
107
108
=cut
109
110
sub add_bib1_field_map {
111
    my ($self, $class, $field, $server, $attributes) = @_;
112
113
    $self->add_search_field( $class => $field );
114
    return $self->_add_field_mapping($self->bib1_field_map, $class, $field, $server, $attributes);
115
}
116
117
=head2 add_bib1_modifier_map
118
119
    $QParser->add_bib1_modifier_map($name => $server => \%attributes);
120
121
    $QParser->add_bib1_modifier_map('ascending' => 'biblioserver' =>
122
                                    { '7' => '1' });
123
124
Adds a search modifier<->bib1 attribute mapping for the specified server. The
125
%attributes hash contains maps Bib-1 Attributes to the appropropriate
126
values. Not all attributes must be specified.
127
128
=cut
129
130
sub add_bib1_modifier_map {
131
    my ($self, $name, $server, $attributes) = @_;
132
133
    $self->add_search_modifier( $name );
134
135
    return $self->_add_mapping($self->bib1_modifier_map, $name, $server, $attributes);
136
}
137
138
=head2 add_bib1_filter_map
139
140
    $QParser->add_bib1_filter_map($name => $server => \%attributes);
141
142
    $QParser->add_bib1_filter_map('date' => 'biblioserver' =>
143
                                    { 'callback' => &_my_callback });
144
145
Adds a search filter<->bib1 attribute mapping for the specified server. The
146
%attributes hash maps Bib-1 Attributes to the appropropriate values and
147
provides a callback for the filter. Not all attributes must be specified.
148
149
=cut
150
151
sub add_bib1_filter_map {
152
    my ($self, $name, $server, $attributes) = @_;
153
154
    $self->add_search_filter( $name, $attributes->{'callback'} );
155
156
    return $self->_add_mapping($self->bib1_filter_map, $name, $server, $attributes);
157
}
158
159
=head2 add_relevance_bump
160
161
    $QParser->add_relevance_bump($class, $field, $server, $multiplier, $active);
162
    $QParser->add_relevance_bump('title' => 'exact' => 'biblioserver' => 34, 1);
163
164
Add a relevance bump to the specified field. When searching for a class without
165
any fields, all the relevance bumps for the specified class will be 'OR'ed
166
together.
167
168
=cut
169
170
sub add_relevance_bump {
171
    my ($self, $class, $field, $server, $multiplier, $active) = @_;
172
    my $attributes = { '9' => $multiplier, '2' => '102', 'active' => $active };
173
174
    $self->add_search_field( $class => $field );
175
    return $self->_add_field_mapping($self->bib1_relevance_bump_map, $class, $field, $server, $attributes);
176
}
177
178
179
=head2 target_syntax
180
181
    my $pqf = $QParser->target_syntax($server, [$query]);
182
    my $pqf = $QParser->target_syntax('biblioserver', 'author|personal:smith');
183
    print $pqf; # assuming all the indexes are configured,
184
                # prints '@attr 1=1003 @attr 4=6 "smith"'
185
186
Transforms the current or specified query into a PQF query string for the
187
specified server.
188
189
=cut
190
191
sub target_syntax {
192
    my ($self, $server, $query) = @_;
193
    my $pqf = '';
194
    $self->parse($query) if $query;
195
    warn "QP query for $server: " . $self->query . "\n" if $self->debug;
196
    $pqf = $self->parse_tree->target_syntax($server);
197
    warn "PQF query: $pqf\n" if $self->debug;
198
    $pqf =~ s/ +/ /g;
199
    $pqf =~ s/^ //;
200
    $pqf =~ s/ $//;
201
    return $pqf;
202
}
203
204
=head2 date_filter_target_callback
205
206
    $QParser->add_bib1_filter_map($server, { 'target_syntax_callback' => \&Koha::QueryParser::Driver::PQF::date_filter_target_callback, '1' => 'pubdate' });
207
208
Callback for date filters. Note that although the first argument is the QParser
209
object, this is technically not an object-oriented routine. This has no
210
real-world implications.
211
212
=cut
213
214
sub date_filter_target_callback {
215
    my ($QParser, $filter, $params, $negate, $server) = @_;
216
    my $attr_string = $QParser->bib1_mapping_by_name( 'filter', $filter, $server )->{'attr_string'};
217
    my $pqf = '';
218
    foreach my $datespec (@$params) {
219
        my $datepqf = ' ';
220
        if ($datespec) {
221
            if ($datespec =~ m/(.*)-(.*)/) {
222
                if ($1) {
223
                    $datepqf .= $attr_string . ' @attr 2=4 "' . $1 . '"';
224
                }
225
                if ($2) {
226
                    $datepqf .= $attr_string . ' @attr 2=2 "' . $2 . '"';
227
                    $datepqf = ' @and ' . $datepqf if $1;
228
                }
229
            } else {
230
                $datepqf .= $attr_string . ' "' . $datespec . '"';
231
            }
232
        }
233
        $pqf = ' @or ' . ($negate ? '@not @attr 1=_ALLRECORDS @attr 2=103 "" ' : '') . $pqf if $pqf;
234
        $pqf .= $datepqf;
235
    }
236
    return $pqf;
237
}
238
239
=head2 _map
240
241
    return $self->_map('bib1_field_map', $map);
242
243
Retrieves or sets a map.
244
245
=cut
246
247
sub _map {
248
    my ($self, $name, $map) = @_;
249
    $self->custom_data->{$name} ||= {};
250
    $self->custom_data->{$name} = $map if ($map);
251
    return $self->custom_data->{$name};
252
}
253
254
=head2 _add_mapping
255
256
    return $self->_add_mapping($map, $name, $server, $attributes)
257
258
Adds a mapping. Note that this is not used for mappings relating to fields.
259
260
=cut
261
262
sub _add_mapping {
263
    my ($self, $map, $name, $server, $attributes) = @_;
264
265
    my $attr_string = Koha::QueryParser::Driver::PQF::Util::attributes_to_attr_string($attributes);
266
    $attributes->{'attr_string'} = $attr_string;
267
268
    $map->{'by_name'}{$name}{$server} = $attributes;
269
    $map->{'by_attr'}{$server}{$attr_string} = { 'name' => $name, %$attributes };
270
271
    return $map;
272
}
273
274
=head2 _add_field_mapping
275
276
    return $self->_add_field_mapping($map, $class, $field, $server, $attributes)
277
278
Adds a mapping for field-related data.
279
280
=cut
281
282
sub _add_field_mapping {
283
    my ($self, $map, $class, $field, $server, $attributes) = @_;
284
    my $attr_string = Koha::QueryParser::Driver::PQF::Util::attributes_to_attr_string($attributes);
285
    $attributes->{'attr_string'} = $attr_string;
286
287
    $map->{'by_name'}{$class}{$field}{$server} = $attributes;
288
    $map->{'by_attr'}{$server}{$attr_string} = { 'classname' => $class, 'field' => $field, %$attributes };
289
    return $map;
290
}
291
292
293
=head2 bib1_mapping_by_name
294
295
    my $attributes = $QParser->bib1_mapping_by_name($type, $name[, $subname], $server);
296
    my $attributes = $QParser->bib1_mapping_by_name('field', 'author', 'personal', 'biblioserver');
297
    my $attributes = $QParser->bib1_mapping_by_name('filter', 'pubdate', 'biblioserver');
298
299
Retrieve the Bib-1 attribute set associated with the specified mapping.
300
=cut
301
302
sub bib1_mapping_by_name {
303
    my $server = pop;
304
    my ($self, $type, $name, $field) = @_;
305
306
    return unless ($server && $name);
307
    return unless ($type eq 'field' || $type eq 'modifier' || $type eq 'filter' || $type eq 'relevance_bump');
308
    if ($type eq 'field' || $type eq 'relevance_bump') {
309
    # Unfortunately field is a special case thanks to the class->field hierarchy
310
        return $self->_map('bib1_' . $type . '_map')->{'by_name'}{$name}{$field}{$server} if $field;
311
        return $self->_map('bib1_' . $type . '_map')->{'by_name'}{$name};
312
    } else {
313
        return $self->_map('bib1_' . $type . '_map')->{'by_name'}{$name}{$server};
314
    }
315
}
316
317
=head2 bib1_mapping_by_attr
318
319
    my $field = $QParser->bib1_mapping_by_attr($type, $server, \%attr);
320
    my $field = $QParser->bib1_mapping_by_attr('field', 'biblioserver', {'1' => '1004'});
321
    print $field->{'classname'}; # prints "author"
322
    print $field->{'field'}; # prints "personal"
323
324
Retrieve the search field/modifier/filter used for the specified Bib-1 attribute set.
325
326
=cut
327
328
sub bib1_mapping_by_attr {
329
    my ($self, $type, $server, $attributes) = @_;
330
    return unless ($server && $attributes);
331
332
    my $attr_string = Koha::QueryParser::Driver::PQF::Util::attributes_to_attr_string($attributes);
333
334
    return $self->bib1_mapping_by_attr_string($type, $server, $attr_string);
335
}
336
337
=head2 bib1_mapping_by_attr_string
338
339
    my $field = $QParser->bib1_mapping_by_attr_string($type, $server, $attr_string);
340
    my $field = $QParser->bib1_mapping_by_attr_string('field', 'biblioserver', '@attr 1=1004');
341
    print $field->{'classname'}; # prints "author"
342
    print $field->{'field'}; # prints "personal"
343
344
Retrieve the search field/modifier/filter used for the specified Bib-1 attribute string
345
(i.e. PQF snippet).
346
347
=cut
348
349
sub bib1_mapping_by_attr_string {
350
    my ($self, $type, $server, $attr_string) = @_;
351
    return unless ($server && $attr_string);
352
    return unless ($type eq 'field' || $type eq 'modifier' || $type eq 'filter' || $type eq 'relevance_bump');
353
354
    return $self->_map('bib1_' . $type . '_map')->{'by_attr'}{$server}{$attr_string};
355
}
356
357
=head2 clear_all_configuration
358
359
    $QParser->clear_all_configuration
360
361
Clear all configuration. This is a highly destructive method. You may
362
not want to use it.
363
364
=cut
365
366
sub clear_all_configuration {
367
    my ($self) = @_;
368
    %OpenILS::QueryParser::parser_config = (
369
        'OpenILS::QueryParser' => {
370
            filters => [],
371
            modifiers => [],
372
            operators => {
373
                'and' => '&&',
374
                'or' => '||',
375
                float_start => '{{',
376
                float_end => '}}',
377
                group_start => '(',
378
                group_end => ')',
379
                required => '+',
380
                disallowed => '-',
381
                modifier => '#',
382
                negated => '!'
383
            }
384
        }
385
    );
386
    return $self;
387
}
388
389
=head2 clear_all_mappings
390
391
    $QParser->clear_all_mappings
392
393
Clear all bib-1 mappings.
394
395
=cut
396
397
sub clear_all_mappings {
398
    my ($self) = @_;
399
400
    foreach my $name (qw(field modifier filter relevance_bump)) {
401
        $self->custom_data->{'bib1_' . $name . '_map'} = { };
402
    }
403
    return $self;
404
}
405
406
407
=head2 _canonicalize_field_map
408
409
Convert a field map into its canonical form for serialization. Used only for
410
fields and relevance bumps.
411
412
=cut
413
414
sub _canonicalize_field_map {
415
    my ( $map, $aliases ) = @_;
416
    my $canonical_map = {};
417
418
    foreach my $class ( keys %{ $map->{'by_name'} } ) {
419
        $canonical_map->{$class} ||= {};
420
        foreach my $field ( keys %{ $map->{'by_name'}->{$class} } ) {
421
            my $field_map = {
422
                'index'   => $field,
423
                'label'   => ucfirst($field),
424
                'enabled' => '1',
425
            };
426
            foreach
427
              my $server ( keys %{ $map->{'by_name'}->{$class}->{$field} } )
428
            {
429
                $field_map->{'bib1_mapping'} ||= {};
430
                $field_map->{'bib1_mapping'}->{$server} =
431
                  $map->{'by_name'}->{$class}->{$field}->{$server};
432
                delete $field_map->{'bib1_mapping'}->{$server}->{'attr_string'}
433
                  if defined(
434
                          $field_map->{'bib1_mapping'}->{$server}
435
                            ->{'attr_string'}
436
                  );
437
            }
438
            if ($aliases) {
439
                $field_map->{'aliases'} = [];
440
                foreach my $alias ( @{ $aliases->{$class}->{$field} } ) {
441
                    push @{ $field_map->{'aliases'} },
442
                      $alias;
443
                }
444
            }
445
            $canonical_map->{$class}->{$field} = $field_map;
446
        }
447
    }
448
    return $canonical_map;
449
}
450
451
=head2 _canonicalize_map
452
453
Convert a map into its canonical form for serialization. Not used for fields.
454
455
=cut
456
457
sub _canonicalize_map {
458
    my ($map) = @_;
459
    my $canonical_map = {};
460
461
    foreach my $name ( keys %{ $map->{'by_name'} } ) {
462
        $canonical_map->{$name} = {
463
            'label'        => ucfirst($name),
464
            'enabled'      => 1,
465
            'bib1_mapping' => {}
466
        };
467
        foreach my $server ( keys %{ $map->{'by_name'}->{$name} } ) {
468
            $canonical_map->{$name}->{'bib1_mapping'}->{$server} =
469
              $map->{'by_name'}->{$name}->{$server};
470
            delete $canonical_map->{$name}->{'bib1_mapping'}->{$server}
471
              ->{'attr_string'}
472
              if defined(
473
                      $canonical_map->{$name}->{'bib1_mapping'}->{$server}
474
                        ->{'attr_string'}
475
              );
476
        }
477
    }
478
    return $canonical_map;
479
}
480
481
=head2 serialize_mappings
482
483
    my $yaml = $QParser->serialize_mappings;
484
    my $json = $QParser->serialize_mappings('json');
485
486
Serialize Bib-1 mappings to YAML or JSON.
487
488
=cut
489
490
sub serialize_mappings {
491
    my ( $self, $format ) = @_;
492
    $format ||= 'yaml';
493
    my $config;
494
495
    $config->{'field_mappings'} =
496
      _canonicalize_field_map( $self->bib1_field_map,
497
        $self->search_field_aliases );
498
    $config->{'modifier_mappings'} =
499
      _canonicalize_map( $self->bib1_modifier_map );
500
    $config->{'filter_mappings'} = _canonicalize_map( $self->bib1_filter_map );
501
    $config->{'relevance_bumps'} =
502
      _canonicalize_field_map( $self->bib1_relevance_bump_map );
503
504
    if ( $format eq 'json' && can_load( modules => { 'JSON' => undef } ) ) {
505
        return JSON::to_json($config);
506
    }
507
    elsif ( can_load( modules => { 'YAML::Any' => undef } ) ) {
508
        return YAML::Any::Dump($config);
509
    }
510
    return;
511
}
512
513
=head2 initialize
514
515
    $QParser->initialize( { 'bib1_field_mappings' => \%bib1_field_mappings,
516
                            'search_field_alias_mappings' => \%search_field_alias_mappings,
517
                            'bib1_modifier_mappings' => \%bib1_modifier_mappings,
518
                            'bib1_filter_mappings' => \%bib1_filter_mappings,
519
                            'relevance_bumps' => \%relevance_bumps });
520
521
Initialize the QueryParser mapping tables based on the provided configuration.
522
This method was written to play nice with YAML configuration files loaded by load_config.
523
524
=cut
525
526
sub initialize {
527
    my ( $self, $args ) = @_;
528
529
    my $field_mappings    = $args->{'field_mappings'};
530
    my $modifier_mappings = $args->{'modifier_mappings'};
531
    my $filter_mappings   = $args->{'filter_mappings'};
532
    my $relbumps          = $args->{'relevance_bumps'};
533
    my ( $server, $bib1_mapping );
534
    foreach my $class ( keys %$field_mappings ) {
535
        foreach my $field ( keys %{ $field_mappings->{$class} } ) {
536
            if ( $field_mappings->{$class}->{$field}->{'enabled'} ) {
537
                while ( ( $server, $bib1_mapping ) =
538
                    each
539
                    %{ $field_mappings->{$class}->{$field}->{'bib1_mapping'} } )
540
                {
541
                    $self->add_bib1_field_map(
542
                        $class => $field => $server => $bib1_mapping );
543
                }
544
                foreach my $alias (
545
                    @{ $field_mappings->{$class}->{$field}->{'aliases'} } )
546
                {
547
                    $self->add_search_field_alias( $class => $field => $alias );
548
                }
549
            }
550
        }
551
    }
552
    foreach my $modifier ( keys %$modifier_mappings ) {
553
        if ( $modifier_mappings->{$modifier}->{'enabled'} ) {
554
            while ( ( $server, $bib1_mapping ) =
555
                each %{ $modifier_mappings->{$modifier}->{'bib1_mapping'} } )
556
            {
557
                $self->add_bib1_modifier_map(
558
                    $modifier => $server => $bib1_mapping );
559
            }
560
        }
561
    }
562
    foreach my $filter ( keys %$filter_mappings ) {
563
        if ( $filter_mappings->{$filter}->{'enabled'} ) {
564
            while ( ( $server, $bib1_mapping ) =
565
                each %{ $filter_mappings->{$filter}->{'bib1_mapping'} } )
566
            {
567
                if ( $bib1_mapping->{'target_syntax_callback'} eq
568
                    'date_filter_target_callback' )
569
                {
570
                    $bib1_mapping->{'target_syntax_callback'} =
571
                      \&Koha::QueryParser::Driver::PQF::date_filter_target_callback;
572
                }
573
                $self->add_bib1_filter_map(
574
                    $filter => $server => $bib1_mapping );
575
            }
576
        }
577
    }
578
    foreach my $class ( keys %$relbumps ) {
579
        foreach my $field ( keys %{ $relbumps->{$class} } ) {
580
            if ( $relbumps->{$class}->{$field}->{'enabled'} ) {
581
                while ( ( $server, $bib1_mapping ) =
582
                    each %{ $relbumps->{$class}->{$field}->{'bib1_mapping'} } )
583
                {
584
                    $self->add_relevance_bump(
585
                        $class => $field => $server => $bib1_mapping,
586
                        1
587
                    );
588
                }
589
            }
590
        }
591
    }
592
    return $self;
593
}
594
595
=head2 load_config
596
597
  $QParser->load_config($file_name);
598
599
Load a YAML file with a parser configuration. The YAML file should match the following format:
600
601
    ---
602
    field_mappings:
603
      author:
604
        "":
605
          aliases:
606
            - au
607
          bib1_mapping:
608
            biblioserver:
609
              1: 1003
610
          enabled: 1
611
          index: ''
612
          label: ''
613
        conference:
614
          aliases:
615
            - conference
616
            - cfn
617
          bib1_mapping:
618
            biblioserver:
619
              1: 1006
620
          enabled: 1
621
          index: conference
622
          label: Conference
623
    filter_mappings:
624
      acqdate:
625
        bib1_mapping:
626
          biblioserver:
627
            1: Date-of-acquisition
628
            4: 4
629
            target_syntax_callback: date_filter_target_callback
630
        enabled: 1
631
        label: Acqdate
632
    modifier_mappings:
633
      AuthidAsc:
634
        bib1_mapping:
635
          authorityserver:
636
            "": 0
637
            1: Local-Number
638
            7: 1
639
            op: "@or"
640
        enabled: 1
641
        label: AuthidAsc
642
    ...
643
644
=cut
645
646
sub load_config {
647
    my ($self, $file) = @_;
648
    require YAML::Any;
649
    return unless ($file && -f $file);
650
    my $config = YAML::Any::LoadFile($file);
651
    return unless ($config);
652
    $self->initialize($config);
653
    return 1;
654
}
655
656
=head2 TEST_SETUP
657
658
    $QParser->TEST_SETUP
659
660
This routine initializes the QueryParser driver with a reasonable set of
661
defaults. This is intended only for testing. Although such test stubs are
662
generally not included in Koha, this type of test stub is used by other
663
QueryParser implementations, and it seems sensible to maintain consistency
664
as much as possible.
665
666
=cut
667
668
sub TEST_SETUP {
669
    my ($self) = @_;
670
671
    $self->default_search_class( 'keyword' );
672
673
    $self->add_bib1_field_map('keyword' => 'abstract' => 'biblioserver' => { '1' => '62' } );
674
    $self->add_search_field_alias( 'keyword' => 'abstract' => 'ab' );
675
    $self->add_bib1_field_map('keyword' => '' => 'biblioserver' => { '1' => '1016' } );
676
    $self->add_search_field_alias( 'keyword' => '' => 'kw' );
677
    $self->add_bib1_field_map('author' => '' => 'biblioserver' => { '1' => '1003' } );
678
    $self->add_search_field_alias( 'author' => '' => 'au' );
679
    $self->add_bib1_field_map('author' => 'personal' => 'biblioserver' => { '1' => '1004' } );
680
    $self->add_bib1_field_map('author' => 'corporate' => 'biblioserver' => { '1' => '1005' } );
681
    $self->add_search_field_alias( 'author' => 'corporate' => 'cpn' );
682
    $self->add_bib1_field_map('author' => 'conference' => 'biblioserver' => { '1' => '1006' } );
683
    $self->add_search_field_alias( 'author' => 'conference' => 'cfn' );
684
    $self->add_bib1_field_map('keyword' => 'local-classification' => 'biblioserver' => { '1' => '20' } );
685
    $self->add_search_field_alias( 'keyword' => 'local-classification' => 'lcn' );
686
    $self->add_search_field_alias( 'keyword' => 'local-classification' => 'callnum' );
687
    $self->add_bib1_field_map('keyword' => 'bib-level' => 'biblioserver' => { '1' => '1021' } );
688
    $self->add_bib1_field_map('keyword' => 'code-institution' => 'biblioserver' => { '1' => '56' } );
689
    $self->add_bib1_field_map('keyword' => 'language' => 'biblioserver' => { '1' => '54' } );
690
    $self->add_search_field_alias( 'keyword' => 'language' => 'ln' );
691
    $self->add_bib1_field_map('keyword' => 'record-type' => 'biblioserver' => { '1' => '1001' } );
692
    $self->add_search_field_alias( 'keyword' => 'record-type' => 'rtype' );
693
    $self->add_search_field_alias( 'keyword' => 'record-type' => 'mc-rtype' );
694
    $self->add_search_field_alias( 'keyword' => 'record-type' => 'mus' );
695
    $self->add_bib1_field_map('keyword' => 'content-type' => 'biblioserver' => { '1' => '1034' } );
696
    $self->add_search_field_alias( 'keyword' => 'content-type' => 'ctype' );
697
    $self->add_bib1_field_map('keyword' => 'lc-card-number' => 'biblioserver' => { '1' => '9' } );
698
    $self->add_search_field_alias( 'keyword' => 'lc-card-number' => 'lc-card' );
699
    $self->add_bib1_field_map('keyword' => 'local-number' => 'biblioserver' => { '1' => '12' } );
700
    $self->add_search_field_alias( 'keyword' => 'local-number' => 'sn' );
701
    $self->add_bib1_filter_map( 'biblioserver', 'copydate', { 'target_syntax_callback' => \&Koha::QueryParser::Driver::PQF::date_filter_target_callback, '1' => '30', '4' => '4' });
702
    $self->add_bib1_filter_map( 'biblioserver', 'pubdate', { 'target_syntax_callback' => \&Koha::QueryParser::Driver::PQF::date_filter_target_callback, '1' => 'pubdate', '4' => '4' });
703
    $self->add_bib1_filter_map( 'biblioserver', 'acqdate', { 'target_syntax_callback' => \&Koha::QueryParser::Driver::PQF::date_filter_target_callback, '1' => 'Date-of-acquisition', '4' => '4' });
704
    $self->add_bib1_field_map('keyword' => 'isbn' => 'biblioserver' => { '1' => '7' } );
705
    $self->add_search_field_alias( 'keyword' => 'isbn' => 'nb' );
706
    $self->add_bib1_field_map('keyword' => 'issn' => 'biblioserver' => { '1' => '8' } );
707
    $self->add_search_field_alias( 'keyword' => 'issn' => 'ns' );
708
    $self->add_bib1_field_map('keyword' => 'identifier-standard' => 'biblioserver' => { '1' => '1007' } );
709
    $self->add_search_field_alias( 'keyword' => 'identifier-standard' => 'ident' );
710
    $self->add_bib1_field_map('keyword' => 'upc' => 'biblioserver' => { '1' => 'UPC' } );
711
    $self->add_search_field_alias( 'keyword' => 'upc' => 'upc' );
712
    $self->add_bib1_field_map('keyword' => 'ean' => 'biblioserver' => { '1' => 'EAN' } );
713
    $self->add_search_field_alias( 'keyword' => 'ean' => 'ean' );
714
    $self->add_bib1_field_map('keyword' => 'music' => 'biblioserver' => { '1' => 'Music-number' } );
715
    $self->add_search_field_alias( 'keyword' => 'music' => 'music' );
716
    $self->add_bib1_field_map('keyword' => 'stock-number' => 'biblioserver' => { '1' => '1028' } );
717
    $self->add_search_field_alias( 'keyword' => 'stock-number' => 'stock-number' );
718
    $self->add_bib1_field_map('keyword' => 'material-type' => 'biblioserver' => { '1' => '1031' } );
719
    $self->add_search_field_alias( 'keyword' => 'material-type' => 'material-type' );
720
    $self->add_bib1_field_map('keyword' => 'place-publication' => 'biblioserver' => { '1' => '59' } );
721
    $self->add_search_field_alias( 'keyword' => 'place-publication' => 'pl' );
722
    $self->add_bib1_field_map('keyword' => 'personal-name' => 'biblioserver' => { '1' => 'Personal-name' } );
723
    $self->add_search_field_alias( 'keyword' => 'personal-name' => 'pn' );
724
    $self->add_bib1_field_map('keyword' => 'publisher' => 'biblioserver' => { '1' => '1018' } );
725
    $self->add_search_field_alias( 'keyword' => 'publisher' => 'pb' );
726
    $self->add_bib1_field_map('keyword' => 'note' => 'biblioserver' => { '1' => '63' } );
727
    $self->add_search_field_alias( 'keyword' => 'note' => 'nt' );
728
    $self->add_bib1_field_map('keyword' => 'record-control-number' => 'biblioserver' => { '1' => '1045' } );
729
    $self->add_search_field_alias( 'keyword' => 'record-control-number' => 'rcn' );
730
    $self->add_bib1_field_map('subject' => '' => 'biblioserver' => { '1' => '21' } );
731
    $self->add_search_field_alias( 'subject' => '' => 'su' );
732
    $self->add_search_field_alias( 'subject' => '' => 'su-to' );
733
    $self->add_search_field_alias( 'subject' => '' => 'su-geo' );
734
    $self->add_search_field_alias( 'subject' => '' => 'su-ut' );
735
    $self->add_bib1_field_map('subject' => 'name-personal' => 'biblioserver' => { '1' => '1009' } );
736
    $self->add_search_field_alias( 'subject' => 'name-personal' => 'su-na' );
737
    $self->add_bib1_field_map('title' => '' => 'biblioserver' => { '1' => '4' } );
738
    $self->add_search_field_alias( 'title' => '' => 'ti' );
739
    $self->add_bib1_field_map('title' => 'cover' => 'biblioserver' => { '1' => '36' } );
740
    $self->add_search_field_alias( 'title' => 'cover' => 'title-cover' );
741
    $self->add_bib1_field_map('keyword' => 'host-item' => 'biblioserver' => { '1' => '1033' } );
742
    $self->add_bib1_field_map('keyword' => 'video-mt' => 'biblioserver' => { '1' => 'Video-mt' } );
743
    $self->add_bib1_field_map('keyword' => 'graphics-type' => 'biblioserver' => { '1' => 'Graphic-type' } );
744
    $self->add_bib1_field_map('keyword' => 'graphics-support' => 'biblioserver' => { '1' => 'Graphic-support' } );
745
    $self->add_bib1_field_map('keyword' => 'type-of-serial' => 'biblioserver' => { '1' => 'Type-Of-Serial' } );
746
    $self->add_bib1_field_map('keyword' => 'regularity-code' => 'biblioserver' => { '1' => 'Regularity-code' } );
747
    $self->add_bib1_field_map('keyword' => 'material-type' => 'biblioserver' => { '1' => 'Material-type' } );
748
    $self->add_bib1_field_map('keyword' => 'literature-code' => 'biblioserver' => { '1' => 'Literature-Code' } );
749
    $self->add_bib1_field_map('keyword' => 'biography-code' => 'biblioserver' => { '1' => 'Biography-code' } );
750
    $self->add_bib1_field_map('keyword' => 'illustration-code' => 'biblioserver' => { '1' => 'Illustration-code' } );
751
    $self->add_bib1_field_map('title' => 'series' => 'biblioserver' => { '1' => '5' } );
752
    $self->add_search_field_alias( 'title' => 'series' => 'title-series' );
753
    $self->add_search_field_alias( 'title' => 'series' => 'se' );
754
    $self->add_bib1_field_map('title' => 'uniform' => 'biblioserver' => { '1' => 'Title-uniform' } );
755
    $self->add_search_field_alias( 'title' => 'uniform' => 'title-uniform' );
756
    $self->add_bib1_field_map('subject' => 'authority-number' => 'biblioserver' => { '1' => 'Koha-Auth-Number' } );
757
    $self->add_search_field_alias( 'subject' => 'authority-number' => 'an' );
758
    $self->add_bib1_field_map('keyword' => 'control-number' => 'biblioserver' => { '1' => '9001' } );
759
    $self->add_bib1_field_map('keyword' => 'biblionumber' => 'biblioserver' => { '1' => '9002', '5' => '100' } );
760
    $self->add_bib1_field_map('keyword' => 'totalissues' => 'biblioserver' => { '1' => '9003' } );
761
    $self->add_bib1_field_map('keyword' => 'cn-bib-source' => 'biblioserver' => { '1' => '9004' } );
762
    $self->add_bib1_field_map('keyword' => 'cn-bib-sort' => 'biblioserver' => { '1' => '9005' } );
763
    $self->add_bib1_field_map('keyword' => 'itemtype' => 'biblioserver' => { '1' => '9006' } );
764
    $self->add_search_field_alias( 'keyword' => 'itemtype' => 'mc-itemtype' );
765
    $self->add_bib1_field_map('keyword' => 'cn-class' => 'biblioserver' => { '1' => '9007' } );
766
    $self->add_bib1_field_map('keyword' => 'cn-item' => 'biblioserver' => { '1' => '9008' } );
767
    $self->add_bib1_field_map('keyword' => 'cn-prefix' => 'biblioserver' => { '1' => '9009' } );
768
    $self->add_bib1_field_map('keyword' => 'cn-suffix' => 'biblioserver' => { '1' => '9010' } );
769
    $self->add_bib1_field_map('keyword' => 'suppress' => 'biblioserver' => { '1' => '9011' } );
770
    $self->add_bib1_field_map('keyword' => 'id-other' => 'biblioserver' => { '1' => '9012' } );
771
    $self->add_bib1_field_map('keyword' => 'date-entered-on-file' => 'biblioserver' => { '1' => 'date-entered-on-file' } );
772
    $self->add_bib1_field_map('keyword' => 'extent' => 'biblioserver' => { '1' => 'Extent' } );
773
    $self->add_bib1_field_map('keyword' => 'llength' => 'biblioserver' => { '1' => 'llength' } );
774
    $self->add_bib1_field_map('keyword' => 'summary' => 'biblioserver' => { '1' => 'Summary' } );
775
    $self->add_bib1_field_map('keyword' => 'withdrawn' => 'biblioserver' => { '1' => '8001' } );
776
    $self->add_bib1_field_map('keyword' => 'lost' => 'biblioserver' => { '1' => '8002' } );
777
    $self->add_bib1_field_map('keyword' => 'classification-source' => 'biblioserver' => { '1' => '8003' } );
778
    $self->add_bib1_field_map('keyword' => 'materials-specified' => 'biblioserver' => { '1' => '8004' } );
779
    $self->add_bib1_field_map('keyword' => 'damaged' => 'biblioserver' => { '1' => '8005' } );
780
    $self->add_bib1_field_map('keyword' => 'restricted' => 'biblioserver' => { '1' => '8006' } );
781
    $self->add_bib1_field_map('keyword' => 'cn-sort' => 'biblioserver' => { '1' => '8007' } );
782
    $self->add_bib1_field_map('keyword' => 'notforloan' => 'biblioserver' => { '1' => '8008', '4' => '109' } );
783
    $self->add_bib1_field_map('keyword' => 'ccode' => 'biblioserver' => { '1' => '8009' } );
784
    $self->add_search_field_alias( 'keyword' => 'ccode' => 'mc-ccode' );
785
    $self->add_bib1_field_map('keyword' => 'itemnumber' => 'biblioserver' => { '1' => '8010' } );
786
    $self->add_bib1_field_map('keyword' => 'homebranch' => 'biblioserver' => { '1' => 'homebranch' } );
787
    $self->add_search_field_alias( 'keyword' => 'homebranch' => 'branch' );
788
    $self->add_bib1_field_map('keyword' => 'holdingbranch' => 'biblioserver' => { '1' => '8012' } );
789
    $self->add_bib1_field_map('keyword' => 'location' => 'biblioserver' => { '1' => '8013' } );
790
    $self->add_search_field_alias( 'keyword' => 'location' => 'mc-loc' );
791
    $self->add_bib1_field_map('keyword' => 'acqsource' => 'biblioserver' => { '1' => '8015' } );
792
    $self->add_bib1_field_map('keyword' => 'coded-location-qualifier' => 'biblioserver' => { '1' => '8016' } );
793
    $self->add_bib1_field_map('keyword' => 'price' => 'biblioserver' => { '1' => '8017' } );
794
    $self->add_bib1_field_map('keyword' => 'stocknumber' => 'biblioserver' => { '1' => '1062' } );
795
    $self->add_search_field_alias( 'keyword' => 'stocknumber' => 'inv' );
796
    $self->add_bib1_field_map('keyword' => 'stack' => 'biblioserver' => { '1' => '8018' } );
797
    $self->add_bib1_field_map('keyword' => 'issues' => 'biblioserver' => { '1' => '8019' } );
798
    $self->add_bib1_field_map('keyword' => 'renewals' => 'biblioserver' => { '1' => '8020' } );
799
    $self->add_bib1_field_map('keyword' => 'reserves' => 'biblioserver' => { '1' => '8021' } );
800
    $self->add_bib1_field_map('keyword' => 'local-classification' => 'biblioserver' => { '1' => '8022' } );
801
    $self->add_bib1_field_map('keyword' => 'barcode' => 'biblioserver' => { '1' => '8023' } );
802
    $self->add_search_field_alias( 'keyword' => 'barcode' => 'bc' );
803
    $self->add_bib1_field_map('keyword' => 'onloan' => 'biblioserver' => { '1' => '8024' } );
804
    $self->add_bib1_field_map('keyword' => 'datelastseen' => 'biblioserver' => { '1' => '8025' } );
805
    $self->add_bib1_field_map('keyword' => 'datelastborrowed' => 'biblioserver' => { '1' => '8026' } );
806
    $self->add_bib1_field_map('keyword' => 'copynumber' => 'biblioserver' => { '1' => '8027' } );
807
    $self->add_bib1_field_map('keyword' => 'uri' => 'biblioserver' => { '1' => '8028' } );
808
    $self->add_bib1_field_map('keyword' => 'replacementprice' => 'biblioserver' => { '1' => '8029' } );
809
    $self->add_bib1_field_map('keyword' => 'replacementpricedate' => 'biblioserver' => { '1' => '8030' } );
810
    $self->add_bib1_field_map('keyword' => 'itype' => 'biblioserver' => { '1' => '8031' } );
811
    $self->add_search_field_alias( 'keyword' => 'itype' => 'mc-itype' );
812
    $self->add_bib1_field_map('keyword' => 'ff8-22' => 'biblioserver' => { '1' => '8822' } );
813
    $self->add_bib1_field_map('keyword' => 'ff8-23' => 'biblioserver' => { '1' => '8823' } );
814
    $self->add_bib1_field_map('keyword' => 'ff8-34' => 'biblioserver' => { '1' => '8834' } );
815
# Audience
816
    $self->add_bib1_field_map('keyword' => 'audience' => 'biblioserver' => { '1' => '8822' } );
817
    $self->add_search_field_alias( 'keyword' => 'audience' => 'aud' );
818
819
# Content and Literary form
820
    $self->add_bib1_field_map('keyword' => 'fiction' => 'biblioserver' => { '1' => '8833' } );
821
    $self->add_search_field_alias( 'keyword' => 'fiction' => 'fic' );
822
    $self->add_bib1_field_map('keyword' => 'biography' => 'biblioserver' => { '1' => '8834' } );
823
    $self->add_search_field_alias( 'keyword' => 'biography' => 'bio' );
824
825
# Format
826
    $self->add_bib1_field_map('keyword' => 'format' => 'biblioserver' => { '1' => '8823' } );
827
# format used as a limit FIXME: needed?
828
    $self->add_bib1_field_map('keyword' => 'l-format' => 'biblioserver' => { '1' => '8703' } );
829
830
    $self->add_bib1_field_map('keyword' => 'illustration-code' => 'biblioserver' => { '1' => 'Illustration-code ' } );
831
832
# Lexile Number
833
    $self->add_bib1_field_map('keyword' => 'lex' => 'biblioserver' => { '1' => '9903 r=r' } );
834
835
#Accelerated Reader Level
836
    $self->add_bib1_field_map('keyword' => 'arl' => 'biblioserver' => { '1' => '9904 r=r' } );
837
838
#Accelerated Reader Point
839
    $self->add_bib1_field_map('keyword' => 'arp' => 'biblioserver' => { '1' => '9014 r=r' } );
840
841
# Curriculum
842
    $self->add_bib1_field_map('keyword' => 'curriculum' => 'biblioserver' => { '1' => '9658' } );
843
844
## Statuses
845
    $self->add_bib1_field_map('keyword' => 'popularity' => 'biblioserver' => { '1' => 'issues' } );
846
847
## Type Limits
848
    $self->add_bib1_field_map('keyword' => 'dt-bks' => 'biblioserver' => { '1' => '8700' } );
849
    $self->add_bib1_field_map('keyword' => 'dt-vis' => 'biblioserver' => { '1' => '8700' } );
850
    $self->add_bib1_field_map('keyword' => 'dt-sr' => 'biblioserver' => { '1' => '8700' } );
851
    $self->add_bib1_field_map('keyword' => 'dt-cf' => 'biblioserver' => { '1' => '8700' } );
852
    $self->add_bib1_field_map('keyword' => 'dt-map' => 'biblioserver' => { '1' => '8700' } );
853
854
    $self->add_bib1_field_map('keyword' => 'name' => 'biblioserver' => { '1' => '1002' } );
855
    $self->add_bib1_field_map('keyword' => 'item' => 'biblioserver' => { '1' => '9520' } );
856
    $self->add_bib1_field_map('keyword' => 'host-item-number' => 'biblioserver' => { '1' => '8911' } );
857
    $self->add_search_field_alias( 'keyword' => 'host-item-number' => 'hi' );
858
859
    $self->add_bib1_field_map('keyword' => 'alwaysmatch' => 'biblioserver' => { '1' => '_ALLRECORDS', '2' => '103' } );
860
    $self->add_bib1_field_map('subject' => 'complete' => 'biblioserver' => { '1' => '21', '3' => '1', '4' => '1', '5' => '100', '6' => '3' } );
861
862
    $self->add_bib1_modifier_map('relevance' => 'biblioserver' => { '2' => '102' } );
863
    $self->add_bib1_modifier_map('title-sort-za' => 'biblioserver' => { '7' => '2', '1' => '36', '' => '0', 'op' => '@or' } );
864
    $self->add_bib1_modifier_map('title-sort-az' => 'biblioserver' => { '7' => '1', '1' => '36', '' => '0', 'op' => '@or' } );
865
    $self->add_bib1_modifier_map('relevance_dsc' => 'biblioserver' => { '2' => '102' } );
866
    $self->add_bib1_modifier_map('title_dsc' => 'biblioserver' => { '7' => '2', '1' => '4', '' => '0', 'op' => '@or' } );
867
    $self->add_bib1_modifier_map('title_asc' => 'biblioserver' => { '7' => '1', '1' => '4', '' => '0', 'op' => '@or' } );
868
    $self->add_bib1_modifier_map('author_asc' => 'biblioserver' => { '7' => '2', '1' => '1003', '' => '0', 'op' => '@or' } );
869
    $self->add_bib1_modifier_map('author_dsc' => 'biblioserver' => { '7' => '1', '1' => '1003', '' => '0', 'op' => '@or' } );
870
    $self->add_bib1_modifier_map('popularity_asc' => 'biblioserver' => { '7' => '2', '1' => '9003', '' => '0', 'op' => '@or' } );
871
    $self->add_bib1_modifier_map('popularity_dsc' => 'biblioserver' => { '7' => '1', '1' => '9003', '' => '0', 'op' => '@or' } );
872
    $self->add_bib1_modifier_map('call_number_asc' => 'biblioserver' => { '7' => '2', '1' => '8007', '' => '0', 'op' => '@or' } );
873
    $self->add_bib1_modifier_map('call_number_dsc' => 'biblioserver' => { '7' => '1', '1' => '8007', '' => '0', 'op' => '@or' } );
874
    $self->add_bib1_modifier_map('pubdate_asc' => 'biblioserver' => { '7' => '2', '1' => '31', '' => '0', 'op' => '@or' } );
875
    $self->add_bib1_modifier_map('pubdate_dsc' => 'biblioserver' => { '7' => '1', '1' => '31', '' => '0', 'op' => '@or' } );
876
    $self->add_bib1_modifier_map('acqdate_asc' => 'biblioserver' => { '7' => '2', '1' => '32', '' => '0', 'op' => '@or' } );
877
    $self->add_bib1_modifier_map('acqdate_dsc' => 'biblioserver' => { '7' => '1', '1' => '32', '' => '0', 'op' => '@or' } );
878
879
    $self->add_bib1_modifier_map('title_za' => 'biblioserver' => { '7' => '2', '1' => '4', '' => '0', 'op' => '@or' } );
880
    $self->add_bib1_modifier_map('title_az' => 'biblioserver' => { '7' => '1', '1' => '4', '' => '0', 'op' => '@or' } );
881
    $self->add_bib1_modifier_map('author_za' => 'biblioserver' => { '7' => '2', '1' => '1003', '' => '0', 'op' => '@or' } );
882
    $self->add_bib1_modifier_map('author_az' => 'biblioserver' => { '7' => '1', '1' => '1003', '' => '0', 'op' => '@or' } );
883
    $self->add_bib1_modifier_map('ascending' => 'biblioserver' => { '7' => '1' } );
884
    $self->add_bib1_modifier_map('descending' => 'biblioserver' => { '7' => '2' } );
885
886
    $self->add_bib1_field_map('title' => 'exacttitle' => 'biblioserver' => { '1' => '4', '4' => '1', '6' => '3' } );
887
    $self->add_search_field_alias( 'title' => 'exacttitle' => 'ti,ext' );
888
    $self->add_bib1_field_map('author' => 'exactauthor' => 'biblioserver' => { '1' => '1003', '4' => '1', '6' => '3' } );
889
    $self->add_search_field_alias( 'author' => 'exactauthor' => 'au,ext' );
890
891
    $self->add_bib1_field_map('subject' => 'headingmain' => 'authorityserver' => { '1' => 'Heading-Main' } );
892
    $self->add_bib1_field_map('subject' => 'heading' => 'authorityserver' => { '1' => 'Heading' } );
893
    $self->add_bib1_field_map('subject' => 'matchheading' => 'authorityserver' => { '1' => 'Match-heading' } );
894
    $self->add_bib1_field_map('subject' => 'seefrom' => 'authorityserver' => { '1' => 'Match-heading-see-from' } );
895
    $self->add_bib1_field_map('subject' => '' => 'authorityserver' => { '1' => 'Match-heading' } );
896
    $self->add_bib1_field_map('keyword' => 'alwaysmatch' => 'authorityserver' => { '1' => '_ALLRECORDS', '2' => '103' } );
897
    $self->add_bib1_field_map('keyword' => 'match' => 'authorityserver' => { '1' => 'Match' } );
898
    $self->add_bib1_field_map('keyword' => 'thesaurus' => 'authorityserver' => { '1' => 'Subject-heading-thesaurus' } );
899
    $self->add_bib1_field_map('keyword' => 'authtype' => 'authorityserver' => { '1' => 'authtype', '5' => '100' } );
900
    $self->add_bib1_field_map('keyword' => '' => 'authorityserver' => { '1' => 'Any' } );
901
    $self->add_search_field_alias( 'subject' => 'headingmain' => 'mainmainentry' );
902
    $self->add_search_field_alias( 'subject' => 'heading' => 'mainentry' );
903
    $self->add_search_field_alias( 'subject' => 'heading' => 'he' );
904
    $self->add_search_field_alias( 'subject' => 'matchheading' => 'match-heading' );
905
    $self->add_search_field_alias( 'keyword' => '' => 'any' );
906
    $self->add_search_field_alias( 'keyword' => 'match' => 'match' );
907
    $self->add_search_field_alias( 'subject' => 'seefrom' => 'see-from' );
908
    $self->add_search_field_alias( 'keyword' => 'thesaurus' => 'thesaurus' );
909
    $self->add_search_field_alias( 'keyword' => 'alwaysmatch' => 'all' );
910
    $self->add_search_field_alias( 'keyword' => 'authtype' => 'authtype' );
911
    $self->add_search_field_alias( 'keyword' => 'authtype' => 'at' );
912
913
    $self->add_bib1_field_map('subject' => 'start' => 'authorityserver' => { '3' => '2', '4' => '1', '5' => '1' } );
914
    $self->add_bib1_field_map('subject' => 'exact' => 'authorityserver' => { '4' => '1', '5' => '100', '6' => '3' } );
915
916
    $self->add_bib1_modifier_map('HeadingAsc' => 'authorityserver' => { '7' => '1', '1' => 'Heading', '' => '0', 'op' => '@or' } );
917
    $self->add_bib1_modifier_map('HeadingDsc' => 'authorityserver' => { '7' => '2', '1' => 'Heading', '' => '0', 'op' => '@or' } );
918
    $self->add_bib1_modifier_map('AuthidAsc' => 'authorityserver' => { '7' => '1', '1' => 'Local-Number', '' => '0', 'op' => '@or' } );
919
    $self->add_bib1_modifier_map('AuthidDsc' => 'authorityserver' => { '7' => '2', '1' => 'Local-Number', '' => '0', 'op' => '@or' } );
920
    $self->add_bib1_modifier_map('Relevance' => 'authorityserver' => { '2' => '102' } );
921
922
    return $self;
923
}
924
925
1;
(-)a/Koha/QueryParser/Driver/PQF/Util.pm (-54 lines)
Lines 1-54 Link Here
1
package Koha::QueryParser::Driver::PQF::Util;
2
3
# This file is part of Koha.
4
#
5
# Copyright 2012 C & P Bibliography Services
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
use Scalar::Util qw(looks_like_number);
21
22
use strict;
23
use warnings;
24
25
=head1 NAME
26
27
    Koha::QueryParser::Driver::PQF::Util - Utility module for PQF QueryParser driver
28
29
=head1 FUNCTIONS
30
31
=head2 attributes_to_attr_string
32
33
    Koha::QueryParser::Driver::PQF::Util(%attributes);
34
35
    Koha::QueryParser::Driver::PQF::Util({ '1' => '1003', '4' => '6' });
36
37
Convert a hashref with a Bib-1 mapping into its PQF string representation.
38
39
=cut
40
41
sub attributes_to_attr_string {
42
    my ($attributes) = @_;
43
    my $attr_string = '';
44
    foreach my $key ( sort keys %{$attributes} ) {
45
        next unless looks_like_number($key);
46
        $attr_string .= ' @attr ' . $key . '=' . $attributes->{ $key } . ' ';
47
    }
48
    $attr_string =~ s/^\s*//;
49
    $attr_string =~ s/\s*$//;
50
    $attr_string .= ' ' . $attributes->{''} if defined $attributes->{''};
51
    return $attr_string;
52
}
53
54
1;
(-)a/Koha/QueryParser/Driver/PQF/query_plan.pm (-70 lines)
Lines 1-70 Link Here
1
package Koha::QueryParser::Driver::PQF::query_plan;
2
3
# This file is part of Koha.
4
#
5
# Copyright 2012 C & P Bibliography Services
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
use base 'OpenILS::QueryParser::query_plan';
21
22
use strict;
23
use warnings;
24
25
=head1 NAME
26
27
Koha::QueryParser::Driver::PQF::query_plan - query_plan subclass for PQF driver
28
29
=head1 FUNCTIONS
30
31
=head2 Koha::QueryParser::Driver::PQF::query_plan::target_syntax
32
33
    my $pqf = $query_plan->target_syntax($server);
34
35
Transforms an OpenILS::QueryParser::query_plan object into PQF. Do not use directly.
36
37
=cut
38
39
sub target_syntax {
40
    my ($self, $server) = @_;
41
    my $pqf = '';
42
    my $node_pqf;
43
    my $node_count = 0;
44
45
    for my $node ( @{$self->query_nodes} ) {
46
47
        if (ref($node)) {
48
            $node_pqf = $node->target_syntax($server);
49
            $node_count++ if $node_pqf;
50
            $pqf .= $node_pqf;
51
        }
52
    }
53
    $pqf = ($self->joiner eq '|' ? ' @or ' : ' @and ') x ($node_count - 1) . $pqf if $node_count > 1;
54
    $node_count = ($node_count ? '1' : '0');
55
    for my $node ( @{$self->filters} ) {
56
        if (ref($node)) {
57
            $node_pqf = $node->target_syntax($server);
58
            $node_count++ if $node_pqf;
59
            $pqf .= $node_pqf;
60
        }
61
    }
62
    $pqf = ($self->joiner eq '|' ? ' @or ' : ' @and ') x ($node_count - 1) . $pqf if $node_count > 1;
63
    foreach my $modifier ( @{$self->modifiers} ) {
64
        my $modifierpqf = $modifier->target_syntax($server, $self);
65
        $pqf = $modifierpqf . ' ' . $pqf if $modifierpqf;
66
    }
67
    return ($self->negate ? '@not @attr 1=_ALLRECORDS @attr 2=103 "" ' : '') . $pqf;
68
}
69
70
1;
(-)a/Koha/QueryParser/Driver/PQF/query_plan/facet.pm (-46 lines)
Lines 1-46 Link Here
1
package Koha::QueryParser::Driver::PQF::query_plan::facet;
2
3
# This file is part of Koha.
4
#
5
# Copyright 2012 C & P Bibliography Services
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
use base 'OpenILS::QueryParser::query_plan::facet';
21
22
use strict;
23
use warnings;
24
25
=head1 NAME
26
27
Koha::QueryParser::Driver::PQF::query_plan::facet - facet subclass for PQF driver
28
29
=head1 FUNCTIONS
30
31
=head2 Koha::QueryParser::Driver::PQF::query_plan::facet::target_syntax
32
33
    my $pqf = $facet->target_syntax($server);
34
35
Transforms an OpenILS::QueryParser::query_plan::facet object into PQF. Do not use
36
directly.
37
38
=cut
39
40
sub target_syntax {
41
    my ($self, $server) = @_;
42
43
    return '';
44
}
45
46
1;
(-)a/Koha/QueryParser/Driver/PQF/query_plan/filter.pm (-51 lines)
Lines 1-51 Link Here
1
package Koha::QueryParser::Driver::PQF::query_plan::filter;
2
3
# This file is part of Koha.
4
#
5
# Copyright 2012 C & P Bibliography Services
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
use base 'OpenILS::QueryParser::query_plan::filter';
21
22
use strict;
23
use warnings;
24
25
=head1 NAME
26
27
Koha::QueryParser::Driver::PQF::query_plan::filter - filter subclass for PQF driver
28
29
=head1 FUNCTIONS
30
31
=head2 Koha::QueryParser::Driver::PQF::query_plan::filter::target_syntax
32
33
    my $pqf = $filter->target_syntax($server);
34
35
Transforms an OpenILS::QueryParser::query_plan::filter object into PQF. Do not use
36
directly.
37
38
=cut
39
40
sub target_syntax {
41
    my ($self, $server) = @_;
42
    my $attributes = $self->plan->QueryParser->bib1_mapping_by_name( 'filter', $self->name, $server );
43
44
    if ($attributes->{'target_syntax_callback'}) {
45
        return $attributes->{'target_syntax_callback'}->($self->plan->QueryParser, $self->name, $self->args, $self->negate, $server);
46
    } else {
47
        return '';
48
    }
49
}
50
51
1;
(-)a/Koha/QueryParser/Driver/PQF/query_plan/modifier.pm (-50 lines)
Lines 1-50 Link Here
1
package Koha::QueryParser::Driver::PQF::query_plan::modifier;
2
3
# This file is part of Koha.
4
#
5
# Copyright 2012 C & P Bibliography Services
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
use base 'OpenILS::QueryParser::query_plan::modifier';
21
22
use strict;
23
use warnings;
24
25
=head1 NAME
26
27
Koha::QueryParser::Driver::PQF::query_plan::modifer - modifier subclass for PQF driver
28
29
=head1 FUNCTIONS
30
31
=head2 Koha::QueryParser::Driver::PQF::query_plan::modifier::target_syntax
32
33
    my $pqf = $modifier->target_syntax($server, $query_plan);
34
35
Transforms an OpenILS::QueryParser::query_plan::modifier object into PQF. Do not use
36
directly. The second argument points ot the query_plan, since modifiers do
37
not have a reference to their parent query_plan.
38
39
=cut
40
41
sub target_syntax {
42
    my ($self, $server, $query_plan) = @_;
43
    my $pqf = '';
44
45
    my $attributes = $query_plan->QueryParser->bib1_mapping_by_name('modifier', $self->name, $server);
46
    $pqf = ($attributes->{'op'} ? $attributes->{'op'} . ' ' : '') . ($self->negate ? '@not @attr 1=_ALLRECORDS @attr 2=103 "" ' : '') . $attributes->{'attr_string'};
47
    return $pqf;
48
}
49
50
1;
(-)a/Koha/QueryParser/Driver/PQF/query_plan/node.pm (-106 lines)
Lines 1-106 Link Here
1
package Koha::QueryParser::Driver::PQF::query_plan::node;
2
3
# This file is part of Koha.
4
#
5
# Copyright 2012 C & P Bibliography Services
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
use base 'OpenILS::QueryParser::query_plan::node';
21
22
use strict;
23
use warnings;
24
25
=head1 NAME
26
27
Koha::QueryParser::Driver::PQF::query_plan::node - node subclass for PQF driver
28
29
=head1 FUNCTIONS
30
31
=head2 Koha::QueryParser::Driver::PQF::query_plan::node::target_syntax
32
33
    my $pqf = $node->target_syntax($server);
34
35
Transforms an OpenILS::QueryParser::query_plan::node object into PQF. Do not use directly.
36
37
=cut
38
39
sub target_syntax {
40
    my ($self, $server) = @_;
41
    my $pqf = '';
42
    my $atom_content;
43
    my $atom_count = 0;
44
    my @fields = ();
45
    my $fieldobj;
46
    my $relbump;
47
48
    if (scalar(@{$self->fields})) {
49
        foreach my $field (@{$self->fields}) {
50
            $fieldobj = $self->plan->QueryParser->bib1_mapping_by_name('field', $self->classname, $field, $server);
51
            $relbump = $self->plan->QueryParser->bib1_mapping_by_name('relevance_bump', $self->classname, $field, $server);
52
            if ($relbump && defined $relbump->{'attr_string'}) {
53
                $fieldobj->{'attr_string'} .= ' ' . $relbump->{'attr_string'};
54
            }
55
            push @fields, $fieldobj unless (!defined($fieldobj) || ($field eq $self->classname && @{$self->fields} > 1));
56
        }
57
    } else {
58
        $fieldobj = $self->plan->QueryParser->bib1_mapping_by_name('field', $self->classname, $self->classname, $server);
59
        my $relbumps = $self->plan->QueryParser->bib1_mapping_by_name('relevance_bump', $self->classname, '', $server);
60
        push @fields, $fieldobj;
61
        if ($relbumps) {
62
            foreach my $field (keys %$relbumps) {
63
                $relbump = $relbumps->{$field};
64
                $fieldobj = $self->plan->QueryParser->bib1_mapping_by_name('field', $relbump->{'classname'}, $relbump->{'field'}, $server);
65
                $fieldobj->{'attr_string'} ||= '';
66
                $fieldobj->{'attr_string'} .= ' ' . $relbump->{$server}{'attr_string'} if $relbump->{$server}{'attr_string'};
67
                push @fields, $fieldobj;
68
            }
69
        }
70
    }
71
72
    if (@{$self->phrases}) {
73
        foreach my $phrase (@{$self->phrases}) {
74
            if ($phrase) {
75
                $phrase =~ s/"/\\"/g;
76
                $pqf .= ' @or ' x (scalar(@fields) - 1);
77
                foreach my $attributes (@fields) {
78
                    $attributes->{'attr_string'} ||= '';
79
                    $pqf .= $attributes->{'attr_string'} . ($attributes->{'4'} ? '' : ' @attr 4=1') . ' "' . $phrase . '" ';
80
                }
81
                $atom_count++;
82
            }
83
        }
84
    } else {
85
        foreach my $atom (@{$self->query_atoms}) {
86
            if (ref($atom)) {
87
                $atom_content = $atom->target_syntax($server);
88
                if ($atom_content) {
89
                    $pqf .= ' @or ' x (scalar(@fields) - 1);
90
                    foreach my $attributes (@fields) {
91
                        $attributes->{'attr_string'} ||= '';
92
                        if ($self->plan->QueryParser->custom_data->{'QueryAutoTruncate'} || $atom->suffix eq '*') {
93
                            $attributes->{'attr_string'} .= ($attributes->{'5'} ? '' : ' @attr 5=1 ');
94
                        }
95
                        $pqf .= $attributes->{'attr_string'} . ($attributes->{'4'} ? '' : ' @attr 4=6 ') . $atom_content . ' ';
96
                    }
97
                    $atom_count++;
98
                }
99
            }
100
        }
101
    }
102
    $pqf = (OpenILS::QueryParser::_util::default_joiner eq '|' ? ' @or ' : ' @and ') x ($atom_count - 1) . $pqf if $atom_count > 1;
103
    return ($self->negate ? '@not @attr 1=_ALLRECORDS @attr 2=103 "" ' : '') . $pqf;
104
}
105
106
1;
(-)a/Koha/QueryParser/Driver/PQF/query_plan/node/atom.pm (-49 lines)
Lines 1-49 Link Here
1
package Koha::QueryParser::Driver::PQF::query_plan::node::atom;
2
3
# This file is part of Koha.
4
#
5
# Copyright 2012 C & P Bibliography Services
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
use base 'OpenILS::QueryParser::query_plan::node::atom';
21
22
use strict;
23
use warnings;
24
25
=head1 NAME
26
27
Koha::QueryParser::Driver::PQF::query_plan::node::atom - atom subclass for PQF driver
28
29
=head1 FUNCTIONS
30
31
=head2 Koha::QueryParser::Driver::PQF::query_plan::node::atom::target_syntax
32
33
    my $pqf = $atom->target_syntax($server);
34
35
Transforms an OpenILS::QueryParser::query_plan::node::atom object into PQF. Do not use
36
directly.
37
38
=cut
39
40
sub target_syntax {
41
    my ($self, $server) = @_;
42
43
    my $content = $self->content;
44
    $content =~ s/"/\\"/g;
45
46
    return ' "' .  $content . '" ';
47
}
48
49
1;
(-)a/OpenILS/QueryParser.pm (-2242 lines)
Lines 1-2242 Link Here
1
use strict;
2
use warnings;
3
4
package OpenILS::QueryParser;
5
use JSON;
6
7
=head1 NAME
8
9
OpenILS::QueryParser - basic QueryParser class
10
11
=head1 SYNOPSIS
12
13
use OpenILS::QueryParser;
14
my $QParser = OpenILS::QueryParser->new(%args);
15
16
=head1 DESCRIPTION
17
18
Main entrypoint into the QueryParser functionality.
19
20
=head1 FUNCTIONS
21
22
=cut
23
24
# Note that the first key must match the name of the package.
25
our %parser_config = (
26
    'OpenILS::QueryParser' => {
27
        filters => [],
28
        modifiers => [],
29
        operators => {
30
            'and' => '&&',
31
            'or' => '||',
32
            float_start => '{{',
33
            float_end => '}}',
34
            group_start => '(',
35
            group_end => ')',
36
            required => '+',
37
            disallowed => '-',
38
            modifier => '#',
39
            negated => '!'
40
        }
41
    }
42
);
43
44
sub canonicalize {
45
    my $self = shift;
46
    return OpenILS::QueryParser::Canonicalize::abstract_query2str_impl(
47
        $self->parse_tree->to_abstract_query(@_)
48
    );
49
}
50
51
52
=head2 facet_class_count
53
54
    $count = $QParser->facet_class_count();
55
=cut
56
57
sub facet_class_count {
58
    my $self = shift;
59
    return @{$self->facet_classes};
60
}
61
62
=head2 search_class_count
63
64
    $count = $QParser->search_class_count();
65
=cut
66
67
sub search_class_count {
68
    my $self = shift;
69
    return @{$self->search_classes};
70
}
71
72
=head2 filter_count
73
74
    $count = $QParser->filter_count();
75
=cut
76
77
sub filter_count {
78
    my $self = shift;
79
    return @{$self->filters};
80
}
81
82
=head2 modifier_count
83
84
    $count = $QParser->modifier_count();
85
=cut
86
87
sub modifier_count {
88
    my $self = shift;
89
    return @{$self->modifiers};
90
}
91
92
=head2 custom_data
93
94
    $data = $QParser->custom_data($class);
95
=cut
96
97
sub custom_data {
98
    my $class = shift;
99
    $class = ref($class) || $class;
100
101
    $parser_config{$class}{custom_data} ||= {};
102
    return $parser_config{$class}{custom_data};
103
}
104
105
=head2 operators
106
107
    $operators = $QParser->operators();
108
109
Returns hashref of the configured operators.
110
=cut
111
112
sub operators {
113
    my $class = shift;
114
    $class = ref($class) || $class;
115
116
    $parser_config{$class}{operators} ||= {};
117
    return $parser_config{$class}{operators};
118
}
119
120
sub allow_nested_modifiers {
121
    my $class = shift;
122
    my $v = shift;
123
    $class = ref($class) || $class;
124
125
    $parser_config{$class}{allow_nested_modifiers} = $v if (defined $v);
126
    return $parser_config{$class}{allow_nested_modifiers};
127
}
128
129
=head2 filters
130
131
    $filters = $QParser->filters();
132
133
Returns arrayref of the configured filters.
134
=cut
135
136
sub filters {
137
    my $class = shift;
138
    $class = ref($class) || $class;
139
140
    $parser_config{$class}{filters} ||= [];
141
    return $parser_config{$class}{filters};
142
}
143
144
=head2 filter_callbacks
145
146
    $filter_callbacks = $QParser->filter_callbacks();
147
148
Returns hashref of the configured filter callbacks.
149
=cut
150
151
sub filter_callbacks {
152
    my $class = shift;
153
    $class = ref($class) || $class;
154
155
    $parser_config{$class}{filter_callbacks} ||= {};
156
    return $parser_config{$class}{filter_callbacks};
157
}
158
159
=head2 modifiers
160
161
    $modifiers = $QParser->modifiers();
162
163
Returns arrayref of the configured modifiers.
164
=cut
165
166
sub modifiers {
167
    my $class = shift;
168
    $class = ref($class) || $class;
169
170
    $parser_config{$class}{modifiers} ||= [];
171
    return $parser_config{$class}{modifiers};
172
}
173
174
=head2 new
175
176
    $QParser = OpenILS::QueryParser->new(%args);
177
178
Creates a new QueryParser object.
179
=cut
180
181
sub new {
182
    my $class = shift;
183
    $class = ref($class) || $class;
184
185
    my %opts = @_;
186
187
    my $self = bless {} => $class;
188
189
    for my $o (keys %{OpenILS::QueryParser->operators}) {
190
        $class->operator($o => OpenILS::QueryParser->operator($o)) unless ($class->operator($o));
191
    }
192
193
    for my $opt ( keys %opts) {
194
        $self->$opt( $opts{$opt} ) if ($self->can($opt));
195
    }
196
197
    return $self;
198
}
199
200
=head2 new_plan
201
202
    $query_plan = $QParser->new_plan();
203
204
Create a new query plan.
205
=cut
206
207
sub new_plan {
208
    my $self = shift;
209
    my $pkg = ref($self) || $self;
210
    return do{$pkg.'::query_plan'}->new( QueryParser => $self, @_ );
211
}
212
213
=head2 add_search_filter
214
215
    $QParser->add_search_filter($filter, [$callback]);
216
217
Adds a filter with the specified name and an optional callback to the
218
QueryParser configuration.
219
=cut
220
221
sub add_search_filter {
222
    my $pkg = shift;
223
    $pkg = ref($pkg) || $pkg;
224
    my $filter = shift;
225
    my $callback = shift;
226
227
    return $filter if (grep { $_ eq $filter } @{$pkg->filters});
228
    push @{$pkg->filters}, $filter;
229
    $pkg->filter_callbacks->{$filter} = $callback if ($callback);
230
    return $filter;
231
}
232
233
=head2 add_search_modifier
234
235
    $QParser->add_search_modifier($modifier);
236
237
Adds a modifier with the specified name to the QueryParser configuration.
238
=cut
239
240
sub add_search_modifier {
241
    my $pkg = shift;
242
    $pkg = ref($pkg) || $pkg;
243
    my $modifier = shift;
244
245
    return $modifier if (grep { $_ eq $modifier } @{$pkg->modifiers});
246
    push @{$pkg->modifiers}, $modifier;
247
    return $modifier;
248
}
249
250
=head2 add_facet_class
251
252
    $QParser->add_facet_class($facet_class);
253
254
Adds a facet class with the specified name to the QueryParser configuration.
255
=cut
256
257
sub add_facet_class {
258
    my $pkg = shift;
259
    $pkg = ref($pkg) || $pkg;
260
    my $class = shift;
261
262
    return $class if (grep { $_ eq $class } @{$pkg->facet_classes});
263
264
    push @{$pkg->facet_classes}, $class;
265
    $pkg->facet_fields->{$class} = [];
266
267
    return $class;
268
}
269
270
=head2 add_search_class
271
272
    $QParser->add_search_class($class);
273
274
Adds a search class with the specified name to the QueryParser configuration.
275
=cut
276
277
sub add_search_class {
278
    my $pkg = shift;
279
    $pkg = ref($pkg) || $pkg;
280
    my $class = shift;
281
282
    return $class if (grep { $_ eq $class } @{$pkg->search_classes});
283
284
    push @{$pkg->search_classes}, $class;
285
    $pkg->search_fields->{$class} = [];
286
    $pkg->default_search_class( $pkg->search_classes->[0] ) if (@{$pkg->search_classes} == 1);
287
288
    return $class;
289
}
290
291
=head2 add_search_modifier
292
293
    $op = $QParser->operator($operator, [$newvalue]);
294
295
Retrieves or sets value for the specified operator. Valid operators and
296
their defaults are as follows:
297
298
=over 4
299
300
=item * and => &&
301
302
=item * or => ||
303
304
=item * group_start => (
305
306
=item * group_end => )
307
308
=item * required => +
309
310
=item * disallowed => -
311
312
=item * modifier => #
313
314
=back
315
316
=cut
317
318
sub operator {
319
    my $class = shift;
320
    $class = ref($class) || $class;
321
    my $opname = shift;
322
    my $op = shift;
323
324
    return unless ($opname);
325
326
    $parser_config{$class}{operators} ||= {};
327
    $parser_config{$class}{operators}{$opname} = $op if ($op);
328
329
    return $parser_config{$class}{operators}{$opname};
330
}
331
332
=head2 facet_classes
333
334
    $classes = $QParser->facet_classes([\@newclasses]);
335
336
Returns arrayref of all configured facet classes after optionally
337
replacing configuration.
338
=cut
339
340
sub facet_classes {
341
    my $class = shift;
342
    $class = ref($class) || $class;
343
    my $classes = shift;
344
345
    $parser_config{$class}{facet_classes} ||= [];
346
    $parser_config{$class}{facet_classes} = $classes if (ref($classes) && @$classes);
347
    return $parser_config{$class}{facet_classes};
348
}
349
350
=head2 search_classes
351
352
    $classes = $QParser->search_classes([\@newclasses]);
353
354
Returns arrayref of all configured search classes after optionally
355
replacing the previous configuration.
356
=cut
357
358
sub search_classes {
359
    my $class = shift;
360
    $class = ref($class) || $class;
361
    my $classes = shift;
362
363
    $parser_config{$class}{classes} ||= [];
364
    $parser_config{$class}{classes} = $classes if (ref($classes) && @$classes);
365
    return $parser_config{$class}{classes};
366
}
367
368
=head2 add_query_normalizer
369
370
    $function = $QParser->add_query_normalizer($class, $field, $func, [\@params]);
371
372
=cut
373
374
sub add_query_normalizer {
375
    my $pkg = shift;
376
    $pkg = ref($pkg) || $pkg;
377
    my $class = shift;
378
    my $field = shift;
379
    my $func = shift;
380
    my $params = shift || [];
381
382
    # do not add if function AND params are identical to existing member
383
    return $func if (grep {
384
        $_->{function} eq $func and
385
        to_json($_->{params}) eq to_json($params)
386
    } @{$pkg->query_normalizers->{$class}->{$field}});
387
388
    push(@{$pkg->query_normalizers->{$class}->{$field}}, { function => $func, params => $params });
389
390
    return $func;
391
}
392
393
=head2 query_normalizers
394
395
    $normalizers = $QParser->query_normalizers($class, $field);
396
397
Returns a list of normalizers associated with the specified search class
398
and field
399
=cut
400
401
sub query_normalizers {
402
    my $pkg = shift;
403
    $pkg = ref($pkg) || $pkg;
404
405
    my $class = shift;
406
    my $field = shift;
407
408
    $parser_config{$pkg}{normalizers} ||= {};
409
    if ($class) {
410
        if ($field) {
411
            $parser_config{$pkg}{normalizers}{$class}{$field} ||= [];
412
            return $parser_config{$pkg}{normalizers}{$class}{$field};
413
        } else {
414
            return $parser_config{$pkg}{normalizers}{$class};
415
        }
416
    }
417
418
    return $parser_config{$pkg}{normalizers};
419
}
420
421
=head2 add_filter_normalizer
422
423
    $normalizer = $QParser->add_filter_normalizer($filter, $func, [\@params]);
424
425
Adds a normalizer function to the specified filter.
426
=cut
427
428
sub add_filter_normalizer {
429
    my $pkg = shift;
430
    $pkg = ref($pkg) || $pkg;
431
    my $filter = shift;
432
    my $func = shift;
433
    my $params = shift || [];
434
435
    return $func if (grep { $_ eq $func } @{$pkg->filter_normalizers->{$filter}});
436
437
    push(@{$pkg->filter_normalizers->{$filter}}, { function => $func, params => $params });
438
439
    return $func;
440
}
441
442
=head2 filter_normalizers
443
444
    $normalizers = $QParser->filter_normalizers($filter);
445
446
Return arrayref of normalizer functions associated with the specified filter.
447
=cut
448
449
sub filter_normalizers {
450
    my $pkg = shift;
451
    $pkg = ref($pkg) || $pkg;
452
453
    my $filter = shift;
454
455
    $parser_config{$pkg}{filter_normalizers} ||= {};
456
    if ($filter) {
457
        $parser_config{$pkg}{filter_normalizers}{$filter} ||= [];
458
        return $parser_config{$pkg}{filter_normalizers}{$filter};
459
    }
460
461
    return $parser_config{$pkg}{filter_normalizers};
462
}
463
464
=head2 default_search_class
465
466
    $default_class = $QParser->default_search_class([$class]);
467
468
Set or return the default search class.
469
=cut
470
471
sub default_search_class {
472
    my $pkg = shift;
473
    $pkg = ref($pkg) || $pkg;
474
    my $class = shift;
475
    $OpenILS::QueryParser::parser_config{$pkg}{default_class} = $pkg->add_search_class( $class ) if $class;
476
477
    return $OpenILS::QueryParser::parser_config{$pkg}{default_class};
478
}
479
480
=head2 remove_facet_class
481
482
    $QParser->remove_facet_class($class);
483
484
Remove the specified facet class from the configuration.
485
=cut
486
487
sub remove_facet_class {
488
    my $pkg = shift;
489
    $pkg = ref($pkg) || $pkg;
490
    my $class = shift;
491
492
    return $class if (!grep { $_ eq $class } @{$pkg->facet_classes});
493
494
    $pkg->facet_classes( [ grep { $_ ne $class } @{$pkg->facet_classes} ] );
495
    delete $OpenILS::QueryParser::parser_config{$pkg}{facet_fields}{$class};
496
497
    return $class;
498
}
499
500
=head2 remove_search_class
501
502
    $QParser->remove_search_class($class);
503
504
Remove the specified search class from the configuration.
505
=cut
506
507
sub remove_search_class {
508
    my $pkg = shift;
509
    $pkg = ref($pkg) || $pkg;
510
    my $class = shift;
511
512
    return $class if (!grep { $_ eq $class } @{$pkg->search_classes});
513
514
    $pkg->search_classes( [ grep { $_ ne $class } @{$pkg->search_classes} ] );
515
    delete $OpenILS::QueryParser::parser_config{$pkg}{fields}{$class};
516
517
    return $class;
518
}
519
520
=head2 add_facet_field
521
522
    $QParser->add_facet_field($class, $field);
523
524
Adds the specified field (and facet class if it doesn't already exist)
525
to the configuration.
526
=cut
527
528
sub add_facet_field {
529
    my $pkg = shift;
530
    $pkg = ref($pkg) || $pkg;
531
    my $class = shift;
532
    my $field = shift;
533
534
    $pkg->add_facet_class( $class );
535
536
    return { $class => $field }  if (grep { $_ eq $field } @{$pkg->facet_fields->{$class}});
537
538
    push @{$pkg->facet_fields->{$class}}, $field;
539
540
    return { $class => $field };
541
}
542
543
=head2 facet_fields
544
545
    $fields = $QParser->facet_fields($class);
546
547
Returns arrayref with list of fields for specified facet class.
548
=cut
549
550
sub facet_fields {
551
    my $class = shift;
552
    $class = ref($class) || $class;
553
554
    $parser_config{$class}{facet_fields} ||= {};
555
    return $parser_config{$class}{facet_fields};
556
}
557
558
=head2 add_search_field
559
560
    $QParser->add_search_field($class, $field);
561
562
Adds the specified field (and facet class if it doesn't already exist)
563
to the configuration.
564
=cut
565
566
sub add_search_field {
567
    my $pkg = shift;
568
    $pkg = ref($pkg) || $pkg;
569
    my $class = shift;
570
    my $field = shift;
571
572
    $pkg->add_search_class( $class );
573
574
    return { $class => $field }  if (grep { $_ eq $field } @{$pkg->search_fields->{$class}});
575
576
    push @{$pkg->search_fields->{$class}}, $field;
577
578
    return { $class => $field };
579
}
580
581
=head2 search_fields
582
583
    $fields = $QParser->search_fields();
584
585
Returns arrayref with list of configured search fields.
586
=cut
587
588
sub search_fields {
589
    my $class = shift;
590
    $class = ref($class) || $class;
591
592
    $parser_config{$class}{fields} ||= {};
593
    return $parser_config{$class}{fields};
594
}
595
596
=head2 add_search_class_alias
597
598
    $QParser->add_search_class_alias($class, $alias);
599
=cut
600
601
sub add_search_class_alias {
602
    my $pkg = shift;
603
    $pkg = ref($pkg) || $pkg;
604
    my $class = shift;
605
    my $alias = shift;
606
607
    $pkg->add_search_class( $class );
608
609
    return { $class => $alias }  if (grep { $_ eq $alias } @{$pkg->search_class_aliases->{$class}});
610
611
    push @{$pkg->search_class_aliases->{$class}}, $alias;
612
613
    return { $class => $alias };
614
}
615
616
=head2 search_class_aliases
617
618
    $aliases = $QParser->search_class_aliases($class);
619
=cut
620
621
sub search_class_aliases {
622
    my $class = shift;
623
    $class = ref($class) || $class;
624
625
    $parser_config{$class}{class_map} ||= {};
626
    return $parser_config{$class}{class_map};
627
}
628
629
=head2 add_search_field_alias
630
631
    $QParser->add_search_field_alias($class, $field, $alias);
632
=cut
633
634
sub add_search_field_alias {
635
    my $pkg = shift;
636
    $pkg = ref($pkg) || $pkg;
637
    my $class = shift;
638
    my $field = shift;
639
    my $alias = shift;
640
641
    return { $class => { $field => $alias } }  if (grep { $_ eq $alias } @{$pkg->search_field_aliases->{$class}{$field}});
642
643
    push @{$pkg->search_field_aliases->{$class}{$field}}, $alias;
644
645
    return { $class => { $field => $alias } };
646
}
647
648
=head2 search_field_aliases
649
650
    $aliases = $QParser->search_field_aliases();
651
=cut
652
653
sub search_field_aliases {
654
    my $class = shift;
655
    $class = ref($class) || $class;
656
657
    $parser_config{$class}{field_alias_map} ||= {};
658
    return $parser_config{$class}{field_alias_map};
659
}
660
661
=head2 remove_facet_field
662
663
    $QParser->remove_facet_field($class, $field);
664
=cut
665
666
sub remove_facet_field {
667
    my $pkg = shift;
668
    $pkg = ref($pkg) || $pkg;
669
    my $class = shift;
670
    my $field = shift;
671
672
    return { $class => $field }  if (!$pkg->facet_fields->{$class} || !grep { $_ eq $field } @{$pkg->facet_fields->{$class}});
673
674
    $pkg->facet_fields->{$class} = [ grep { $_ ne $field } @{$pkg->facet_fields->{$class}} ];
675
676
    return { $class => $field };
677
}
678
679
=head2 remove_search_field
680
681
    $QParser->remove_search_field($class, $field);
682
=cut
683
684
sub remove_search_field {
685
    my $pkg = shift;
686
    $pkg = ref($pkg) || $pkg;
687
    my $class = shift;
688
    my $field = shift;
689
690
    return { $class => $field }  if (!$pkg->search_fields->{$class} || !grep { $_ eq $field } @{$pkg->search_fields->{$class}});
691
692
    $pkg->search_fields->{$class} = [ grep { $_ ne $field } @{$pkg->search_fields->{$class}} ];
693
694
    return { $class => $field };
695
}
696
697
=head2 remove_search_field_alias
698
699
    $QParser->remove_search_field_alias($class, $field, $alias);
700
=cut
701
702
sub remove_search_field_alias {
703
    my $pkg = shift;
704
    $pkg = ref($pkg) || $pkg;
705
    my $class = shift;
706
    my $field = shift;
707
    my $alias = shift;
708
709
    return { $class => { $field => $alias } }  if (!$pkg->search_field_aliases->{$class}{$field} || !grep { $_ eq $alias } @{$pkg->search_field_aliases->{$class}{$field}});
710
711
    $pkg->search_field_aliases->{$class}{$field} = [ grep { $_ ne $alias } @{$pkg->search_field_aliases->{$class}{$field}} ];
712
713
    return { $class => { $field => $alias } };
714
}
715
716
=head2 remove_search_class_alias
717
718
    $QParser->remove_search_class_alias($class, $alias);
719
=cut
720
721
sub remove_search_class_alias {
722
    my $pkg = shift;
723
    $pkg = ref($pkg) || $pkg;
724
    my $class = shift;
725
    my $alias = shift;
726
727
    return { $class => $alias }  if (!$pkg->search_class_aliases->{$class} || !grep { $_ eq $alias } @{$pkg->search_class_aliases->{$class}});
728
729
    $pkg->search_class_aliases->{$class} = [ grep { $_ ne $alias } @{$pkg->search_class_aliases->{$class}} ];
730
731
    return { $class => $alias };
732
}
733
734
=head2 debug
735
736
    $debug = $QParser->debug([$debug]);
737
738
Return or set whether debugging output is enabled.
739
=cut
740
741
sub debug {
742
    my $self = shift;
743
    my $q = shift;
744
    $self->{_debug} = $q if (defined $q);
745
    return $self->{_debug};
746
}
747
748
=head2 query
749
750
    $query = $QParser->query([$query]);
751
752
Return or set the query.
753
=cut
754
755
sub query {
756
    my $self = shift;
757
    my $q = shift;
758
    $self->{_query} = " $q " if (defined $q);
759
    return $self->{_query};
760
}
761
762
=head2 parse_tree
763
764
    $parse_tree = $QParser->parse_tree([$parse_tree]);
765
766
Return or set the parse tree associated with the QueryParser.
767
=cut
768
769
sub parse_tree {
770
    my $self = shift;
771
    my $q = shift;
772
    $self->{_parse_tree} = $q if (defined $q);
773
    return $self->{_parse_tree};
774
}
775
776
sub floating_plan {
777
    my $self = shift;
778
    my $q = shift;
779
    $self->{_top} = $q if (defined $q);
780
    return $self->{_top};
781
}
782
783
=head2 parse
784
785
    $QParser->parse([$query]);
786
787
Parse the specified query, or the query already associated with the QueryParser
788
object.
789
=cut
790
791
sub parse {
792
    my $self = shift;
793
    my $pkg = ref($self) || $self;
794
    warn " ** parse package is $pkg\n" if $self->debug;
795
#    $self->parse_tree(
796
#        $self->decompose(
797
#            $self->query( shift() )
798
#        )
799
#    );
800
801
    undef $self->{_parse_tree};
802
803
    $self->decompose( $self->query( shift() ) );
804
805
    if ($self->floating_plan) {
806
        $self->floating_plan->add_node( $self->parse_tree );
807
        $self->parse_tree( $self->floating_plan );
808
    }
809
810
    $self->parse_tree->plan_level(0);
811
812
    return $self;
813
}
814
815
=head2 decompose
816
817
    ($struct, $remainder) = $QParser->decompose($querystring, [$current_class], [$recursing], [$phrase_helper]);
818
819
This routine does the heavy work of parsing the query string recursively.
820
Returns the top level query plan, or the query plan from a lower level plus
821
the portion of the query string that needs to be processed at a higher level.
822
=cut
823
824
our $last_class = '';
825
our $last_type = '';
826
our $floating = 0;
827
our $fstart;
828
829
sub decompose {
830
    my $self = shift;
831
    my $pkg = ref($self) || $self;
832
833
834
    $_ = shift;
835
    my $current_class = shift || $self->default_search_class;
836
837
    my $recursing = shift || 0;
838
    my $phrase_helper = shift || 0;
839
840
    # Build the search class+field uber-regexp
841
    my $search_class_re = '^\s*(';
842
    my $first_class = 1;
843
844
    warn '  'x$recursing." ** decompose package is $pkg\n" if $self->debug;
845
846
    my %seen_classes;
847
    for my $class ( keys %{$pkg->search_field_aliases} ) {
848
        warn '  'x$recursing." *** ... Looking for search fields in $class\n" if $self->debug;
849
850
        for my $field ( keys %{$pkg->search_field_aliases->{$class}} ) {
851
            warn '  'x$recursing." *** ... Looking for aliases of $field\n" if $self->debug;
852
853
            for my $alias ( @{$pkg->search_field_aliases->{$class}{$field}} ) {
854
                next unless ($alias);
855
                my $aliasr = qr/$alias/;
856
                s/(^|\s+)$aliasr\|/$1$class\|$field#$alias\|/g;
857
                s/(^|\s+)$aliasr[:=]/$1$class\|$field#$alias:/g;
858
                warn '  'x$recursing." *** Rewriting: $alias ($aliasr) as $class\|$field\n" if $self->debug;
859
            }
860
        }
861
862
        $search_class_re .= '|' unless ($first_class);
863
        $first_class = 0;
864
        $search_class_re .= $class . '(?:[|#][^:|]+)*';
865
        $seen_classes{$class} = 1;
866
    }
867
868
    for my $class ( keys %{$pkg->search_class_aliases} ) {
869
870
        for my $alias ( @{$pkg->search_class_aliases->{$class}} ) {
871
            next unless ($alias);
872
            my $aliasr = qr/$alias/;
873
            s/(^|[^|])\b$aliasr\|/$1$class#$alias\|/g;
874
            s/(^|[^|])\b$aliasr[:=]/$1$class#$alias:/g;
875
            warn '  'x$recursing." *** Rewriting: $alias ($aliasr) as $class\n" if $self->debug;
876
        }
877
878
        if (!$seen_classes{$class}) {
879
            $search_class_re .= '|' unless ($first_class);
880
            $first_class = 0;
881
882
            $search_class_re .= $class . '(?:[|#][^:|]+)*';
883
            $seen_classes{$class} = 1;
884
        }
885
    }
886
    $search_class_re .= '):';
887
888
    warn '  'x$recursing." ** Rewritten query: $_\n" if $self->debug;
889
    warn '  'x$recursing." ** Search class RE: $search_class_re\n" if $self->debug;
890
891
    my $required_op = $pkg->operator('required');
892
    my $required_re = qr/\Q$required_op\E/;
893
894
    my $disallowed_op = $pkg->operator('disallowed');
895
    my $disallowed_re = qr/\Q$disallowed_op\E/;
896
897
    my $negated_op = $pkg->operator('negated');
898
    my $negated_re = qr/\Q$negated_op\E/;
899
900
    my $and_op = $pkg->operator('and');
901
    my $and_re = qr/^\s*\Q$and_op\E/;
902
903
    my $or_op = $pkg->operator('or');
904
    my $or_re = qr/^\s*\Q$or_op\E/;
905
906
    my $group_start = $pkg->operator('group_start');
907
    my $group_start_re = qr/^\s*($negated_re|$disallowed_re)?\Q$group_start\E/;
908
909
    my $group_end = $pkg->operator('group_end');
910
    my $group_end_re = qr/^\s*\Q$group_end\E/;
911
912
    my $float_start = $pkg->operator('float_start');
913
    my $float_start_re = qr/^\s*\Q$float_start\E/;
914
915
    my $float_end = $pkg->operator('float_end');
916
    my $float_end_re = qr/^\s*\Q$float_end\E/;
917
918
    my $modifier_tag = $pkg->operator('modifier');
919
    my $modifier_tag_re = qr/^\s*\Q$modifier_tag\E/;
920
921
    # Group start/end normally are ( and ), but can be overridden.
922
    # We thus include ( and ) specifically due to filters, as well as : for classes.
923
    my $phrase_cleanup_re = qr/\s*(\Q$required_op\E|\Q$disallowed_op\E|\Q$and_op\E|\Q$or_op\E|\Q$group_start\E|\Q$group_end\E|\Q$float_start\E|\Q$float_end\E|\Q$modifier_tag\E|\Q$negated_op\E|:|\(|\))/;
924
925
    # Build the filter and modifier uber-regexps
926
    my $facet_re = '^\s*(-?)((?:' . join( '|', @{$pkg->facet_classes}) . ')(?:\|\w+)*)\[(.+?)\]';
927
    warn '  'x$recursing." ** Facet RE: $facet_re\n" if $self->debug;
928
929
    my $filter_re = '^\s*(-?)(' . join( '|', @{$pkg->filters}) . ')\(([^()]+)\)';
930
    my $filter_as_class_re = '^\s*(-?)(' . join( '|', @{$pkg->filters}) . '):\s*(\S+)';
931
932
    my $modifier_re = '^\s*'.$modifier_tag_re.'(' . join( '|', @{$pkg->modifiers}) . ')\b';
933
    my $modifier_as_class_re = '^\s*(' . join( '|', @{$pkg->modifiers}) . '):\s*(\S+)';
934
935
    my $struct = shift || $self->new_plan( level => $recursing );
936
    $self->parse_tree( $struct ) if (!$self->parse_tree);
937
938
    my $remainder = '';
939
940
    while (!$remainder) {
941
        warn '  'x$recursing."Start of the loop. last_type: $last_type, joiner: ".$struct->joiner.", struct: $struct\n" if $self->debug;
942
        if ($last_type eq 'FEND' and $fstart and $fstart !=  $struct) { # fall back further
943
            $remainder = $_;
944
            last;
945
        } elsif ($last_type eq 'FEND') {
946
            $fstart = undef;
947
            $last_type = '';
948
        }
949
950
        if (/^\s*$/) { # end of an explicit group
951
            local $last_type = '';
952
            last;
953
        } elsif (/$float_end_re/) { # end of an explicit group
954
            warn '  'x$recursing."Encountered explicit float end, remainder: $'\n" if $self->debug;
955
956
            $remainder = $';
957
            $_ = '';
958
959
            $floating = 0;
960
            $last_type = 'FEND';
961
            last;
962
        } elsif (/$group_end_re/) { # end of an explicit group
963
            warn '  'x$recursing."Encountered explicit group end, remainder: $'\n" if $self->debug;
964
965
            $remainder = $';
966
            $_ = '';
967
968
            local $last_type = '';
969
        } elsif ($self->filter_count && /$filter_re/) { # found a filter
970
            warn '  'x$recursing."Encountered search filter: $1$2 set to $3\n" if $self->debug;
971
972
            my $negate = ($1 eq $pkg->operator('disallowed')) ? 1 : 0;
973
            $_ = $';
974
975
            my $filter = $2;
976
            my $params = [ split '[,]+', $3 ];
977
978
            if ($pkg->filter_callbacks->{$filter}) {
979
                my $replacement = $pkg->filter_callbacks->{$filter}->($self, $struct, $filter, $params, $negate);
980
                $_ = "$replacement $_" if ($replacement);
981
            } else {
982
                $struct->new_filter( $filter => $params, $negate );
983
            }
984
985
986
            local $last_type = '';
987
        } elsif ($self->filter_count && /$filter_as_class_re/) { # found a filter
988
            warn '  'x$recursing."Encountered search filter: $1$2 set to $3\n" if $self->debug;
989
990
            my $negate = ($1 eq $pkg->operator('disallowed')) ? 1 : 0;
991
            $_ = $';
992
993
            my $filter = $2;
994
            my $params = [ split '[,]+', $3 ];
995
996
            if ($pkg->filter_callbacks->{$filter}) {
997
                my $replacement = $pkg->filter_callbacks->{$filter}->($self, $struct, $filter, $params, $negate);
998
                $_ = "$replacement $_" if ($replacement);
999
            } else {
1000
                $struct->new_filter( $filter => $params, $negate );
1001
            }
1002
1003
            local $last_type = '';
1004
        } elsif ($self->modifier_count && /$modifier_re/) { # found a modifier
1005
            warn '  'x$recursing."Encountered search modifier: $1\n" if $self->debug;
1006
1007
            $_ = $';
1008
            if (!($struct->top_plan || $parser_config{$pkg}->{allow_nested_modifiers})) {
1009
                warn '  'x$recursing."  Search modifiers only allowed at the top level of the query\n" if $self->debug;
1010
            } else {
1011
                $struct->new_modifier($1);
1012
            }
1013
1014
            local $last_type = '';
1015
        } elsif ($self->modifier_count && /$modifier_as_class_re/) { # found a modifier
1016
            warn '  'x$recursing."Encountered search modifier: $1\n" if $self->debug;
1017
1018
            my $mod = $1;
1019
1020
            $_ = $';
1021
            if (!($struct->top_plan || $parser_config{$pkg}->{allow_nested_modifiers})) {
1022
                warn '  'x$recursing."  Search modifiers only allowed at the top level of the query\n" if $self->debug;
1023
            } elsif ($2 =~ /^[ty1]/i) {
1024
                $struct->new_modifier($mod);
1025
            }
1026
1027
            local $last_type = '';
1028
        } elsif (/$float_start_re/) { # start of an explicit float
1029
            warn '  'x$recursing."Encountered explicit float start\n" if $self->debug;
1030
            $floating = 1;
1031
            $fstart = $struct;
1032
1033
            $last_class = $current_class;
1034
            $current_class = undef;
1035
1036
            $self->floating_plan( $self->new_plan( floating => 1 ) ) if (!$self->floating_plan);
1037
1038
            # pass the floating_plan struct to be modified by the float'ed chunk
1039
            my ($floating_plan, $subremainder) = $self->new( debug => $self->debug )->decompose( $', undef, undef, undef,  $self->floating_plan);
1040
            $_ = $subremainder;
1041
            warn '  'x$recursing."Remainder after explicit float: $_\n" if $self->debug;
1042
1043
            $current_class = $last_class;
1044
1045
            $last_type = '';
1046
        } elsif (/$group_start_re/) { # start of an explicit group
1047
            warn '  'x$recursing."Encountered explicit group start\n" if $self->debug;
1048
            my $negate = $1;
1049
            my ($substruct, $subremainder) = $self->decompose( $', $current_class, $recursing + 1 );
1050
            $substruct->negate(1) if ($substruct && $negate);
1051
            $struct->add_node( $substruct ) if ($substruct);
1052
            $_ = $subremainder;
1053
            warn '  'x$recursing."Query remainder after bool group: $_\n" if $self->debug;
1054
1055
            local $last_type = '';
1056
1057
        } elsif (/$and_re/) { # ANDed expression
1058
            $_ = $';
1059
            warn '  'x$recursing."Encountered AND\n" if $self->debug;
1060
            do {warn '  'x$recursing."!!! Already doing the bool dance for AND\n" if $self->debug; next} if ($last_type eq 'AND');
1061
            do {warn '  'x$recursing."!!! Already doing the bool dance for OR\n" if $self->debug; next} if ($last_type eq 'OR');
1062
            local $last_type = 'AND';
1063
1064
            warn '  'x$recursing."Saving LHS, building RHS\n" if $self->debug;
1065
            my $LHS = $struct;
1066
            #my ($RHS, $subremainder) = $self->decompose( "$group_start $_ $group_end", $current_class, $recursing + 1 );
1067
            my ($RHS, $subremainder) = $self->decompose( $_, $current_class, $recursing + 1 );
1068
            $_ = $subremainder;
1069
1070
            warn '  'x$recursing."RHS built\n" if $self->debug;
1071
            warn '  'x$recursing."Post-AND remainder: $subremainder\n" if $self->debug;
1072
1073
            my $wrapper = $self->new_plan( level => $recursing + 1 );
1074
1075
            if ($LHS->floating) {
1076
                $wrapper->{query} = $LHS->{query};
1077
                my $outer_wrapper = $self->new_plan( level => $recursing + 1 );
1078
                $outer_wrapper->add_node($_) for ($wrapper,$RHS);
1079
                $LHS->{query} = [$outer_wrapper];
1080
                $struct = $LHS;
1081
            } else {
1082
                $wrapper->add_node($_) for ($LHS, $RHS);
1083
                $wrapper->plan_level($wrapper->plan_level); # reset levels all the way down
1084
                $struct = $self->new_plan( level => $recursing );
1085
                $struct->add_node($wrapper);
1086
            }
1087
1088
            $self->parse_tree( $struct ) if ($self->parse_tree == $LHS);
1089
1090
            local $last_type = '';
1091
        } elsif (/$or_re/) { # ORed expression
1092
            $_ = $';
1093
            warn '  'x$recursing."Encountered OR\n" if $self->debug;
1094
            do {warn '  'x$recursing."!!! Already doing the bool dance for AND\n" if $self->debug; next} if ($last_type eq 'AND');
1095
            do {warn '  'x$recursing."!!! Already doing the bool dance for OR\n" if $self->debug; next} if ($last_type eq 'OR');
1096
            local $last_type = 'OR';
1097
1098
            warn '  'x$recursing."Saving LHS, building RHS\n" if $self->debug;
1099
            my $LHS = $struct;
1100
            #my ($RHS, $subremainder) = $self->decompose( "$group_start $_ $group_end", $current_class, $recursing + 1 );
1101
            my ($RHS, $subremainder) = $self->decompose( $_, $current_class, $recursing + 2 );
1102
            $_ = $subremainder;
1103
1104
            warn '  'x$recursing."RHS built\n" if $self->debug;
1105
            warn '  'x$recursing."Post-OR remainder: $subremainder\n" if $self->debug;
1106
1107
            my $wrapper = $self->new_plan( level => $recursing + 1, joiner => '|' );
1108
1109
            if ($LHS->floating) {
1110
                $wrapper->{query} = $LHS->{query};
1111
                my $outer_wrapper = $self->new_plan( level => $recursing + 1, joiner => '|' );
1112
                $outer_wrapper->add_node($_) for ($wrapper,$RHS);
1113
                $LHS->{query} = [$outer_wrapper];
1114
                $struct = $LHS;
1115
            } else {
1116
                $wrapper->add_node($_) for ($LHS, $RHS);
1117
                $wrapper->plan_level($wrapper->plan_level); # reset levels all the way down
1118
                $struct = $self->new_plan( level => $recursing );
1119
                $struct->add_node($wrapper);
1120
            }
1121
1122
            $self->parse_tree( $struct ) if ($self->parse_tree == $LHS);
1123
1124
            local $last_type = '';
1125
        } elsif ($self->facet_class_count && /$facet_re/) { # changing current class
1126
            warn '  'x$recursing."Encountered facet: $1$2 => $3\n" if $self->debug;
1127
1128
            my $negate = ($1 eq $pkg->operator('disallowed')) ? 1 : 0;
1129
            my $facet = $2;
1130
            my $facet_value = [ split '\s*#\s*', $3 ];
1131
            $struct->new_facet( $facet => $facet_value, $negate );
1132
            $_ = $';
1133
1134
            local $last_type = '';
1135
        } elsif ($self->search_class_count && /$search_class_re/) { # changing current class
1136
1137
            if ($last_type eq 'CLASS') {
1138
                $struct->remove_last_node( $current_class );
1139
                warn '  'x$recursing."Encountered class change with no searches!\n" if $self->debug;
1140
            }
1141
1142
            warn '  'x$recursing."Encountered class change: $1\n" if $self->debug;
1143
1144
            $current_class = $struct->classed_node( $1 )->requested_class();
1145
            $_ = $';
1146
1147
            local $last_type = 'CLASS';
1148
        } elsif (/^\s*($required_re|$disallowed_re|$negated_re)?"([^"]+)"/) { # phrase, always anded
1149
            warn '  'x$recursing.'Encountered' . ($1 ? " ['$1' modified]" : '') . " phrase: $2\n" if $self->debug;
1150
1151
            my $req_ness = $1 || '';
1152
            $req_ness = $disallowed_op if ($req_ness eq $negated_op);
1153
            my $phrase = $2;
1154
1155
            if (!$phrase_helper) {
1156
                warn '  'x$recursing."Recursing into decompose with the phrase as a subquery\n" if $self->debug;
1157
                my $after = $';
1158
                my ($substruct, $subremainder) = $self->decompose( qq/$req_ness"$phrase"/, $current_class, $recursing + 1, 1 );
1159
                $struct->add_node( $substruct ) if ($substruct);
1160
                $_ = $after;
1161
            } else {
1162
                warn '  'x$recursing."Directly parsing the phrase subquery\n" if $self->debug;
1163
                $struct->joiner( '&' );
1164
1165
                my $class_node = $struct->classed_node($current_class);
1166
1167
                if ($req_ness eq $disallowed_op) {
1168
                    $class_node->negate(1);
1169
                }
1170
                $class_node->add_phrase( $phrase );
1171
1172
                # Save $' before we clean up $phrase
1173
                my $temp_val = $';
1174
1175
                # Cleanup the phrase to make it so that we don't parse things in it as anything other than atoms
1176
                $phrase =~ s/$phrase_cleanup_re/ /g;
1177
1178
                $_ = $temp_val;
1179
1180
            }
1181
1182
            local $last_type = '';
1183
1184
        } elsif (/^\s*($required_re|$disallowed_re)([^${group_end}${float_end}\s"]+)/) { # convert require/disallow word to {un}phrase
1185
            warn '  'x$recursing."Encountered required atom (mini phrase), transforming for phrase parse: $1\n" if $self->debug;
1186
1187
            $_ = $1 . '"' . $2 . '"' . $';
1188
1189
            local $last_type = '';
1190
        } elsif (/^\s*([^${group_end}${float_end}\s]+)/o) { # atom
1191
            warn '  'x$recursing."Encountered atom: $1\n" if $self->debug;
1192
            warn '  'x$recursing."Remainder: $'\n" if $self->debug;
1193
1194
            my $atom = $1;
1195
            my $after = $';
1196
1197
            $_ = $after;
1198
            local $last_type = '';
1199
1200
            my $class_node = $struct->classed_node($current_class);
1201
1202
            my $prefix = ($atom =~ s/^$negated_re//o) ? '!' : '';
1203
            my $truncate = ($atom =~ s/\*$//o) ? '*' : '';
1204
1205
            if ($atom ne '' and !grep { $atom =~ /^\Q$_\E+$/ } ('&','|')) { # throw away & and |, not allowed in tsquery, and not really useful anyway
1206
#                $class_node->add_phrase( $atom ) if ($atom =~ s/^$required_re//o);
1207
1208
                $class_node->add_fts_atom( $atom, suffix => $truncate, prefix => $prefix, node => $class_node );
1209
                $struct->joiner( '&' );
1210
            }
1211
1212
            local $last_type = '';
1213
        }
1214
1215
        last unless ($_);
1216
1217
    }
1218
1219
    $struct = undef if
1220
        scalar(@{$struct->query_nodes}) == 0 &&
1221
        scalar(@{$struct->filters}) == 0 &&
1222
        !$struct->top_plan;
1223
1224
    return $struct if !wantarray;
1225
    return ($struct, $remainder);
1226
}
1227
1228
=head2 find_class_index
1229
1230
    $index = $QParser->find_class_index($class, $query);
1231
=cut
1232
1233
sub find_class_index {
1234
    my $class = shift;
1235
    my $query = shift;
1236
1237
    my ($class_part, @field_parts) = split '\|', $class;
1238
    $class_part ||= $class;
1239
1240
    for my $idx ( 0 .. scalar(@$query) - 1 ) {
1241
        next unless ref($$query[$idx]);
1242
        return $idx if ( $$query[$idx]{requested_class} && $class eq $$query[$idx]{requested_class} );
1243
    }
1244
1245
    push(@$query, { classname => $class_part, (@field_parts ? (fields => \@field_parts) : ()), requested_class => $class, ftsquery => [], phrases => [] });
1246
    return -1;
1247
}
1248
1249
=head2 core_limit
1250
1251
    $limit = $QParser->core_limit([$limit]);
1252
1253
Return and/or set the core_limit.
1254
=cut
1255
1256
sub core_limit {
1257
    my $self = shift;
1258
    my $l = shift;
1259
    $self->{core_limit} = $l if ($l);
1260
    return $self->{core_limit};
1261
}
1262
1263
=head2 superpage
1264
1265
    $superpage = $QParser->superpage([$superpage]);
1266
1267
Return and/or set the superpage.
1268
=cut
1269
1270
sub superpage {
1271
    my $self = shift;
1272
    my $l = shift;
1273
    $self->{superpage} = $l if ($l);
1274
    return $self->{superpage};
1275
}
1276
1277
=head2 superpage_size
1278
1279
    $size = $QParser->superpage_size([$size]);
1280
1281
Return and/or set the superpage size.
1282
=cut
1283
1284
sub superpage_size {
1285
    my $self = shift;
1286
    my $l = shift;
1287
    $self->{superpage_size} = $l if ($l);
1288
    return $self->{superpage_size};
1289
}
1290
1291
1292
#-------------------------------
1293
package OpenILS::QueryParser::_util;
1294
1295
# At this level, joiners are always & or |.  This is not
1296
# the external, configurable representation of joiners that
1297
# defaults to # && and ||.
1298
sub is_joiner {
1299
    my $str = shift;
1300
1301
    return (not ref $str and ($str eq '&' or $str eq '|'));
1302
}
1303
1304
sub default_joiner { '&' }
1305
1306
# 0 for different, 1 for the same.
1307
sub compare_abstract_atoms {
1308
    my ($left, $right) = @_;
1309
1310
    foreach (qw/prefix suffix content/) {
1311
        no warnings;    # undef can stand in for '' here
1312
        return 0 unless $left->{$_} eq $right->{$_};
1313
    }
1314
1315
    return 1;
1316
}
1317
1318
sub fake_abstract_atom_from_phrase {
1319
    my $phrase = shift;
1320
    my $neg = shift;
1321
    my $qp_class = shift || 'OpenILS::QueryParser';
1322
1323
    my $prefix = '"';
1324
    if ($neg) {
1325
        $prefix =
1326
            $OpenILS::QueryParser::parser_config{$qp_class}{operators}{disallowed} .
1327
            $prefix;
1328
    }
1329
1330
    return {
1331
        "type" => "atom", "prefix" => $prefix, "suffix" => '"',
1332
        "content" => $phrase
1333
    }
1334
}
1335
1336
sub find_arrays_in_abstract {
1337
    my ($hash) = @_;
1338
1339
    my @arrays;
1340
    foreach my $key (keys %$hash) {
1341
        if (ref $hash->{$key} eq "ARRAY") {
1342
            push @arrays, $hash->{$key};
1343
            foreach (@{$hash->{$key}}) {
1344
                push @arrays, find_arrays_in_abstract($_);
1345
            }
1346
        }
1347
    }
1348
1349
    return @arrays;
1350
}
1351
1352
#-------------------------------
1353
package OpenILS::QueryParser::Canonicalize;  # not OO
1354
use Data::Dumper;
1355
1356
sub _abstract_query2str_filter {
1357
    my $f = shift;
1358
    my $qp_class = shift || 'OpenILS::QueryParser';
1359
    my $qpconfig = $OpenILS::QueryParser::parser_config{$qp_class};
1360
1361
    return sprintf(
1362
        '%s%s(%s)',
1363
        $f->{negate} ? $qpconfig->{operators}{disallowed} : "",
1364
        $f->{name},
1365
        join(",", @{$f->{args}})
1366
    );
1367
}
1368
1369
sub _abstract_query2str_modifier {
1370
    my $f = shift;
1371
    my $qp_class = shift || 'OpenILS::QueryParser';
1372
    my $qpconfig = $OpenILS::QueryParser::parser_config{$qp_class};
1373
1374
    return $qpconfig->{operators}{modifier} . $f;
1375
}
1376
1377
sub _kid_list {
1378
    my $children = shift;
1379
    my $op = (keys %$children)[0];
1380
    return @{$$children{$op}};
1381
}
1382
1383
1384
# This should produce an equivalent query to the original, given an
1385
# abstract_query.
1386
sub abstract_query2str_impl {
1387
    my $abstract_query  = shift;
1388
    my $depth = shift || 0;
1389
1390
    my $qp_class ||= shift || 'OpenILS::QueryParser';
1391
    my $force_qp_node = shift || 0;
1392
    my $qpconfig = $OpenILS::QueryParser::parser_config{$qp_class};
1393
1394
    my $fs = $qpconfig->{operators}{float_start};
1395
    my $fe = $qpconfig->{operators}{float_end};
1396
    my $gs = $qpconfig->{operators}{group_start};
1397
    my $ge = $qpconfig->{operators}{group_end};
1398
    my $and = $qpconfig->{operators}{and};
1399
    my $or = $qpconfig->{operators}{or};
1400
    my $ng = $qpconfig->{operators}{negated};
1401
1402
    my $isnode = 0;
1403
    my $negate = '';
1404
    my $size = 0;
1405
    my $q = "";
1406
1407
    if (exists $abstract_query->{type}) {
1408
        if ($abstract_query->{type} eq 'query_plan') {
1409
            $q .= join(" ", map { _abstract_query2str_filter($_, $qp_class) } @{$abstract_query->{filters}}) if
1410
                exists $abstract_query->{filters};
1411
1412
            $q .= ($q ? ' ' : '') . join(" ", map { _abstract_query2str_modifier($_, $qp_class) } @{$abstract_query->{modifiers}}) if
1413
                exists $abstract_query->{modifiers};
1414
1415
            $size = _kid_list($abstract_query->{children});
1416
            if ($abstract_query->{negate}) {
1417
                $isnode = 1;
1418
                $negate = $ng;
1419
            }
1420
            $isnode = 1 if ($size > 1 and ($force_qp_node or $depth));
1421
            #warn "size: $size, depth: $depth, isnode: $isnode, AQ: ".Dumper($abstract_query);
1422
        } elsif ($abstract_query->{type} eq 'node') {
1423
            if ($abstract_query->{alias}) {
1424
                $q .= ($q ? ' ' : '') . $abstract_query->{alias};
1425
                $q .= "|$_" foreach @{$abstract_query->{alias_fields}};
1426
            } else {
1427
                $q .= ($q ? ' ' : '') . $abstract_query->{class};
1428
                $q .= "|$_" foreach @{$abstract_query->{fields}};
1429
            }
1430
            $q .= ":";
1431
            $isnode = 1;
1432
        } elsif ($abstract_query->{type} eq 'atom') {
1433
            my $prefix = $abstract_query->{prefix} || '';
1434
            $prefix = $qpconfig->{operators}{negated} if $prefix eq '!';
1435
            $q .= ($q ? ' ' : '') . $prefix .
1436
                ($abstract_query->{content} || '') .
1437
                ($abstract_query->{suffix} || '');
1438
        } elsif ($abstract_query->{type} eq 'facet') {
1439
            # facet syntax [ # ] is hardcoded I guess?
1440
            my $prefix = $abstract_query->{negate} ? $qpconfig->{operators}{disallowed} : '';
1441
            $q .= ($q ? ' ' : '') . $prefix . $abstract_query->{name} . "[" .
1442
                join(" # ", @{$abstract_query->{values}}) . "]";
1443
        }
1444
    }
1445
1446
    my $next_depth = int($size > 1);
1447
1448
    if (exists $abstract_query->{children}) {
1449
1450
        my $op = (keys(%{$abstract_query->{children}}))[0];
1451
1452
        if ($abstract_query->{floating}) { # always the top node!
1453
            my $sub_node = pop @{$abstract_query->{children}{$op}};
1454
1455
            $abstract_query->{floating} = 0;
1456
            $q = $fs . " " . abstract_query2str_impl($abstract_query,0,$qp_class, 1) . $fe. " ";
1457
1458
            $abstract_query = $sub_node;
1459
        }
1460
1461
        if ($abstract_query && exists $abstract_query->{children}) {
1462
            $op = (keys(%{$abstract_query->{children}}))[0];
1463
            $q .= ($q ? ' ' : '') . join(
1464
                ($op eq '&' ? ' ' : " $or "),
1465
                map {
1466
                    my $x = abstract_query2str_impl($_, $depth + $next_depth, $qp_class, $force_qp_node); $x =~ s/^\s+//; $x =~ s/\s+$//; $x;
1467
                } @{$abstract_query->{children}{$op}}
1468
            );
1469
        }
1470
    } elsif ($abstract_query->{'&'} or $abstract_query->{'|'}) {
1471
        my $op = (keys(%{$abstract_query}))[0];
1472
        $q .= ($q ? ' ' : '') . join(
1473
            ($op eq '&' ? ' ' : " $or "),
1474
            map {
1475
                    my $x = abstract_query2str_impl($_, $depth + $next_depth, $qp_class, $force_qp_node); $x =~ s/^\s+//; $x =~ s/\s+$//; $x;
1476
            } @{$abstract_query->{$op}}
1477
        );
1478
    }
1479
1480
    $q = "$gs$q$ge" if ($isnode);
1481
    $q = $negate . $q if ($q);;
1482
1483
    return $q;
1484
}
1485
1486
#-------------------------------
1487
package OpenILS::QueryParser::query_plan;
1488
1489
sub QueryParser {
1490
    my $self = shift;
1491
    return unless ref($self);
1492
    return $self->{QueryParser};
1493
}
1494
1495
sub new {
1496
    my $pkg = shift;
1497
    $pkg = ref($pkg) || $pkg;
1498
    my %args = (query => [], joiner => '&', @_);
1499
1500
    return bless \%args => $pkg;
1501
}
1502
1503
sub new_node {
1504
    my $self = shift;
1505
    my $pkg = ref($self) || $self;
1506
    my $node = do{$pkg.'::node'}->new( plan => $self, @_ );
1507
    $self->add_node( $node );
1508
    return $node;
1509
}
1510
1511
sub new_facet {
1512
    my $self = shift;
1513
    my $pkg = ref($self) || $self;
1514
    my $name = shift;
1515
    my $args = shift;
1516
    my $negate = shift;
1517
1518
    my $node = do{$pkg.'::facet'}->new( plan => $self, name => $name, 'values' => $args, negate => $negate );
1519
    $self->add_node( $node );
1520
1521
    return $node;
1522
}
1523
1524
sub new_filter {
1525
    my $self = shift;
1526
    my $pkg = ref($self) || $self;
1527
    my $name = shift;
1528
    my $args = shift;
1529
    my $negate = shift;
1530
1531
    my $node = do{$pkg.'::filter'}->new( plan => $self, name => $name, args => $args, negate => $negate );
1532
    $self->add_filter( $node );
1533
1534
    return $node;
1535
}
1536
1537
1538
sub _merge_filters {
1539
    my $left_filter = shift;
1540
    my $right_filter = shift;
1541
    my $join = shift;
1542
1543
    return unless $left_filter or $right_filter;
1544
    return $right_filter unless $left_filter;
1545
    return $left_filter unless $right_filter;
1546
1547
    my $args = $left_filter->{args} || [];
1548
1549
    if ($join eq '|') {
1550
        push(@$args, @{$right_filter->{args}});
1551
1552
    } else {
1553
        # find the intersect values
1554
        my %new_vals;
1555
        map { $new_vals{$_} = 1 } @{$right_filter->{args} || []};
1556
        $args = [ grep { $new_vals{$_} } @$args ];
1557
    }
1558
1559
    $left_filter->{args} = $args;
1560
    return $left_filter;
1561
}
1562
1563
sub collapse_filters {
1564
    my $self = shift;
1565
    my $name = shift;
1566
1567
    # start by merging any filters at this level.
1568
    # like-level filters are always ORed together
1569
1570
    my $cur_filter;
1571
    my @cur_filters = grep {$_->name eq $name } @{ $self->filters };
1572
    if (@cur_filters) {
1573
        $cur_filter = shift @cur_filters;
1574
        my $args = $cur_filter->{args} || [];
1575
        $cur_filter = _merge_filters($cur_filter, $_, '|') for @cur_filters;
1576
    }
1577
1578
    # next gather the collapsed filters from sub-plans and
1579
    # merge them with our own
1580
1581
    my @subquery = @{$self->{query}};
1582
1583
    while (@subquery) {
1584
        my $blob = shift @subquery;
1585
        shift @subquery; # joiner
1586
        next unless $blob->isa('OpenILS::QueryParser::query_plan');
1587
        my $sub_filter = $blob->collapse_filters($name);
1588
        $cur_filter = _merge_filters($cur_filter, $sub_filter, $self->joiner);
1589
    }
1590
1591
    if ($self->QueryParser->debug) {
1592
        my @args = ($cur_filter and $cur_filter->{args}) ? @{$cur_filter->{args}} : ();
1593
        warn "collapse_filters($name) => [@args]\n";
1594
    }
1595
1596
    return $cur_filter;
1597
}
1598
1599
sub find_filter {
1600
    my $self = shift;
1601
    my $needle = shift;;
1602
    return unless ($needle);
1603
1604
    my $filter = $self->collapse_filters($needle);
1605
1606
    warn "find_filter($needle) => " .
1607
        (($filter and $filter->{args}) ? "@{$filter->{args}}" : '[]') . "\n"
1608
        if $self->QueryParser->debug;
1609
1610
    return $filter ? ($filter) : ();
1611
}
1612
1613
sub find_modifier {
1614
    my $self = shift;
1615
    my $needle = shift;;
1616
    return unless ($needle);
1617
    return grep { $_->name eq $needle } @{ $self->modifiers };
1618
}
1619
1620
sub new_modifier {
1621
    my $self = shift;
1622
    my $pkg = ref($self) || $self;
1623
    my $name = shift;
1624
1625
    my $node = do{$pkg.'::modifier'}->new( $name );
1626
    $self->add_modifier( $node );
1627
1628
    return $node;
1629
}
1630
1631
sub classed_node {
1632
    my $self = shift;
1633
    my $requested_class = shift;
1634
1635
    my $node;
1636
    for my $n (@{$self->{query}}) {
1637
        next unless (ref($n) && $n->isa( 'OpenILS::QueryParser::query_plan::node' ));
1638
        if ($n->requested_class eq $requested_class) {
1639
            $node = $n;
1640
            last;
1641
        }
1642
    }
1643
1644
    if (!$node) {
1645
        $node = $self->new_node;
1646
        $node->requested_class( $requested_class );
1647
    }
1648
1649
    return $node;
1650
}
1651
1652
sub remove_last_node {
1653
    my $self = shift;
1654
    my $requested_class = shift;
1655
1656
    my $old = pop(@{$self->query_nodes});
1657
    pop(@{$self->query_nodes}) if (@{$self->query_nodes});
1658
1659
    return $old;
1660
}
1661
1662
sub query_nodes {
1663
    my $self = shift;
1664
    return $self->{query};
1665
}
1666
1667
sub floating {
1668
    my $self = shift;
1669
    my $f = shift;
1670
    $self->{floating} = $f if (defined $f);
1671
    return $self->{floating};
1672
}
1673
1674
sub add_node {
1675
    my $self = shift;
1676
    my $node = shift;
1677
1678
    $self->{query} ||= [];
1679
    push(@{$self->{query}}, $self->joiner) if (@{$self->{query}});
1680
    push(@{$self->{query}}, $node);
1681
1682
    return $self;
1683
}
1684
1685
sub top_plan {
1686
    my $self = shift;
1687
1688
    return $self->{level} ? 0 : 1;
1689
}
1690
1691
sub plan_level {
1692
    my $self = shift;
1693
    my $level = shift;
1694
1695
    if (defined $level) {
1696
        $self->{level} = $level;
1697
        for (@{$self->query_nodes}) {
1698
            $_->plan_level($level + 1) if (ref and $_->isa('OpenILS::QueryParser::query_plan'));
1699
        }
1700
    }
1701
1702
    return $self->{level};
1703
}
1704
1705
sub joiner {
1706
    my $self = shift;
1707
    my $joiner = shift;
1708
1709
    $self->{joiner} = $joiner if ($joiner);
1710
    return $self->{joiner};
1711
}
1712
1713
sub modifiers {
1714
    my $self = shift;
1715
    $self->{modifiers} ||= [];
1716
    return $self->{modifiers};
1717
}
1718
1719
sub add_modifier {
1720
    my $self = shift;
1721
    my $modifier = shift;
1722
1723
    $self->{modifiers} ||= [];
1724
    $self->{modifiers} = [ grep {$_->name ne $modifier->name} @{$self->{modifiers}} ];
1725
1726
    push(@{$self->{modifiers}}, $modifier);
1727
1728
    return $self;
1729
}
1730
1731
sub facets {
1732
    my $self = shift;
1733
    $self->{facets} ||= [];
1734
    return $self->{facets};
1735
}
1736
1737
sub add_facet {
1738
    my $self = shift;
1739
    my $facet = shift;
1740
1741
    $self->{facets} ||= [];
1742
    $self->{facets} = [ grep {$_->name ne $facet->name} @{$self->{facets}} ];
1743
1744
    push(@{$self->{facets}}, $facet);
1745
1746
    return $self;
1747
}
1748
1749
sub filters {
1750
    my $self = shift;
1751
    $self->{filters} ||= [];
1752
    return $self->{filters};
1753
}
1754
1755
sub add_filter {
1756
    my $self = shift;
1757
    my $filter = shift;
1758
1759
    $self->{filters} ||= [];
1760
1761
    push(@{$self->{filters}}, $filter);
1762
1763
    return $self;
1764
}
1765
1766
sub negate {
1767
    my $self = shift;
1768
    my $negate = shift;
1769
1770
    $self->{negate} = $negate if (defined $negate);
1771
1772
    return $self->{negate};
1773
}
1774
1775
# %opts supports two options at this time:
1776
#   no_phrases :
1777
#       If true, do not do anything to the phrases
1778
#       fields on any discovered nodes.
1779
#   with_config :
1780
#       If true, also return the query parser config as part of the blob.
1781
#       This will get set back to 0 before recursion to avoid repetition.
1782
sub to_abstract_query {
1783
    my $self = shift;
1784
    my %opts = @_;
1785
1786
    my $pkg = ref $self->QueryParser || $self->QueryParser;
1787
1788
    my $abstract_query = {
1789
        type => "query_plan",
1790
        floating => $self->floating,
1791
        level => $self->plan_level,
1792
        filters => [map { $_->to_abstract_query } @{$self->filters}],
1793
        modifiers => [map { $_->to_abstract_query } @{$self->modifiers}],
1794
        negate => $self->negate
1795
    };
1796
1797
    if ($opts{with_config}) {
1798
        $opts{with_config} = 0;
1799
        $abstract_query->{config} = $OpenILS::QueryParser::parser_config{$pkg};
1800
    }
1801
1802
    my $kids = [];
1803
1804
    for my $qnode (@{$self->query_nodes}) {
1805
        # Remember: qnode can be a joiner string, a node, or another query_plan
1806
1807
        if (OpenILS::QueryParser::_util::is_joiner($qnode)) {
1808
            if ($abstract_query->{children}) {
1809
                my $open_joiner = (keys(%{$abstract_query->{children}}))[0];
1810
                next if $open_joiner eq $qnode;
1811
1812
                my $oldroot = $abstract_query->{children};
1813
                $kids = [$oldroot];
1814
                $abstract_query->{children} = {$qnode => $kids};
1815
            } else {
1816
                $abstract_query->{children} = {$qnode => $kids};
1817
            }
1818
        } else {
1819
            push @$kids, $qnode->to_abstract_query(%opts);
1820
        }
1821
    }
1822
1823
    $abstract_query->{children} ||= { OpenILS::QueryParser::_util::default_joiner() => $kids };
1824
    return $abstract_query;
1825
}
1826
1827
1828
#-------------------------------
1829
package OpenILS::QueryParser::query_plan::node;
1830
use Data::Dumper;
1831
$Data::Dumper::Indent = 0;
1832
1833
sub new {
1834
    my $pkg = shift;
1835
    $pkg = ref($pkg) || $pkg;
1836
    my %args = @_;
1837
1838
    return bless \%args => $pkg;
1839
}
1840
1841
sub new_atom {
1842
    my $self = shift;
1843
    my $pkg = ref($self) || $self;
1844
    return do{$pkg.'::atom'}->new( @_ );
1845
}
1846
1847
sub requested_class { # also split into classname, fields and alias
1848
    my $self = shift;
1849
    my $class = shift;
1850
1851
    if ($class) {
1852
        my @afields;
1853
        my (undef, $alias) = split '#', $class;
1854
        if ($alias) {
1855
            $class =~ s/#[^|]+//;
1856
            ($alias, @afields) = split '\|', $alias;
1857
        }
1858
1859
        my @fields = @afields;
1860
        my ($class_part, @field_parts) = split '\|', $class;
1861
        for my $f (@field_parts) {
1862
             push(@fields, $f) unless (grep { $f eq $_ } @fields);
1863
        }
1864
1865
        $class_part ||= $class;
1866
1867
        $self->{requested_class} = $class;
1868
        $self->{alias} = $alias if $alias;
1869
        $self->{alias_fields} = \@afields if $alias;
1870
        $self->{classname} = $class_part;
1871
        $self->{fields} = \@fields;
1872
    }
1873
1874
    return $self->{requested_class};
1875
}
1876
1877
sub plan {
1878
    my $self = shift;
1879
    my $plan = shift;
1880
1881
    $self->{plan} = $plan if ($plan);
1882
    return $self->{plan};
1883
}
1884
1885
sub alias {
1886
    my $self = shift;
1887
    my $alias = shift;
1888
1889
    $self->{alias} = $alias if ($alias);
1890
    return $self->{alias};
1891
}
1892
1893
sub alias_fields {
1894
    my $self = shift;
1895
    my $alias = shift;
1896
1897
    $self->{alias_fields} = $alias if ($alias);
1898
    return $self->{alias_fields};
1899
}
1900
1901
sub classname {
1902
    my $self = shift;
1903
    my $class = shift;
1904
1905
    $self->{classname} = $class if ($class);
1906
    return $self->{classname};
1907
}
1908
1909
sub fields {
1910
    my $self = shift;
1911
    my @fields = @_;
1912
1913
    $self->{fields} ||= [];
1914
    $self->{fields} = \@fields if (@fields);
1915
    return $self->{fields};
1916
}
1917
1918
sub phrases {
1919
    my $self = shift;
1920
    my @phrases = @_;
1921
1922
    $self->{phrases} ||= [];
1923
    $self->{phrases} = \@phrases if (@phrases);
1924
    return $self->{phrases};
1925
}
1926
1927
sub add_phrase {
1928
    my $self = shift;
1929
    my $phrase = shift;
1930
1931
    push(@{$self->phrases}, $phrase);
1932
1933
    return $self;
1934
}
1935
1936
sub negate {
1937
    my $self = shift;
1938
    my $negate = shift;
1939
1940
    $self->{negate} = $negate if (defined $negate);
1941
1942
    return $self->{negate};
1943
}
1944
1945
sub query_atoms {
1946
    my $self = shift;
1947
    my @query_atoms = @_;
1948
1949
    $self->{query_atoms} ||= [];
1950
    $self->{query_atoms} = \@query_atoms if (@query_atoms);
1951
    return $self->{query_atoms};
1952
}
1953
1954
sub add_fts_atom {
1955
    my $self = shift;
1956
    my $atom = shift;
1957
1958
    if (!ref($atom)) {
1959
        my $content = $atom;
1960
        my @parts = @_;
1961
1962
        $atom = $self->new_atom( content => $content, @parts );
1963
    }
1964
1965
    push(@{$self->query_atoms}, $self->plan->joiner) if (@{$self->query_atoms});
1966
    push(@{$self->query_atoms}, $atom);
1967
1968
    return $self;
1969
}
1970
1971
sub add_dummy_atom {
1972
    my $self = shift;
1973
    my @parts = @_;
1974
1975
    my $atom = $self->new_atom( @parts, dummy => 1 );
1976
1977
    push(@{$self->query_atoms}, $self->plan->joiner) if (@{$self->query_atoms});
1978
    push(@{$self->query_atoms}, $atom);
1979
1980
    return $self;
1981
}
1982
1983
# This will find up to one occurence of @$short_list within @$long_list, and
1984
# replace it with the single atom $replacement.
1985
sub replace_phrase_in_abstract_query {
1986
    my ($self, $short_list, $long_list, $replacement) = @_;
1987
1988
    my $success = 0;
1989
    my @already = ();
1990
    my $goal = scalar @$short_list;
1991
1992
    for (my $i = 0; $i < scalar (@$long_list); $i++) {
1993
        my $right = $long_list->[$i];
1994
1995
        if (OpenILS::QueryParser::_util::compare_abstract_atoms(
1996
            $short_list->[scalar @already], $right
1997
        )) {
1998
            push @already, $i;
1999
        } elsif (scalar @already) {
2000
            @already = ();
2001
            next;
2002
        }
2003
2004
        if (scalar @already == $goal) {
2005
            splice @$long_list, $already[0], scalar(@already), $replacement;
2006
            $success = 1;
2007
            last;
2008
        }
2009
    }
2010
2011
    return $success;
2012
}
2013
2014
sub to_abstract_query {
2015
    my $self = shift;
2016
    my %opts = @_;
2017
2018
    my $pkg = ref $self->plan->QueryParser || $self->plan->QueryParser;
2019
2020
    my $abstract_query = {
2021
        "type" => "node",
2022
        "alias" => $self->alias,
2023
        "alias_fields" => $self->alias_fields,
2024
        "class" => $self->classname,
2025
        "fields" => $self->fields
2026
    };
2027
2028
    my $kids = [];
2029
2030
    for my $qatom (@{$self->query_atoms}) {
2031
        if (OpenILS::QueryParser::_util::is_joiner($qatom)) {
2032
            if ($abstract_query->{children}) {
2033
                my $open_joiner = (keys(%{$abstract_query->{children}}))[0];
2034
                next if $open_joiner eq $qatom;
2035
2036
                my $oldroot = $abstract_query->{children};
2037
                $kids = [$oldroot];
2038
                $abstract_query->{children} = {$qatom => $kids};
2039
            } else {
2040
                $abstract_query->{children} = {$qatom => $kids};
2041
            }
2042
        } else {
2043
            push @$kids, $qatom->to_abstract_query;
2044
        }
2045
    }
2046
2047
    $abstract_query->{children} ||= { OpenILS::QueryParser::_util::default_joiner() => $kids };
2048
2049
    if ($self->{phrases} and not $opts{no_phrases}) {
2050
        for my $phrase (@{$self->{phrases}}) {
2051
            # Phrases appear duplication in a real QP tree, and we don't want
2052
            # that duplication in our abstract query.  So for all our phrases,
2053
            # break them into atoms as QP would, and remove any matching
2054
            # sequences of atoms from our abstract query.
2055
2056
            my $tmp_prefix = '';
2057
            $tmp_prefix = $OpenILS::QueryParser::parser_config{$pkg}{operators}{disallowed} if ($self->{negate});
2058
2059
            my $tmptree = $self->{plan}->{QueryParser}->new(query => $tmp_prefix.'"'.$phrase.'"')->parse->parse_tree;
2060
            if ($tmptree) {
2061
                # For a well-behaved phrase, we should now have only one node
2062
                # in the $tmptree query plan, and that node should have an
2063
                # orderly list of atoms and joiners.
2064
2065
                if ($tmptree->{query} and scalar(@{$tmptree->{query}}) == 1) {
2066
                    my $tmplist;
2067
2068
                    eval {
2069
                        $tmplist = $tmptree->{query}->[0]->to_abstract_query(
2070
                            no_phrases => 1
2071
                        )->{children}->{'&'}->[0]->{children}->{'&'};
2072
                    };
2073
                    next if $@;
2074
2075
                    foreach (
2076
                        OpenILS::QueryParser::_util::find_arrays_in_abstract($abstract_query->{children})
2077
                    ) {
2078
                        last if $self->replace_phrase_in_abstract_query(
2079
                            $tmplist,
2080
                            $_,
2081
                            OpenILS::QueryParser::_util::fake_abstract_atom_from_phrase($phrase, $self->{negate}, $pkg)
2082
                        );
2083
                    }
2084
                }
2085
            }
2086
        }
2087
    }
2088
2089
    $abstract_query->{children} ||= { OpenILS::QueryParser::_util::default_joiner() => $kids };
2090
    return $abstract_query;
2091
}
2092
2093
#-------------------------------
2094
package OpenILS::QueryParser::query_plan::node::atom;
2095
2096
sub new {
2097
    my $pkg = shift;
2098
    $pkg = ref($pkg) || $pkg;
2099
    my %args = @_;
2100
2101
    return bless \%args => $pkg;
2102
}
2103
2104
sub node {
2105
    my $self = shift;
2106
    return unless (ref $self);
2107
    return $self->{node};
2108
}
2109
2110
sub content {
2111
    my $self = shift;
2112
    return unless (ref $self);
2113
    return $self->{content};
2114
}
2115
2116
sub prefix {
2117
    my $self = shift;
2118
    return unless (ref $self);
2119
    return $self->{prefix};
2120
}
2121
2122
sub suffix {
2123
    my $self = shift;
2124
    return unless (ref $self);
2125
    return $self->{suffix};
2126
}
2127
2128
sub to_abstract_query {
2129
    my ($self) = @_;
2130
2131
    return {
2132
        (map { $_ => $self->$_ } qw/prefix suffix content/),
2133
        "type" => "atom"
2134
    };
2135
}
2136
#-------------------------------
2137
package OpenILS::QueryParser::query_plan::filter;
2138
2139
sub new {
2140
    my $pkg = shift;
2141
    $pkg = ref($pkg) || $pkg;
2142
    my %args = @_;
2143
2144
    return bless \%args => $pkg;
2145
}
2146
2147
sub plan {
2148
    my $self = shift;
2149
    return $self->{plan};
2150
}
2151
2152
sub name {
2153
    my $self = shift;
2154
    return $self->{name};
2155
}
2156
2157
sub negate {
2158
    my $self = shift;
2159
    return $self->{negate};
2160
}
2161
2162
sub args {
2163
    my $self = shift;
2164
    return $self->{args};
2165
}
2166
2167
sub to_abstract_query {
2168
    my ($self) = @_;
2169
2170
    return {
2171
        map { $_ => $self->$_ } qw/name negate args/
2172
    };
2173
}
2174
2175
#-------------------------------
2176
package OpenILS::QueryParser::query_plan::facet;
2177
2178
sub new {
2179
    my $pkg = shift;
2180
    $pkg = ref($pkg) || $pkg;
2181
    my %args = @_;
2182
2183
    return bless \%args => $pkg;
2184
}
2185
2186
sub plan {
2187
    my $self = shift;
2188
    return $self->{plan};
2189
}
2190
2191
sub name {
2192
    my $self = shift;
2193
    return $self->{name};
2194
}
2195
2196
sub negate {
2197
    my $self = shift;
2198
    return $self->{negate};
2199
}
2200
2201
sub values {
2202
    my $self = shift;
2203
    return $self->{'values'};
2204
}
2205
2206
sub to_abstract_query {
2207
    my ($self) = @_;
2208
2209
    return {
2210
        (map { $_ => $self->$_ } qw/name negate values/),
2211
        "type" => "facet"
2212
    };
2213
}
2214
2215
#-------------------------------
2216
package OpenILS::QueryParser::query_plan::modifier;
2217
2218
sub new {
2219
    my $pkg = shift;
2220
    $pkg = ref($pkg) || $pkg;
2221
    my $modifier = shift;
2222
    my $negate = shift;
2223
2224
    return bless { name => $modifier, negate => $negate } => $pkg;
2225
}
2226
2227
sub name {
2228
    my $self = shift;
2229
    return $self->{name};
2230
}
2231
2232
sub negate {
2233
    my $self = shift;
2234
    return $self->{negate};
2235
}
2236
2237
sub to_abstract_query {
2238
    my ($self) = @_;
2239
2240
    return $self->name;
2241
}
2242
1;
(-)a/about.pl (-29 lines)
Lines 230-264 if ( ! C4::Context->config('tmp_path') ) { Link Here
230
    }
230
    }
231
}
231
}
232
232
233
# Test QueryParser configuration sanity
234
if ( C4::Context->preference( 'UseQueryParser' ) ) {
235
    # Get the QueryParser configuration file name
236
    my $queryparser_file          = C4::Context->config( 'queryparser_config' );
237
    my $queryparser_fallback_file = '/etc/koha/searchengine/queryparser.yaml';
238
    # Check QueryParser is functional
239
    my $QParser = C4::Context->queryparser();
240
    my $queryparser_error = {};
241
    if ( ! defined $QParser || ref($QParser) ne 'Koha::QueryParser::Driver::PQF' ) {
242
        # Error initializing the QueryParser object
243
        # Get the used queryparser.yaml file path to report the user
244
        $queryparser_error->{ fallback } = ( defined $queryparser_file ) ? 0 : 1;
245
        $queryparser_error->{ file }     = ( defined $queryparser_file )
246
                                                ? $queryparser_file
247
                                                : $queryparser_fallback_file;
248
        # Report error data to the template
249
        $template->param( QueryParserError => $queryparser_error );
250
    } else {
251
        # Check for an absent queryparser_config entry in koha-conf.xml
252
        if ( ! defined $queryparser_file ) {
253
            # Not an error but a warning for the missing entry in koha-conf-xml
254
            push @xml_config_warnings, {
255
                    error => 'queryparser_entry_missing',
256
                    file  => $queryparser_fallback_file
257
            };
258
        }
259
    }
260
}
261
262
# Test Zebra facets configuration
233
# Test Zebra facets configuration
263
if ( !defined C4::Context->config('use_zebra_facets') ) {
234
if ( !defined C4::Context->config('use_zebra_facets') ) {
264
    push @xml_config_warnings, { error => 'use_zebra_facets_entry_missing' };
235
    push @xml_config_warnings, { error => 'use_zebra_facets_entry_missing' };
(-)a/cataloguing/addbooks.pl (-9 / +2 lines)
Lines 68-88 if ($query) { Link Here
68
    # build query
68
    # build query
69
    my @operands = $query;
69
    my @operands = $query;
70
70
71
    my $QParser;
72
    $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser'));
73
    my $builtquery;
71
    my $builtquery;
74
    my $query_cgi;
72
    my $query_cgi;
75
    my $builder = Koha::SearchEngine::QueryBuilder->new(
73
    my $builder = Koha::SearchEngine::QueryBuilder->new(
76
        { index => $Koha::SearchEngine::BIBLIOS_INDEX } );
74
        { index => $Koha::SearchEngine::BIBLIOS_INDEX } );
77
    my $searcher = Koha::SearchEngine::Search->new(
75
    my $searcher = Koha::SearchEngine::Search->new(
78
        { index => $Koha::SearchEngine::BIBLIOS_INDEX } );
76
        { index => $Koha::SearchEngine::BIBLIOS_INDEX } );
79
    if ($QParser) {
77
    ( undef, $builtquery, undef, $query_cgi, undef, undef, undef, undef, undef, undef ) =
80
        $builtquery = $query;
78
      $builder->build_query_compat( undef, \@operands, undef, undef, undef, 0, $lang );
81
        $query_cgi = "q=".uri_escape_utf8($query);
82
    } else {
83
        ( undef, $builtquery, undef, $query_cgi, undef, undef, undef, undef, undef, undef ) =
84
          $builder->build_query_compat( undef, \@operands, undef, undef, undef, 0, $lang );
85
    }
86
79
87
    $template->param( search_query => $builtquery ) if C4::Context->preference('DumpSearchQueryTemplate');
80
    $template->param( search_query => $builtquery ) if C4::Context->preference('DumpSearchQueryTemplate');
88
81
(-)a/cataloguing/value_builder/marc21_linking_section.pl (-8 / +1 lines)
Lines 172-186 my $launcher = sub { Link Here
172
        my $startfrom      = $query->param('startfrom');
172
        my $startfrom      = $query->param('startfrom');
173
        my $resultsperpage = $query->param('resultsperpage') || 20;
173
        my $resultsperpage = $query->param('resultsperpage') || 20;
174
        my $orderby;
174
        my $orderby;
175
        my $QParser;
175
        my $op = 'and';
176
        $QParser = C4::Context->queryparser if ( C4::Context->preference('UseQueryParser') );
177
        my $op;
178
176
179
        if ($QParser) {
180
            $op = '&&';
181
        } else {
182
            $op = 'and';
183
        }
184
        my $searcher = Koha::SearchEngine::Search->new(
177
        my $searcher = Koha::SearchEngine::Search->new(
185
            { index => $Koha::SearchEngine::BIBLIOS_INDEX } );
178
            { index => $Koha::SearchEngine::BIBLIOS_INDEX } );
186
        $search = 'kw:' . $search . " $op mc-itemtype:" . $itype if $itype;
179
        $search = 'kw:' . $search . " $op mc-itemtype:" . $itype if $itype;
(-)a/cataloguing/value_builder/unimarc_field_4XX.pl (-8 / +1 lines)
Lines 359-372 sub plugin { Link Here
359
        my $startfrom      = $query->param('startfrom');
359
        my $startfrom      = $query->param('startfrom');
360
        my $resultsperpage = $query->param('resultsperpage') || 20;
360
        my $resultsperpage = $query->param('resultsperpage') || 20;
361
        my $orderby;
361
        my $orderby;
362
        my $QParser;
362
        my $op = 'and';
363
        $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser'));
364
        my $op;
365
        if ($QParser) {
366
            $op = '&&';
367
        } else {
368
            $op = 'and';
369
        }
370
        $search = 'kw:'.$search." $op mc-itemtype:".$itype if $itype;
363
        $search = 'kw:'.$search." $op mc-itemtype:".$itype if $itype;
371
        my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
364
        my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
372
        my ( $errors, $results, $total_hits ) = $searcher->simple_search_compat($search, $startfrom * $resultsperpage, $resultsperpage );
365
        my ( $errors, $results, $total_hits ) = $searcher->simple_search_compat($search, $startfrom * $resultsperpage, $resultsperpage );
(-)a/debian/templates/koha-conf-site.xml.in (-1 lines)
Lines 304-310 __END_SRU_PUBLICSERVER__ Link Here
304
 <zebra_lockdir>/var/lock/koha/__KOHASITE__</zebra_lockdir>
304
 <zebra_lockdir>/var/lock/koha/__KOHASITE__</zebra_lockdir>
305
 <use_zebra_facets>1</use_zebra_facets>
305
 <use_zebra_facets>1</use_zebra_facets>
306
 <zebra_max_record_size>1024</zebra_max_record_size>
306
 <zebra_max_record_size>1024</zebra_max_record_size>
307
 <queryparser_config>/etc/koha/searchengine/queryparser.yaml</queryparser_config>
308
 <log4perl_conf>__KOHA_CONF_DIR__/log4perl.conf</log4perl_conf>
307
 <log4perl_conf>__KOHA_CONF_DIR__/log4perl.conf</log4perl_conf>
309
 <!-- Uncomment/edit next setting if you want to adjust zebra log levels.
308
 <!-- Uncomment/edit next setting if you want to adjust zebra log levels.
310
      Default is: none,fatal,warn.
309
      Default is: none,fatal,warn.
(-)a/etc/koha-conf.xml (-1 lines)
Lines 121-127 __PAZPAR2_TOGGLE_XML_POST__ Link Here
121
 <zebra_lockdir>__ZEBRA_LOCK_DIR__</zebra_lockdir>
121
 <zebra_lockdir>__ZEBRA_LOCK_DIR__</zebra_lockdir>
122
 <use_zebra_facets>1</use_zebra_facets>
122
 <use_zebra_facets>1</use_zebra_facets>
123
 <zebra_max_record_size>1024</zebra_max_record_size>
123
 <zebra_max_record_size>1024</zebra_max_record_size>
124
 <queryparser_config>__KOHA_CONF_DIR__/searchengine/queryparser.yaml</queryparser_config>
125
 <log4perl_conf>__KOHA_CONF_DIR__/log4perl.conf</log4perl_conf>
124
 <log4perl_conf>__KOHA_CONF_DIR__/log4perl.conf</log4perl_conf>
126
 <!-- Uncomment/edit next setting if you want to adjust zebra log levels.
125
 <!-- Uncomment/edit next setting if you want to adjust zebra log levels.
127
      Default is: none,fatal,warn.
126
      Default is: none,fatal,warn.
(-)a/etc/searchengine/queryparser.yaml (-1647 lines)
Lines 1-1647 Link Here
1
---
2
field_mappings:
3
  author:
4
    author:
5
      aliases:
6
        - au
7
      bib1_mapping:
8
        biblioserver:
9
          1: 1003
10
      enabled: 1
11
      index: ''
12
      label: ''
13
    conference:
14
      aliases:
15
        - conference
16
        - cfn
17
      bib1_mapping:
18
        biblioserver:
19
          1: 1006
20
      enabled: 1
21
      index: conference
22
      label: Conference
23
    corporate:
24
      aliases:
25
        - corporate
26
        - cpn
27
      bib1_mapping:
28
        biblioserver:
29
          1: 1005
30
      enabled: 1
31
      index: corporate
32
      label: Corporate
33
    exactauthor:
34
      aliases:
35
        - exactauthor
36
        - au,ext
37
      bib1_mapping:
38
        biblioserver:
39
          1: 1003
40
          4: 1
41
          6: 3
42
      enabled: 1
43
      index: exactauthor
44
      label: Exactauthor
45
    personal:
46
      aliases:
47
        - personal
48
      bib1_mapping:
49
        biblioserver:
50
          1: 1004
51
      enabled: 1
52
      index: personal
53
      label: Personal
54
  keyword:
55
    keyword:
56
      aliases:
57
        - any
58
        - kw
59
      bib1_mapping:
60
        authorityserver:
61
          1: Any
62
        biblioserver:
63
          1: 1016
64
      enabled: 1
65
      index: 'keyword'
66
      label: 'Keyword'
67
    abstract:
68
      aliases:
69
        - abstract
70
        - ab
71
      bib1_mapping:
72
        biblioserver:
73
          1: 62
74
      enabled: 1
75
      index: abstract
76
      label: Abstract
77
    acqsource:
78
      aliases:
79
        - acqsource
80
      bib1_mapping:
81
        biblioserver:
82
          1: 8015
83
      enabled: 1
84
      index: acqsource
85
      label: Acqsource
86
    alwaysmatch:
87
      aliases:
88
        - alwaysmatch
89
        - all
90
      bib1_mapping:
91
        authorityserver:
92
          1: _ALLRECORDS
93
          2: 103
94
        biblioserver:
95
          1: _ALLRECORDS
96
          2: 103
97
      enabled: 1
98
      index: alwaysmatch
99
      label: Alwaysmatch
100
    arl:
101
      aliases:
102
        - arl
103
      bib1_mapping:
104
        biblioserver:
105
          1: 9904 r=r
106
      enabled: 1
107
      index: arl
108
      label: Arl
109
    arp:
110
      aliases:
111
        - arp
112
      bib1_mapping:
113
        biblioserver:
114
          1: 9013 r=r
115
      enabled: 1
116
      index: arp
117
      label: Arp
118
    audience:
119
      aliases:
120
        - audience
121
        - aud
122
      bib1_mapping:
123
        biblioserver:
124
          1: 8822
125
      enabled: 1
126
      index: audience
127
      label: Audience
128
    authtype:
129
      aliases:
130
        - authtype
131
        - at
132
      bib1_mapping:
133
        authorityserver:
134
          1: authtype
135
          5: 100
136
      enabled: 1
137
      index: authtype
138
      label: Authtype
139
    authorext:
140
      alias: []
141
      bib1_mapping:
142
        biblioserver:
143
          1: 1003
144
          4: 1
145
          6: 2
146
      enabled: 1
147
      index: author
148
      label: Authorkw
149
    authorkw:
150
      alias: []
151
      bib1_mapping:
152
        biblioserver:
153
          1: 1003
154
      enabled: 1
155
      index: author
156
      label: Authorkw
157
    barcode:
158
      aliases:
159
        - barcode
160
        - bc
161
      bib1_mapping:
162
        biblioserver:
163
          1: 8023
164
      enabled: 1
165
      index: barcode
166
      label: Barcode
167
    bib-level:
168
      bib1_mapping:
169
        biblioserver:
170
          1: 1021
171
      enabled: 1
172
      index: bib-level
173
      aliases:
174
        - bib-level
175
      label: Bib-level
176
    biblionumber:
177
      bib1_mapping:
178
        biblioserver:
179
          1: 9002
180
          5: 100
181
      enabled: 1
182
      index: biblionumber
183
      aliases:
184
        - biblionumber
185
      label: Biblionumber
186
    biography:
187
      bib1_mapping:
188
        biblioserver:
189
          1: 8834
190
      enabled: 1
191
      index: biography
192
      aliases:
193
        - biography
194
        - bio
195
      label: Biography
196
    biography-code:
197
      bib1_mapping:
198
        biblioserver:
199
          1: Biography-code
200
      enabled: 1
201
      index: biography-code
202
      aliases:
203
        - biography-code
204
      label: Biography-code
205
    ccode:
206
      bib1_mapping:
207
        biblioserver:
208
          1: 8009
209
      enabled: 1
210
      index: ccode
211
      aliases:
212
        - ccode
213
        - mc-ccode
214
      label: Ccode
215
    classification-source:
216
      bib1_mapping:
217
        biblioserver:
218
          1: 8003
219
      enabled: 1
220
      index: classification-source
221
      aliases:
222
        - classification-source
223
      label: Classification-source
224
    cn-bib-sort:
225
      bib1_mapping:
226
        biblioserver:
227
          1: 9005
228
      enabled: 1
229
      index: cn-bib-sort
230
      aliases:
231
        - cn-bib-sort
232
      label: Cn-bib-sort
233
    cn-bib-source:
234
      bib1_mapping:
235
        biblioserver:
236
          1: 9004
237
      enabled: 1
238
      index: cn-bib-source
239
      aliases:
240
        - cn-bib-source
241
      label: Cn-bib-source
242
    cn-class:
243
      bib1_mapping:
244
        biblioserver:
245
          1: 9007
246
      enabled: 1
247
      index: cn-class
248
      aliases:
249
        - cn-class
250
      label: Cn-class
251
    cn-item:
252
      bib1_mapping:
253
        biblioserver:
254
          1: 9008
255
      enabled: 1
256
      index: cn-item
257
      aliases:
258
        - cn-item
259
      label: Cn-item
260
    cn-prefix:
261
      bib1_mapping:
262
        biblioserver:
263
          1: 9009
264
      enabled: 1
265
      index: cn-prefix
266
      aliases:
267
        - cn-prefix
268
      label: Cn-prefix
269
    cn-sort:
270
      bib1_mapping:
271
        biblioserver:
272
          1: 8007
273
      enabled: 1
274
      index: cn-sort
275
      aliases:
276
        - cn-sort
277
      label: Cn-sort
278
    cn-suffix:
279
      bib1_mapping:
280
        biblioserver:
281
          1: 9010
282
      enabled: 1
283
      index: cn-suffix
284
      aliases:
285
        - cn-suffix
286
      label: Cn-suffix
287
    code-institution:
288
      bib1_mapping:
289
        biblioserver:
290
          1: 56
291
      enabled: 1
292
      index: code-institution
293
      aliases:
294
        - code-institution
295
      label: Code-institution
296
    coded-location-qualifier:
297
      bib1_mapping:
298
        biblioserver:
299
          1: 8016
300
      enabled: 1
301
      index: coded-location-qualifier
302
      aliases:
303
        - coded-location-qualifier
304
      label: Coded-location-qualifier
305
    content-type:
306
      bib1_mapping:
307
        biblioserver:
308
          1: 1034
309
      enabled: 1
310
      index: content-type
311
      aliases:
312
        - content-type
313
        - ctype
314
      label: Content-type
315
    control-number:
316
      bib1_mapping:
317
        biblioserver:
318
          1: 9001
319
      enabled: 1
320
      index: control-number
321
      aliases:
322
        - control-number
323
      label: Control-number
324
    copynumber:
325
      bib1_mapping:
326
        biblioserver:
327
          1: 8027
328
      enabled: 1
329
      index: copynumber
330
      aliases:
331
        - copynumber
332
      label: Copynumber
333
    curriculum:
334
      bib1_mapping:
335
        biblioserver:
336
          1: 9658
337
      enabled: 1
338
      index: curriculum
339
      aliases:
340
        - curriculum
341
      label: Curriculum
342
    damaged:
343
      bib1_mapping:
344
        biblioserver:
345
          1: 8005
346
      enabled: 1
347
      index: damaged
348
      aliases:
349
        - damaged
350
      label: Damaged
351
    date-entered-on-file:
352
      bib1_mapping:
353
        biblioserver:
354
          1: date-entered-on-file
355
      enabled: 1
356
      index: date-entered-on-file
357
      aliases:
358
        - date-entered-on-file
359
      label: Date-entered-on-file
360
    datelastborrowed:
361
      bib1_mapping:
362
        biblioserver:
363
          1: 8026
364
      enabled: 1
365
      index: datelastborrowed
366
      aliases:
367
        - datelastborrowed
368
      label: Datelastborrowed
369
    datelastseen:
370
      bib1_mapping:
371
        biblioserver:
372
          1: 8025
373
      enabled: 1
374
      index: datelastseen
375
      aliases:
376
        - datelastseen
377
      label: Datelastseen
378
    dissertation-information:
379
      bib1_mapping:
380
        biblioserver:
381
          1: 1056
382
      enabled: 1
383
      index: dissertation-information
384
      aliases:
385
        - dissertation-information
386
      label: Dissertation-information
387
    dt-bks:
388
      bib1_mapping:
389
        biblioserver:
390
          1: 8700
391
      enabled: 1
392
      index: dt-bks
393
      aliases:
394
        - dt-bks
395
      label: Dt-bks
396
    dt-cf:
397
      bib1_mapping:
398
        biblioserver:
399
          1: 8700
400
      enabled: 1
401
      index: dt-cf
402
      aliases:
403
        - dt-cf
404
      label: Dt-cf
405
    dt-map:
406
      bib1_mapping:
407
        biblioserver:
408
          1: 8700
409
      enabled: 1
410
      index: dt-map
411
      aliases:
412
        - dt-map
413
      label: Dt-map
414
    dt-sr:
415
      bib1_mapping:
416
        biblioserver:
417
          1: 8700
418
      enabled: 1
419
      index: dt-sr
420
      aliases:
421
        - dt-sr
422
      label: Dt-sr
423
    dt-vis:
424
      bib1_mapping:
425
        biblioserver:
426
          1: 8700
427
      enabled: 1
428
      index: dt-vis
429
      aliases:
430
        - dt-vis
431
      label: Dt-vis
432
    ean:
433
      bib1_mapping:
434
        biblioserver:
435
          1: 1214
436
      enabled: 1
437
      index: ean
438
      aliases:
439
        - ean
440
      label: Ean
441
    extent:
442
      bib1_mapping:
443
        biblioserver:
444
          1: Extent
445
      enabled: 1
446
      index: extent
447
      aliases:
448
        - extent
449
      label: Extent
450
    ff8-22:
451
      bib1_mapping:
452
        biblioserver:
453
          1: 8822
454
      enabled: 1
455
      index: ff8-22
456
      aliases:
457
        - ff8-22
458
      label: Ff8-22
459
    ff8-23:
460
      bib1_mapping:
461
        biblioserver:
462
          1: 8823
463
      enabled: 1
464
      index: ff8-23
465
      aliases:
466
        - ff8-23
467
      label: Ff8-23
468
    ff8-34:
469
      bib1_mapping:
470
        biblioserver:
471
          1: 8834
472
      enabled: 1
473
      index: ff8-34
474
      aliases:
475
        - ff8-34
476
      label: Ff8-34
477
    fiction:
478
      bib1_mapping:
479
        biblioserver:
480
          1: 8833
481
      enabled: 1
482
      index: fiction
483
      aliases:
484
        - fiction
485
        - fic
486
      label: Fiction
487
    format:
488
      bib1_mapping:
489
        biblioserver:
490
          1: 8823
491
      enabled: 1
492
      index: format
493
      aliases:
494
        - format
495
      label: Format
496
    graphics-support:
497
      bib1_mapping:
498
        biblioserver:
499
          1: Graphic-support
500
      enabled: 1
501
      index: graphics-support
502
      aliases:
503
        - graphics-support
504
      label: Graphics-support
505
    graphics-type:
506
      bib1_mapping:
507
        biblioserver:
508
          1: Graphic-type
509
      enabled: 1
510
      index: graphics-type
511
      aliases:
512
        - graphics-type
513
      label: Graphics-type
514
    holdingbranch:
515
      bib1_mapping:
516
        biblioserver:
517
          1: 8012
518
      enabled: 1
519
      index: holdingbranch
520
      aliases:
521
        - holdingbranch
522
      label: Holdingbranch
523
    homebranch:
524
      bib1_mapping:
525
        biblioserver:
526
          1: homebranch
527
      enabled: 1
528
      index: homebranch
529
      aliases:
530
        - homebranch
531
        - branch
532
      label: Homebranch
533
    host-item:
534
      bib1_mapping:
535
        biblioserver:
536
          1: 1033
537
      enabled: 1
538
      index: host-item
539
      aliases:
540
        - host-item
541
      label: Host-item
542
    host-item-number:
543
      bib1_mapping:
544
        biblioserver:
545
          1: 8911
546
      enabled: 1
547
      index: host-item-number
548
      aliases:
549
        - host-item-number
550
        - hi
551
      label: Host-item-number
552
    id-other:
553
      bib1_mapping:
554
        biblioserver:
555
          1: 9012
556
      enabled: 1
557
      index: id-other
558
      aliases:
559
        - id-other
560
      label: Id-other
561
    identifier-standard:
562
      bib1_mapping:
563
        biblioserver:
564
          1: 1007
565
      enabled: 1
566
      index: identifier-standard
567
      aliases:
568
        - identifier-standard
569
        - ident
570
      label: Identifier-standard
571
    illustration-code:
572
      bib1_mapping:
573
        biblioserver:
574
          1: "Illustration-code "
575
      enabled: 1
576
      index: illustration-code
577
      aliases:
578
        - illustration-code
579
      label: Illustration-code
580
    isbn:
581
      bib1_mapping:
582
        biblioserver:
583
          1: 7
584
      enabled: 1
585
      index: isbn
586
      aliases:
587
        - isbn
588
        - nb
589
      label: Isbn
590
    issn:
591
      bib1_mapping:
592
        biblioserver:
593
          1: 8
594
      enabled: 1
595
      index: issn
596
      aliases:
597
        - issn
598
        - ns
599
      label: Issn
600
    issues:
601
      bib1_mapping:
602
        biblioserver:
603
          1: 8019
604
      enabled: 1
605
      index: issues
606
      aliases:
607
        - issues
608
      label: Issues
609
    item:
610
      bib1_mapping:
611
        biblioserver:
612
          1: 9520
613
      enabled: 1
614
      index: item
615
      aliases:
616
        - item
617
      label: Item
618
    itemnumber:
619
      bib1_mapping:
620
        biblioserver:
621
          1: 8010
622
      enabled: 1
623
      index: itemnumber
624
      aliases:
625
        - itemnumber
626
      label: Itemnumber
627
    itemtype:
628
      bib1_mapping:
629
        biblioserver:
630
          1: 9006
631
      enabled: 1
632
      index: itemtype
633
      aliases:
634
        - itemtype
635
        - mc-itemtype
636
      label: Itemtype
637
    itype:
638
      bib1_mapping:
639
        biblioserver:
640
          1: 8031
641
      enabled: 1
642
      index: itype
643
      aliases:
644
        - itype
645
        - mc-itype
646
      label: Itype
647
    l-format:
648
      bib1_mapping:
649
        biblioserver:
650
          1: 8703
651
      enabled: 1
652
      index: l-format
653
      aliases:
654
        - l-format
655
      label: L-format
656
    language:
657
      bib1_mapping:
658
        biblioserver:
659
          1: 54
660
      enabled: 1
661
      index: language
662
      aliases:
663
        - language
664
        - ln
665
      label: Language
666
    language-original:
667
      bib1_mapping:
668
        biblioserver:
669
          1: 1095
670
      enabled: 1
671
      index: language-original
672
      aliases:
673
        - language-original
674
      label: Language of original
675
    ln-audio:
676
      bib1_mapping:
677
        biblioserver:
678
          1: 9906
679
      enabled: 1
680
      index: ln-audio
681
      aliases:
682
        - ln-audio
683
      label: Language code of text/sound track or separate title
684
    ln-subtitle:
685
      bib1_mapping:
686
        biblioserver:
687
          1: 9907
688
      enabled: 1
689
      index: ln-subtitle
690
      aliases:
691
        - ln-subtitle
692
      label: Language of subtitles
693
    lc-card-number:
694
      bib1_mapping:
695
        biblioserver:
696
          1: 9
697
      enabled: 1
698
      index: lc-card-number
699
      aliases:
700
        - lc-card-number
701
        - lc-card
702
      label: Lc-card-number
703
    literature-code:
704
      bib1_mapping:
705
        biblioserver:
706
          1: Literature-Code
707
      enabled: 1
708
      index: literature-code
709
      aliases:
710
        - literature-code
711
      label: Literature-code
712
    llength:
713
      bib1_mapping:
714
        biblioserver:
715
          1: llength
716
      enabled: 1
717
      index: llength
718
      aliases:
719
        - llength
720
      label: Llength
721
    local-classification:
722
      bib1_mapping:
723
        biblioserver:
724
          1: 8022
725
      enabled: 1
726
      index: local-classification
727
      aliases:
728
        - local-classification
729
        - lcn
730
        - callnum
731
      label: Local-classification
732
    local-number:
733
      bib1_mapping:
734
        biblioserver:
735
          1: 12
736
      enabled: 1
737
      index: local-number
738
      aliases:
739
        - local-number
740
        - sn
741
      label: Local-number
742
    location:
743
      bib1_mapping:
744
        biblioserver:
745
          1: 8013
746
      enabled: 1
747
      index: location
748
      aliases:
749
        - location
750
        - mc-loc
751
      label: Location
752
    lost:
753
      bib1_mapping:
754
        biblioserver:
755
          1: 8002
756
      enabled: 1
757
      index: lost
758
      aliases:
759
        - lost
760
      label: Lost
761
    match:
762
      bib1_mapping:
763
        authorityserver:
764
          1: Match
765
      enabled: 1
766
      index: match
767
      aliases:
768
        - match
769
      label: Match
770
    material-type:
771
      bib1_mapping:
772
        biblioserver:
773
          1: Material-type
774
      enabled: 1
775
      index: material-type
776
      aliases:
777
        - material-type
778
      label: Material-type
779
    materials-specified:
780
      bib1_mapping:
781
        biblioserver:
782
          1: 8004
783
      enabled: 1
784
    lc-card-number:
785
      bib1_mapping:
786
        biblioserver:
787
          1: 9
788
      enabled: 1
789
      index: lc-card-number
790
      aliases:
791
        - lc-card-number
792
        - lc-card
793
      label: Lc-card-number
794
    lex:
795
      bib1_mapping:
796
        biblioserver:
797
          1: 9903
798
      enabled: 1
799
      index: lex
800
      aliases:
801
        - lex
802
      label: Lexile
803
    literature-code:
804
      bib1_mapping:
805
        biblioserver:
806
          1: Literature-Code
807
      enabled: 1
808
      index: literature-code
809
      aliases:
810
        - literature-code
811
      label: Literature-code
812
    llength:
813
      bib1_mapping:
814
        biblioserver:
815
          1: llength
816
      enabled: 1
817
      index: llength
818
      aliases:
819
        - llength
820
      label: Llength
821
    local-classification:
822
      bib1_mapping:
823
        biblioserver:
824
          1: 8022
825
      enabled: 1
826
      index: local-classification
827
      aliases:
828
        - local-classification
829
        - lcn
830
        - callnum
831
      label: Local-classification
832
    local-number:
833
      bib1_mapping:
834
        biblioserver:
835
          1: 12
836
      enabled: 1
837
      index: local-number
838
      aliases:
839
        - local-number
840
        - sn
841
      label: Local-number
842
    location:
843
      bib1_mapping:
844
        biblioserver:
845
          1: 8013
846
      enabled: 1
847
      index: location
848
      aliases:
849
        - location
850
        - mc-loc
851
      label: Location
852
    lost:
853
      bib1_mapping:
854
        biblioserver:
855
          1: 8002
856
      enabled: 1
857
      index: lost
858
      aliases:
859
        - lost
860
      label: Lost
861
    match:
862
      bib1_mapping:
863
        authorityserver:
864
          1: Match
865
      enabled: 1
866
      index: match
867
      aliases:
868
        - match
869
      label: Match
870
    material-type:
871
      bib1_mapping:
872
        biblioserver:
873
          1: Material-type
874
      enabled: 1
875
      index: material-type
876
      aliases:
877
        - material-type
878
      label: Material-type
879
    materials-specified:
880
      bib1_mapping:
881
        biblioserver:
882
          1: 8004
883
      enabled: 1
884
      index: materials-specified
885
      aliases:
886
        - materials-specified
887
      label: Materials-specified
888
    music:
889
      bib1_mapping:
890
        biblioserver:
891
          1: Music-number
892
      enabled: 1
893
      index: music
894
      aliases:
895
        - music
896
      label: Music
897
    name:
898
      bib1_mapping:
899
        biblioserver:
900
          1: 1002
901
      enabled: 1
902
      index: name
903
      aliases:
904
        - name
905
      label: Name
906
    note:
907
      bib1_mapping:
908
        biblioserver:
909
          1: 63
910
      enabled: 1
911
      index: note
912
      aliases:
913
        - note
914
        - nt
915
      label: Note
916
    notforloan:
917
      bib1_mapping:
918
        biblioserver:
919
          1: 8008
920
          4: 109
921
      enabled: 1
922
      index: notforloan
923
      aliases:
924
        - notforloan
925
      label: Notforloan
926
    onloan:
927
      bib1_mapping:
928
        biblioserver:
929
          1: 8024
930
      enabled: 1
931
      index: onloan
932
      aliases:
933
        - onloan
934
      label: Onloan
935
    personal-name:
936
      bib1_mapping:
937
        biblioserver:
938
          1: Personal-name
939
      enabled: 1
940
      index: personal-name
941
      aliases:
942
        - personal-name
943
        - pn
944
      label: Personal-name
945
    place-publication:
946
      bib1_mapping:
947
        biblioserver:
948
          1: 59
949
      enabled: 1
950
      index: place-publication
951
      aliases:
952
        - place-publication
953
        - pl
954
      label: Place-publication
955
    popularity:
956
      bib1_mapping:
957
        biblioserver:
958
          1: issues
959
      enabled: 1
960
      index: popularity
961
      aliases:
962
        - popularity
963
      label: Popularity
964
    price:
965
      bib1_mapping:
966
        biblioserver:
967
          1: 8017
968
      enabled: 1
969
      index: price
970
      aliases:
971
        - price
972
      label: Price
973
    publisher:
974
      bib1_mapping:
975
        biblioserver:
976
          1: 1018
977
      enabled: 1
978
      index: publisher
979
      aliases:
980
        - publisher
981
        - pb
982
      label: Publisher
983
    record-control-number:
984
      bib1_mapping:
985
        biblioserver:
986
          1: 1045
987
      enabled: 1
988
      index: record-control-number
989
      aliases:
990
        - record-control-number
991
        - rcn
992
      label: Record-control-number
993
    record-type:
994
      bib1_mapping:
995
        biblioserver:
996
          1: 1001
997
      enabled: 1
998
      index: record-type
999
      aliases:
1000
        - record-type
1001
        - rtype
1002
        - mc-rtype
1003
        - mus
1004
      label: Record-type
1005
    regularity-code:
1006
      bib1_mapping:
1007
        biblioserver:
1008
          1: Regularity-code
1009
      enabled: 1
1010
      index: regularity-code
1011
      aliases:
1012
        - regularity-code
1013
      label: Regularity-code
1014
    renewals:
1015
      bib1_mapping:
1016
        biblioserver:
1017
          1: 8020
1018
      enabled: 1
1019
      index: renewals
1020
      aliases:
1021
        - renewals
1022
      label: Renewals
1023
    replacementprice:
1024
      bib1_mapping:
1025
        biblioserver:
1026
          1: 8029
1027
      enabled: 1
1028
      index: replacementprice
1029
      aliases:
1030
        - replacementprice
1031
      label: Replacementprice
1032
    replacementpricedate:
1033
      bib1_mapping:
1034
        biblioserver:
1035
          1: 8030
1036
      enabled: 1
1037
      index: replacementpricedate
1038
      aliases:
1039
        - replacementpricedate
1040
      label: Replacementpricedate
1041
    reserves:
1042
      bib1_mapping:
1043
        biblioserver:
1044
          1: 8021
1045
      enabled: 1
1046
      index: reserves
1047
      aliases:
1048
        - reserves
1049
      label: Reserves
1050
    restricted:
1051
      bib1_mapping:
1052
        biblioserver:
1053
          1: 8006
1054
      enabled: 1
1055
      index: restricted
1056
      aliases:
1057
        - restricted
1058
      label: Restricted
1059
    stack:
1060
      bib1_mapping:
1061
        biblioserver:
1062
          1: 8018
1063
      enabled: 1
1064
      index: stack
1065
      aliases:
1066
        - stack
1067
      label: Stack
1068
    stock-number:
1069
      bib1_mapping:
1070
        biblioserver:
1071
          1: 1028
1072
      enabled: 1
1073
      index: stock-number
1074
      aliases:
1075
        - stock-number
1076
      label: Stock-number
1077
    number-local-acquisition:
1078
      bib1_mapping:
1079
        biblioserver:
1080
          1: 1062
1081
      enabled: 1
1082
      index: number-local-acquisition
1083
      aliases:
1084
        - number-local-acquisition
1085
        - stocknumber
1086
        - inv
1087
      label: Number-local-acquisition
1088
    summary:
1089
      bib1_mapping:
1090
        biblioserver:
1091
          1: Summary
1092
      enabled: 1
1093
      index: summary
1094
      aliases:
1095
        - summary
1096
      label: Summary
1097
    suppress:
1098
      bib1_mapping:
1099
        biblioserver:
1100
          1: 9011
1101
      enabled: 1
1102
      index: suppress
1103
      aliases:
1104
        - suppress
1105
      label: Suppress
1106
    titleext:
1107
      bib1_mapping:
1108
        biblioserver:
1109
          1: 4
1110
          4: 1
1111
          6: 2
1112
      enabled: 1
1113
      index: title
1114
      aliases: []
1115
      label: Title ext
1116
    titlekw:
1117
      bib1_mapping:
1118
        biblioserver:
1119
          1: 4
1120
      enabled: 1
1121
      index: title
1122
      aliases: []
1123
      label: Title kw
1124
    thesaurus:
1125
      bib1_mapping:
1126
        authorityserver:
1127
          1: Subject-heading-thesaurus
1128
      enabled: 1
1129
      index: thesaurus
1130
      aliases:
1131
        - thesaurus
1132
      label: Thesaurus
1133
    totalissues:
1134
      bib1_mapping:
1135
        biblioserver:
1136
          1: 9003
1137
      enabled: 1
1138
      index: totalissues
1139
      aliases:
1140
        - totalissues
1141
      label: Totalissues
1142
    type-of-serial:
1143
      bib1_mapping:
1144
        biblioserver:
1145
          1: Type-Of-Serial
1146
      enabled: 1
1147
      index: type-of-serial
1148
      aliases:
1149
        - type-of-serial
1150
      label: Type-of-serial
1151
    upc:
1152
      bib1_mapping:
1153
        biblioserver:
1154
          1: UPC
1155
      enabled: 1
1156
      index: upc
1157
      aliases:
1158
        - upc
1159
      label: Upc
1160
    uri:
1161
      bib1_mapping:
1162
        biblioserver:
1163
          1: 8028
1164
      enabled: 1
1165
      index: uri
1166
      aliases:
1167
        - uri
1168
      label: Uri
1169
    video-mt:
1170
      bib1_mapping:
1171
        biblioserver:
1172
          1: Video-mt
1173
      enabled: 1
1174
      index: video-mt
1175
      aliases:
1176
        - video-mt
1177
      label: Video-mt
1178
    withdrawn:
1179
      bib1_mapping:
1180
        biblioserver:
1181
          1: 8001
1182
      enabled: 1
1183
      index: withdrawn
1184
      aliases:
1185
        - withdrawn
1186
      label: Withdrawn
1187
  subject:
1188
    subject:
1189
      bib1_mapping:
1190
        authorityserver:
1191
          1: Match-heading
1192
        biblioserver:
1193
          1: 21
1194
      enabled: 1
1195
      index: ''
1196
      label: ''
1197
      aliases:
1198
        - su
1199
        - su-to
1200
        - su-geo
1201
        - su-ut
1202
    authority-number:
1203
      bib1_mapping:
1204
        biblioserver:
1205
          1: Koha-Auth-Number
1206
          3: 1
1207
          4: 1
1208
          5: 100
1209
      enabled: 1
1210
      index: authority-number
1211
      label: Authority-number
1212
      aliases:
1213
        - authority-number
1214
        - an
1215
    complete:
1216
      bib1_mapping:
1217
        biblioserver:
1218
          1: 21
1219
          3: 1
1220
          4: 1
1221
          5: 100
1222
          6: 3
1223
      enabled: 1
1224
      index: complete
1225
      label: Complete
1226
      aliases:
1227
        - complete
1228
    exact:
1229
      bib1_mapping:
1230
        authorityserver:
1231
          4: 1
1232
          5: 100
1233
          6: 3
1234
      enabled: 1
1235
      index: exact
1236
      label: Exact
1237
      aliases:
1238
        - exact
1239
    heading:
1240
      bib1_mapping:
1241
        authorityserver:
1242
          1: Heading
1243
      enabled: 1
1244
      index: heading
1245
      label: Heading
1246
      aliases:
1247
        - heading
1248
        - mainentry
1249
        - he
1250
    headingmain:
1251
      bib1_mapping:
1252
        authorityserver:
1253
          1: Heading-Main
1254
      enabled: 1
1255
      index: headingmain
1256
      label: Headingmain
1257
      aliases:
1258
        - headingmain
1259
        - mainmainentry
1260
    matchheading:
1261
      bib1_mapping:
1262
        authorityserver:
1263
          1: Match-heading
1264
          4: 1
1265
          5: 100
1266
          6: 3
1267
      enabled: 1
1268
      index: matchheading
1269
      label: Matchheading
1270
      aliases:
1271
        - matchheading
1272
        - match-heading
1273
    name-personal:
1274
      bib1_mapping:
1275
        biblioserver:
1276
          1: 1009
1277
      enabled: 1
1278
      index: name-personal
1279
      label: Name-personal
1280
      aliases:
1281
        - name-personal
1282
        - su-na
1283
    index-term-genre:
1284
      bib1_mapping:
1285
        biblioserver:
1286
          1: 9655
1287
      enabled: 1
1288
      index: index-term-genre
1289
      label: Genre term
1290
      aliases:
1291
        - index-term-genre
1292
        - itg
1293
    index-term-uncontrolled:
1294
      bib1_mapping:
1295
        biblioserver:
1296
          1: 9653
1297
      enabled: 1
1298
      index: index-term-uncontrolled
1299
      label: Uncontrolled index term
1300
      aliases:
1301
        - index-term-uncontrolled
1302
        - itu
1303
    seefrom:
1304
      bib1_mapping:
1305
        authorityserver:
1306
          1: Match-heading-see-from
1307
      enabled: 1
1308
      index: seefrom
1309
      label: Seefrom
1310
      aliases:
1311
        - seefrom
1312
        - see-from
1313
    start:
1314
      bib1_mapping:
1315
        authorityserver:
1316
          3: 2
1317
          4: 1
1318
          5: 1
1319
      enabled: 1
1320
      index: start
1321
      label: Start
1322
      aliases:
1323
        - start
1324
  title:
1325
    title:
1326
      bib1_mapping:
1327
        biblioserver:
1328
          1: 4
1329
      enabled: 1
1330
      index: ''
1331
      label: ''
1332
      aliases:
1333
        - ti
1334
    cover:
1335
      bib1_mapping:
1336
        biblioserver:
1337
          1: 36
1338
      enabled: 1
1339
      index: cover
1340
      label: Cover
1341
      aliases:
1342
        - cover
1343
        - title-cover
1344
    exacttitle:
1345
      bib1_mapping:
1346
        biblioserver:
1347
          1: 4
1348
          4: 1
1349
          6: 3
1350
      enabled: 1
1351
      index: exacttitle
1352
      label: Exacttitle
1353
      aliases:
1354
        - exacttitle
1355
        - ti,ext
1356
    series:
1357
      bib1_mapping:
1358
        biblioserver:
1359
          1: 5
1360
      enabled: 1
1361
      index: series
1362
      label: Series
1363
      aliases:
1364
        - series
1365
        - title-series
1366
        - se
1367
    uniform:
1368
      bib1_mapping:
1369
        biblioserver:
1370
          1: Title-uniform
1371
      enabled: 1
1372
      index: uniform
1373
      label: Uniform
1374
      aliases:
1375
        - uniform
1376
        - title-uniform
1377
filter_mappings:
1378
  acqdate:
1379
    bib1_mapping:
1380
      biblioserver:
1381
        1: 32
1382
        4: 4
1383
        target_syntax_callback: date_filter_target_callback
1384
    enabled: 1
1385
    label: Acqdate
1386
  copydate:
1387
    bib1_mapping:
1388
      biblioserver:
1389
        1: 30
1390
        4: 4
1391
        target_syntax_callback: date_filter_target_callback
1392
1393
    enabled: 1
1394
    label: Copydate
1395
  pubdate:
1396
    bib1_mapping:
1397
      biblioserver:
1398
        1: 31
1399
        4: 4
1400
        target_syntax_callback: date_filter_target_callback
1401
    enabled: 1
1402
    label: Pubdate
1403
modifier_mappings:
1404
  AuthidAsc:
1405
    bib1_mapping:
1406
      authorityserver:
1407
        "": 0
1408
        1: Local-Number
1409
        7: 1
1410
        op: "@or"
1411
    enabled: 1
1412
    label: AuthidAsc
1413
  AuthidDsc:
1414
    bib1_mapping:
1415
      authorityserver:
1416
        "": 0
1417
        1: Local-Number
1418
        7: 2
1419
        op: "@or"
1420
    enabled: 1
1421
    label: AuthidDsc
1422
  HeadingAsc:
1423
    bib1_mapping:
1424
      authorityserver:
1425
        "": 0
1426
        1: Heading
1427
        7: 1
1428
        op: "@or"
1429
    enabled: 1
1430
    label: HeadingAsc
1431
  HeadingDsc:
1432
    bib1_mapping:
1433
      authorityserver:
1434
        "": 0
1435
        1: Heading
1436
        7: 2
1437
        op: "@or"
1438
    enabled: 1
1439
    label: HeadingDsc
1440
  Relevance:
1441
    bib1_mapping:
1442
      authorityserver:
1443
        2: 102
1444
    enabled: 1
1445
    label: Relevance
1446
  acqdate_asc:
1447
    bib1_mapping:
1448
      biblioserver:
1449
        "": 0
1450
        1: 32
1451
        7: 2
1452
        op: "@or"
1453
    enabled: 1
1454
    label: Acqdate_asc
1455
  acqdate_dsc:
1456
    bib1_mapping:
1457
      biblioserver:
1458
        "": 0
1459
        1: 32
1460
        7: 1
1461
        op: "@or"
1462
    enabled: 1
1463
    label: Acqdate_dsc
1464
  ascending:
1465
    bib1_mapping:
1466
      biblioserver:
1467
        7: 1
1468
    enabled: 1
1469
    label: Ascending
1470
  author_asc:
1471
    bib1_mapping:
1472
      biblioserver:
1473
        "": 0
1474
        1: 1003
1475
        7: 2
1476
        op: "@or"
1477
    enabled: 1
1478
    label: Author_asc
1479
  author_az:
1480
    bib1_mapping:
1481
      biblioserver:
1482
        "": 0
1483
        1: 1003
1484
        7: 1
1485
        op: "@or"
1486
    enabled: 1
1487
    label: Author_az
1488
  author_dsc:
1489
    bib1_mapping:
1490
      biblioserver:
1491
        "": 0
1492
        1: 1003
1493
        7: 1
1494
        op: "@or"
1495
    enabled: 1
1496
    label: Author_dsc
1497
  author_za:
1498
    bib1_mapping:
1499
      biblioserver:
1500
        "": 0
1501
        1: 1003
1502
        7: 2
1503
        op: "@or"
1504
    enabled: 1
1505
    label: Author_za
1506
  call_number_asc:
1507
    bib1_mapping:
1508
      biblioserver:
1509
        "": 0
1510
        1: 8007
1511
        7: 2
1512
        op: "@or"
1513
    enabled: 1
1514
    label: Call_number_asc
1515
  call_number_dsc:
1516
    bib1_mapping:
1517
      biblioserver:
1518
        "": 0
1519
        1: 8007
1520
        7: 1
1521
        op: "@or"
1522
    enabled: 1
1523
    label: Call_number_dsc
1524
  descending:
1525
    bib1_mapping:
1526
      biblioserver:
1527
        7: 2
1528
    enabled: 1
1529
    label: Descending
1530
  popularity_asc:
1531
    bib1_mapping:
1532
      biblioserver:
1533
        "": 0
1534
        1: 9003
1535
        7: 2
1536
        op: "@or"
1537
    enabled: 1
1538
    label: Popularity_asc
1539
  popularity_dsc:
1540
    bib1_mapping:
1541
      biblioserver:
1542
        "": 0
1543
        1: 9003
1544
        7: 1
1545
        op: "@or"
1546
    enabled: 1
1547
    label: Popularity_dsc
1548
  pubdate_asc:
1549
    bib1_mapping:
1550
      biblioserver:
1551
        "": 0
1552
        1: 31
1553
        7: 2
1554
        op: "@or"
1555
    enabled: 1
1556
    label: Pubdate_asc
1557
  pubdate_dsc:
1558
    bib1_mapping:
1559
      biblioserver:
1560
        "": 0
1561
        1: 31
1562
        7: 1
1563
        op: "@or"
1564
    enabled: 1
1565
    label: Pubdate_dsc
1566
  relevance:
1567
    bib1_mapping:
1568
      biblioserver:
1569
        2: 102
1570
    enabled: 1
1571
    label: Relevance
1572
  relevance_dsc:
1573
    bib1_mapping:
1574
      biblioserver:
1575
        2: 102
1576
    enabled: 1
1577
    label: Relevance_dsc
1578
  relevance_asc:
1579
    bib1_mapping:
1580
      biblioserver:
1581
        2: 102
1582
    enabled: 1
1583
    label: Relevance_asc
1584
  title-sort-az:
1585
    bib1_mapping:
1586
      biblioserver:
1587
        "": 0
1588
        1: 36
1589
        7: 1
1590
        op: "@or"
1591
    enabled: 1
1592
    label: Title-sort-az
1593
  title-sort-za:
1594
    bib1_mapping:
1595
      biblioserver:
1596
        "": 0
1597
        1: 36
1598
        7: 2
1599
        op: "@or"
1600
    enabled: 1
1601
    label: Title-sort-za
1602
  title_asc:
1603
    bib1_mapping:
1604
      biblioserver:
1605
        "": 0
1606
        1: 4
1607
        7: 1
1608
        op: "@or"
1609
    enabled: 1
1610
    label: Title_asc
1611
  title_az:
1612
    bib1_mapping:
1613
      biblioserver:
1614
        "": 0
1615
        1: 4
1616
        7: 1
1617
        op: "@or"
1618
    enabled: 1
1619
    label: Title_az
1620
  title_dsc:
1621
    bib1_mapping:
1622
      biblioserver:
1623
        "": 0
1624
        1: 4
1625
        7: 2
1626
        op: "@or"
1627
    enabled: 1
1628
    label: Title_dsc
1629
  title_za:
1630
    bib1_mapping:
1631
      biblioserver:
1632
        "": 0
1633
        1: 4
1634
        7: 2
1635
        op: "@or"
1636
    enabled: 1
1637
    label: Title_za
1638
relevance_bumps:
1639
  keyword:
1640
    titleext:
1641
      enabled: 1
1642
      bib1_mapping:
1643
        biblioserver: 34
1644
    titlekw:
1645
      enabled: 1
1646
      bib1_mapping:
1647
        biblioserver: 20
(-)a/installer/data/mysql/atomicupdate/bug_xxxxx.perl (+9 lines)
Line 0 Link Here
1
$DBversion = 'XXX'; # will be replaced by the RM
2
if( CheckVersion( $DBversion ) ) {
3
    $dbh->do( q|
4
        DELETE FROM systempreferences WHERE variable="UseQueryParser"
5
    |);
6
7
    SetVersion( $DBversion );
8
    print "Upgrade to $DBversion done (Bug XXXXX - Remove UseQueryParser system preference)\n";
9
}
(-)a/installer/data/mysql/sysprefs.sql (-1 lines)
Lines 673-679 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
673
('UseEmailReceipts','0','','Send email receipts for payments and write-offs','YesNo'),
673
('UseEmailReceipts','0','','Send email receipts for payments and write-offs','YesNo'),
674
('UseICU','0','1','Tell Koha if ICU indexing is in use for Zebra or not.','YesNo'),
674
('UseICU','0','1','Tell Koha if ICU indexing is in use for Zebra or not.','YesNo'),
675
('UseKohaPlugins','0','','Enable or disable the ability to use Koha Plugins.','YesNo'),
675
('UseKohaPlugins','0','','Enable or disable the ability to use Koha Plugins.','YesNo'),
676
('UseQueryParser','0',NULL,'If enabled, try to use QueryParser for queries.','YesNo'),
677
('UseTransportCostMatrix','0','','Use Transport Cost Matrix when filling holds','YesNo'),
676
('UseTransportCostMatrix','0','','Use Transport Cost Matrix when filling holds','YesNo'),
678
('UseWYSIWYGinSystemPreferences','0','','Show WYSIWYG editor when editing certain HTML system preferences.','YesNo'),
677
('UseWYSIWYGinSystemPreferences','0','','Show WYSIWYG editor when editing certain HTML system preferences.','YesNo'),
679
('viewISBD','1','','Allow display of ISBD view of bibiographic records','YesNo'),
678
('viewISBD','1','','Allow display of ISBD view of bibiographic records','YesNo'),
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/about.tt (-21 / +2 lines)
Lines 184-190 Link Here
184
        </div>
184
        </div>
185
185
186
        <div id="sysinfo">
186
        <div id="sysinfo">
187
    [% IF warnPrefBiblioAddsAuthorities || warnPrefEasyAnalyticalRecords || warnPrefAnonymousPatronOPACPrivacy || warnPrefAnonymousPatronAnonSuggestions || warnPrefAnonymousPatronOPACPrivacy_PatronDoesNotExist || warnPrefAnonymousPatronAnonSuggestions_PatronDoesNotExist || warnNoActiveCurrency || QueryParserError || warnIsRootUser || xml_config_warnings.size || AutoSelfCheckPatronDoesNotHaveSelfCheckPerm || AutoSelfCheckPatronHasTooManyPerm || warnStatisticsFieldsError || warnNoTemplateCaching || warnILLConfiguration || has_ai_issues || oauth2_missing_deps || bad_yaml_prefs || warnRelationships %]
187
    [% IF warnPrefBiblioAddsAuthorities || warnPrefEasyAnalyticalRecords || warnPrefAnonymousPatronOPACPrivacy || warnPrefAnonymousPatronAnonSuggestions || warnPrefAnonymousPatronOPACPrivacy_PatronDoesNotExist || warnPrefAnonymousPatronAnonSuggestions_PatronDoesNotExist || warnNoActiveCurrency || warnIsRootUser || xml_config_warnings.size || AutoSelfCheckPatronDoesNotHaveSelfCheckPerm || AutoSelfCheckPatronHasTooManyPerm || warnStatisticsFieldsError || warnNoTemplateCaching || warnILLConfiguration || has_ai_issues || oauth2_missing_deps || bad_yaml_prefs || warnRelationships %]
188
        [% IF (warnIsRootUser) %]
188
        [% IF (warnIsRootUser) %]
189
            <h2>Warning regarding current user</h2>
189
            <h2>Warning regarding current user</h2>
190
            <p>You are logged in as the database administrative user. This is not recommended because some parts of Koha will not function as expected when using this account.</p>
190
            <p>You are logged in as the database administrative user. This is not recommended because some parts of Koha will not function as expected when using this account.</p>
Lines 246-252 Link Here
246
            [% END %]
246
            [% END %]
247
        [% END %]
247
        [% END %]
248
248
249
        [% IF warnPrefBiblioAddsAuthorities || warnPrefEasyAnalyticalRecords || warnPrefAnonymousPatronOPACPrivacy || warnPrefAnonymousPatronAnonSuggestions || warnPrefAnonymousPatronOPACPrivacy_PatronDoesNotExist || warnPrefAnonymousPatronAnonSuggestions_PatronDoesNotExist || warnNoActiveCurrency || QueryParserError || AutoSelfCheckPatronDoesNotHaveSelfCheckPerm || AutoSelfCheckPatronHasTooManyPerm || warnStatisticsFieldsError || warnNoTemplateCaching || warnILLConfiguration || oauth2_missing_deps || bad_yaml_prefs || warnIssuingRules %]
249
        [% IF warnPrefBiblioAddsAuthorities || warnPrefEasyAnalyticalRecords || warnPrefAnonymousPatronOPACPrivacy || warnPrefAnonymousPatronAnonSuggestions || warnPrefAnonymousPatronOPACPrivacy_PatronDoesNotExist || warnPrefAnonymousPatronAnonSuggestions_PatronDoesNotExist || warnNoActiveCurrency || AutoSelfCheckPatronDoesNotHaveSelfCheckPerm || AutoSelfCheckPatronHasTooManyPerm || warnStatisticsFieldsError || warnNoTemplateCaching || warnILLConfiguration || oauth2_missing_deps || bad_yaml_prefs || warnIssuingRules %]
250
            <h2>Warnings regarding the system configuration</h2>
250
            <h2>Warnings regarding the system configuration</h2>
251
            <table>
251
            <table>
252
                <caption>Preferences and parameters</caption>
252
                <caption>Preferences and parameters</caption>
Lines 274-291 Link Here
274
                [% IF warnStatisticsFieldsError %]
274
                [% IF warnStatisticsFieldsError %]
275
                    <tr><th scope="row"><b>Warning</b> </th><td>System preference 'StatisticsFields' contains field names not belonging to the items database table: [% warnStatisticsFieldsError | html %] </td></tr>
275
                    <tr><th scope="row"><b>Warning</b> </th><td>System preference 'StatisticsFields' contains field names not belonging to the items database table: [% warnStatisticsFieldsError | html %] </td></tr>
276
                [% END %]
276
                [% END %]
277
                [% IF QueryParserError %]
278
                    <tr><th scope="row"><b>Warning</b> </th><td>
279
                        You have set UseQueryParser but there was a problem inititializing QueryParser.
280
                    [% IF QueryParserError.fallback %]
281
                        The 'queryparser_config' entry is missing in your configuration file.
282
                        The following configuration file was used without success: [% QueryParserError.file | html %].
283
                    [% ELSE %]
284
                        The following configuration file was used without success: [% QueryParserError.file | html %].
285
                    [% END %]
286
                        </td>
287
                    </tr>
288
                [% END %]
289
                [% IF AutoSelfCheckPatronDoesNotHaveSelfCheckPerm %]
277
                [% IF AutoSelfCheckPatronDoesNotHaveSelfCheckPerm %]
290
                    <tr><th scope="row"><b>Warning</b> </th><td>
278
                    <tr><th scope="row"><b>Warning</b> </th><td>
291
                    The patron used for the self checkout module at the OPAC does not have the self_check => self_checkout_module permission.
279
                    The patron used for the self checkout module at the OPAC does not have the self_check => self_checkout_module permission.
Lines 357-369 Link Here
357
                        <a href="https://wiki.koha-community.org/wiki/Switching_to_dom_indexing">Switching to dom indexing</a>
345
                        <a href="https://wiki.koha-community.org/wiki/Switching_to_dom_indexing">Switching to dom indexing</a>
358
                    </td>
346
                    </td>
359
                </tr>
347
                </tr>
360
                [% ELSIF config_entry.error == 'queryparser_entry_missing' %]
361
                <tr>
362
                    <th scope="row"><b>Warning</b></th>
363
                    <td>You have set UseQueryParser but the 'queryparser_config' entry is missing in your configuration
364
                        file. [% config_entry.file | html %] is used as a fallback.
365
                    </td>
366
                </tr>
367
                [% ELSIF config_entry.error == 'use_zebra_facets_entry_missing' %]
348
                [% ELSIF config_entry.error == 'use_zebra_facets_entry_missing' %]
368
                <tr>
349
                <tr>
369
                    <th scope="row"><b>Warning</b></th>
350
                    <th scope="row"><b>Warning</b></th>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref (-2 / +2 lines)
Lines 258-264 Cataloging: Link Here
258
              choices:
258
              choices:
259
                  yes: "do"
259
                  yes: "do"
260
                  no: "don't"
260
                  no: "don't"
261
            - attempt to match aggressively by trying all variations of the ISBNs in the imported record as a phrase in the ISBN fields of already cataloged records.  Note that this preference has no effect if UseQueryParser is on.
261
            - attempt to match aggressively by trying all variations of the ISBNs in the imported record as a phrase in the ISBN fields of already cataloged records.
262
        -
262
        -
263
            - Display the MARC field/subfields
263
            - Display the MARC field/subfields
264
            - pref: AdditionalFieldsInZ3950ResultSearch
264
            - pref: AdditionalFieldsInZ3950ResultSearch
Lines 269-275 Cataloging: Link Here
269
              choices:
269
              choices:
270
                  yes: "do"
270
                  yes: "do"
271
                  no: "don't"
271
                  no: "don't"
272
            - attempt to match aggressively by trying all variations of the ISSNs in the imported record as a phrase in the ISSN fields of already cataloged records.  Note that this preference has no effect if UseQueryParser is on.
272
            - attempt to match aggressively by trying all variations of the ISSNs in the imported record as a phrase in the ISSN fields of already cataloged records.
273
273
274
    Exporting:
274
    Exporting:
275
        -
275
        -
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/searching.pref (-8 / +1 lines)
Lines 66-78 Searching: Link Here
66
                  no: "Not using"
66
                  no: "Not using"
67
            - 'ICU Zebra indexing. Please note: This setting will not affect Zebra indexing, it should only be used to tell Koha that you have activated ICU indexing if you have actually done so, since there is no way for Koha to figure this out on its own.'
67
            - 'ICU Zebra indexing. Please note: This setting will not affect Zebra indexing, it should only be used to tell Koha that you have activated ICU indexing if you have actually done so, since there is no way for Koha to figure this out on its own.'
68
        -
68
        -
69
            - pref: UseQueryParser
70
              default: 1
71
              choices:
72
                  yes: Try
73
                  no: "Do not try"
74
            - 'to use the QueryParser module for parsing queries. Please note: enabling this will have no impact if you do not have QueryParser installed, and everything will continue to work as usual.'
75
        -
76
            - pref: IncludeSeeFromInSearches
69
            - pref: IncludeSeeFromInSearches
77
              default: 0
70
              default: 0
78
              choices:
71
              choices:
Lines 250-256 Searching: Link Here
250
              choices:
243
              choices:
251
                  yes: "search"
244
                  yes: "search"
252
                  no: "don't search"
245
                  no: "don't search"
253
            - on all variations of the ISBN. Note that this preference has no effect if UseQueryParser is on.
246
            - on all variations of the ISBN.
254
    Did you Mean/Spell checking:
247
    Did you Mean/Spell checking:
255
        -
248
        -
256
            - "Swedish service for spellchecking.<br/>"
249
            - "Swedish service for spellchecking.<br/>"
(-)a/labels/label-item-search.pl (-17 / +4 lines)
Lines 67-74 my $display_columns = [ {_add => {label => "Add Item", link_fi Link Here
67
                      ];
67
                      ];
68
68
69
if ( $op eq "do_search" ) {
69
if ( $op eq "do_search" ) {
70
    my $QParser;
71
    $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser'));
72
    $idx         = $query->param('idx');
70
    $idx         = $query->param('idx');
73
    $ccl_textbox = $query->param('ccl_textbox');
71
    $ccl_textbox = $query->param('ccl_textbox');
74
    if ( $ccl_textbox && $idx ) {
72
    if ( $ccl_textbox && $idx ) {
Lines 82-95 if ( $op eq "do_search" ) { Link Here
82
        $datefrom = eval { dt_from_string ( $datefrom ) };
80
        $datefrom = eval { dt_from_string ( $datefrom ) };
83
        if ($datefrom) {
81
        if ($datefrom) {
84
            $datefrom = output_pref( { dt => $datefrom, dateonly => 1, dateformat => 'iso' } );
82
            $datefrom = output_pref( { dt => $datefrom, dateonly => 1, dateformat => 'iso' } );
85
            if ($QParser) {
83
            $ccl_query .= ' and ' if $ccl_textbox;
86
                $ccl_query .= ' && ' if $ccl_textbox;
84
            $ccl_query .= "acqdate,ge,st-date-normalized=" . $datefrom;
87
                $ccl_query .=
88
                    "acqdate(" . $datefrom . '-)';
89
            } else {
90
                $ccl_query .= ' and ' if $ccl_textbox;
91
                $ccl_query .= "acqdate,ge,st-date-normalized=" . $datefrom;
92
            }
93
        }
85
        }
94
    }
86
    }
95
87
Lines 97-109 if ( $op eq "do_search" ) { Link Here
97
        $dateto = eval { dt_from_string ( $dateto ) };
89
        $dateto = eval { dt_from_string ( $dateto ) };
98
        if ($dateto) {
90
        if ($dateto) {
99
           $dateto = output_pref( { dt => $dateto, dateonly => 1, dateformat => 'iso' } );
91
           $dateto = output_pref( { dt => $dateto, dateonly => 1, dateformat => 'iso' } );
100
            if ($QParser) {
92
            $ccl_query .= ' and ' if ( $ccl_textbox || $datefrom );
101
                $ccl_query .= ' && ' if ( $ccl_textbox || $datefrom );
93
            $ccl_query .= "acqdate,le,st-date-normalized=" . $dateto;
102
                $ccl_query .= "acqdate(-" . $dateto . ')';
103
            } else {
104
                $ccl_query .= ' and ' if ( $ccl_textbox || $datefrom );
105
                $ccl_query .= "acqdate,le,st-date-normalized=" . $dateto;
106
            }
107
        }
94
        }
108
    }
95
    }
109
96
(-)a/misc/migration_tools/bulkmarcimport.pl (-16 / +2 lines)
Lines 598-611 sub build_query { Link Here
598
	  my $string = build_simplequery($matchingpoint,$record);
598
	  my $string = build_simplequery($matchingpoint,$record);
599
	  push @searchstrings,$string if (length($string)>0);
599
	  push @searchstrings,$string if (length($string)>0);
600
        }
600
        }
601
    my $QParser;
601
    my $op = 'and';
602
    $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser'));
603
    my $op;
604
    if ($QParser) {
605
        $op = '&&';
606
    } else {
607
        $op = 'and';
608
    }
609
    return join(" $op ",@searchstrings);
602
    return join(" $op ",@searchstrings);
610
}
603
}
611
sub build_simplequery {
604
sub build_simplequery {
Lines 621-634 sub build_simplequery { Link Here
621
		  }
614
		  }
622
        }
615
        }
623
    }
616
    }
624
    my $QParser;
617
    my $op = 'and';
625
    $QParser = C4::Context->queryparser if (C4::Context->preference('UseQueryParser'));
626
    my $op;
627
    if ($QParser) {
628
        $op = '&&';
629
    } else {
630
        $op = 'and';
631
    }
632
    return join(" $op ",@searchstrings);
618
    return join(" $op ",@searchstrings);
633
}
619
}
634
sub report_item_errors {
620
sub report_item_errors {
(-)a/serials/subscription-bib-search.pl (-1 / +1 lines)
Lines 92-98 if ( $op eq "do_search" && $query ) { Link Here
92
    # add the limits if applicable
92
    # add the limits if applicable
93
    my $itemtypelimit = $input->param('itemtypelimit');
93
    my $itemtypelimit = $input->param('itemtypelimit');
94
    my $ccodelimit    = $input->param('ccodelimit');
94
    my $ccodelimit    = $input->param('ccodelimit');
95
    my $op = C4::Context->preference('UseQueryParser') ? '&&' : 'and';
95
    my $op = 'and';
96
    $query .= " $op $itype_or_itemtype:$itemtypelimit" if $itemtypelimit;
96
    $query .= " $op $itype_or_itemtype:$itemtypelimit" if $itemtypelimit;
97
    $query .= " $op ccode:$ccodelimit" if $ccodelimit;
97
    $query .= " $op ccode:$ccodelimit" if $ccodelimit;
98
    $debug && warn $query;
98
    $debug && warn $query;
(-)a/t/QueryParser.t (-136 lines)
Lines 1-136 Link Here
1
#!/usr/bin/perl
2
3
use strict;
4
use warnings;
5
6
use Test::More;
7
use Test::Deep;
8
use Module::Load::Conditional qw(can_load);
9
10
BEGIN {
11
    use_ok( 'Koha::QueryParser::Driver::PQF' );
12
}
13
14
my $QParser = Koha::QueryParser::Driver::PQF->new();
15
16
ok(defined $QParser, 'Successfully created empty QP object');
17
ok($QParser->load_config('./etc/searchengine/queryparser.yaml'), 'Loaded QP config');
18
19
is($QParser->search_class_count, 4, 'Initialized 4 search classes');
20
is (scalar(@{$QParser->search_fields()->{'keyword'}}), 111, "Correct number of search fields for 'keyword' class");
21
22
# Set keyword search as the default
23
$QParser->default_search_class('keyword');
24
25
my $kwd_search = q/@attr 1=1016 @attr 4=6/;
26
my $weight1    = q/@attr 2=102 @attr 9=20 @attr 4=6/;
27
my $weight2    = q/@attr 2=102 @attr 9=34 @attr 4=6/;
28
29
like( $QParser->target_syntax('biblioserver', 'smith'),
30
    qr/\@or \@or $kwd_search "smith" ($weight1 "smith" $weight2 "smith"|$weight2 "smith" $weight1 "smith")/,
31
    'super simple keyword query');
32
33
is($QParser->target_syntax('biblioserver', 'au:smith'),
34
    '@attr 1=1003 @attr 4=6 "smith"', 'simple author query');
35
36
is($QParser->target_syntax('biblioserver', 'keyword|publisher:smith'),
37
    '@attr 1=1018 @attr 4=6 "smith"', 'fielded publisher query');
38
39
is($QParser->target_syntax('biblioserver', 'ti:"little engine that could"'),
40
    '@attr 1=4 @attr 4=1 "little engine that could"', 'phrase query');
41
42
is($QParser->target_syntax('biblioserver', 'keyword|titlekw:smith'),
43
    '@attr 1=4 @attr 2=102 @attr 9=20 @attr 4=6 "smith"',
44
    'relevance-bumped query');
45
46
is($QParser->target_syntax('biblioserver', 'au:smith && johnson'),
47
    '@and @attr 1=1003 @attr 4=6 "smith" @attr 1=1003 @attr 4=6 "johnson"',
48
    'query with boolean &&');
49
50
is($QParser->target_syntax('biblioserver', 'au:smith && ti:johnson'),
51
    '@and @attr 1=1003 @attr 4=6 "smith" @attr 1=4 @attr 4=6 "johnson"', 'query with boolean &&');
52
53
is($QParser->target_syntax('biblioserver', 'au:smith pubdate(-2008)'),
54
    '@and @attr 1=1003 @attr 4=6 "smith" @attr 1=31 @attr 4=4 @attr 2=2 "2008"',
55
    'keyword search with pubdate limited to -2008');
56
57
is($QParser->target_syntax('biblioserver', 'au:smith pubdate(2008-)'),
58
    '@and @attr 1=1003 @attr 4=6 "smith" @attr 1=31 @attr 4=4 @attr 2=4 "2008"',
59
    'keyword search with pubdate limited to 2008-');
60
61
is($QParser->target_syntax('biblioserver', 'au:smith pubdate(2008)'),
62
    '@and @attr 1=1003 @attr 4=6 "smith" @attr 1=31 @attr 4=4 "2008"',
63
    'keyword search with pubdate limited to 2008');
64
65
is($QParser->target_syntax('biblioserver', 'au:smith pubdate(1980,2008)'),
66
    '@and @attr 1=1003 @attr 4=6 "smith" @or @attr 1=31 @attr 4=4 "1980" @attr 1=31 @attr 4=4 "2008"',
67
    'keyword search with pubdate limited to 1980, 2008');
68
69
is($QParser->target_syntax('biblioserver', 'au:smith #acqdate_dsc'),
70
    '@or @attr 1=32 @attr 7=1 0 @attr 1=1003 @attr 4=6 "smith"',
71
    'keyword search sorted by acqdate descending');
72
73
is($QParser->bib1_mapping_by_attr('field', 'biblioserver', {'1' => '1004'})->{'field'},
74
    'personal', 'retrieve field by attr');
75
76
is($QParser->bib1_mapping_by_attr_string('field', 'biblioserver', '@attr 1=1004')->{'field'},
77
    'personal', 'retrieve field by attrstring');
78
79
is ($QParser->clear_all_mappings, $QParser, 'clear all mappings returns self');
80
is ($QParser->clear_all_configuration, $QParser, 'clear all configuration returns self');
81
is (scalar(keys(%{$QParser->search_fields})), 0, "All mapping erased");
82
83
$QParser->add_bib1_field_map('author' => 'personal' => 'biblioserver' => { '1' => '1004' } );
84
$QParser->add_bib1_modifier_map('relevance' => 'biblioserver' => { '2' => '102' } );
85
my $desired_config = {
86
  'field_mappings' => {
87
    'author' => {
88
      'personal' => {
89
        'aliases' => [ ],
90
        'bib1_mapping' => {
91
          'biblioserver' => {
92
            '1' => '1004'
93
          }
94
        },
95
        'enabled' => '1',
96
        'index' => 'personal',
97
        'label' => 'Personal'
98
      }
99
    }
100
  },
101
  'filter_mappings' => {},
102
  'modifier_mappings' => {
103
    'relevance' => {
104
      'bib1_mapping' => {
105
        'biblioserver' => {
106
          '2' => '102'
107
        }
108
      },
109
      'enabled' => 1,
110
      'label' => 'Relevance'
111
    }
112
  },
113
  'relevance_bumps' => {}
114
};
115
116
SKIP: {
117
    my $got_config;
118
    skip 'YAML is unavailable', 2 unless can_load('modules' => { 'YAML::Any' => undef });
119
    $got_config = YAML::Any::Load($QParser->serialize_mappings());
120
    ok(ref $got_config, 'serialized YAML valid');
121
    is_deeply($got_config, $desired_config, 'Mappings serialized correctly to YAML');
122
123
    skip 'JSON is unavailable', 2 unless can_load('modules' => { 'JSON' => undef });
124
    undef $got_config;
125
    eval {
126
        $got_config = JSON::from_json($QParser->serialize_mappings('json'));
127
    };
128
    is($@, '', 'serialized JSON valid');
129
    is_deeply($got_config, $desired_config, 'Mappings serialized correctly to JSON');
130
}
131
132
$QParser->clear_all_mappings;
133
is($QParser->TEST_SETUP, $QParser, 'TEST_SETUP returns self');
134
is($QParser->search_class_count, 4, 'Initialized 4 search classes in test setup');
135
136
done_testing();
(-)a/t/db_dependent/QueryParser.t (-70 lines)
Lines 1-70 Link Here
1
#!/usr/bin/perl
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use Test::More tests => 7;
21
22
use File::Basename;
23
use t::lib::Mocks;
24
use C4::Context;
25
26
t::lib::Mocks::mock_preference( "UseQueryParser", 1 );
27
my $QParser = C4::Context->queryparser();
28
29
# Check initialization correctly parsed the config file
30
ok( defined $QParser && ref($QParser) eq "Koha::QueryParser::Driver::PQF"
31
    , 'C4::Context successfully created a QP object' );
32
33
is( $QParser->search_class_count, 4,
34
    "Initialized 4 search classes" );
35
is( scalar(@{$QParser->search_fields()->{'keyword'}}), 111,
36
    "Correct number of search fields for 'keyword' class");
37
is( scalar(@{$QParser->search_fields()->{'author'}}), 5,
38
    "Correct number of search fields for 'author' class");
39
is( scalar(@{$QParser->search_fields()->{'subject'}}), 12,
40
    "Correct number of search fields for 'subject' class");
41
is( scalar(@{$QParser->search_fields()->{'title'}}), 5,
42
    "Correct number of search fields for 'title' class");
43
44
# Load C4::Context 4 times with different randomization seeds
45
$ENV{ PERL_PERTURB_KEYS } = "1";
46
my $hash_seed = "AB123";
47
my $default_search_class1 = get_default_search_class();
48
$hash_seed = "CD456";
49
my $default_search_class2 = get_default_search_class();
50
$hash_seed = "ABCDE";
51
my $default_search_class3 = get_default_search_class();
52
$hash_seed = "123456";
53
my $default_search_class4 = get_default_search_class();
54
55
ok( $default_search_class1 eq 'keyword' &&
56
    $default_search_class2 eq 'keyword' &&
57
    $default_search_class3 eq 'keyword' &&
58
    $default_search_class4 eq 'keyword',
59
    "C4::Context correctly sets the default search class to 'keyword' (Bug 12738)");
60
61
sub get_default_search_class {
62
    # get the default search class from a forked proccess
63
    # that just loads C4::Context
64
    $ENV{ PERL_HASH_SEED }    = $hash_seed;
65
    my $running_dir = dirname(__FILE__);
66
    my $default_search_class = qx/$running_dir\/default_search_class.pl/;
67
68
    return $default_search_class;
69
}
70
(-)a/t/db_dependent/Search.t (-108 / +2 lines)
Lines 22-27 use utf8; Link Here
22
use YAML;
22
use YAML;
23
23
24
use C4::Debug;
24
use C4::Debug;
25
use C4::AuthoritiesMarc qw( SearchAuthorities );
25
use C4::XSLT;
26
use C4::XSLT;
26
require C4::Context;
27
require C4::Context;
27
28
Lines 97-103 our $QueryStemming = 0; Link Here
97
our $QueryAutoTruncate = 0;
98
our $QueryAutoTruncate = 0;
98
our $QueryWeightFields = 0;
99
our $QueryWeightFields = 0;
99
our $QueryFuzzy = 0;
100
our $QueryFuzzy = 0;
100
our $UseQueryParser = 0;
101
our $SearchEngine = 'Zebra';
101
our $SearchEngine = 'Zebra';
102
our $marcflavour = 'MARC21';
102
our $marcflavour = 'MARC21';
103
our $htdocs = File::Spec->rel2abs(dirname($0));
103
our $htdocs = File::Spec->rel2abs(dirname($0));
Lines 118-125 $contextmodule->mock('preference', sub { Link Here
118
        return $QueryWeightFields;
118
        return $QueryWeightFields;
119
    } elsif ($pref eq 'QueryFuzzy') {
119
    } elsif ($pref eq 'QueryFuzzy') {
120
        return $QueryFuzzy;
120
        return $QueryFuzzy;
121
    } elsif ($pref eq 'UseQueryParser') {
122
        return $UseQueryParser;
123
    } elsif ($pref eq 'SearchEngine') {
121
    } elsif ($pref eq 'SearchEngine') {
124
        return $SearchEngine;
122
        return $SearchEngine;
125
    } elsif ($pref eq 'maxRecordsForFacets') {
123
    } elsif ($pref eq 'maxRecordsForFacets') {
Lines 196-206 $contextmodule->mock('preference', sub { Link Here
196
        return 0;
194
        return 0;
197
    }
195
    }
198
});
196
});
199
$contextmodule->mock('queryparser', sub {
200
    my $QParser     = Koha::QueryParser::Driver::PQF->new();
201
    $QParser->load_config("$datadir/etc/searchengine/queryparser.yaml");
202
    return $QParser;
203
});
204
197
205
our $bibliomodule = new Test::MockModule('C4::Biblio');
198
our $bibliomodule = new Test::MockModule('C4::Biblio');
206
199
Lines 269-275 sub run_marc21_search_tests { Link Here
269
    $QueryAutoTruncate = 0;
262
    $QueryAutoTruncate = 0;
270
    $QueryWeightFields = 0;
263
    $QueryWeightFields = 0;
271
    $QueryFuzzy = 0;
264
    $QueryFuzzy = 0;
272
    $UseQueryParser = 0;
273
    $marcflavour = 'MARC21';
265
    $marcflavour = 'MARC21';
274
266
275
    my $indexes = C4::Search::getIndexes();
267
    my $indexes = C4::Search::getIndexes();
Lines 485-503 ok(MARC::Record::new_from_xml($results_hashref->{biblioserver}->{RECORDS}->[0],' Link Here
485
    is($results_hashref->{biblioserver}->{hits}, 12,
477
    is($results_hashref->{biblioserver}->{hits}, 12,
486
       'search using index whose name contains "ns" returns expected results (bug 10271)');
478
       'search using index whose name contains "ns" returns expected results (bug 10271)');
487
479
488
    $UseQueryParser = 1;
489
    ( $error, $query, $simple_query, $query_cgi,
490
    $query_desc, $limit, $limit_cgi, $limit_desc,
491
    $query_type ) = buildQuery([], [ 'book' ], [ 'kw' ], [], [], 0, 'en');
492
    ($error, $results_hashref, $facets_loop) = getRecords($query,$simple_query,[ ], [ 'biblioserver' ],20,0,\%branches,\%itemtypes,$query_type,0);
493
    is($results_hashref->{biblioserver}->{hits}, 101, "Search for 'book' with index set to 'kw' returns 101 hits");
494
    ( $error, $query, $simple_query, $query_cgi,
495
    $query_desc, $limit, $limit_cgi, $limit_desc,
496
    $query_type ) = buildQuery([ 'and' ], [ 'book', 'another' ], [ 'kw', 'kw' ], [], [], 0, 'en');
497
    ($error, $results_hashref, $facets_loop) = getRecords($query,$simple_query,[ ], [ 'biblioserver' ],20,0,\%branches,\%itemtypes,$query_type,0);
498
    is($results_hashref->{biblioserver}->{hits}, 1, "Search for 'kw:book && kw:another' returns 1 hit");
499
    $UseQueryParser = 0;
500
501
    ( $error, $query, $simple_query, $query_cgi,
480
    ( $error, $query, $simple_query, $query_cgi,
502
    $query_desc, $limit, $limit_cgi, $limit_desc,
481
    $query_desc, $limit, $limit_cgi, $limit_desc,
503
    $query_type ) = buildQuery([], [ '' ], [ 'kw' ], [ 'available' ], [], 0, 'en');
482
    $query_type ) = buildQuery([], [ '' ], [ 'kw' ], [ 'available' ], [], 0, 'en');
Lines 700-765 ok(MARC::Record::new_from_xml($results_hashref->{biblioserver}->{RECORDS}->[0],' Link Here
700
        return '', [ $record->as_usmarc() ], 1;
679
        return '', [ $record->as_usmarc() ], 1;
701
    });
680
    });
702
681
703
    $UseQueryParser = 1;
704
    $term = 'Arizona';
705
    ( $error, $query, $simple_query, $query_cgi,
706
    $query_desc, $limit, $limit_cgi, $limit_desc,
707
    $query_type ) = buildQuery([], [ $term ], [ 'su-br' ], [  ], [], 0, 'en');
708
    matchesExplodedTerms("Advanced search for broader subjects", $query, 'Arizona', 'United States');
709
710
    ( $error, $query, $simple_query, $query_cgi,
711
    $query_desc, $limit, $limit_cgi, $limit_desc,
712
    $query_type ) = buildQuery([], [ $term ], [ 'su-na' ], [  ], [], 0, 'en');
713
    matchesExplodedTerms("Advanced search for narrower subjects", $query, 'Arizona', 'Maricopa County', 'Navajo County', 'Pima County');
714
715
    ( $error, $query, $simple_query, $query_cgi,
716
    $query_desc, $limit, $limit_cgi, $limit_desc,
717
    $query_type ) = buildQuery([], [ $term ], [ 'su-rl' ], [  ], [], 0, 'en');
718
    matchesExplodedTerms("Advanced search for related subjects", $query, 'Arizona', 'United States', 'Maricopa County', 'Navajo County', 'Pima County');
719
720
    ( $error, $query, $simple_query, $query_cgi,
721
    $query_desc, $limit, $limit_cgi, $limit_desc,
722
    $query_type ) = buildQuery([], [ "$term", 'history' ], [ 'su-rl', 'kw' ], [  ], [], 0, 'en');
723
    matchesExplodedTerms("Advanced search for related subjects and keyword 'history' searches related subjects", $query, 'Arizona', 'United States', 'Maricopa County', 'Navajo County', 'Pima County');
724
    like($query, qr/history/, "Advanced search for related subjects and keyword 'history' searches for 'history'");
725
726
    ( $error, $query, $simple_query, $query_cgi,
727
    $query_desc, $limit, $limit_cgi, $limit_desc,
728
    $query_type ) = buildQuery([], [ 'history', "$term" ], [ 'kw', 'su-rl' ], [  ], [], 0, 'en');
729
    matchesExplodedTerms("Order of terms doesn't matter for advanced search", $query, 'Arizona', 'United States', 'Maricopa County', 'Navajo County', 'Pima County');
730
    like($query, qr/history/, "Order of terms doesn't matter for advanced search");
731
732
    ( $error, $query, $simple_query, $query_cgi,
733
    $query_desc, $limit, $limit_cgi, $limit_desc,
734
    $query_type ) = buildQuery([], [ "su-br($term)" ], [  ], [  ], [], 0, 'en');
735
    matchesExplodedTerms("Simple search for broader subjects", $query, 'Arizona', 'United States');
736
737
    ( $error, $query, $simple_query, $query_cgi,
738
    $query_desc, $limit, $limit_cgi, $limit_desc,
739
    $query_type ) = buildQuery([], [ "su-na($term)" ], [  ], [  ], [], 0, 'en');
740
    matchesExplodedTerms("Simple search for narrower subjects", $query, 'Arizona', 'Maricopa County', 'Navajo County', 'Pima County');
741
742
    ( $error, $query, $simple_query, $query_cgi,
743
    $query_desc, $limit, $limit_cgi, $limit_desc,
744
    $query_type ) = buildQuery([], [ "su-rl($term)" ], [  ], [  ], [], 0, 'en');
745
    matchesExplodedTerms("Simple search for related subjects", $query, 'Arizona', 'United States', 'Maricopa County', 'Navajo County', 'Pima County');
746
747
    ( $error, $query, $simple_query, $query_cgi,
748
    $query_desc, $limit, $limit_cgi, $limit_desc,
749
    $query_type ) = buildQuery([], [ "history && su-rl($term)" ], [  ], [  ], [], 0, 'en');
750
    matchesExplodedTerms("Simple search for related subjects and keyword 'history' searches related subjects", $query, 'Arizona', 'United States', 'Maricopa County', 'Navajo County', 'Pima County');
751
    like($query, qr/history/, "Simple search for related subjects and keyword 'history' searches for 'history'");
752
753
    sub matchesExplodedTerms {
754
        my ($message, $query, @terms) = @_;
755
        my $match = '(' . join ('|', map { " \@attr 1=Subject \@attr 4=1 \"$_\"" } @terms) . "){" . scalar(@terms) . "}";
756
        like($query, qr/$match/, $message);
757
    }
758
759
    # authority records
760
    use_ok('C4::AuthoritiesMarc');
761
    $UseQueryParser = 0;
762
763
    my ($auths, $count) = SearchAuthorities(
682
    my ($auths, $count) = SearchAuthorities(
764
        ['mainentry'], ['and'], [''], ['starts'],
683
        ['mainentry'], ['and'], [''], ['starts'],
765
        ['shakespeare'], 0, 10, '', '', 1
684
        ['shakespeare'], 0, 10, '', '', 1
Lines 791-819 ok(MARC::Record::new_from_xml($results_hashref->{biblioserver}->{RECORDS}->[0],' Link Here
791
    );
710
    );
792
    is($count, 1, 'MARC21 authorities: one hit on "all" (entire record) contains "professional wrestler"');
711
    is($count, 1, 'MARC21 authorities: one hit on "all" (entire record) contains "professional wrestler"');
793
712
794
    $UseQueryParser = 1;
795
796
    ($auths, $count) = SearchAuthorities(
797
        ['mainentry'], ['and'], [''], ['starts'],
798
        ['shakespeare'], 0, 10, '', '', 1
799
    );
800
    is($count, 1, 'MARC21 authorities: one hit on mainentry starts with "shakespeare" (QP)');
801
    ($auths, $count) = SearchAuthorities(
802
        ['mainentry'], ['and'], [''], ['starts'],
803
        ['shakespeare'], 0, 10, '', 'HeadingAsc', 1
804
    );
805
    is($count, 1, 'MARC21 authorities: one hit on mainentry starts with "shakespeare" sorted by heading ascending (QP)');
806
    ($auths, $count) = SearchAuthorities(
807
        ['mainentry'], ['and'], [''], ['starts'],
808
        ['shakespeare'], 0, 10, '', 'HeadingDsc', 1
809
    );
810
    is($count, 1, 'MARC21 authorities: one hit on mainentry starts with "shakespeare" sorted by heading descending (QP)');
811
    ($auths, $count) = SearchAuthorities(
812
        ['match'], ['and'], [''], ['contains'],
813
        ['沙士北亞威廉姆'], 0, 10, '', '', 1
814
    );
815
    is($count, 1, 'MARC21 authorities: one hit on match contains "沙士北亞威廉姆" (QP)');
816
817
    # retrieve records that are larger than the MARC limit of 99,999 octets
713
    # retrieve records that are larger than the MARC limit of 99,999 octets
818
    ( undef, $results_hashref, $facets_loop ) =
714
    ( undef, $results_hashref, $facets_loop ) =
819
        getRecords('ti:marc the large record', '', [], [ 'biblioserver' ], '20', 0, \%branches, \%itemtypes, 'ccl', undef);
715
        getRecords('ti:marc the large record', '', [], [ 'biblioserver' ], '20', 0, \%branches, \%itemtypes, 'ccl', undef);
Lines 899-905 sub run_unimarc_search_tests { Link Here
899
    $QueryAutoTruncate = 0;
795
    $QueryAutoTruncate = 0;
900
    $QueryWeightFields = 0;
796
    $QueryWeightFields = 0;
901
    $QueryFuzzy = 0;
797
    $QueryFuzzy = 0;
902
    $UseQueryParser = 0;
903
    $marcflavour = 'UNIMARC';
798
    $marcflavour = 'UNIMARC';
904
799
905
    index_sample_records_and_launch_zebra($datadir, 'unimarc');
800
    index_sample_records_and_launch_zebra($datadir, 'unimarc');
Lines 917-923 sub run_unimarc_search_tests { Link Here
917
812
918
    # authority records
813
    # authority records
919
    use_ok('C4::AuthoritiesMarc');
814
    use_ok('C4::AuthoritiesMarc');
920
    $UseQueryParser = 0;
921
815
922
    my ($auths, $count) = SearchAuthorities(
816
    my ($auths, $count) = SearchAuthorities(
923
        ['mainentry'], ['and'], [''], ['contains'],
817
        ['mainentry'], ['and'], [''], ['contains'],
Lines 971-977 sub run_unimarc_search_tests { Link Here
971
}
865
}
972
866
973
subtest 'MARC21 + DOM' => sub {
867
subtest 'MARC21 + DOM' => sub {
974
    plan tests => 112;
868
    plan tests => 84;
975
    run_marc21_search_tests();
869
    run_marc21_search_tests();
976
};
870
};
977
871
(-)a/t/db_dependent/UsageStats.t (-1 lines)
Lines 566-572 sub mocking_systempreferences_to_a_set_value { Link Here
566
        TraceCompleteSubfields
566
        TraceCompleteSubfields
567
        TraceSubjectSubdivisions
567
        TraceSubjectSubdivisions
568
        UseICU
568
        UseICU
569
        UseQueryParser
570
        defaultSortField
569
        defaultSortField
571
        displayFacetCount
570
        displayFacetCount
572
        OPACdefaultSortField
571
        OPACdefaultSortField
(-)a/t/db_dependent/default_search_class.pl (-37 lines)
Lines 1-37 Link Here
1
#!/usr/bin/perl
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
=head1 default_search_class.pl
21
22
The only purpose of this script is to load C4::Context and print
23
the default search class from the QueryParser object
24
25
=cut
26
27
use t::lib::Mocks;
28
use C4::Context;
29
30
t::lib::Mocks::mock_preference("UseQueryParser","1");
31
32
my $QParser = C4::Context->queryparser();
33
my $default_search_class = $QParser->default_search_class();
34
35
print $default_search_class;
36
37
exit 0;
(-)a/t/db_dependent/zebra_config.pl (-2 lines)
Lines 49-55 $ENV{'__DB_USER__'} = C4::Context->config('user') // 'kohaadmin'; Link Here
49
$ENV{'__DB_PASS__'} = C4::Context->config('pass')      // 'katikoan';
49
$ENV{'__DB_PASS__'} = C4::Context->config('pass')      // 'katikoan';
50
50
51
my @files = ( "$source/etc/koha-conf.xml",
51
my @files = ( "$source/etc/koha-conf.xml",
52
              "$source/etc/searchengine/queryparser.yaml",
53
            );
52
            );
54
53
55
find(sub { push @files, $File::Find::name if ( -f $File::Find::name ); }, "$source/etc/zebradb");
54
find(sub { push @files, $File::Find::name if ( -f $File::Find::name ); }, "$source/etc/zebradb");
56
- 

Return to bug 24735