Browse Source
Splits Session to GenericSession and ZebraSession where Generic supports any search backend via the SearchEngine classes and Zebra maintains the direct channel to the Zebra server. Adds config files required for mapping BIB-1 attributes to Koha search fields and SRU indexes to BIB-1 attributes. Adds PODs. Sponsored-by: National Library of Finland Signed-off-by: Stefan Berndtsson <stefan.berndtsson@ub.gu.se> Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>remotes/origin/19.11.x
Ere Maijala
6 years ago
committed by
Martin Renvoize
9 changed files with 916 additions and 179 deletions
@ -0,0 +1,113 @@ |
|||
#!/usr/bin/perl |
|||
|
|||
package Koha::Z3950Responder::GenericSession; |
|||
|
|||
# Copyright The National Library of Finland 2018 |
|||
# |
|||
# This file is part of Koha. |
|||
# |
|||
# Koha is free software; you can redistribute it and/or modify it under the |
|||
# terms of the GNU General Public License as published by the Free Software |
|||
# Foundation; either version 3 of the License, or (at your option) any later |
|||
# version. |
|||
# |
|||
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY |
|||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR |
|||
# A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
|||
# |
|||
# You should have received a copy of the GNU General Public License along |
|||
# with Koha; if not, write to the Free Software Foundation, Inc., |
|||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
|||
|
|||
use Modern::Perl; |
|||
|
|||
use base qw( Koha::Z3950Responder::Session ); |
|||
|
|||
use Koha::Logger; |
|||
use Koha::SearchEngine::Search; |
|||
use Koha::SearchEngine::QueryBuilder; |
|||
use Koha::Z3950Responder::RPN; |
|||
|
|||
=head1 NAME |
|||
|
|||
Koha::Z3950Responder::genericSession |
|||
|
|||
=head1 SYNOPSIS |
|||
|
|||
Backend-agnostic session class that uses C<Koha::Session> as the base class. Utilizes |
|||
C<Koha::SearchEngine> for the actual functionality. |
|||
|
|||
=head2 INSTANCE METHODS |
|||
|
|||
=head3 start_search |
|||
|
|||
my ($resultset, $hits) = $self->start_search( $args, $self->{server}->{num_to_prefetch} ); |
|||
|
|||
Perform a search using C<Koha::SearchEngine>'s QueryBuilder and Search. |
|||
|
|||
=cut |
|||
|
|||
sub start_search { |
|||
my ( $self, $args, $num_to_prefetch ) = @_; |
|||
|
|||
if (!defined $self->{'attribute_mappings'}) { |
|||
require YAML; |
|||
$self->{'attribute_mappings'} = YAML::LoadFile($self->{server}->{config_dir} . 'attribute_mappings.yaml'); |
|||
} |
|||
|
|||
my $database = $args->{DATABASES}->[0]; |
|||
my $builder = Koha::SearchEngine::QueryBuilder->new({ index => $database }); |
|||
my $searcher = Koha::SearchEngine::Search->new({ index => $database }); |
|||
|
|||
my $built_query; |
|||
my $query = $args->{RPN}->{'query'}->to_koha($self->{'attribute_mappings'}->{$database}); |
|||
$self->log_debug(" parsed search: $query"); |
|||
my @operands = $query; |
|||
(undef, $built_query) = $builder->build_query_compat( undef, \@operands, undef, undef, undef, 0); |
|||
|
|||
my ($error, $marcresults, $hits ) = $searcher->simple_search_compat($built_query, 0, $num_to_prefetch); |
|||
if (defined $error) { |
|||
$self->set_error($args, $self->ERR_SEARCH_FAILED, 'Search failed'); |
|||
return; |
|||
} |
|||
|
|||
my $resultset = { |
|||
query => $built_query, |
|||
database => $database, |
|||
cached_offset => 0, |
|||
cached_results => $marcresults, |
|||
hits => $hits |
|||
}; |
|||
|
|||
return ($resultset, $hits); |
|||
} |
|||
|
|||
=head3 fetch_record |
|||
|
|||
my $record = $self->fetch_record( $resultset, $args, $offset, $server->{num_to_prefetch} ); |
|||
|
|||
Fetch a record from SearchEngine. Caches records in session to avoid too many fetches. |
|||
|
|||
=cut |
|||
|
|||
sub fetch_record { |
|||
my ( $self, $resultset, $args, $index, $num_to_prefetch ) = @_; |
|||
|
|||
# Fetch more records if necessary |
|||
my $offset = $args->{OFFSET} - 1; |
|||
if ($offset < $resultset->{cached_offset} || $offset >= $resultset->{cached_offset} + $num_to_prefetch) { |
|||
$self->log_debug(" fetch uncached, fetching $num_to_prefetch records starting at $offset"); |
|||
my $searcher = Koha::SearchEngine::Search->new({ index => $resultset->{'database'} }); |
|||
my ($error, $marcresults, $num_hits ) = $searcher->simple_search_compat($resultset->{'query'}, $offset, $num_to_prefetch); |
|||
if (defined $error) { |
|||
$self->set_error($args, $self->ERR_TEMPORARY_ERROR, 'Fetch failed'); |
|||
return; |
|||
} |
|||
|
|||
$resultset->{cached_offset} = $offset; |
|||
$resultset->{cached_results} = $marcresults; |
|||
} |
|||
return $resultset->{cached_results}[$offset - $resultset->{cached_offset}]; |
|||
} |
|||
|
|||
1; |
@ -0,0 +1,117 @@ |
|||
#!/usr/bin/perl |
|||
|
|||
# Copyright The National Library of Finland 2018 |
|||
# |
|||
# This file is part of Koha. |
|||
# |
|||
# Koha is free software; you can redistribute it and/or modify it under thes |
|||
# terms of the GNU General Public License as published by the Free Software |
|||
# Foundation; either version 3 of the License, or (at your option) any later |
|||
# version. |
|||
# |
|||
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY |
|||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR |
|||
# A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
|||
# |
|||
# You should have received a copy of the GNU General Public License along |
|||
# with Koha; if not, write to the Free Software Foundation, Inc., |
|||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
|||
|
|||
use Modern::Perl; |
|||
|
|||
=head1 NAME |
|||
|
|||
Koha::Z3950Responder::RPN |
|||
|
|||
=head1 SYNOPSIS |
|||
|
|||
Overrides for the C<Net::Z3950::RPN> classes adding a C<to_koha> method that |
|||
converts the query to a syntax that C<Koha::SearchEngine> understands. |
|||
|
|||
=head1 DESCRIPTION |
|||
|
|||
The method used here is described in C<samples/render-search.pl> of |
|||
C<Net::Z3950::SimpleServer>. |
|||
|
|||
=cut |
|||
|
|||
package Net::Z3950::RPN::Term; |
|||
sub to_koha { |
|||
my ($self, $mappings) = @_; |
|||
|
|||
my $attrs = $self->{'attributes'}; |
|||
my $fields = $mappings->{use}{default} // '_all'; |
|||
my $split = 0; |
|||
my $quote = ''; |
|||
my $prefix = ''; |
|||
my $suffix = ''; |
|||
my $term = $self->{'term'}; |
|||
|
|||
if ($attrs) { |
|||
foreach my $attr (@$attrs) { |
|||
if ($attr->{'attributeType'} == 1) { # use |
|||
my $use = $attr->{'attributeValue'}; |
|||
$fields = $mappings->{use}{$use} if defined $mappings->{use}{$use}; |
|||
} elsif ($attr->{'attributeType'} == 4) { # structure |
|||
$split = 1 if ($attr->{'attributeValue'} == 2); |
|||
$quote = '"' if ($attr->{'attributeValue'} == 1); |
|||
} elsif ($attr->{'attributeType'} == 5) { # truncation |
|||
my $truncation = $attr->{'attributeValue'}; |
|||
$prefix = '*' if ($truncation == 2 || $truncation == 3); |
|||
$suffix = '*' if ($truncation == 1 || $truncation == 3); |
|||
} |
|||
} |
|||
} |
|||
|
|||
$fields = [$fields] unless ref($fields) eq 'ARRAY'; |
|||
|
|||
if ($split) { |
|||
my @terms; |
|||
foreach my $word (split(/\s/, $term)) { |
|||
$word =~ s/^[\,\.;:\\\/\"\'\-\=]+//g; |
|||
$word =~ s/[\,\.;:\\\/\"\'\-\=]+$//g; |
|||
next if (!$word); |
|||
my @words; |
|||
foreach my $field (@{$fields}) { |
|||
push(@words, "$field:($prefix$word$suffix)"); |
|||
} |
|||
push (@terms, join(' OR ', @words)); |
|||
} |
|||
return '(' . join(' AND ', @terms) . ')'; |
|||
} |
|||
|
|||
my @terms; |
|||
foreach my $field (@{$fields}) { |
|||
push(@terms, "$field:($prefix$term$suffix)"); |
|||
} |
|||
return '(' . join(' OR ', @terms) . ')'; |
|||
} |
|||
|
|||
package Net::Z3950::RPN::And; |
|||
sub to_koha |
|||
{ |
|||
my ($self, $mappings) = @_; |
|||
|
|||
return '(' . $self->[0]->to_koha($mappings) . ' AND ' . |
|||
$self->[1]->to_koha($mappings) . ')'; |
|||
} |
|||
|
|||
package Net::Z3950::RPN::Or; |
|||
sub to_koha |
|||
{ |
|||
my ($self, $mappings) = @_; |
|||
|
|||
return '(' . $self->[0]->to_koha($mappings) . ' OR ' . |
|||
$self->[1]->to_koha($mappings) . ')'; |
|||
} |
|||
|
|||
package Net::Z3950::RPN::AndNot; |
|||
sub to_koha |
|||
{ |
|||
my ($self, $mappings) = @_; |
|||
|
|||
return '(' . $self->[0]->to_koha($mappings) . ' NOT ' . |
|||
$self->[1]->to_koha($mappings) . ')'; |
|||
} |
|||
|
|||
1; |
@ -0,0 +1,163 @@ |
|||
#!/usr/bin/perl |
|||
|
|||
package Koha::Z3950Responder::ZebraSession; |
|||
|
|||
# Copyright ByWater Solutions 2016 |
|||
# |
|||
# This file is part of Koha. |
|||
# |
|||
# Koha is free software; you can redistribute it and/or modify it under the |
|||
# terms of the GNU General Public License as published by the Free Software |
|||
# Foundation; either version 3 of the License, or (at your option) any later |
|||
# version. |
|||
# |
|||
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY |
|||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR |
|||
# A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
|||
# |
|||
# You should have received a copy of the GNU General Public License along |
|||
# with Koha; if not, write to the Free Software Foundation, Inc., |
|||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
|||
|
|||
use Modern::Perl; |
|||
|
|||
use base qw( Koha::Z3950Responder::Session ); |
|||
|
|||
use Koha::Logger; |
|||
|
|||
use ZOOM; |
|||
|
|||
=head1 NAME |
|||
|
|||
Koha::Z3950Responder::ZebraSession |
|||
|
|||
=head1 SYNOPSIS |
|||
|
|||
Zebra-specific session class that uses C<Koha::Session> as the base class. |
|||
|
|||
=head1 FUNCTIONS |
|||
|
|||
=head2 INSTANCE METHODS |
|||
|
|||
=head3 start_search |
|||
|
|||
my ($resultset, $hits) = $self->_start_search( $args, $self->{server}->{num_to_prefetch} ); |
|||
|
|||
Connect to Zebra and do the search |
|||
|
|||
=cut |
|||
|
|||
sub start_search { |
|||
my ( $self, $args, $num_to_prefetch, $in_retry ) = @_; |
|||
|
|||
my $database = $args->{DATABASES}->[0]; |
|||
my ( $connection, $results ); |
|||
|
|||
eval { |
|||
$connection = C4::Context->Zconn( |
|||
# We're depending on the caller to have done some validation. |
|||
$database eq 'biblios' ? 'biblioserver' : 'authorityserver', |
|||
0 # No, no async, doesn't really help much for single-server searching |
|||
); |
|||
|
|||
$results = $connection->search_pqf( $args->{QUERY} ); |
|||
|
|||
$self->log_debug(' retry successful') if ($in_retry); |
|||
}; |
|||
if ($@) { |
|||
die $@ if ( ref($@) ne 'ZOOM::Exception' ); |
|||
|
|||
if ( $@->diagset() eq 'ZOOM' && $@->code() == 10004 && !$in_retry ) { |
|||
$self->log_debug(' upstream server lost connection, retrying'); |
|||
return $self->_start_search( $args, $num_to_prefetch, 1 ); |
|||
} |
|||
|
|||
$self->_set_error_from_zoom( $args, $@ ); |
|||
$connection = undef; |
|||
} |
|||
|
|||
my $hits = $results ? $results->size() : -1; |
|||
my $resultset = { |
|||
database => $database, |
|||
connection => $connection, |
|||
results => $results, |
|||
query => $args->{QUERY}, |
|||
hits => $hits |
|||
}; |
|||
|
|||
return ( $resultset, $hits ); |
|||
} |
|||
|
|||
=head3 fetch_record |
|||
|
|||
my $record = $self->_fetch_record( $resultset, $args, $offset, $server->{num_to_prefetch} ); |
|||
|
|||
Fetch a record from Zebra. Caches records in session to avoid too many fetches. |
|||
|
|||
=cut |
|||
|
|||
sub fetch_record { |
|||
my ( $self, $resultset, $args, $index, $num_to_prefetch ) = @_; |
|||
|
|||
my $record; |
|||
|
|||
eval { |
|||
if ( !$resultset->{results}->record_immediate( $index ) ) { |
|||
my $start = $num_to_prefetch ? int( $index / $num_to_prefetch ) * $num_to_prefetch : $index; |
|||
|
|||
if ( $start + $num_to_prefetch >= $resultset->{results}->size() ) { |
|||
$num_to_prefetch = $resultset->{results}->size() - $start; |
|||
} |
|||
|
|||
$self->log_debug(" fetch uncached, fetching $num_to_prefetch records starting at $start"); |
|||
|
|||
$resultset->{results}->records( $start, $num_to_prefetch, 0 ); |
|||
} |
|||
|
|||
$record = $resultset->{results}->record_immediate( $index )->raw(); |
|||
}; |
|||
if ($@) { |
|||
die $@ if ( ref($@) ne 'ZOOM::Exception' ); |
|||
$self->_set_error_from_zoom( $args, $@ ); |
|||
return; |
|||
} else { |
|||
return $record; |
|||
} |
|||
} |
|||
|
|||
=head3 close_handler |
|||
|
|||
Callback that is called when a session is terminated |
|||
|
|||
=cut |
|||
|
|||
sub close_handler { |
|||
my ( $self, $args ) = @_; |
|||
|
|||
foreach my $resultset ( values %{ $self->{resultsets} } ) { |
|||
$resultset->{results}->destroy(); |
|||
} |
|||
} |
|||
|
|||
=head3 _set_error_from_zoom |
|||
|
|||
$self->_set_error_from_zoom( $args, $@ ); |
|||
|
|||
Log and set error code and diagnostic message from a ZOOM exception |
|||
|
|||
=cut |
|||
|
|||
sub _set_error_from_zoom { |
|||
my ( $self, $args, $exception ) = @_; |
|||
|
|||
$self->set_error( $args, $self->ERR_TEMPORARY_ERROR, 'Cannot connect to upstream server' ); |
|||
$self->log_error( |
|||
"Zebra upstream error: " . |
|||
$exception->message() . " (" . |
|||
$exception->code() . ") " . |
|||
( $exception->addinfo() // '' ) . " " . |
|||
$exception->diagset() |
|||
); |
|||
} |
|||
|
|||
1; |
@ -0,0 +1,46 @@ |
|||
--- |
|||
# Mappings from Z39.50 USE attributes to Koha search fields |
|||
authorities: |
|||
# BIB-1 use attributes to index fields |
|||
use: |
|||
1: Personal-name |
|||
2: Heading |
|||
3: Heading |
|||
9: LC-card-number |
|||
12: Local-number |
|||
default: _all |
|||
biblios: |
|||
# BIB-1 use attributes to index fields |
|||
use: |
|||
1: author |
|||
2: author |
|||
3: author |
|||
4: title |
|||
5: se |
|||
7: isbn |
|||
8: issn |
|||
9: LC-card-number |
|||
10: bnb-card-number |
|||
11: bgf-number |
|||
12: Local-number |
|||
20: local-classification |
|||
21: subject |
|||
30: |
|||
- acqdate |
|||
- copydate |
|||
- pubdate |
|||
31: pubdate |
|||
32: acqdate |
|||
52: control-number |
|||
1003: author |
|||
1007: identifier-standard |
|||
1011: date-entered-on-file |
|||
1012: date-time-last-modified |
|||
1018: publisher |
|||
1019: record-source |
|||
1021: bib-level |
|||
1028: barcode |
|||
1031: itype |
|||
1033: Host-Item-Number |
|||
1045: control-number |
|||
default: _all |
@ -0,0 +1,12 @@ |
|||
<yazgfs> |
|||
<server> |
|||
<cql2rpn>pqf.properties</cql2rpn> |
|||
<explain xmlns="http://explain.z3950.org/dtd/2.0/"> |
|||
<retrievalinfo> |
|||
<retrieval syntax="usmarc" name="marc21"/> |
|||
<retrieval syntax="unimarc" name="unimarc"/> |
|||
<retrieval syntax="xml" name="marcxml" identifier="info:srw/schema/1/marcxml-v1.1"/> |
|||
</retrievalinfo> |
|||
</explain> |
|||
</server> |
|||
</yazgfs> |
@ -0,0 +1,162 @@ |
|||
# |
|||
# Propeties file to drive org.z3950.zing.cql.CQLNode's toPQF() |
|||
# back-end and the YAZ CQL-to-PQF converter. This specifies the |
|||
# interpretation of various CQL indexes, relations, etc. in terms |
|||
# of Type-1 query attributes. |
|||
# |
|||
# This configuration file generates queries using BIB-1 attributes. |
|||
# See http://www.loc.gov/z3950/agency/zing/cql/dc-indexes.html |
|||
# for the Maintenance Agency's work-in-progress mapping of Dublin Core |
|||
# indexes to Attribute Architecture (util, XD and BIB-2) |
|||
# attributes. |
|||
|
|||
# Identifiers for prefixes used in this file. (index.*) |
|||
set.cql = info:srw/cql-context-set/1/cql-v1.1 |
|||
set.rec = info:srw/cql-context-set/2/rec-1.0 |
|||
set.dc = info:srw/cql-context-set/1/dc-v1.1 |
|||
set.bath = http://zing.z3950.org/cql/bath/2.0/ |
|||
|
|||
# default set (in query) |
|||
set = info:srw/cql-context-set/1/dc-v1.1 |
|||
|
|||
# The default access point and result-set references |
|||
index.cql.serverChoice = 1=1016 |
|||
# srw.serverChoice is deprecated in favour of cql.serverChoice |
|||
# BIB-1 "any" |
|||
|
|||
index.rec.id = 1=12 |
|||
index.dc.identifier = 1=1007 |
|||
index.dc.title = 1=4 |
|||
index.dc.subject = 1=21 |
|||
index.dc.creator = 1=1003 |
|||
index.dc.author = 1=1003 |
|||
index.dc.itemtype = 1=1031 |
|||
index.dc.barcode = 1=1028 |
|||
index.dc.branch = 1=1033 |
|||
index.dc.isbn = 1=7 |
|||
index.dc.issn = 1=8 |
|||
index.dc.any = 1=1016 |
|||
index.dc.note = 1=63 |
|||
|
|||
# personal name experimental |
|||
index.dc.pname = 1=1 |
|||
### Unofficial synonym for "creator" |
|||
index.dc.editor = 1=1020 |
|||
index.dc.publisher = 1=1018 |
|||
index.dc.description = 1=62 |
|||
# "abstract" |
|||
index.dc.date = 1=30 |
|||
index.dc.resourceType = 1=1031 |
|||
# guesswork: "Material-type" |
|||
index.dc.format = 1=1034 |
|||
# guesswork: "Content-type" |
|||
index.dc.resourceIdentifier = 1=12 |
|||
# "Local number" |
|||
#index.dc.source = 1=1019 |
|||
# "Record-source" |
|||
index.dc.language = 1=54 |
|||
# "Code--language" |
|||
|
|||
index.dc.Place-publication = 1=59 |
|||
# "Place-publication" |
|||
|
|||
#index.dc.relation = 1=? |
|||
### No idea how to represent this |
|||
#index.dc.coverage = 1=? |
|||
### No idea how to represent this |
|||
#index.dc.rights = 1=? |
|||
### No idea how to represent this |
|||
|
|||
# Relation attributes are selected according to the CQL relation by |
|||
# looking up the "relation.<relation>" property: |
|||
# |
|||
relation.< = 2=1 |
|||
relation.le = 2=2 |
|||
relation.eq = 2=3 |
|||
relation.exact = 2=3 |
|||
relation.ge = 2=4 |
|||
relation.> = 2=5 |
|||
relation.<> = 2=6 |
|||
|
|||
### These two are not really right: |
|||
relation.all = 2=3 |
|||
relation.any = 2=3 |
|||
|
|||
# BIB-1 doesn't have a server choice relation, so we just make the |
|||
# choice here, and use equality (which is clearly correct). |
|||
relation.scr = 2=3 |
|||
|
|||
# Relation modifiers. |
|||
# |
|||
relationModifier.relevant = 2=102 |
|||
relationModifier.fuzzy = 5=103 |
|||
### 100 is "phonetic", which is not quite the same thing |
|||
relationModifier.stem = 2=101 |
|||
relationModifier.phonetic = 2=100 |
|||
|
|||
# Position attributes may be specified for anchored terms (those |
|||
# beginning with "^", which is stripped) and unanchored (those not |
|||
# beginning with "^"). This may change when we get a BIB-1 truncation |
|||
# attribute that says "do what CQL does". |
|||
# |
|||
position.first = 3=1 6=1 |
|||
# "first in field" |
|||
position.any = 3=3 6=1 |
|||
# "any position in field" |
|||
position.last = 3=4 6=1 |
|||
# not a standard BIB-1 attribute |
|||
position.firstAndLast = 3=3 6=3 |
|||
# search term is anchored to be complete field |
|||
|
|||
# Structure attributes may be specified for individual relations; a |
|||
# default structure attribute my be specified by the pseudo-relation |
|||
# "*", to be used whenever a relation not listed here occurs. |
|||
# |
|||
structure.exact = 4=108 |
|||
# string |
|||
structure.all = 4=2 |
|||
structure.any = 4=2 |
|||
structure.* = 4=1 |
|||
# phrase |
|||
|
|||
# Truncation attributes used to implement CQL wildcard patterns. The |
|||
# simpler forms, left, right- and both-truncation will be used for the |
|||
# simplest patterns, so that we produce PQF queries that conform more |
|||
# closely to the Bath Profile. However, when a more complex pattern |
|||
# such as "foo*bar" is used, we fall back on Z39.58-style masking. |
|||
# |
|||
truncation.right = 5=1 |
|||
truncation.left = 5=2 |
|||
truncation.both = 5=3 |
|||
truncation.none = 5=100 |
|||
truncation.z3958 = 5=104 |
|||
|
|||
# Finally, any additional attributes that should always be included |
|||
# with each term can be specified in the "always" property. |
|||
# |
|||
always = 6=1 |
|||
# 6=1: completeness = incomplete subfield |
|||
|
|||
|
|||
# Bath Profile support, added Thu Dec 18 13:06:20 GMT 2003 |
|||
# See the Bath Profile for SRW at |
|||
# http://zing.z3950.org/cql/bath.html |
|||
# including the Bath Context Set defined within that document. |
|||
# |
|||
# In this file, we only map index-names to BIB-1 use attributes, doing |
|||
# so in accordance with the specifications of the Z39.50 Bath Profile, |
|||
# and leaving the relations, wildcards, etc. to fend for themselves. |
|||
|
|||
index.bath.keyTitle = 1=33 |
|||
index.bath.possessingInstitution = 1=1044 |
|||
index.bath.name = 1=1002 |
|||
index.bath.personalName = 1=1 |
|||
index.bath.corporateName = 1=2 |
|||
index.bath.conferenceName = 1=3 |
|||
index.bath.uniformTitle = 1=6 |
|||
index.bath.isbn = 1=7 |
|||
index.bath.issn = 1=8 |
|||
index.bath.geographicName = 1=58 |
|||
index.bath.notes = 1=63 |
|||
index.bath.topicalSubject = 1=1079 |
|||
index.bath.genreForm = 1=1075 |
Loading…
Reference in new issue