root/main/trunk/greenstone2/perllib/plugins/LOMPlugin.pm @ 24951

Revision 24951, 19.3 KB (checked in by ak19, 8 years ago)

All perlcode that accesses extrametakeys, extrametadata, extrametafile data structures has been moved into a new perl module called extrametautil.pm. The next step will be to ensure that the file_regexes used to index into these data structures are consistent (using consistent slashes, like URL style slashes).

  • Property svn:keywords set to Author Date Id Revision
Line 
1###########################################################################
2#
3# LOMPlugin.pm -- plugin for import the collection from LOM
4#
5# A component of the Greenstone digital library software
6# from the New Zealand Digital Library Project at the
7# University of Waikato, New Zealand.
8#
9# Copyright (C) 2005 New Zealand Digital Library Project
10#
11# This program is free software; you can redistribute it and/or modify
12# it under the terms of the GNU General Public License as published by
13# the Free Software Foundation; either version 2 of the License, or
14# (at your option) any later version.
15#
16# This program is distributed in the hope that it will be useful,
17# but WITHOUT ANY WARRANTY; without even the implied warranty of
18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19# GNU General Public License for more details.
20#
21# You should have received a copy of the GNU General Public License
22# along with this program; if not, write to the Free Software
23# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24#
25###########################################################################
26
27### Note this plugin currently can't download source documents from outside if you are behind a firewall.
28# Unless, you set the http_proxy environment variable to be your proxy server,
29# and set proxy_user and proxy_password in .wgetrc file in home directory.
30# (does that work on windows??)
31
32package LOMPlugin;
33
34use extrametautil;
35use ReadTextFile;
36use MetadataPass;
37use MetadataRead;
38use XMLParser;
39use Cwd;
40
41# methods with identical signatures take precedence in the order given in the ISA list.
42sub BEGIN {
43    @ISA = ('MetadataRead', 'ReadTextFile', 'MetadataPass');
44}
45
46use strict; # every perl program should have this!
47no strict 'refs'; # make an exception so we can use variables as filehandles
48
49
50my $arguments =
51    [ { 'name' => "process_exp",
52    'desc' => "{BasePlugin.process_exp}",
53    'type' => "string",
54    'deft' => &get_default_process_exp(),
55    'reqd' => "no" },
56      { 'name' => "root_tag",
57    'desc' => "{LOMPlugin.root_tag}",
58    'type' => "regexp",
59    'deft' => q/^(?i)lom$/,
60    'reqd' => "no" },
61      { 'name' => "check_timestamp",
62    'desc' => "{LOMPlugin.check_timestamp}",
63    'type' => "flag" },
64      { 'name' => "download_srcdocs",
65    'desc' => "{LOMPlugin.download_srcdocs}",
66    'type' => "regexp",
67    'deft' => "",
68    'reqd' => "no" }];
69
70my $options = { 'name'     => "LOMPlugin",
71        'desc'     => "{LOMPlugin.desc}",
72        'abstract' => "no",
73        'inherits' => "yes",
74        'args'     => $arguments };
75
76
77
78my ($self);
79sub new {
80    my $class = shift (@_);
81    my ($pluginlist,$inputargs,$hashArgOptLists) = @_;
82    push(@$pluginlist, $class);
83   
84    push(@{$hashArgOptLists->{"ArgList"}},@{$arguments});
85    push(@{$hashArgOptLists->{"OptList"}},$options);
86   
87    $self = new ReadTextFile($pluginlist, $inputargs, $hashArgOptLists);
88
89    if ($self->{'info_only'}) {
90    # don't worry about creating the XML parser as all we want is the
91    # list of plugin options
92    return bless $self, $class;
93    }
94
95    #create XML::Parser object for parsing dublin_core.xml files
96    my $parser = new XML::Parser('Style' => 'Stream',
97                 'Handlers' => {'Char' => \&Char,
98                        'Doctype' => \&Doctype
99                        });
100    $self->{'parser'} = $parser;
101
102    $self->{'extra_blocks'} = {};
103
104    return bless $self, $class;
105}
106
107sub get_default_process_exp {
108    my $self = shift (@_);
109
110    return q^(?i)\.xml$^;
111}
112
113
114sub can_process_this_file {
115    my $self = shift(@_);
116    my ($filename) = @_;
117
118    if ($self->SUPER::can_process_this_file($filename) && $self->check_doctype($filename)) {
119    return 1; # its a file for us
120    }
121    return 0;
122}
123
124sub metadata_read {
125    my $self = shift (@_);
126    my ($pluginfo, $base_dir, $file, $block_hash,
127    $extrametakeys, $extrametadata, $extrametafile,
128    $processor, $gli, $aux) = @_;
129
130    my $outhandle = $self->{'outhandle'};
131
132    # can we process this file??
133    my ($filename_full_path, $filename_no_path) = &util::get_full_filenames($base_dir, $file);
134    return undef unless $self->can_process_this_file_for_metadata($filename_full_path);
135
136    $file =~ s/^[\/\\]+//; # $file often begins with / so we'll tidy it up
137   
138    print $outhandle "LOMPlugin: extracting metadata from $file\n"
139    if $self->{'verbosity'} > 1;
140
141    my ($dir,$tail) = $filename_full_path =~ /^(.*?)([^\/\\]*)$/;
142    $self->{'output_dir'} = $dir;
143
144    eval {
145    $self->{'parser'}->parsefile($filename_full_path);
146    };
147   
148    if ($@) {
149    print $outhandle "LOMPlugin: skipping $filename_full_path as not conformant to LOM syntax\n" if ($self->{'verbosity'} > 1);
150    print $outhandle "\n Perl Error:\n $@\n" if ($self->{'verbosity'}>2);
151    return 0;
152    }
153
154    $self->{'output_dir'} = undef;
155
156    my $file_re;
157    my $lom_srcdoc = $self->{'lom_srcdoc'};
158
159    if (defined $lom_srcdoc) {
160    my $dirsep = &util::get_re_dirsep();
161    $lom_srcdoc =~ s/^$base_dir($dirsep)//;
162    $self->{'extra_blocks'}->{$file}++;
163    $file_re = $lom_srcdoc;
164    }
165    else {
166    $file_re = $tail;
167    }
168    $file_re = &util::filename_to_regex($file_re);
169    $self->{'lom_srcdoc'} = undef; # reset for next file to be processed
170
171    &extrametautil::addmetakey($extrametakeys, $file_re);
172    &extrametautil::setmetadata($extrametadata, $file_re, $self->{'saved_metadata'});
173    if (defined $lom_srcdoc) {
174    # copied from oaiplugin
175    if (!defined &extrametautil::getmetafile($extrametafile, $file_re)) {
176        &extrametautil::setmetafile($extrametafile, $file_re, {});
177    }
178     #maps the file to full path
179    &extrametautil::setmetafile_for_named_file($extrametafile, $file_re, $file, $filename_full_path);
180    }
181   
182    return 1;
183}
184
185sub check_doctype {
186    $self = shift (@_);
187   
188    my ($filename) = @_;
189   
190    if (open(XMLIN,"<$filename")) {
191    my $doctype = $self->{'root_tag'};
192    ## check whether the doctype has the same name as the root element tag
193    while (defined (my $line = <XMLIN>)) {
194        ## find the root element
195        if ($line =~ /<([\w\d:]+)[\s>]/){
196        my $root = $1;
197        if ($root !~ $doctype){
198            close(XMLIN);
199            return 0;
200        }
201        else {
202            close(XMLIN);
203            return 1;
204        }
205        }
206    }
207    close(XMLIN);
208    }
209   
210    return undef; # haven't found a valid line
211   
212}
213
214sub read_file {
215    my $self = shift (@_);
216    my ($filename, $encoding, $language, $textref) = @_;
217
218    my $metadata_table = $self->{'metadata_table'};
219
220    my $rawtext = $metadata_table->{'rawtext'};
221
222    delete $metadata_table->{'rawtext'};
223
224    $$textref = $rawtext;
225}
226
227sub read {
228    my $self = shift (@_);
229    my ($pluginfo, $base_dir, $file, $block_hash, $metadata, $processor, $maxdocs, $total_count, $gli) = @_;
230
231    my $outhandle = $self->{'outhandle'};
232
233    return 0 if (defined $self->{'extra_blocks'}->{$file});
234
235    # can we process this file??
236    my ($filename_full_path, $filename_no_path) = &util::get_full_filenames($base_dir, $file);
237    return undef unless $self->can_process_this_file($filename_full_path);
238
239    $self->{'metadata_table'} = $metadata;
240
241    my $lom_language = $metadata->{'lom_language'};
242
243    my $store_input_encoding;
244    my $store_extract_language;
245    my $store_default_language;
246    my $store_default_encoding;
247
248    if (defined $lom_language) {
249    delete $metadata->{'lom_language'};
250
251    $store_input_encoding   = $self->{'input_encoding'};
252    $store_extract_language = $self->{'extract_language'};
253    $store_default_language = $self->{'default_language'};
254    $store_default_encoding = $self->{'default_encoding'};
255
256    $self->{'input_encoding'}   = "utf8";
257    $self->{'extract_language'} = 0;
258    $self->{'default_language'} = $lom_language;
259    $self->{'default_encoding'} = "utf8";
260    }
261
262    my $rv = $self->SUPER::read(@_);
263
264    if (defined $lom_language) {   
265    $self->{'input_encoding'}   = $store_input_encoding;
266    $self->{'extract_language'} = $store_extract_language;
267    $self->{'default_language'} = $store_default_language;
268    $self->{'default_encoding'} = $store_default_encoding;
269    }
270
271    $self->{'metadata_table'} = undef;
272
273    return $rv;
274}
275
276# do plugin specific processing of doc_obj
277sub process {
278    my $self = shift (@_);
279    my ($textref, $pluginfo, $base_dir, $file, $metadata, $doc_obj, $gli) = @_;
280    my $outhandle = $self->{'outhandle'};
281
282    my $cursection = $doc_obj->get_top_section();
283    $doc_obj->add_utf8_text($cursection, $$textref);
284
285    return 1;
286}
287
288sub Doctype {
289    my ($expat, $name, $sysid, $pubid, $internal) = @_;
290
291    my $root_tag = $self->{'root_tag'};
292
293    if ($name !~ /$root_tag/) {
294    die "Root tag $name does not match regular expression $root_tag";
295    }
296}
297
298sub StartTag {
299    my ($expat, $element) = @_;
300
301    my %attr = %_;
302   
303    my $raw_tag = "&lt;$element";
304    map { $raw_tag .= " $_=\"$attr{$_}\""; } keys %attr;
305    $raw_tag .= "&gt;";
306
307    if ($element =~ m/$self->{'root_tag'}/) {
308    $self->{'raw_text'} = $raw_tag;
309
310    $self->{'saved_metadata'} = {};
311    $self->{'metaname_stack'} = [];
312    $self->{'lom_datatype'} = "";
313    $self->{'lom_language'} = undef;
314    $self->{'metadatatext'} = "<table class=\"metadata\" width=\"_pagewidth_\" >\n";
315    }
316    else {
317    my $xml_depth = scalar(@{$self->{'metaname_stack'}});
318    $self->{'raw_text'} .= "\n";
319    $self->{'raw_text'} .= "&nbsp;&nbsp;" x $xml_depth;
320    $self->{'raw_text'} .= $raw_tag;
321
322    my $metaname_stack = $self->{'metaname_stack'};
323    push(@$metaname_stack,$element);
324    if (scalar(@$metaname_stack)==1) {
325        # top level LOM category
326        my $style = "class=\"metadata\"";
327        my $open_close
328        = "<a id=\"${element}opencloselink\" href=\"javascript:hideTBodyArea('$element')\">\n";
329        $open_close
330        .= "<img id=\"${element}openclose\" border=\"0\" src=\"_httpopenmdicon_\"></a>\n";
331
332        my $header_line = "  <tr $style ><th $style colspan=\"3\">$open_close \u$element</th></tr>\n";
333        my $md_tbody = "<tbody id=\"$element\">\n";
334
335        $self->{'mdheader'}     = $header_line;
336        $self->{'mdtbody'}      = $md_tbody;
337        $self->{'mdtbody_text'} = "";
338    }
339    }
340}
341
342sub EndTag {
343    my ($expat, $element) = @_;
344
345    my $raw_tag = "&lt;/$element&gt;";
346   
347    if ($element =~ m/$self->{'root_tag'}/) {
348    $self->{'raw_text'} .= $raw_tag;
349
350    my $metadatatext = $self->{'metadatatext'};
351    $metadatatext .= "</table>";
352
353    my $raw_text = $self->{'raw_text'};
354
355    $self->{'saved_metadata'}->{'MetadataTable'} =  $metadatatext;
356    $self->{'metadatatext'} = "";
357
358    $self->{'saved_metadata'}->{'rawtext'} =  $raw_text;
359    $self->{'raw_text'} = "";
360
361    if (defined $self->{'lom_language'}) {
362        $self->{'saved_metadata'}->{'lom_language'} = $self->{'lom_language'};
363        $self->{'lom_language'} = undef;
364    }
365    }
366    else {
367    my $metaname_stack = $self->{'metaname_stack'};
368
369    if (scalar(@$metaname_stack)==1) {
370        my $header_line = $self->{'mdheader'};
371        my $tbody_start = $self->{'mdtbody'};
372        my $tbody_text  = $self->{'mdtbody_text'};
373        if ($tbody_text !~ m/^\s*$/s) {
374        my $tbody_end = "</tbody>\n";
375        my $table_chunk
376            = $header_line.$tbody_start.$tbody_text.$tbody_end;
377
378        $self->{'metadatatext'} .= $table_chunk;
379        }
380        $self->{'mdtheader'}    = "";
381        $self->{'mdtbody'}      = "";
382        $self->{'mdtbody_text'} = "";
383    }
384
385    pop(@$metaname_stack);
386
387    my $xml_depth = scalar(@{$self->{'metaname_stack'}});
388    $self->{'raw_text'} .= "\n";
389    $self->{'raw_text'} .= "&nbsp;&nbsp;" x $xml_depth;
390    $self->{'raw_text'} .= $raw_tag;
391    }
392}
393
394sub process_datatype_info
395{
396    my $self = shift(@_);
397    my ($metaname_stack,$md_content) = @_;
398
399    my @without_dt_stack = @$metaname_stack; # without datatype stack
400
401    my $innermost_element = $without_dt_stack[$#without_dt_stack];
402
403    # Loose last item if encoding datatype information
404    if ($innermost_element =~ m/^(lang)?string$/) {
405    $self->{'lom_datatype'} = $innermost_element;
406
407    pop @without_dt_stack;
408    $innermost_element = $without_dt_stack[$#without_dt_stack];
409    }
410    elsif ($innermost_element =~ m/^date(Time)?$/i) {
411    if ($innermost_element =~ m/^date$/i) {
412        $self->{'lom_datatype'} = "dateTime";
413    }
414    else {
415        $self->{'lom_datatype'} = $innermost_element;
416
417        pop @without_dt_stack;
418        $innermost_element = $without_dt_stack[$#without_dt_stack];
419    }
420
421    if ($md_content =~ m/^(\d{1,2})\s*(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\w*\s*(\d{4})/i) {
422        my ($day,$mon,$year) = ($1,$2,$3);
423       
424        my %month_lookup = ( 'jan' =>  1, 'feb' =>  2, 'mar' =>  3,
425                 'apr' =>  4, 'may' =>  5, 'jun' =>  6,
426                 'jul' =>  7, 'aug' =>  8, 'sep' =>  9,
427                 'oct' => 10, 'nov' => 11, 'dec' => 12 );
428
429        my $mon_num = $month_lookup{lc($mon)};
430
431        $md_content = sprintf("%d%02d%02d",$year,$mon_num,$day);
432    }
433
434    $md_content =~ s/\-//g;
435    }
436
437    if ($innermost_element eq "source") {
438    $self->{'lom_source'} = $md_content;
439    }
440    elsif ($innermost_element eq "value") {
441    $self->{'lom_value'} = $md_content;
442    }
443
444    return (\@without_dt_stack,$innermost_element,$md_content);
445}
446
447sub reset_datatype_info
448{
449    my $self = shift(@_);
450
451    $self->{'lom_datatype'} = "";
452}
453
454
455sub pretty_print_text
456{
457    my $self = shift(@_);
458   
459    my ($pretty_print_text) = @_;
460
461##    $metavalue_utf8 = &util::hyperlink_text($metavalue_utf8);
462    $pretty_print_text = &util::hyperlink_text($pretty_print_text);
463   
464####    $pretty_print_text =~ s/(BEGIN:vCard.*END:vCard)/<pre>$1<\/pre>/sg;
465
466    if ($self->{'lom_datatype'} eq "dateTime") {
467    if ($pretty_print_text =~ m/^(\d{4})(\d{2})(\d{2})$/) {
468        $pretty_print_text = "$1-$2-$3";
469    }
470    }
471
472    return $pretty_print_text;
473}
474
475sub pretty_print_table_tr
476{
477    my $self = shift (@_);
478    my ($without_dt_stack) = @_;
479
480    my $style = "class=\"metadata\"";
481
482    my $innermost_element = $without_dt_stack->[scalar(@$without_dt_stack)-1];
483    my $outermost_element = $without_dt_stack->[0];
484
485    # Loose top level stack item (already named in pretty print table)
486    my @pretty_print_stack = @$without_dt_stack;
487    shift @pretty_print_stack;
488
489    if ($innermost_element eq "source") {
490    return if (!defined $self->{'lom_value'});
491    }
492
493    if ($innermost_element eq "value") {
494    return if (!defined $self->{'lom_source'});
495    }
496
497    my $pretty_print_text = "";
498
499    if (($innermost_element eq "value") || ($innermost_element eq "source")) {
500    my $source = $self->{'lom_source'};
501    my $value  = $self->pretty_print_text($self->{'lom_value'});
502
503    $self->{'lom_source'} = undef;
504    $self->{'lom_value'} = undef;
505
506    pop @pretty_print_stack;
507   
508    $pretty_print_text = "<td $style>$source</td><td $style>$value</td>";
509    }
510    else {
511    $pretty_print_text = $self->pretty_print_text($_);
512    $pretty_print_text = "<td $style colspan=2>$pretty_print_text</td>";
513    }
514    my $pretty_print_fmn = join(' : ',map { "\u$_"; } @pretty_print_stack);
515
516
517    # my $tr_attr = "id=\"$outermost_element\" style=\"display:block;\"";
518    my $tr_attr = "$style id=\"$outermost_element\"";
519
520    my $mdtext_line = "  <tr $tr_attr><td $style><nobr>$pretty_print_fmn</nobr></td>$pretty_print_text</tr>\n";
521    $self->{'mdtbody_text'} .= $mdtext_line;
522}
523
524
525sub check_for_language
526{
527    my $self = shift(@_);
528    my ($innermost_element,$md_content) = @_;
529
530    # Look for 'language' tag
531    if ($innermost_element eq "language") {
532    my $lom_lang = $self->{'lom_language'};
533   
534    if (defined $lom_lang) {
535        my $new_lom_lang = $md_content;
536        $new_lom_lang =~ s/-.*//; # remove endings like -US or -GB
537
538        if ($lom_lang ne $new_lom_lang) {
539        my $outhandle = $self->{'outhandle'};
540       
541        print $outhandle "Warning: Conflicting general language in record\n";
542        print $outhandle "         $new_lom_lang (previous value for language = $lom_lang)\n";
543        }
544        # otherwise, existing value OK => do nothing
545    }
546    else {
547        $lom_lang = $md_content;
548        $lom_lang =~ s/-.*//; # remove endings like -US or -GB
549       
550        $self->{'lom_language'} = $lom_lang;
551    }
552    }
553}
554
555sub found_specific_identifier
556{
557    my $self = shift(@_);
558    my ($specific_id,$full_mname,$md_content) = @_;
559
560    my $found_id = 0;
561    if ($full_mname eq $specific_id) {
562    if ($md_content =~ m/^(http|ftp):/) {
563        $found_id = 1;
564    }
565    }
566
567    return $found_id;
568}
569
570sub download_srcdoc
571{
572    my $self = shift(@_);
573    my ($doc_url) = @_;
574
575    my $outhandle  = $self->{'outhandle'};
576    my $output_dir = $self->{'output_dir'};
577
578    $output_dir = &util::filename_cat($output_dir,"_gsdldown.all");
579
580    if (! -d $output_dir) {
581    mkdir $output_dir;
582    }
583
584    my $re_dirsep = &util::get_re_dirsep();
585    my $os_dirsep = &util::get_dirsep();
586
587    my $file_url = $doc_url;
588    $file_url =~ s/$re_dirsep/$os_dirsep/g;
589    $file_url =~ s/^(http|ftp):\/\///;
590    $file_url .= "index.html" if ($file_url =~ m/\/$/);
591
592    my $full_file_url = &util::filename_cat($output_dir,$file_url);
593    # the path to srcdoc will be used later in extrametadata to associate
594    # the lom metadata with the document. Needs to be relative to current
595    # directory.
596    my $srcdoc_path = &util::filename_cat("_gsdldown.all", $file_url);
597    my $check_timestamp = $self->{'check_timestamp'};
598    my $status;
599
600    if (($check_timestamp) || (!$check_timestamp && !-e $full_file_url)) {
601    if (!-e $full_file_url) {
602        print $outhandle "Mirroring $doc_url\n";
603    }
604    else {
605        print $outhandle "Checking to see if update needed for $doc_url\n";
606    }
607
608    # on linux, if we pass an absolute path as -P arg to wget, then it
609    # stuffs up the
610    # URL rewriting in the file. Need a relative path or none, so now
611    # we change working directory first.
612    my $changed_dir = 0;
613    my $current_dir = cwd();
614    my $wget_cmd = "";
615    if ($ENV{'GSDLOS'} ne "windows") {
616        $changed_dir = 1;
617       
618        chdir "$output_dir";
619        $wget_cmd = "wget -nv  --timestamping -k -p \"$doc_url\"";
620    } else {
621        $wget_cmd = "wget -nv -P \"$output_dir\" --timestamping -k -p \"$doc_url\"";
622    }
623    ##print STDERR "**** wget = $wget_cmd\n";
624
625   
626    $status = system($wget_cmd);
627    if ($changed_dir) {
628        chdir $current_dir;
629    }
630    if ($status==0) {
631        $self->{'lom_srcdoc'} = $srcdoc_path;   
632    }
633    else {
634        $self->{'lom_srcdoc'} = undef;
635        print $outhandle "Error: failed to execute $wget_cmd\n";
636    }
637    }
638    else {
639    # not time-stamping and file already exists
640    $status=0;
641    $self->{'lom_srcdoc'} = $srcdoc_path;   
642    }
643
644    return $status==0;
645   
646}
647
648
649sub check_for_identifier
650{
651    my $self = shift(@_);
652    my ($full_mname,$md_content) = @_;
653
654    my $success = 0;
655
656    my $download_re = $self->{'download_srcdocs'};
657    if (($download_re ne "") && $md_content =~ m/$download_re/) {
658   
659    if ($self->found_specific_identifier("general^identifier^entry",$full_mname,$md_content)) {
660        $success = $self->download_srcdoc($md_content);
661    }
662
663    if (!$success) {
664        if ($self->found_specific_identifier("technical^location",$full_mname,$md_content)) {
665        $success = $self->download_srcdoc($md_content);
666        }
667    }
668    }
669
670    return $success;
671}
672
673
674sub Text {
675    if ($_ !~ m/^\s*$/) {
676    #
677    # Work out indentations and line wraps for raw XML
678    #
679    my $xml_depth = scalar(@{$self->{'metaname_stack'}})+1;
680    my $indent = "&nbsp;&nbsp;" x $xml_depth;
681   
682    my $formatted_text = "\n".$_;
683
684    # break into lines < 80 chars on space
685    $formatted_text =~ s/(.{50,80})\s+/$1\n/mg;
686    $formatted_text =~ s/^/$indent/mg;
687    ## $formatted_text =~ s/\s+$//s;
688
689    $self->{'raw_text'} .= $formatted_text;
690    }
691
692    my $metaname_stack = $self->{'metaname_stack'};
693    if (($_ !~ /^\s*$/) && (scalar(@$metaname_stack)>0)) {
694
695    my ($without_dt_stack,$innermost_element,$md_content)
696        = $self->process_datatype_info($metaname_stack,$_);
697
698    $self->pretty_print_table_tr($without_dt_stack);
699
700    my $full_mname = join('^',@{$without_dt_stack});
701    $self->set_filere_metadata(lc($full_mname),$md_content);
702
703    $self->check_for_language($innermost_element,$md_content);
704    $self->check_for_identifier($full_mname,$md_content); # source doc
705
706    $self->reset_datatype_info();
707    }
708}
709
710# This Char function overrides the one in XML::Parser::Stream to overcome a
711# problem where $expat->{Text} is treated as the return value, slowing
712# things down significantly in some cases.
713sub Char {
714  $_[0]->{'Text'} .= $_[1];
715  return undef;
716}
717
7181;
Note: See TracBrowser for help on using the browser.