implemented tag display
[migration-tools.git] / yaz-cleanup
1 #!/usr/bin/perl
2
3 use strict;
4 use warnings;
5
6 use Getopt::Long;
7 use Term::ReadLine;
8
9 $| = 1;
10
11 my $term = new Term::ReadLine 'yaz-cleanup';
12 my $OUT = $term->OUT || \*STDOUT;
13
14 my $count = 0;
15 my $reccount = 0;
16 my $line = '';
17
18 my @record = (); # current record storage
19 my %reccontext = ();
20 my @linecontext= (); # last 5 lines of file
21
22 open MARC, '<', 'incoming.marc.xml';
23 open my $NUMARC, '>', 'incoming.clean.marc.xml';
24 print $NUMARC '<?xml version="1.0" encoding="UTF-8"?>',"\n";
25 open my $EXMARC, '>', 'incoming.exceptions.marc.xml';
26 print $EXMARC '<?xml version="1.0" encoding="UTF-8"?>',"\n";
27 open MARC2, '<', 'incoming.marc.xml';
28 <MARC2>;
29
30 # this is the dispatch table which drives command selection in
31 # edit(), below
32 my %commands = ( c => \&print_context,
33                  C => \&print_linecontext,
34                  k => \&kill_line,
35                  o => \&show_original,
36                  t => \&commit_edit,
37                  x => \&dump_record,
38                  q => \&quit,
39                  '?' => \&help,
40                  h   => \&help,
41                  help => \&help,
42                );
43
44 my @spinner = qw(- / | \\);
45 my $sidx = 0;
46
47 while (my $line = getline()) {
48     unless ($count % 2000) {
49         print "\rWorking... ", $spinner[$sidx];
50         $sidx = $sidx > $#spinner ? 0 : $sidx++;
51     }
52     update_linecontext();
53
54     # catch empty datafield elements
55     if ($line =~ m|</datafield>|) {
56         if ($record[-2] =~ m/<datafield tag="..." ind1="." ind2=".">/) {
57             pop @record; pop @record;
58             print $OUT "\rEmpty datafield scrubbed at line $count\n";
59             next;
60         }
61     }
62
63     # clean misplaced dollarsigns
64     if ($line =~ m|<subfield code="\$">c?\d+\.\d{2}|) {
65         $line =~ s|"\$">c?(\d+\.\d{2})|"c">\$$1|;
66         print $OUT "\rDollar sign in subfield code corrected at line $count\n";
67     }
68
69     # clean up tags with spaces in them
70     $line =~ s/tag="  /tag="00/g;
71     $line =~ s/tag=" /tag="0/g;
72     $line =~ s/tag="-/tag="0/g;
73     $line =~ s/tag="(\d\d) /tag="0$1/g;
74
75     # stow tag data if we're looking at it
76     if ($line =~ m/<datafield tag="(\d{3})" ind1="(.)" ind2="(.)">/) {
77         $reccontext{tag}  = $1;
78         $reccontext{ind1} = $2;
79         $reccontext{ind2} = $3;
80     }
81
82     # naked ampersands
83     if ($line =~ /&/ && $line !~ /&\w{1,7};/)
84       { edit("Looks like naked ampersand", $line); next }
85
86     # subfields can't be non-alphanumeric
87       if ($line =~ /<subfield code="[^[:alnum:]]"/)
88         { edit("Junk in subfield", $line); next }
89
90 }
91 print $NUMARC "</xml>\n";
92 print $EXMARC "</xml>\n";
93
94 =head2 edit
95
96 Handles the Term::ReadLine loop
97
98 =cut
99
100 sub edit {
101     my ($msg, $line_in) = @_;
102     print $OUT "\r".$msg, " at line $count:\n";
103     print_context();
104     while (1) {
105         my $line = $term->readline('yaz-cleanup>');
106         if (length $line == 1)
107           { next unless (defined $commands{$line}) }
108         if (defined $commands{$line}) {
109             my $term = $commands{$line}->($line_in);
110             last if $term;
111         } else {
112             if ($linecontext[3] eq " [LINE KILLED]\n") {
113                 push @record, "$line\n"
114             } else {
115                 $record[-1] = "$line\n";
116             }
117             $linecontext[3] = "$line\n";
118             print_linecontext();
119         }
120     }
121 }
122
123 =head2 getline
124
125 Reads from the incoming MARC file; returns lines into the driver
126 loop. Batches records for output, and maintains the linecontext listing.
127
128 =cut
129
130 sub getline {
131     my $l = <MARC>;
132     $count++;
133     if (defined $l) {
134         if ($l =~ /<record>/) {
135             @record = ($l);
136             %reccontext = ();
137             $reccount++;
138         } elsif ($l =~ m|</record>|) {
139             write_record($NUMARC) if $reccount;
140         } else {
141             push @record, $l;
142         }
143     }
144     return $l;
145 }
146
147 sub write_record {
148     my ($FH) = @_;
149     print $FH '<collection xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd" xmlns="http://www.loc.gov/MARC21/slim">',"\n";
150     print $FH @record;
151     print $FH "</collection>\n";
152 }
153
154 sub update_linecontext {
155     my $line2 = <MARC2>;
156     push @linecontext, $line2;
157     shift @linecontext if (@linecontext > 5);
158 }
159
160 #-----------------------------------------------------------------------------------
161 # command routines
162 #-----------------------------------------------------------------------------------
163
164 sub print_context {
165     print "\n    Tag: ",$reccontext{tag}, " Ind1: '",
166       $reccontext{ind1},"' Ind2: '", $reccontext{ind2}, "'";
167     print_linecontext();
168     return 0;
169 }
170
171 sub print_linecontext {
172     print $OUT "\n", join('    |','',@linecontext[0..2]);
173     print $OUT '==> |', $linecontext[3];
174     print $OUT '    |', $linecontext[4],"\n";
175     return 0;
176 }
177
178 sub show_original {
179     my ($line_in) = @_;
180     print $OUT "\n$line_in\n";
181     return 0;
182 }
183
184 sub kill_line {
185     pop @record;
186     $linecontext[3] = " [LINE KILLED]\n";
187     print_linecontext();
188     return 0;
189 }
190
191 sub dump_record {
192     my $line = <MARC>; $count++;
193     update_linecontext();
194     until ($line =~ m|</record>|) {
195         push @record, $line;
196         $line = <MARC>; $count++;
197         update_linecontext();
198     }
199     push @record, $line;
200     write_record($EXMARC);
201     return 1;
202 }
203
204 sub commit_edit { return 1 }
205
206 sub help {
207 print $OUT <<HELP;
208
209 Type a replacement for the indicated line, or enter a command.
210
211 Commands: c  Show record context ('C' for brief context)
212           k  Kill this line (remove from record)
213           m  Merge indicated line with previous line [NOT IMPLEMENTED]
214           o  Show original line
215           t  Commit changes and resume stream edit
216           x  Write this record to the exception file instead of output
217           q  Quit
218
219 HELP
220 return 0;
221 }
222
223 sub quit { exit }