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