/[webpac]/branches/hidra/all2xml.pl
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Diff of /branches/hidra/all2xml.pl

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 7 by dpavlin, Sat Jan 11 16:44:03 2003 UTC revision 90 by dpavlin, Sun Jul 13 13:22:50 2003 UTC
# Line 6  use Getopt::Std; Line 6  use Getopt::Std;
6  use Data::Dumper;  use Data::Dumper;
7  use XML::Simple;  use XML::Simple;
8  use Text::Unaccent 1.02;        # 1.01 won't compile on my platform,  use Text::Unaccent 1.02;        # 1.01 won't compile on my platform,
9  require Unicode::Map8;  use Text::Iconv;
10    use Config::IniFiles;
11    use Encode;
12    
13  my $config=XMLin(undef, forcearray => [ 'isis' ], forcecontent => 1);  $|=1;
14    
15    my $config_file = $0;
16    $config_file =~ s/\.pl$/.conf/;
17    die "FATAL: can't find configuration file '$config_file'" if (! -e $config_file);
18    
19    my $config;
20    
21    #use index_DBI;         # default DBI module for index
22    use index_DBI_cache;    # faster DBI module using memory cache
23    my $index;
24    
25  my %opts;  my %opts;
26    
# Line 20  my %opts; Line 32  my %opts;
32    
33  getopts('d:m:qs', \%opts);  getopts('d:m:qs', \%opts);
34    
35  my $db_dir = $opts{d} || "ps";  # FIX  my $path;       # this is name of database
36    
37    Text::Iconv->raise_error(0);     # Conversion errors don't raise exceptions
38    
39    # this is encoding of all files on disk, including import_xml/*.xml file and
40    # filter/*.pm files! It will be used to store strings in perl internally!
41    my $codepage = 'ISO-8859-2';
42    
43    my $utf2cp = Text::Iconv->new('UTF-8',$codepage);
44    # this function will convert data from XML files to local encoding
45    sub x {
46            return $utf2cp->convert($_[0]);
47    }
48    
49    # decode isis/excel or other import codepage
50    my $import2cp;
51    
52  #die "usage: $0 -d [database_dir] -m [database1,database2] " if (! %opts);  # outgoing xml must be in UTF-8
53    my $cp2utf = Text::Iconv->new($codepage,'UTF-8');
54    
55  #print Dumper($config->{indexer});  # mapping between data type and tag which specify
56  #print "-" x 70,"\n";  # format in XML file
57    my %type2tag = (
58            'isis' => 'isis',
59            'excel' => 'column',
60            'marc' => 'marc',
61            'feed' => 'feed'
62    );
63    
64  # how to convert isis code page to UTF8?  sub data2xml {
 my $isis_map = Unicode::Map8->new($config->{isis_codepage}) || die;  
65    
66  sub isis2xml {          use xmlify;
67    
68            my $type = shift @_;
69          my $row = shift @_;          my $row = shift @_;
70            my $add_xml = shift @_;
71            # needed to read values from configuration file
72            my $cfg = shift @_;
73            my $database = shift @_;
74    
75          my $xml;          my $xml;
         $xml->{db_dir} = [ $db_dir ];   # FIX remove?  
76    
77          sub isis_sf {          use parse_format;
78                  my $row = shift @_;  
79                  my $isis_id = shift @_;          my $html = "";          # html formatted display output
80                  my $subfield = shift @_;  
81                  if ($row->{$isis_id}->[0]) {          my %field_usage;        # counter for usage of each field
82                          my $sf = OpenIsis::subfields($row->{$isis_id}->[0]);  
83                          if (! defined $subfield || length($subfield) == 0) {          # sort subrouting using order="" attribute
84                                  # subfield list undef, empty or no defined subfields for this record          sub by_order {
85                                  my $all_sf = $row->{$isis_id}->[0];                  return 0 if (! $config->{indexer}->{$a}->{order});
86                                  $all_sf =~ s/\^./ /g;   nuke definirions                  return 0 if (! $config->{indexer}->{$b}->{order});
87                                  return $all_sf;  
88                          } elsif ($sf->{$subfield}) {                  return $config->{indexer}->{$a}->{order} <=>
89                                  return $sf->{$subfield};                          $config->{indexer}->{$b}->{order} ;
                         }  
                 }  
90          }          }
91    
92          foreach my $field (keys %{$config->{indexer}}) {          foreach my $field (sort by_order keys %{$config->{indexer}}) {
93    
94                    $field=x($field);
95    
96                    $field_usage{$field}++;
97    
98                    my $swish_data = "";
99                  my $display_data = "";                  my $display_data = "";
100                  my $index_data = "";                  my $line_delimiter;
101    
102                    my ($swish,$display);
103    
104                    my $tag = $type2tag{$type} || die "can't find which tag to use for type $type";
105                    foreach my $x (@{$config->{indexer}->{$field}->{$tag}}) {
106    
107                            my $format = x($x->{content});
108                            my $delimiter = x($x->{delimiter}) || ' ';
109    
110                  foreach my $x (@{$config->{indexer}->{$field}->{isis}}) {                          my $repeat_off = 0;             # repeatable offset
111    
112                          my $display_tmp = "";                          my ($s,$d,$i) = (1,1,0);        # swish, display default
113                          my $index_tmp = "";                          $s = 0 if (lc($x->{type}) eq "display");
114                            $d = 0 if (lc($x->{type}) eq "swish");
115                            ($s,$d,$i) = (0,0,1) if (lc($x->{type}) eq "index");
116    
117                            # what will separate last line from this one?
118                            if ($display_data && $x->{append} && $x->{append} eq "1") {
119                                    $line_delimiter = ' ';
120                            } elsif ($display_data) {
121                                    $line_delimiter = '<br/>';
122                            }
123    
124                            # init vars so that we go into while...
125                            ($swish,$display) = (1,1);
126    
127                            # while because of repeatable fields
128                            while ($swish || $display) {
129                                    ($swish,$display) = parse_format($type, $format,$row,$repeat_off++,$import2cp);
130                                    if ($repeat_off > 1000) {
131                                            print STDERR "loop (more than 1000 repeatable fields) deteced in $row, $format\n";
132                                            last;
133                                    }
134                                    
135                                    # filter="name" ; filter this field through
136                                    # filter/[name].pm
137                                    my $filter = $x->{filter};
138                                    if ($filter) {
139                                            require "filter/".$filter.".pm";
140                                    }
141                                    # type="swish" ; field for swish
142                                    if ($s && $swish) {
143                                            if ($filter) {
144                                                    no strict 'refs';
145                                                    $swish_data .= join(" ",&$filter($swish));
146                                            } else {
147                                                    $swish_data .= $swish;
148                                            }
149                                    }
150    
151                          my $format = $x->{content};                                  # type="display" ; field for display
152                          my $i = 1;      # index only                                  if ($d && $display) {
153                          my $d = 1;      # display only                                          if ($line_delimiter && $display_data) {
154                          $i = 0 if (lc($x->{type}) eq "display");                                                  $display_data .= $line_delimiter;
155                          $d = 0 if (lc($x->{type}) eq "index");                                                  undef $line_delimiter;
 #print "## i: $i d: $d ## $format ##";    
                         # parse format  
                         my $prefix = "";  
                         if ($format =~ s/^([^\d]+)//) {  
                                 $prefix = $1;  
                         }  
                         while ($format) {  
                                 if ($format =~ s/^(\d\d\d)(\w?)//) {  
                                         my $isis_tmp = isis_sf($row,$1,$2);  
                                         if ($isis_tmp) {  
 #                                               $display_tmp .= $prefix . "/$1/$2/".$isis_tmp if ($d);  
                                                 $display_tmp .= $prefix . $isis_tmp if ($d);  
                                                 $index_tmp .= $isis_tmp." " if ($i);  
 #print " $isis_tmp <--\n";  
156                                          }                                          }
157                                          $prefix = "";                                          if ($filter) {
158                                  } elsif ($format =~ s/^([^\d]+)//) {                                                  no strict 'refs';
159                                          $prefix = $1;                                                  $display_data .= join($delimiter,&$filter($display));
160                                            } else {
161                                                    if ($display_data) {
162                                                            $display_data .= $delimiter.$display;
163                                                    } else {
164                                                            $display_data .= $display;
165                                                    }
166                                            }
167                                    }
168                                                    
169                                    # type="index" ; insert into index
170                                    if ($i && $display) {
171                                            my $index_data = $display;
172                                            if ($filter) {
173                                                    no strict 'refs';
174                                                    foreach my $d (&$filter($index_data)) {
175                                                            $index->insert($field, $d, $path);
176                                                    }
177                                            } else {
178                                                    $index->insert($field, $index_data, $path);
179                                            }
180                                    }
181                            }
182                    }
183    
184                    # now try to parse variables from configuration file
185                    foreach my $x (@{$config->{indexer}->{$field}->{'config'}}) {
186    
187                            my $delimiter = x($x->{delimiter}) || ' ';
188                            my $val = $cfg->val($database, x($x->{content}));
189    
190                            my ($s,$d,$i) = (1,1,0);        # swish, display default
191                            $s = 0 if (lc($x->{type}) eq "display");
192                            $d = 0 if (lc($x->{type}) eq "swish");
193                            ($s,$d,$i) = (0,0,1) if (lc($x->{type}) eq "index");
194    
195                            if ($val) {
196                                    $display_data .= $delimiter.$val if ($d);
197                                    $swish_data .= $val if ($s);
198                                    $index->insert($field, $val, $path) if ($i);
199                            }
200    
201                    }
202    
203    
204                    if ($display_data) {
205    
206                            if ($field eq "headline") {
207                                    $xml .= xmlify("headline", $display_data);
208                            } else {
209    
210                                    # find field name (signular, plural)
211                                    my $field_name = "";
212                                    if ($config->{indexer}->{$field}->{name_singular} && $field_usage{$field} == 1) {
213                                            $field_name = $config->{indexer}->{$field}->{name_singular}."#-#";
214                                    } elsif ($config->{indexer}->{$field}->{name_plural}) {
215                                            $field_name = $config->{indexer}->{$field}->{name_plural}."#-#";
216                                    } elsif ($config->{indexer}->{$field}->{name}) {
217                                            $field_name = $config->{indexer}->{$field}->{name}."#-#";
218                                  } else {                                  } else {
219                                          print STDERR "WARNING: unparsed format '$format'\n";                                          print STDERR "WARNING: field '$field' doesn't have 'name' attribute!";
220                                          last;                                  }
221                                  };                                  if ($field_name) {
222                                            $html .= x($field_name);
223                                    }
224                                    $html .= $display_data."###\n";
225                          }                          }
226                          # add suffix                  }
227                          $display_tmp .= $prefix if ($display_tmp);                  if ($swish_data) {
228                            # remove extra spaces
229                            $swish_data =~ s/ +/ /g;
230                            $swish_data =~ s/ +$//g;
231    
232  #                       $display_data .= $display_tmp if ($display_tmp ne "");                          $xml .= xmlify($field."_swish", unac_string($codepage,$swish_data));
233  #                       $index_data .= $index_tmp if ($index_tmp ne "");                  }
234                          $display_data .= $display_tmp;  
235                          $index_data .= $index_tmp;  
236            }
237                  }  
238  #print "--display:$display_data\n--index:$index_data\n";          # dump formatted output in <html>
239                  #$xml->{$field."_display"} = $isis_map->tou($display_data)->utf8 if ($display_data);          if ($html) {
240                  #$xml->{$field."_index"} = unac_string($config->{isis_codepage},$index_data) if ($index_data);                  #$xml .= xmlify("html",$html);
241                  $xml->{$field."_display" } = [ $isis_map->tou($display_data)->utf8 ] if ($display_data);                  $xml .= "<html><![CDATA[ $html ]]></html>";
                 $xml->{$field."_index"} = [ unac_string($config->{isis_codepage},$index_data)." jabuka" ] if ($index_data);  
           
242          }          }
243            
244          if ($xml) {          if ($xml) {
245                  return XMLout($xml, rootname => 'xml', keeproot => 0, noattr => 0 );                  $xml .= $add_xml if ($add_xml);
246                    return "<xml>\n$xml</xml>\n";
247          } else {          } else {
248                  return;                  return;
249          }          }
# Line 117  sub isis2xml { Line 251  sub isis2xml {
251    
252  ##########################################################################  ##########################################################################
253    
254  my $last_tell=0;  # read configuration for this script
255    my $cfg = new Config::IniFiles( -file => $config_file );
256    
257  my @isis_dirs = ( '.' );        # use dirname as database name  # read global.conf configuration
258    my $cfg_global = new Config::IniFiles( -file => 'global.conf' );
259    
260  if ($opts{m}) {  # open index
261          @isis_dirs = split(/,/,$opts{m});  $index = new index_DBI(
262  }                  $cfg_global->val('global', 'dbi_dbd'),
263                    $cfg_global->val('global', 'dbi_dsn'),
264                    $cfg_global->val('global', 'dbi_user'),
265                    $cfg_global->val('global', 'dbi_passwd') || '',
266            );
267    
268  my @isis_dbs;  foreach my $database ($cfg->Sections) {
269    
270  foreach (@isis_dirs) {          my $type = lc($cfg -> val($database, 'type')) || die "$database doesn't have 'type' defined";
271          if (-e $config->{isis_data}."/$db_dir/$_/LIBRI") {          my $add_xml = $cfg -> val($database, 'xml');    # optional
272                  push @isis_dbs,$config->{isis_data}."/$db_dir/$_/LIBRI/LIBRI";  
273          }  print STDERR "reading ./import_xml/$type.xml\n";
274          if (-e $config->{isis_data}."/$db_dir/$_/PERI") {  
275                  push @isis_dbs,$config->{isis_data}."/$db_dir/$_/PERI/PERI";          # extract just type basic
276          }          my $type_base = $type;
277          if (-e $config->{isis_data}."/$db_dir/$_/AMS") {          $type_base =~ s/_.+$//g;
278                  push @isis_dbs,$config->{isis_data}."/$db_dir/$_/AMS/AMS";  
279            $config=XMLin("./import_xml/$type.xml", forcearray => [ $type2tag{$type_base}, 'config' ], forcecontent => 1);
280    
281            # output current progress indicator
282            my $last_p = 0;
283            sub progress {
284                    #return if (! $opts{q});        # FIXME
285                    my $current = shift;
286                    my $total = shift || 1;
287                    my $p = int($current * 100 / $total);
288                    if ($p != $last_p) {
289                            printf STDERR ("%5d / %5d [%-51s] %-2d %% \r",$current,$total,"=" x ($p/2).">", $p );
290                            $last_p = $p;
291                    }
292          }          }
293          if (-e $config->{isis_data}."/$db_dir/$_/ARTI") {  
294  #               push @isis_dbs,$config->{isis_data}."/$db_dir/$_/ARTI/ARTI";          my $fake_dir = 1;
295            sub fakeprogress {
296                    my $current = shift @_;
297    
298                    my @ind = ('-','\\','|','/','-','\\','|','/', '-');
299    
300                    $last_p += $fake_dir;
301                    $fake_dir = -$fake_dir if ($last_p > 1000 || $last_p < 0);
302                    if ($last_p % 10 == 0) {
303                            printf STDERR ("%5d / %5s [%-51s]\r",$current,"?"," " x ($last_p/20).$ind[($last_p/20) % $#ind]);
304                    }
305          }          }
 }  
306    
307  print STDERR "FATAL: Can't find isis database.\nPerhaps isis_data (".$config->{isis_data}.") has wrong value?\n" if (! @isis_dbs);          # now read database
308    print STDERR "using: $type...\n";
309    
310  my $db;          if ($type_base eq "isis") {
311    
312  foreach my $isis_db (@isis_dbs) {                  my $isis_db = $cfg -> val($database, 'isis_db') || die "$database doesn't have 'isis_db' defined!";
313    
314                    $import2cp = Text::Iconv->new($config->{isis_codepage},$codepage);
315                    my $db = OpenIsis::open( $isis_db );
316    
317          my $db = OpenIsis::open( $isis_db );                  my $max_rowid = OpenIsis::maxRowid( $db );
         if (0) {  
 #       # FIX  
 #       if (! $db ) {  
                 print STDERR "WARNING: can't open '$isis_db'\n";  
                 next ;  
         }  
318    
319          my $max_rowid = OpenIsis::maxRowid( $db );                  print STDERR "Reading database: $isis_db [$max_rowid rows]\n";
320    
321          print STDERR "Reading database: $isis_db [$max_rowid rows]\n";                  my $path = $database;
322    
323          my $last_p = 0;                  for (my $row_id = 1; $row_id <= $max_rowid; $row_id++ ) {
324                            my $row = OpenIsis::read( $db, $row_id );
325                            if ($row && $row->{mfn}) {
326            
327                                    progress($row->{mfn}, $max_rowid);
328    
329                                    my $swishpath = $path."#".int($row->{mfn});
330    
331                                    if (my $xml = data2xml($type_base,$row,$add_xml,$cfg,$database)) {
332                                            $xml = $cp2utf->convert($xml);
333                                            use bytes;      # as opposed to chars
334                                            print "Path-Name: $swishpath\n";
335                                            print "Content-Length: ".(length($xml)+1)."\n";
336                                            print "Document-Type: XML\n\n$xml\n";
337                                    }
338                            }
339                    }
340                    print STDERR "\n";
341    
342            } elsif ($type_base eq "excel") {
343                    use Spreadsheet::ParseExcel;
344                    use Spreadsheet::ParseExcel::Utility qw(int2col);
345                    
346                    $import2cp = Text::Iconv->new($config->{excel_codepage},$codepage);
347                    my $excel_file = $cfg -> val($database, 'excel_file') || die "$database doesn't have 'excel_file' defined!";
348    
349                    my $sheet = x($config->{sheet}) || die "no sheet in $type.xml";
350                    my $start_row = x($config->{start_row}) - 1 || die "no start_row in $type.xml";
351    
352                    my $oBook = Spreadsheet::ParseExcel::Workbook->Parse($excel_file) || die "can't open Excel file '$excel_file'";
353    
354                    my $sheet_nr = 0;
355                    foreach my $oWks (@{$oBook->{Worksheet}}) {
356                            #print STDERR "-- SHEET $sheet_nr:", $oWks->{Name}, "\n";
357                            last if ($oWks->{Name} eq $sheet);
358                            $sheet_nr++;
359                    }
360    
361                    my $oWorksheet = $oBook->{Worksheet}[$sheet_nr];
362            
363                    print STDERR "using sheet: ",$oWorksheet->{Name},"\n";
364                    defined ($oWorksheet) || die "can't find sheet '$sheet' in $excel_file";
365                    my $end_row = x($config->{end_row}) || $oWorksheet->{MaxRow};
366    
367                    for(my $iR = $start_row ; defined $end_row && $iR <= $end_row ; $iR++) {
368                            my $row;
369                            for(my $iC = $oWorksheet->{MinCol} ; defined $oWorksheet->{MaxCol} && $iC <= $oWorksheet->{MaxCol} ; $iC++) {
370                                    my $cell = $oWorksheet->{Cells}[$iR][$iC];
371                                    if ($cell) {
372                                            $row->{int2col($iC)} = $cell->Value;
373                                    }
374                            }
375    
376                            progress($iR, $end_row);
377    
378    #                       print "row[$iR/$end_row] ";
379    #                       foreach (keys %{$row}) {
380    #                               print "$_: ",$row->{$_},"\t";
381    #                       }
382    #                       print "\n";
383    
384                            my $swishpath = $database."#".$iR;
385    
386                            next if (! $row);
387    
388                            if (my $xml = data2xml($type_base,$row,$add_xml,$cfg,$database)) {
389                                    $xml = $cp2utf->convert($xml);
390                                    use bytes;      # as opposed to chars
391                                    print "Path-Name: $swishpath\n";
392                                    print "Content-Length: ".(length($xml)+1)."\n";
393                                    print "Document-Type: XML\n\n$xml\n";
394                            }
395                    }
396            } elsif ($type_base eq "marc") {
397    
398                    use MARC;
399                    
400                    $import2cp = Text::Iconv->new($config->{marc_codepage},$codepage);
401                    my $marc_file = $cfg -> val($database, 'marc_file') || die "$database doesn't have 'marc_file' defined!";
402    
403                    # optional argument is format
404                    my $format = x($config->{format}) || 'usmarc';
405    
406                    print STDERR "Reading MARC file '$marc_file'\n";
407    
408                    my $marc = new MARC;
409                    my $nr = $marc->openmarc({
410                                    file=>$marc_file, format=>$format
411                            }) || die "Can't open MARC file '$marc_file'";
412    
413                    my $i=0;        # record nr.
414    
415                    my $rec;
416    
417                    while ($marc->nextmarc(1)) {
418    
419                            # XXX
420                            fakeprogress($i++);
421    
422                            my $swishpath = $database."#".$i;
423    
424                            if (my $xml = data2xml($type_base,$marc,$add_xml,$cfg,$database)) {
425                                    $xml = $cp2utf->convert($xml);
426                                    use bytes;      # as opposed to chars
427                                    print "Path-Name: $swishpath\n";
428                                    print "Content-Length: ".(length($xml)+1)."\n";
429                                    print "Document-Type: XML\n\n$xml\n";
430                            }
431                    }
432            } elsif ($type_base eq "feed") {
433    
434                    $import2cp = Text::Iconv->new($config->{feed_codepage},$codepage);
435                    my $prog = x($config->{prog}) || die "$database doesn't have 'prog' defined!";
436    
437                    print STDERR "Reading feed from program '$prog'\n";
438    
439                    open(FEED,"feeds/$prog |") || die "can't start $prog: $!";
440    
441                    my $i=1;        # record nr.
442    
443  #       { my $row_id = 1;                  my $data;
444  # FIX                  my $line=1;
445          for (my $row_id = 1; $row_id <= $max_rowid; $row_id++ ) {  
446                  my $row = OpenIsis::read( $db, $row_id );                  while (<FEED>) {
447                  if ($row && $row->{mfn}) {                          chomp;
448    
449                          # output current process indicator                          if (/^$/) {
450                          my $p = int($row->{mfn} * 100 / $max_rowid);                                  my $swishpath = $database."#".$i++;
451                          if ($p != $last_p) {  
452                                  printf STDERR ("%5d / %5d [%-51s] %-2d %% \r",$row->{mfn},$max_rowid,"=" x ($p/2).">", $p ) if (! $opts{q});                                  if (my $xml = data2xml($type_base,$data,$add_xml,$cfg,$database)) {
453                                  $last_p = $p;                                          $xml = $cp2utf->convert($xml);
454                          }                                          use bytes;      # as opposed to chars
455                                            print "Path-Name: $swishpath\n";
456                          if (my $xml = isis2xml($row)) {                                          print "Content-Length: ".(length($xml)+1)."\n";
457                                  my $path = $isis_db;                                          print "Document-Type: XML\n\n$xml\n";
458                                  $path =~ s#$config->{isis_data}/*##g;                                  }
459                                  my $out = "Path-Name: $path#".$row->{mfn}."\n";                                  $line = 1;
460                                  $out .= "Content-Length: ".(length($xml)+1)."\n";                                  $data = {};
461                                  $out .= "Document-Type: XML\n\n$xml\n";                                  next;
                                 print $out;  
462                          }                          }
463    
464                            $line = $1 if (s/^(\d+):\s*//);
465                            $data->{$line++} = $_;
466    
467                            fakeprogress($i);
468    
469                  }                  }
470          }          }
         print STDERR "\n";  
471  }  }
472    
473    # call this to commit index
474    $index->close;
475    
476  1;  1;
477  __END__  __END__
# Line 196  __END__ Line 479  __END__
479    
480  =head1 NAME  =head1 NAME
481    
482  isis2xml.pl - read isis file and dump XML  all2xml.pl - read various file formats and dump XML for SWISH-E
483    
484  =head1 DESCRIPTION  =head1 DESCRIPTION
485    
486  This command will read ISIS data file using OpenIsis perl module and  This command will read ISIS data file using OpenIsis perl module, MARC
487  create XML file for usage with I<SWISH-E>  records using MARC module and optionally Micro$oft Excel files to
488  indexer. Dispite it's name, this script B<isn't general xml generator>  create one XML file for usage with I<SWISH-E> indexer. Dispite it's name,
489  from isis files (isis allready has something like that). Output of this  this script B<isn't general xml generator> from isis files (isis allready
490  script is tailor-made for SWISH-E.  has something like that). Output of this script is tailor-made for SWISH-E.
491    
492    =head1 BUGS
493    
494    Documentation is really lacking. However, in true Open Source spirit, source
495    is best documentation. I even made considerable effort to comment parts
496    which are not intuitively clear, so...
497    
498  =head1 AUTHOR  =head1 AUTHOR
499    

Legend:
Removed from v.7  
changed lines
  Added in v.90

  ViewVC Help
Powered by ViewVC 1.1.26