package Koha::Indexer::Utils; # Copyright (c) 2012 Equinox Software, Inc. # 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, see . use strict; use warnings; use 5.010; use XML::LibXML; =head1 Koha::Indexer::Utils Koha::Indexer::Utils - utility functions for managing search indexes =head1 DESCRIPTION This modules contains utility functions for managing various aspects of Koha's bibliographic and authority search indexes. =head1 FUNCTIONS =cut =head2 zebra_record_abs_to_dom $dom_config = Koha::Indexer::Utils::zebra_record_abs_to_dom($record_abs_config, $marcflavour); Given a string containing the contents of a records.abs configuration file as used by Zebra's GRS-1 filter, emit an equivalent DOM configuration. =cut our $idxNS = 'http://www.koha-community.org/schemas/index-defs'; sub zebra_record_abs_to_dom { my $grs1_cfg = shift; my $marcflavour = shift; chomp $grs1_cfg; my @grs1_cfg_lines = split /\n/, $grs1_cfg, -1; my $grs1_defs = []; # generate an arrayref of structures representing # each records.abs line for (my $i = 0; $i <= $#grs1_cfg_lines; $i++) { my $line = $grs1_cfg_lines[$i]; next if _can_ignore_grs1_cfg_line($line); my $grs1_def = _parse_grs1_cfg_line($line); $grs1_def->{orig_def} = $line; $grs1_def->{lineno} = $i + 1; push @$grs1_defs, $grs1_def; } # map the index definitions to a DOM tree representing # the index definitions -- if you squint hard, you # can see the beginnings of a more general definition language # for Koha index definitions my $dom_cfg = XML::LibXML::Document->new('1.0', 'utf-8'); my $root = $dom_cfg->createElement('index_defs'); $root->setNamespace($idxNS, 'kohaidx'); foreach my $grs1_def (@$grs1_defs) { _append_grs1_def_to_dom_cfg($dom_cfg, $root, $grs1_def, $marcflavour); } # and emit the result as a string $dom_cfg->setDocumentElement($root); return $dom_cfg->toString(1); } # # bunch of utility functions for zebra_record_abs_to_dom # sub _can_ignore_grs1_cfg_line { my $line = shift; return 1 if $line =~ /^\s*$/ or $line =~ /^#/ or $line =~ /^(encoding|name|attset|esetname|marc|systag|xpath)/ or $line =~ /^all/; # DOM filter automatically indexes all tokens, so # no need to deal with 'all any' lines in record.abs return 0; } sub _parse_grs1_cfg_line { my $line = shift; my $grs1_def; if ($line =~ /^melm\s+(.*)/ || $line =~ m!^xelm /record/(.*)!) { $grs1_def = _parse_xelm_melm($1); } return $grs1_def; } sub _parse_xelm_melm { my $line = shift; my ($field, $index_defs) = split /\s+/, $line, 2; # munge fixed field range indicators $index_defs =~ s/range\(data,(\d+),(\d+)\)/$1:$2/g; my ($tag, $subfield) = split /\$/, $field, 2; return { tag => $tag, subfield => $subfield, index_defs => [ map { _parse_grs1_index_def($_) } split /,/, $index_defs ], }; } sub _parse_grs1_index_def { my $index_def = shift; my @parts = split /:/, $index_def, -1; my $parsed_def = {}; $parsed_def->{name} = shift @parts; $parsed_def->{index_type} = shift @parts; $parsed_def->{offset} = shift @parts; $parsed_def->{length} = shift @parts; # if the original index definition didn't specify an index # type, set it 'w' -- the DOM filter needs the index type # to be specified explicitly $parsed_def->{index_type} = 'w' unless defined $parsed_def->{index_type}; return $parsed_def; } sub _append_grs1_def_to_dom_cfg { my $dom_cfg = shift; my $root = shift; my $grs1_def = shift; my $marcflavour = shift; my $comment = $dom_cfg->createComment('record.abs line ' . $grs1_def->{lineno} . ': ' . $grs1_def->{orig_def}); $root->appendChild($comment); if (defined $grs1_def->{tag} && defined $grs1_def->{subfield}) { my $dom_def = $dom_cfg->createElementNS($idxNS, 'index_subfields'); $dom_def->setAttribute('tag', $grs1_def->{tag}); $dom_def->setAttribute('subfields', $grs1_def->{subfield}); _append_target_indexes($dom_cfg, $dom_def, $grs1_def); $root->appendChild($dom_def); } elsif (defined $grs1_def->{tag} and $grs1_def->{tag} eq 'leader') { # we're the leader _append_grs1_defs_for_leader($dom_cfg, $root, $grs1_def); } elsif (defined $grs1_def->{tag} and $grs1_def->{tag} < 10) { # we're a control field _append_grs1_defs_for_control_field($dom_cfg, $root, $grs1_def); } elsif (defined $grs1_def->{tag}) { # we're indexing an entire variable data field my $dom_def = $dom_cfg->createElementNS($idxNS, 'index_data_field'); $dom_def->setAttribute('tag', $grs1_def->{tag}); _append_target_indexes($dom_cfg, $dom_def, $grs1_def); $root->appendChild($dom_def); } } sub _append_target_indexes { my $dom_cfg = shift; my $dom_def = shift; my $grs1_def = shift; foreach my $index_def (@{ $grs1_def->{index_defs} }) { _append_one_target_index($dom_cfg, $dom_def, $index_def); } } sub _append_one_target_index { my $dom_cfg = shift; my $dom_def = shift; my $index_def = shift; my $tgt_idx = $dom_cfg->createElementNS($idxNS, 'target_index'); my $index_name = "$index_def->{name}:$index_def->{index_type}"; $tgt_idx->appendText($index_name); $dom_def->appendChild($tgt_idx); } sub _append_grs1_defs_for_leader { my $dom_cfg = shift; my $root = shift; my $grs1_def = shift; foreach my $index_def (@{ $grs1_def->{index_defs} }) { my $dom_def = $dom_cfg->createElementNS($idxNS, 'index_leader'); if (defined $index_def->{offset} && defined $index_def->{length}) { $dom_def->setAttribute('offset', $index_def->{offset}); $dom_def->setAttribute('length', $index_def->{length}); } _append_one_target_index($dom_cfg, $dom_def, $index_def); $root->appendChild($dom_def); } } sub _append_grs1_defs_for_control_field { my $dom_cfg = shift; my $root = shift; my $grs1_def = shift; foreach my $index_def (@{ $grs1_def->{index_defs} }) { my $dom_def = $dom_cfg->createElementNS($idxNS, 'index_control_field'); $dom_def->setAttribute('tag', $grs1_def->{tag}); if (defined $index_def->{offset} && defined $index_def->{length}) { $dom_def->setAttribute('offset', $index_def->{offset}); $dom_def->setAttribute('length', $index_def->{length}); } _append_one_target_index($dom_cfg, $dom_def, $index_def); $root->appendChild($dom_def); } } 1;