#!/usr/bin/perl -w use strict; use Biblio::Isis 0.23; use Getopt::Std; use Data::Dumper; use XML::Simple; use Text::Iconv; use Config::IniFiles; use Encode; use GDBM_File; use Fcntl; # for O_RDWR #use TDB_File; use Carp; $|=1; my $config_file = $0; $config_file =~ s/\.pl$/.conf/; $config_file = $ARGV[0] if ($ARGV[0] && -f $ARGV[0]); die "FATAL: can't find configuration file '$config_file'" if (! -e $config_file); my $config; #use index_DBI; # default DBI module for index #use index_DBI_cache; # faster DBI module using memory cache use index_DBI_filter; # filter support for indexes my $index; my %opts; # usage: # -d directory name # -m multiple directories # -q quiet # -s run swish getopts('d:m:qs', \%opts); my $path; # this is name of database Text::Iconv->raise_error(0); # Conversion errors don't raise exceptions # this is encoding of all files on disk, including import_xml/*.xml file and # filter/*.pm files! It will be used to store strings in perl internally! my $codepage = 'ISO-8859-2'; my $utf2cp = Text::Iconv->new('UTF-8',$codepage); # this function will convert data from XML files to local encoding sub x { return $utf2cp->convert($_[0]); } # decode isis/excel or other import codepage my $import2cp; # outgoing xml must be in UTF-8 my $cp2utf = Text::Iconv->new($codepage,'UTF-8'); # mapping between data type and tag which specify # format in XML file my %type2tag = ( 'isis' => 'isis', 'excel' => 'column', 'marc' => 'marc', 'feed' => 'feed', ); my $cache; # for cacheing # lookup hash (tied to file) my %lhash; # this option will cache all lookup entries in memory. # if you are tight on memory, turn this off my $use_lhash_cache = 1; my $last_field_name; # cache to prevent repeated fields my $broken_cdata = XMLin(']]>') eq '>'; warn "XML::Simple on this system seems broken with .\n" if ($broken_cdata); sub data2xml { use xmlify; my $type = shift @_; my $row = shift @_; my $add_xml = shift @_; # needed to read values from configuration file my $cfg = shift @_; my $database = shift @_; my $xml; use parse_format; my $html = ""; # html formatted display output my %field_usage; # counter for usage of each field # sort subrouting using order="" attribute sub by_order { my $va = $config->{indexer}->{$a}->{order} || $config->{indexer}->{$a}; my $vb = $config->{indexer}->{$b}->{order} || $config->{indexer}->{$b}; return $va <=> $vb; } my @sorted_tags; if ($cache->{tags_by_order}) { @sorted_tags = @{$cache->{tags_by_order}}; } else { @sorted_tags = sort by_order keys %{$config->{indexer}}; $cache->{tags_by_order} = \@sorted_tags; } if (! @sorted_tags) { print STDERR "WARNING: no tags for this type found in import_xml file!\n"; } # lookup key my $lookup_key; # cache for field in pages delete $cache->{display_data}; delete $cache->{swish_data}; delete $cache->{swish_exact_data}; delete $cache->{index_data}; delete $cache->{index_delimiter}; delete $cache->{distinct}; my @page_fields; # names of fields # subs used to produce output sub get_field_name($$$) { my ($config,$field,$field_usage) = @_; # find field name (signular, plural) my $field_name = ""; if ($config->{indexer}->{$field}->{name_singular} && $field_usage == 1) { $field_name = $config->{indexer}->{$field}->{name_singular}; } elsif ($config->{indexer}->{$field}->{name_plural}) { $field_name = $config->{indexer}->{$field}->{name_plural}; } elsif ($config->{indexer}->{$field}->{name}) { $field_name = $config->{indexer}->{$field}->{name}; } else { print STDERR "WARNING: field '$field' doesn't have 'name' attribute!"; } if ($field_name) { $field_name = x($field_name); if (! $last_field_name) { $last_field_name = $field_name; return $last_field_name; } elsif ($field_name ne $last_field_name) { $last_field_name = $field_name; return $last_field_name; } } } # init variables for different types sub init_visible_type($) { my $type = shift; # swish, swish_exact, display, index, index_lookup # swish and display defaults my ($s,$se,$d,$i,$il) = (1,0,1,0,0); if (lc($type) eq "display") { $s = 0; } elsif (lc($type) eq "swish") { $d = 0; } elsif (lc($type) eq "index") { ($s,$se,$d,$i) = (0,1,0,1); } elsif (lc($type) eq "swish_exact") { ($s,$se,$d,$i) = (0,1,0,0); } elsif (lc($type) =~ /^lookup/) { ($s,$se,$d,$i,$il) = (0,1,0,0,1); } elsif ($type) { print STDERR "WARNING: unknown type: $type\n"; } return ($s,$se,$d,$i,$il); } # convert # # # , # 200a # # # to # # 200a # # but without loosing spaces in delimiter (becasue # new XML::Simple strips spaces in attribute values # as defined in XML specification) # sub unroll_x($) { my $x = shift; if (defined $x->{value}) { my ($v,$d) = ($x->{value}->{content}, $x->{delimiter}->{content}); delete $x->{value}; delete $x->{delimiter}; $x->{content} = $v; $d =~ s#>$## if ($d && $broken_cdata); $x->{delimiter} = $d; } return $x; } # begin real work: go field by field foreach my $field (@sorted_tags) { $field=x($field); $field_usage{$field}++; my $swish_data = ""; my $swish_exact_data = ""; my $display_data = ""; my @index_data; my $line_delimiter; my ($swish,$display); my $tag = $cfg->val($database, 'import_xml_tag') || $type2tag{$type} || die "can't find which tag to use for type $type"; # is this field page-by-page? my $iterate_by_page = $config->{indexer}->{$field}->{iterate_by_page}; push @page_fields,$field if ($iterate_by_page); my %page_max = (); # default line_delimiter if using my $page_line_delimiter = $config->{indexer}->{$field}->{page_line_delimiter} || '
'; $cache->{index_delimiter}->{$field} = $config->{indexer}->{$field}->{index_delimiter}; my $distinct = $config->{indexer}->{$field}->{distinct}; if ($distinct && !$iterate_by_page) { warn "WARNING: distinct is currently not supported without iterate_by_page!\n"; $distinct = 0; } my $format_name = $config->{indexer}->{$field}->{format_name}; my $format_delimiter = $config->{indexer}->{$field}->{format_delimiter}; if ($format_name && $format_delimiter) { $cache->{format}->{$field}->{format_name} = $format_name; $cache->{format}->{$field}->{format_delimiter} = $format_delimiter; } foreach my $x (@{$config->{indexer}->{$field}->{$tag}}) { $x = unroll_x($x); my $format = x($x->{content}); my $delimiter = x($x->{delimiter}) || ' '; my $repeat_off = 0; # init repeatable offset my ($s,$se,$d,$i,$il) = init_visible_type($x->{type}); # what will separate last line from this one? if ($display_data && $x->{append}) { $line_delimiter = $delimiter; } elsif ($display_data) { $line_delimiter = '
'; } # init vars so that we go into while... ($swish,$display) = (1,1); sub mkformat($$) { my $x = shift || die "mkformat needs tag reference"; my $data = shift || return; my $format_name = x($x->{format_name}) || return $data; my $fmt = x($config->{format}->{$format_name}->{content}) || die " is not defined!"; my $format_delimiter = x($x->{format_delimiter}); my @data; if ($format_delimiter) { @data = split(/$format_delimiter/,$data); } else { push @data,$data; } if ($fmt) { my $nr = scalar $fmt =~ s/%s/%s/g; if (($#data+1) == $nr) { return sprintf($fmt,@data); } else { #print STDERR "mkformat: [$data] can't be split on [$format_delimiter] to $nr fields!\n"; return $data; } } else { print STDERR "usage of link '$format_name' without defined format ( tag)\n"; } } # while because of repeatable fields while ($swish || $display) { my $page = $repeat_off; $page_max{$field} = $page if ($iterate_by_page && $page > ($page_max{$field} || 0)); ($swish,$display) = parse_format($type, $format,$row,$repeat_off++,$import2cp); if ($repeat_off > 1000) { print STDERR "loop (more than 1000 repeatable fields) deteced in $row, $format\n"; last; } # is this field is lookup? if ($display && $x->{lookup}) { my $null = ""; if ($use_lhash_cache) { if (!defined($cache->{lhash}->{$display})) { my $new_display = $lhash{$display}; if (defined($new_display)) { #print STDERR "lookup cache store '$display' = '$new_display'\n"; $display = $new_display; $cache->{lhash}->{$display} = $new_display; } else { # print STDERR "WARNING: lookup for '$display' didn't find anything.\n"; $display = ""; $cache->{lhash}->{$display} = $null; } } else { $display = $cache->{lhash}->{$display}; } } else { $display = $lhash{$display} || $null; } } # filter="name" ; filter this field through # filter/[name].pm my $filter = $x->{filter}; if ($filter && !$cache->{filter_loaded}->{$filter}) { require "filter/".$filter.".pm"; $cache->{filter_loaded}->{$filter}++; } # type="swish" ; field for swish if ($swish) { my $tmp = $swish; if ($filter && ($s || $se)) { no strict 'refs'; $tmp = join(" ",&$filter($tmp)) if ($s || $se); } $swish_data .= $tmp if ($s && $tmp); $swish_exact_data .= "xxbxx $tmp xxexx " if ($tmp && $tmp ne "" && $se); } # type="display" ; field for display if ($d && $display) { my $ldel = $delimiter; if ($line_delimiter && $display_data) { $ldel = $line_delimiter; } if ($filter) { no strict 'refs'; my @arr; foreach my $tmp (&$filter($display)) { my $tmp2 = mkformat($x,$tmp); push @arr,$tmp2 if ($tmp2); } $display_data .= $ldel if ($display_data && @arr); $display_data .= join($delimiter,@arr); } else { $display_data .= $ldel if ($display_data); my $tmp = mkformat($x,$display); $display_data .= $tmp if ($tmp); } } # type="index" ; insert into index my $idisplay; if ($i && $display) { $idisplay = $display; if ($filter) { no strict 'refs'; $idisplay = &$filter($idisplay); } push @index_data, $idisplay if ($idisplay && !$iterate_by_page); } # store fields in lookup if ($il && $display) { if (lc($x->{type}) eq "lookup_key") { if ($lookup_key) { print STDERR "WARNING: try to redefine lookup_key (keys shouldn't be repeatable fields!)"; } else { if ($filter) { no strict 'refs'; $lookup_key = &$filter($display); } else { $lookup_key = $display; } } } elsif (lc($x->{type}) eq "lookup_val") { if ($lookup_key) { if ($filter) { no strict 'refs'; $lhash{$lookup_key} = &$filter($display); } else { $lhash{$lookup_key} = $display; } } else { print STDERR "WARNING: no lookup_key defined for '$display'?"; } } } # store data for page-by-page repeatable fields if ($iterate_by_page) { sub iterate_fld($$$$$$) { my ($cache,$what,$field,$page,$data,$append) = @_; return if (!$data); my $ldel = $page_line_delimiter; $ldel = " " if ($append); #print STDERR "line delimiter: ",Dumper($ldel) if ($ldel); if (! $cache->{$what}->{$field}->[$page]) { push @{$cache->{$what}->{$field}->[$page]}, { data => $data, delimiter => $ldel, }; } } if ($display_data) { iterate_fld($cache,'display_data',$field,$page,$display_data,$x->{append}); } $display_data = ""; if ($swish_data) { iterate_fld($cache,'swish_data',$field,$page,$swish_data,$x->{append}); $swish_data = ""; } if ($swish_exact_data) { iterate_fld($cache,'swish_exact_data',$field,$page,$swish_exact_data,$x->{append}); $swish_exact_data = ""; } if ($idisplay) { my $ldel=$page_line_delimiter; my @index_data; if ($cache->{index_data}->{$field}->[$page]) { @index_data = @{$cache->{index_data}->{$field}->[$page]}; } if ($x->{append}) { if (@index_data) { $index_data[$#index_data] .= $idisplay; } else { push @index_data, $idisplay; } } else { push @index_data, $idisplay; } $idisplay = ""; @{$cache->{index_data}->{$field}->[$page]} = @index_data; } } } if (! $iterate_by_page) { my $idel = $x->{index_delimiter}; # fill data in index foreach my $tmp (@index_data) { my $i = $d = $tmp; if ($idel && $tmp =~ m/$idel/) { ($i,$d) = split(/$idel/,$tmp); } $index->insert($field, $i, $d, $path); } @index_data = (); } } # now try to parse variables from configuration file foreach my $x (@{$config->{indexer}->{$field}->{'config'}}) { $x = unroll_x($x); my $delimiter = x($x->{delimiter}) || ' '; my $val = $cfg->val($database, x($x->{content})); # FIXME index_lookup is not supported! my ($s,$se,$d,$i,$il) = init_visible_type($x->{type}); if ($val) { $display_data .= $delimiter.$val if ($d); $swish_data .= " ".$val if ($s); $index->insert($field, $val, $val, $path) if ($i); } if ($iterate_by_page) { # FIXME data from config tag will appear just # on first page!!! my $page = 0; if ($display_data) { push @{$cache->{display_data}->{$field}->[$page]}, { data => $display_data }; $display_data = ""; } if ($swish_data) { push @{$cache->{swish_data}->{$field}->[$page]}, { data => $swish_data }; $swish_data = ""; } if ($swish_exact_data) { push @{$cache->{swish_exact_data}->{$field}->[$page]}, { data => $swish_exact_data }; $swish_exact_data = ""; } } } # save data page-by-page foreach my $field (@page_fields) { my $nr_pages = $page_max{$field} || next; #print STDERR "field '$field' iterate over ",($nr_pages || 0)," pages...\n"; #print STDERR Dumper($cache->{display_data}); my $seen; # used for distinct for (my $page=0; $page <= $nr_pages; $page++) { my $display_data; my $delimiter = ''; foreach my $element (@{ $cache->{display_data}->{$field}->[$page] }) { my $data = $element->{data}; die "BUG! no data in element?" unless ($data); if ($distinct) { next if ($cache->{distinct}->{$field}->{ $data }); $cache->{distinct}->{$field}->{ $data } = 1; } if ($cache->{format}->{$field}) { my $tmp = mkformat($cache->{format}->{$field},$data); $display_data .= $delimiter . $tmp if ($tmp); } else { $display_data .= $delimiter . $data; } $delimiter = $element->{delimiter} if ($element->{delimiter}); } if ($display_data) { # default if ($field eq "headline") { $xml .= xmlify("headline", $display_data); } else { # fallback to empty field name if needed $html .= get_field_name($config,$field,$field_usage{$field}) || ''; $html .= "#-#".$display_data."###\n"; } } my $swish_data = join(" ",map { $_->{data} } @{ $cache->{swish_data}->{$field}->[$page] }); if ($swish_data) { # remove extra spaces $swish_data =~ s/ +/ /g; $swish_data =~ s/ +$//g; $xml .= xmlify($field."_swish", my_unac_string($codepage,$swish_data)); } my $swish_exact_data = join(" ", map { $_->{data} } @{ $cache->{swish_exact_data}->{$field}->[$page] }); if ($swish_exact_data) { $swish_exact_data =~ s/ +/ /g; $swish_exact_data =~ s/ +$//g; # add delimiters before and after word. # That is required to produce exact match $xml .= xmlify($field."_swish_exact", my_unac_string($codepage,$swish_exact_data)); } my $idel = $cache->{index_delimiter}->{$field}; foreach my $tmp (@{$cache->{index_data}->{$field}->[$page]}) { my $i = $tmp; my $d = $tmp; if ($idel && $tmp =~ m/$idel/) { ($i,$d) = split(/$idel/,$tmp); } $index->insert($field, $i, $d, $path); #print STDERR "index [$idel] $field: $i --> $d [$path]\n"; } } } if (! $iterate_by_page) { if ($display_data) { if ($field eq "headline") { $xml .= xmlify("headline", $display_data); } else { # fallback to empty field name if needed $html .= get_field_name($config,$field,$field_usage{$field}) || ''; $html .= "#-#".$display_data."###\n"; } } if ($swish_data) { # remove extra spaces $swish_data =~ s/ +/ /g; $swish_data =~ s/ +$//g; $xml .= xmlify($field."_swish", my_unac_string($codepage,$swish_data)); } if ($swish_exact_data) { $swish_exact_data =~ s/ +/ /g; $swish_exact_data =~ s/ +$//g; # add delimiters before and after word. # That is required to produce exact match $xml .= xmlify($field."_swish_exact", my_unac_string($codepage,$swish_exact_data)); } } } # dump formatted output in if ($html) { #$xml .= xmlify("html",$html); $xml .= ""; } if ($xml) { $xml .= $add_xml if ($add_xml); return "\n$xml\n"; } else { return; } } ########################################################################## # read configuration for this script my $cfg = new Config::IniFiles( -file => $config_file ); # read global.conf configuration my $cfg_global = new Config::IniFiles( -file => 'global.conf' ); # open index $index = new index_DBI( $cfg_global->val('global', 'dbi_dbd'), $cfg_global->val('global', 'dbi_dsn'), $cfg_global->val('global', 'dbi_user'), $cfg_global->val('global', 'dbi_passwd') || '', ); my $show_progress = $cfg_global->val('global', 'show_progress'); my $my_unac_filter = $cfg_global->val('global', 'my_unac_filter'); if ($my_unac_filter) { print STDERR "using $my_unac_filter to filter characters for search\n"; require $my_unac_filter; } else { print STDERR "### fallback to default my_unac_string!\n"; eval q{ sub main::my_unac_string($$) { my ($charset, $string) = (@_); return $string; } }; } foreach my $database ($cfg->Sections) { # save database name in global variable path for later # (need for index filter creation) $path = $database; my $type = lc($cfg -> val($database, 'type')) || die "$database doesn't have 'type' defined"; my $add_xml = $cfg -> val($database, 'xml'); # optional # create new lookup file my $lookup_file = $cfg -> val($database, 'lookup_newfile'); # optional if ($lookup_file) { if (! -e $lookup_file) { open(LOOKUP, "> $lookup_file") || die "can't create $lookup_file': $!"; close(LOOKUP); } tie %lhash, 'GDBM_File', $lookup_file, &GDBM_NEWDB, 0644; #tie %lhash, 'TDB_File', $lookup_file, TDB_CLEAR_IF_FIRST, O_RDWR, 0644; print STDERR "creating lookup file '$lookup_file'\n"; # delete memory cache for lookup file delete $cache->{lhash}; } # open existing lookup file $lookup_file = $cfg -> val($database, 'lookup_open'); # optional if ($lookup_file) { tie %lhash, 'GDBM_File', $lookup_file, &GDBM_READER, 0644; #tie %lhash, 'TDB_File', $lookup_file, TDB_DEFAULT, O_RDWR, 0644; print STDERR "opening lookup file '$lookup_file'\n"; } my $import_xml_type = $cfg->val($database, 'import_xml_file') || $type; my $import_xml_file = "./import_xml/$import_xml_type.xml"; if (! -r $import_xml_file) { print STDERR "ERROR: file $import_xml_file not readable skipping!\n"; next; } print STDERR "reading $import_xml_file\n"; # extract just type basic my $type_base = $type; $type_base =~ s/_.+$//g; my $tag = $cfg->val($database, 'import_xml_tag') || $type2tag{$type_base} || die "can't find which tag to use for type $type"; $config=XMLin($import_xml_file, ForceArray => [ $tag, 'config', 'format' ], ForceContent => 1 ); # check for broken XML::Simple if ( $broken_cdata ) { map { $config->{format}->{$_}->{content} =~ s#>$##; } keys %{ $config->{format} }; } # helper for progress bar sub fmt_time { my $t = shift || 0; my $out = ""; my ($ss,$mm,$hh) = gmtime($t); $out .= "${hh}h" if ($hh); $out .= sprintf("%02d:%02d", $mm,$ss); $out .= " " if ($hh == 0); return $out; } # output current progress indicator my $last_p = 0; my $start_t = time(); sub progress { return if (! $show_progress); my $current = shift; my $total = shift || 1; my $p = int($current * 100 / $total); if ($p < $last_p || $current == 1) { $start_t = time(); $last_p = 0; } elsif ($p != $last_p) { my $rate = ($current / (time() - $start_t || 1)); my $eta = ($total-$current) / ($rate || 1); printf STDERR ("%5d [%-38s] %-5d %0.1f/s %s\r",$current,"=" x ($p/3)."$p%>", $total, $rate, fmt_time($eta)); $last_p = $p; } } my $fake_dir = 1; my $fake_pos = 0; my $last_fake_t = time(); sub fakeprogress { return if (! $show_progress); my $current = shift @_; my @ind = ('-','\\','|','/','-','\\','|','/'); if ($current < $fake_pos) { $start_t = time(); $last_fake_t = 0; $fake_dir = 1; $fake_pos = 0; } if (time()-$last_fake_t >= 1) { $last_fake_t = time(); $fake_pos += $fake_dir; $fake_dir = -$fake_dir if ($fake_pos > 38); } if ($current % 10 == 0) { my $rate = ($current / (time() - $start_t || 1)); printf STDERR ("%5d [%-38s] %0.1f/s\r",$current, " " x $fake_pos .$ind[($current / 10) % 8], $rate); } } # now read database # erase cache for tags by order in this database delete $cache->{tags_by_order}; if ($type_base eq "isis") { my $isis_db = $cfg -> val($database, 'isis_db') || die "$database doesn't have 'isis_db' defined!"; $import2cp = Text::Iconv->new($config->{isis_codepage},$codepage); my $db = new Biblio::Isis( isisdb => $isis_db, join_subfields_with => ' ; ', ); if (! $db) { print STDERR "FATAL: can't read ISIS database: $isis_db, skipping...\n"; next; } my $max_rowid = $db->count if ($db); print STDERR "Reading database: $isis_db [$max_rowid rows]\n"; for (my $row_id = 1; $row_id <= $max_rowid; $row_id++ ) { my $row = $db->to_hash( $row_id ); if ($row) { $row->{mfn} = $row_id; $row->{record} = $db->{record}; progress($row->{mfn}, $max_rowid); my $swishpath = $path."#".int($row->{mfn}); if (my $xml = data2xml($type_base,$row,$add_xml,$cfg,$database)) { $xml = $cp2utf->convert($xml); use bytes; # as opposed to chars print "Path-Name: $swishpath\n"; print "Content-Length: ".(length($xml)+1)."\n"; print "Document-Type: XML\n\n$xml\n"; } } } print STDERR "\n"; } elsif ($type_base eq "excel") { require Spreadsheet::ParseExcel; require Spreadsheet::ParseExcel::Utility; import Spreadsheet::ParseExcel::Utility qw(int2col); $import2cp = Text::Iconv->new($config->{excel_codepage},$codepage); my $excel_file = $cfg -> val($database, 'excel_file') || die "$database doesn't have 'excel_file' defined!"; my $sheet = x($config->{sheet}) || die "no sheet in $type.xml"; my $start_row = x($config->{start_row}) || die "no start_row in $type.xml"; $start_row--; my $oBook = Spreadsheet::ParseExcel::Workbook->Parse($excel_file) || die "can't open Excel file '$excel_file'"; my $sheet_nr = 0; foreach my $oWks (@{$oBook->{Worksheet}}) { #print STDERR "-- SHEET $sheet_nr:", $oWks->{Name}, "\n"; last if ($oWks->{Name} eq $sheet); $sheet_nr++; } my $oWorksheet = $oBook->{Worksheet}[$sheet_nr]; print STDERR "using sheet: ",$oWorksheet->{Name},"\n"; defined ($oWorksheet) || die "can't find sheet '$sheet' in $excel_file"; my $end_row = x($config->{end_row}) || $oWorksheet->{MaxRow}; for(my $iR = $start_row ; defined $end_row && $iR <= $end_row ; $iR++) { my $row; for(my $iC = $oWorksheet->{MinCol} ; defined $oWorksheet->{MaxCol} && $iC <= $oWorksheet->{MaxCol} ; $iC++) { my $cell = $oWorksheet->{Cells}[$iR][$iC]; if ($cell) { # this conversion is a cludge. # Files from Excell could have # characters which don't fit into # destination encoding. $row->{int2col($iC)} = $utf2cp->convert($cell->Value) || $cell->Value; } } progress($iR, $end_row); # print "row[$iR/$end_row] "; # foreach (keys %{$row}) { # print "$_: ",$row->{$_},"\t"; # } # print "\n"; my $swishpath = $database."#".$iR; next if (! $row); if (my $xml = data2xml($type_base,$row,$add_xml,$cfg,$database)) { $xml = $cp2utf->convert($xml); use bytes; # as opposed to chars print "Path-Name: $swishpath\n"; print "Content-Length: ".(length($xml)+1)."\n"; print "Document-Type: XML\n\n$xml\n"; } } print STDERR "\n"; } elsif ($type_base eq "marc") { require MARC::File::USMARC; $import2cp = Text::Iconv->new($config->{marc_codepage},$codepage); my $marc_file = $cfg -> val($database, 'marc_file') || die "$database doesn't have 'marc_file' defined!"; # optional argument is format warn "marc_format is no longer used!" if ($config->{marc_format}); print STDERR "Reading MARC file '$marc_file'\n"; my $marc = MARC::File::USMARC->in( $marc_file ); if (! $marc) { print STDERR "FATAL: can't read MARC file: $marc_file, skipping...\n"; next; } # count records in MARC file sub marc_count { my $filename = shift || die; my $file = MARC::File::USMARC->in($filename) || return; my $count = 0; while ($file->skip()) { $count++; } return $count; } my $count = marc_count($marc_file) || warn "no records in '$marc_file'?"; my $i = 1; while( my $rec = $marc->next() ) { progress($i,$count); my $swishpath = $database."#".$i; if (my $xml = data2xml($type_base,$rec,$add_xml,$cfg,$database)) { $xml = $cp2utf->convert($xml); use bytes; # as opposed to chars print "Path-Name: $swishpath\n"; print "Content-Length: ".(length($xml)+1)."\n"; print "Document-Type: XML\n\n$xml\n"; } $i++; } print STDERR "\n"; } elsif ($type_base eq "feed") { $import2cp = Text::Iconv->new($config->{feed_codepage},$codepage); my $prog = x($config->{prog}) || die "$database doesn't have 'prog' defined!"; print STDERR "Reading feed from program '$prog'\n"; open(FEED,"feeds/$prog |") || die "can't start $prog: $!"; my $i=1; # record nr. my $data; my $line=1; while () { chomp; if (/^$/) { my $swishpath = $database."#".$i++; if (my $xml = data2xml($type_base,$data,$add_xml,$cfg,$database)) { $xml = $cp2utf->convert($xml); use bytes; # as opposed to chars print "Path-Name: $swishpath\n"; print "Content-Length: ".(length($xml)+1)."\n"; print "Document-Type: XML\n\n$xml\n"; } $line = 1; $data = {}; next; } $line = $1 if (s/^(\d+):\s*//); $data->{$line++} = $_; fakeprogress($i); } # close lookup untie %lhash if (%lhash); } elsif ($type_base eq "dbf") { my $dbf_file = $cfg -> val($database, 'dbf_file') || die "$database doesn't have 'dbf_file' defined!"; my $dbf_codepage = $cfg -> val($database, 'dbf_codepage') || die "$database doesn't have 'dbf_codepage' defined!"; my $dbf_mapping = $cfg -> val($database, 'dbf_mapping') || die "$database doesn't have 'dbf_mapping' defined!"; $import2cp = Text::Iconv->new($dbf_codepage,$codepage); require XBase; my $db = new XBase $dbf_file; if (! $db) { print STDERR "ERROR: can't read DBF database: $dbf_file, skipping...\n"; next; } my $max_rowid = $db->last_record; print STDERR "Reading database: $dbf_file [$max_rowid rows]\n"; my %dbf2iso; foreach my $m (split(/[\n\r]+/,$dbf_mapping)) { my ($col,$fld) = split(/\s+/,$m,2); $dbf2iso{$col} = $fld; } #print STDERR "## dbf2iso: ",Dumper(\%dbf2iso),"\n## /dbf2iso\n"; # bad, bad... require "to_hash.pm"; foreach my $row_id (0 .. $max_rowid) { my $dbf_row = $db->get_record_as_hash($row_id); if ($dbf_row) { #print STDERR "## dbf_row: ",Dumper($dbf_row),"\n## /dbf_row\n"; # apply mapping from config file # all unspecified records will get _ in # front of them - _DELETE will be __DELETE my $rec; map { my $new_fld = $dbf2iso{$_} || '_'.$_; my $data = $dbf_row->{$_}; push @{ $rec->{$new_fld} }, $data if ($data && $data !~ /^(?:\s+|\$a\.|)$/); } keys %{$dbf_row}; #print STDERR "## rec: ",Dumper($rec),"\n## /rec\n"; my $row = to_hash($row_id+1, $rec); $row->{mfn} = $row_id+1; $row->{record} = $rec; #print STDERR "## row: ",Dumper($row),"\n## /row\n"; progress($row->{mfn}, $max_rowid); my $swishpath = $path."#".int($row->{mfn}); if (my $xml = data2xml($type_base,$row,$add_xml,$cfg,$database)) { $xml = $cp2utf->convert($xml); use bytes; # as opposed to chars print "Path-Name: $swishpath\n"; print "Content-Length: ".(length($xml)+1)."\n"; print "Document-Type: XML\n\n$xml\n"; } } } print STDERR "\n"; } } # call this to commit index $index->close; 1; __END__ ########################################################################## =head1 NAME all2xml.pl - read various file formats and dump XML for SWISH-E =head1 SYNOPSYS $ all2xml.pl [test.conf] =head1 DESCRIPTION This command will read ISIS data file using Biblio::Isis perl module, MARC records using MARC::File module and optionally Micro$oft Excel files to create one XML file for usage with I indexer. Dispite it's name, this script B from isis files (isis allready has something like that). Output of this script is tailor-made for SWISH-E. If no configuration file is specified, it will use default one called C. =head1 BUGS Documentation is really lacking. However, in true Open Source spirit, source is best documentation. I even made considerable effort to comment parts which are not intuitively clear, so... =head1 AUTHOR Dobrica Pavlinusic =head1 COPYRIGHT GNU Public License (GPL) v2 or later =head1 SEE ALSO SWISH-E web site at http://www.swish-e.org =cut