File Coverage

blib/lib/Dezi/Indexer/Config.pm
Criterion Covered Total %
statement 28 30 93.3
branch n/a
condition n/a
subroutine 10 10 100.0
pod n/a
total 38 40 95.0


line stmt bran cond sub pod time code
1             package Dezi::Indexer::Config;
2 11     11   29954 use Moose;
  11         967965  
  11         84  
3             with 'Dezi::Role';
4 11     11   78290 use Types::Standard qw( Str HashRef );
  11         131214  
  11         119  
5 11     11   13626 use MooseX::Types::Path::Class;
  11         2451538  
  11         133  
6 11     11   10438 use Carp;
  11         27  
  11         819  
7 11     11   14281 use Config::General;
  11         178467  
  11         690  
8 11     11   1870 use Data::Dump qw( dump );
  11         11634  
  11         612  
9 11     11   71 use File::Temp ();
  11         22  
  11         209  
10 11     11   10381 use Search::Tools::XML;
  11         1176686  
  11         562  
11 11     11   102 use Search::Tools::UTF8;
  11         24  
  11         1432  
12 11     11   6924 use Dezi::Utils;
  0            
  0            
13             use File::Spec;
14             use Path::Class::File;
15             use SWISH::3;
16             use overload(
17             '""' => \&stringify,
18             bool => sub {1},
19             fallback => 1,
20             );
21              
22             use namespace::autoclean;
23              
24             our $VERSION = '0.014';
25              
26             # only a few explicitly named attributes.
27             # everything else is through AUTOLOAD.
28             has '_orig_args' => ( is => 'ro', isa => HashRef, default => sub { {} } );
29             has 'file' => ( is => 'rw', isa => 'Path::Class::File', coerce => 1, );
30             has 'swish3_config' => ( is => 'ro', isa => Str );
31              
32             my $XML = Search::Tools::XML->new;
33              
34             my %ReservedFieldNames = map { $_ => 1 } qw(uri title summary mtime);
35              
36             my %unique = map { $_ => 1 } qw(
37             MetaNames
38             PropertyNames
39             PropertyNamesNoStripChars
40             IncludeConfigFile
41             );
42              
43             my %takes_single_value = map { $_ => 1 } qw(
44             IndexFile
45             FuzzyIndexingMode
46             );
47              
48             my @Opts = qw(
49             AbsoluteLinks
50             BeginCharacters
51             BumpPositionCounterCharacters
52             Buzzwords
53             CascadeMetaContext
54             ConvertHTMLEntities
55             DefaultContents
56             Delay
57             DontBumpPositionOnEndTags
58             DontBumpPositionOnStartTags
59             EnableAltSearchSyntax
60             EndCharacters
61             EquivalentServer
62             ExtractPath
63             FileFilter
64             FileFilterMatch
65             FileInfoCompression
66             FileMatch
67             FileRules
68             FollowSymLinks
69             FollowXInclude
70             FuzzyIndexingMode
71             HTMLLinksMetaName
72             IgnoreFirstChar
73             IgnoreLastChar
74             IgnoreLimit
75             IgnoreMetaTags
76             IgnoreNumberChars
77             IgnoreTotalWordCountWhenRanking
78             IgnoreWords
79             ImageLinksMetaName
80             IncludeConfigFile
81             IndexAdmin
82             IndexAltTagMetaName
83             IndexComments
84             IndexContents
85             IndexDescription
86             IndexDir
87             IndexFile
88             IndexName
89             IndexOnly
90             IndexPointer
91             IndexReport
92             MaxDepth
93             MaxWordLimit
94             MetaNameAlias
95             MetaNames
96             MetaNamesRank
97             MinWordLimit
98             NoContents
99             obeyRobotsNoIndex
100             ParserWarnLevel
101             PreSortedIndex
102             PropCompressionLevel
103             PropertyNameAlias
104             PropertyNames
105             PropertyNamesCompareCase
106             PropertyNamesDate
107             PropertyNamesIgnoreCase
108             PropertyNamesMaxLength
109             PropertyNamesNoStripChars
110             PropertyNamesNumeric
111             PropertyNamesSortKeyLength
112             RecursionDepth
113             ReplaceRules
114             ResultExtFormatName
115             SpiderDirectory
116             StoreDescription
117             SwishProgParameters
118             SwishSearchDefaultRule
119             SwishSearchOperators
120             TagAlias
121             TmpDir
122             TranslateCharacters
123             TruncateDocSize
124             UndefinedMetaTags
125             UndefinedMetaNames
126             UndefinedXMLAttributes
127             UseSoundex
128             UseStemming
129             UseWords
130             WordCharacters
131             Words
132             XMLClassAttributes
133             );
134              
135             # easy lookup for AUTOLOAD
136             my %Opts = map { $_ => $_ } @Opts;
137              
138             sub AUTOLOAD {
139             my $self = shift;
140             my $method = our $AUTOLOAD;
141             $method =~ s/.*://;
142             return if $method eq 'DESTROY';
143             if ( !exists $Opts{$method} ) {
144             confess("method '$method' not implemented in $self");
145             }
146             if (@_) {
147             return $self->_set( $method, @_ );
148             }
149             else {
150             return $self->_get($method);
151             }
152             }
153              
154             =head1 NAME
155              
156             Dezi::Indexer::Config - read/write Indexer config files
157              
158             =head1 SYNOPSIS
159              
160             use Dezi::Indexer::Config;
161            
162             my $config = Dezi::Indexer::Config->new;
163             $config->write2();
164             $config->read2('path/to/file.conf');
165             $config->write3();
166            
167            
168             =head1 DESCRIPTION
169              
170             The Dezi::Indexer::Config class reads and writes Swish-e 2.x configuration files,
171             and converts them to Swish3-style XML configuration format.
172              
173             See the Swish-e documentation for a list of configuration parameters.
174             Each parameter has an accessor/mutator method as part of the Config object.
175             L<http://swish-e.org/docs/swish-config.html>.
176              
177             B<NOTE:> Every config parameter can take either a scalar or an array ref as a value.
178             In addition, you may append config values to any existing values by passing an additional
179             true argument. The return value of any 'get' is always an array ref.
180              
181             Example:
182              
183             $config->MetaNameAlias( ['foo bar', 'one two', 'red yellow'] );
184             $config->MetaNameAlias( 'green blue', 1 );
185             print join("\n", @{ $config->MetaNameAlias }), " \n";
186             # would print:
187             # foo bar
188             # one two
189             # red yellow
190             # green blue
191            
192              
193             =head1 METHODS
194              
195             =head2 new( I<params> )
196              
197             Instantiate a new Config object.
198             Takes a hash of key/value pairs, where each key
199             may be a configuration parameter.
200              
201             Example:
202              
203             my $config = Dezi::Indexer::Config->new( DefaultContents => 'HTML*' );
204            
205             print "DefaultContents is ", $config->DefaultContents, "\n";
206            
207             =cut
208              
209             around BUILDARGS => sub {
210             my $orig = shift;
211             my $class = shift;
212              
213             if ( @_ == 1 ) {
214             return $class->$orig( file => $_[0] );
215             }
216             else {
217             return $class->$orig( @_, _orig_args => {@_} );
218             }
219             };
220              
221             =head2 BUILD
222              
223             Internal method called by new().
224              
225             =cut
226              
227             sub BUILD {
228             my $self = shift;
229              
230             # use our custom get/set methods on original args
231             my $orig = $self->_orig_args;
232             for my $k (keys %$orig) {
233             $self->$k( $orig->{$k} );
234             }
235              
236             $self->{swish3} = SWISH::3->new();
237              
238             if ( $self->file ) {
239             if ( !$self->looks_like_swish3_config ) {
240             $self->read2( $self->file );
241             }
242             else {
243             $self->read3( $self->file );
244             }
245             }
246              
247             $self->IgnoreTotalWordCountWhenRanking(0)
248             unless defined $self->IgnoreTotalWordCountWhenRanking;
249             }
250              
251             =head2 looks_like_swish3_config
252              
253             Simple heuristics to test whether B<file> represents a libswish3-style
254             file or string.
255              
256             =cut
257              
258             sub looks_like_swish3_config {
259             my $self = shift;
260             return 0 unless defined $self->file;
261             if ( $self->file =~ m/\.xml/ ) { return 1 } # file
262             if ( !-r $self->file and $self->file =~ m/<swish>/ ) { return 1 } # string
263             return 0;
264             }
265              
266             =head2 as_swish3_config
267              
268             Returns the object as a XML string in libswish3 header format.
269              
270             =cut
271              
272             sub as_swish3_config {
273             my $self = shift;
274             if ( $self->looks_like_swish3_config and $self->swish3_config ) {
275             return $self->swish3_config;
276             }
277             else {
278             return $self->ver2_to_ver3();
279             }
280             }
281              
282             =head2 get_opt_names
283              
284             Class method.
285              
286             Returns array ref of all the option (method) names supported.
287              
288             =cut
289              
290             sub get_opt_names {
291             return [@Opts];
292             }
293              
294             sub _set {
295             my $self = shift;
296             my ( $key, $val, $append ) = @_;
297              
298             if ( $key eq 'file' or $key eq 'debug' ) {
299             confess "Moose should handle $key attribute";
300             return $self->{$key} = $val;
301             }
302             elsif ( exists $unique{$key} ) {
303             return $self->_name_hash( $key, $val );
304             }
305              
306             $self->{$key} = [] unless defined $self->{$key};
307              
308             # save everything as an array ref regardless of input
309             if ( ref $val ) {
310             if ( ref($val) eq 'ARRAY' ) {
311             $self->{$key} = $append ? [ @{ $self->{$key} }, @$val ] : $val;
312             }
313             else {
314             croak "$key cannot accept a " . ref($val) . " ref as a value";
315             }
316             }
317             else {
318             $self->{$key} = $append ? [ @{ $self->{$key} }, $val ] : [$val];
319             }
320              
321             }
322              
323             sub _get {
324             my $self = shift;
325             my $key = shift;
326              
327             if ( exists $unique{$key} ) {
328             return $self->_name_hash($key);
329             }
330             elsif ( exists $takes_single_value{$key} ) {
331             return $self->{$key}->[0];
332             }
333             else {
334             return $self->{$key};
335             }
336             }
337              
338             sub _name_hash {
339             my $self = shift;
340             my $name = shift;
341              
342             if (@_) {
343              
344             #carp "setting $name => " . join(', ', @_);
345             for my $v (@_) {
346             my @v = ref $v ? @$v : ($v);
347             $self->{$name}->{ lc($_) } = 1 for @v;
348             }
349             }
350             else {
351              
352             #carp "getting $name -> " . join(', ', sort keys %{$self->{$name}});
353              
354             }
355              
356             return [ sort keys %{ $self->{$name} } ];
357             }
358              
359             =head2 read2( I<path/file> )
360              
361             Reads version 2 compatible config file and stores in current object.
362             Returns parsed config file as a hashref or undef on failure to parse.
363              
364             Example:
365              
366             use Dezi::Indexer::Config;
367             my $config = Dezi::Indexer::Config->new();
368             my $parsed = $config->read2( 'my/file.cfg' );
369            
370             # should print same thing
371             print $config->WordCharacters->[0], "\n";
372             print $parsed->{WordCharacters}, "\n";
373            
374            
375             =cut
376              
377             sub read2 {
378             my $self = shift;
379             my $file = shift or croak "version2 type file required";
380              
381             # stringify $file in case it is an object
382             my $buf = SWISH::3->slurp("$file");
383              
384             # filter include syntax to work with Config::General's
385             $buf =~ s,IncludeConfigFile (.+?)\n,Include $1\n,g;
386              
387             my ( $volume, $dir, $filename ) = File::Spec->splitpath($file);
388              
389             my $c = Config::General->new(
390             -String => $buf,
391             -IncludeRelative => 1,
392             -ConfigPath => [$dir],
393             -ApacheCompatible => 1,
394             ) or return;
395              
396             my %conf = $c->getall;
397              
398             # not sure why \0 appears sometimes
399             delete $conf{"\0"};
400              
401             $self->debug and carp "Parsed $file: " . dump \%conf;
402              
403             for ( keys %conf ) {
404             my $v = $conf{$_};
405             next unless defined($v) and $_;
406             $self->$_( $v, 1 );
407             }
408              
409             return \%conf;
410             }
411              
412             =head2 read3( I<file> )
413              
414             Slurps I<file> into the swish3_config() attribute.
415              
416             =cut
417              
418             sub read3 {
419             my $self = shift;
420             my $file = shift or confess "version3 type file required";
421              
422             $self->{swish3_config} = SWISH::3->slurp("$file");
423             }
424              
425             =head2 write2( I<path/file> [,I<prog_mode>] )
426              
427             Writes Swish-e version 2 compatible config file.
428              
429             If I<path/file> is omitted, a temp file will be
430             written using File::Temp.
431              
432             If I<prog_mode> is true all config directives
433             inappropriate for the -S prog mode in the Native::Indexer
434             are skipped. The default is false.
435              
436             Returns full path to file.
437              
438             Full path is also available via file() method.
439              
440             =head2 file
441              
442             Returns name of the file written by write2().
443              
444             =cut
445              
446             sub write2 {
447             my $self = shift;
448             my $file = shift || File::Temp->new();
449             my $prog_mode = shift || 0;
450              
451             # stringify both
452             Path::Class::File->new("$file")->spew( $self->stringify($prog_mode) );
453              
454             #warn "$self";
455              
456             warn "wrote config file $file" if $self->debug;
457              
458             # remember file. this especially crucial for File::Temp
459             # since we want it to hang around till $self is DESTROYed
460             if ( ref $file ) {
461             $self->{__tmpfile} = $file;
462             }
463             $self->file("$file");
464              
465             return $self->file;
466             }
467              
468             =head2 write3( I<path/file> )
469              
470             Write config object to file in SWISH::3::Config XML format.
471              
472             =cut
473              
474             sub write3 {
475             my $self = shift;
476             my $file = shift or croak "file required";
477              
478             Path::Class::File->new("$file")->spew( $self->ver2_to_ver3 );
479              
480             warn "wrote config file $file" if $self->debug;
481              
482             return $self;
483             }
484              
485             =head2 as_hash
486              
487             Returns current Config object as a hash ref.
488              
489             =cut
490              
491             sub as_hash {
492             my $self = shift;
493             my $c = Config::General->new( -String => $self->stringify );
494             return { $c->getall };
495             }
496              
497             =head2 all_metanames
498              
499             Returns array ref of all MetaNames, regardless of whether they
500             are declared as MetaNames, MetaNamesRank or MetaNameAlias config
501             options.
502              
503             =cut
504              
505             sub all_metanames {
506             my $self = shift;
507             my @meta = @{ $self->MetaNames };
508             for my $line ( @{ $self->MetaNamesRank || [] } ) {
509             my ( $bias, @list ) = split( m/\ +/, $line );
510             push( @meta, @list );
511             }
512             for my $line ( @{ $self->MetaNameAlias || [] } ) {
513             my ( $orig, @alias ) = split( m/\ +/, $line );
514             push( @meta, @alias );
515             }
516             return \@meta;
517             }
518              
519             =head2 stringify([I<prog_mode>])
520              
521             Returns object as version 2 formatted scalar.
522              
523             If I<prog_mode> is true skips inappropriate directives for
524             running the Native::Indexer. Default is false. See write2().
525              
526             This method is used to overload the object for printing, so these are
527             equivalent:
528              
529             print $config->stringify;
530             print $config;
531              
532             =cut
533              
534             sub stringify {
535             my $self = shift;
536             my $prog_mode = shift || 0;
537             my @config;
538              
539             # must pass metanames and properties first, since others may depend on them
540             # in swish config parsing.
541             for my $method ( keys %unique ) {
542             my $v = $self->$method;
543              
544             next unless scalar(@$v);
545              
546             #carp "adding $method to config";
547             push( @config, "$method " . join( ' ', @$v ) );
548             }
549              
550             for my $name (@Opts) {
551             next if exists $unique{$name};
552             if ( $prog_mode && $name =~ m/^File/ ) {
553             next;
554             }
555              
556             my $v = $self->$name;
557             next unless defined $v;
558             if ( ref $v ) {
559             push( @config, "$name $_" ) for @$v;
560             }
561             else {
562             push( @config, "$name $v" );
563             }
564             }
565              
566             my $buf = join( "\n", @config ) . "\n";
567              
568             print STDERR $buf if $self->debug;
569              
570             return $buf;
571             }
572              
573             sub _write_utf8 {
574             my ( $self, $file, $buf ) = @_;
575             binmode $file, ':utf8';
576             print {$file} $buf;
577             }
578              
579             =head2 ver2_to_ver3( I<file> )
580              
581             Utility method for converting Swish-e version 2 style config files
582             to SWISH::3::Config XML style.
583              
584             Converts I<file> to XML format and returns as XML string.
585              
586             my $xmlconf = $config->ver2_to_ver3( 'my/file.config' );
587              
588             If I<file> is omitted, uses the current values in the calling object.
589              
590             The following fields are converted but are ignored by SWISH::3::Config.
591              
592             AbsoluteLinks
593             BumpPositionCounterCharacters
594             Buzzwords
595             BeginCharacters
596             ConvertHTMLEntities
597             Delay
598             DontBumpPositionOnEndTags
599             DontBumpPositionOnStartTags
600             EnableAltSearchSyntax
601             EndCharacters
602             EquivalentServer
603             ExtractPath
604             FileFilter
605             FileFilterMatch
606             FileMatch
607             FileRules
608             HTMLLinksMetaName
609             IgnoreFirstChar
610             IgnoreLastChar
611             IgnoreLimit
612             IgnoreMetaTags
613             IgnoreNumberChars
614             IgnoreTotalWordCountWhenRanking
615             IgnoreWords
616             ImageLinksMetaName
617             IndexAltTagMetaName
618             IndexComments
619             IndexDir
620             IndexOnly
621             IndexPointer
622             IndexReport
623             MaxDepth
624             MaxWordLimit
625             MinWordLimit
626             NoContents
627             obeyRobotsNoIndex
628             PreSortedIndex
629             PropCompressionLevel
630             RecursionDepth
631             ReplaceRules
632             ResultExtFormatName
633             SpiderDirectory
634             SwishProgParameters
635             SwishSearchDefaultRule
636             SwishSearchOperators
637             TmpDir
638             TranslateCharacters
639             TruncateDocSize
640             UseSoundex
641             UseStemming
642             UseWords
643             WordCharacters
644             Words
645              
646             The following fields are converted to the their SWISH::3::Config
647             equivalents.
648              
649             CascadeMetaContext
650             DefaultContents
651             FollowSymLinks
652             FollowXInclude
653             FuzzyIndexingMode
654             IncludeConfigFile
655             IndexAdmin
656             IndexContents
657             IndexDescription
658             IndexDir
659             IndexFile
660             IndexName
661             IndexReport
662             MetaNameAlias
663             MetaNames
664             MetaNamesRank
665             ParserWarnLevel
666             PropertyNameAlias
667             PropertyNames
668             PropertyNamesCompareCase
669             PropertyNamesDate
670             PropertyNamesIgnoreCase
671             PropertyNamesMaxLength
672             PropertyNamesNoStripChars
673             PropertyNamesNumeric
674             PropertyNamesSortKeyLength
675             StoreDescription
676             TagAlias
677             UndefinedMetaTags
678             UndefinedMetaNames
679             UndefinedXMLAttributes
680             XMLClassAttributes
681            
682             =cut
683              
684             sub ver2_to_ver3 {
685             my $self = shift;
686             my $file = shift;
687             my $no_timestamp = shift || 0;
688              
689             my $s3 = SWISH::3->new();
690              
691             # list of config directives that take arguments to the opt value
692             # i.e. the directive has 3 or more parts
693             my %takes_arg = map { $_ => 1 } qw(
694             DefaultContents
695             ExtractPath
696             FileFilter
697             FileRules
698             IgnoreWords
699             IndexContents
700             MetaNameAlias
701             MetaNamesRank
702             PropertyNameAlias
703             PropertyNamesMaxLength
704             PropertyNamesSortKeyLength
705             ReplaceRules
706             StoreDescription
707             TagAlias
708             Words
709             );
710              
711             my %parser_map = (
712             'XML' => 'application/xml',
713             'HTML' => 'text/html',
714             'TXT' => 'text/plain',
715             );
716              
717             my %remap = (
718             'IndexDir' => 'SourceDir',
719             'IndexOnly' => 'SourceOnly',
720             'IndexReport' => 'Verbosity',
721             );
722              
723             my %unsupported = map { $_ => 1 } qw(
724             AbsoluteLinks
725             BumpPositionCounterCharacters
726             Buzzwords
727             BeginCharacters
728             ConvertHTMLEntities
729             Delay
730             DontBumpPositionOnEndTags
731             DontBumpPositionOnStartTags
732             EnableAltSearchSyntax
733             EndCharacters
734             EquivalentServer
735             ExtractPath
736             FileFilter
737             FileFilterMatch
738             FileMatch
739             FileRules
740             HTMLLinksMetaName
741             IgnoreFirstChar
742             IgnoreLastChar
743             IgnoreLimit
744             IgnoreMetaTags
745             IgnoreNumberChars
746             IgnoreTotalWordCountWhenRanking
747             IgnoreWords
748             ImageLinksMetaName
749             IndexAltTagMetaName
750             IndexComments
751             IndexOnly
752             IndexPointer
753             MaxDepth
754             MaxWordLimit
755             MinWordLimit
756             NoContents
757             obeyRobotsNoIndex
758             PreSortedIndex
759             PropCompressionLevel
760             RecursionDepth
761             ReplaceRules
762             ResultExtFormatName
763             SourceDir
764             SourceOnly
765             SpiderDirectory
766             SwishProgParameters
767             SwishSearchDefaultRule
768             SwishSearchOperators
769             TmpDir
770             TranslateCharacters
771             TruncateDocSize
772             UseSoundex
773             UseStemming
774             UseWords
775             Verbosity
776             WordCharacters
777             Words
778             );
779             my $disclaimer = "<!-- WARNING: CONFIG ignored by Swish3 -->\n ";
780              
781             my $class = ref($self) || $self;
782             my $config = $file ? $class->new->read2($file) : $self->as_hash;
783             my $time = $no_timestamp ? '' : localtime();
784              
785             # if we were not passed a file name, all the config resolution
786             # has already been done, so do not perpetuate.
787             if ( !$file ) {
788             delete $config->{IncludeConfigFile};
789             }
790              
791             my $xml = <<EOF;
792             <?xml version="1.0" encoding="UTF-8"?>
793             <!-- converted with Dezi::Indexer::Config ver2_to_ver3() $time -->
794             <swish>
795             EOF
796              
797             my $debug = ref($self) ? $self->debug : 0;
798             $debug and warn dump $config;
799              
800             # first convert the $config ver2 hash into a ver3 hash
801             my %conf3 = (
802             MetaNames => {},
803             PropertyNames => {},
804             Index => { Format => ['Native'], },
805             MIME => {},
806             Parsers => {},
807             TagAlias => {},
808             );
809              
810             #warn dump $config;
811              
812             KEY: for my $k ( sort keys %$config ) {
813             my @args = ref $config->{$k} ? @{ $config->{$k} } : ( $config->{$k} );
814              
815             $debug and warn "$k => " . dump( \@args );
816              
817             if ( $k eq 'MetaNames' ) {
818             for my $line (@args) {
819             for my $metaname ( split( m/\ +/, $line ) ) {
820             $conf3{'MetaNames'}->{$metaname} ||= {};
821             }
822             }
823             }
824             elsif ( $k eq 'MetaNamesRank' ) {
825             for my $pair (@args) {
826             my ( $bias, $names ) = ( $pair =~ m/^([\-\d]+) +(.+)$/ );
827             for my $name ( split( m/\ +/, $names ) ) {
828             $conf3{'MetaNames'}->{$name}->{bias} = $bias;
829             }
830             }
831             }
832             elsif ( $k eq 'TagAlias' ) {
833             for my $pair (@args) {
834             my ( $name, $aliases ) = ( $pair =~ m/^(\S+) +(.+)$/ );
835             for my $alias ( split( m/\ +/, $aliases ) ) {
836             $conf3{'TagAlias'}->{$alias} = $name;
837             }
838             }
839             }
840             elsif ( $k eq 'MetaNameAlias' ) {
841             for my $pair (@args) {
842             my ( $name, $aliases ) = ( $pair =~ m/^(\S+) +(.+)$/ );
843             for my $alias ( split( m/\ +/, $aliases ) ) {
844             $conf3{'MetaNames'}->{$alias}->{alias_for} = $name;
845             }
846             }
847             }
848             elsif ( $k eq 'PropertyNames' ) {
849             for my $line (@args) {
850             for my $name ( split( m/\ +/, $line ) ) {
851             $conf3{'PropertyNames'}->{$name} ||= {};
852             }
853             }
854             }
855             elsif ( $k eq 'PropertyNamesCompareCase' ) {
856             for my $line (@args) {
857             for my $name ( split( m/\ +/, $line ) ) {
858             $conf3{'PropertyNames'}->{$name}->{ignore_case} = 0;
859             }
860             }
861             }
862             elsif ( $k eq 'PropertyNamesIgnoreCase' ) {
863             for my $line (@args) {
864             for my $name ( split( m/\ +/, $line ) ) {
865             $conf3{'PropertyNames'}->{$name}->{ignore_case} = 1;
866             }
867             }
868             }
869             elsif ( $k eq 'PropertyNamesNoStripChars' ) {
870             for my $line (@args) {
871             for my $name ( split( m/\ +/, $line ) ) {
872             $conf3{'PropertyNames'}->{$name}->{verbatim} = 1;
873             }
874             }
875             }
876             elsif ( $k eq 'PropertyNamesNumeric' ) {
877             for my $line (@args) {
878             for my $name ( split( m/\ +/, $line ) ) {
879             $conf3{'PropertyNames'}->{$name}->{type} = 'int';
880             }
881             }
882             }
883             elsif ( $k eq 'PropertyNamesDate' ) {
884             for my $line (@args) {
885             for my $name ( split( m/\ +/, $line ) ) {
886             $conf3{'PropertyNames'}->{$name}->{type} = 'date';
887             }
888             }
889             }
890             elsif ( $k eq 'PropertyNameAlias' ) {
891             for my $pair (@args) {
892             my ( $name, $aliases ) = ( $pair =~ m/^(\S+) +(.+)$/ );
893             for my $alias ( split( m/\ +/, $aliases ) ) {
894             $conf3{'PropertyNames'}->{$alias}->{alias_for} = $name;
895             }
896             }
897             }
898             elsif ( $k eq 'PropertyNamesMaxLength' ) {
899             for my $pair (@args) {
900             my ( $max, $names ) = ( $pair =~ m/^([\d]+) +(.+)$/ );
901             for my $name ( split( m/\ +/, $names ) ) {
902             $conf3{'PropertyNames'}->{$name}->{max} = $max;
903             }
904             }
905             }
906             elsif ( $k eq 'PropertyNamesSortKeyLength' ) {
907             for my $pair (@args) {
908             my ( $len, $names ) = ( $pair =~ m/^([\d]+) +(.+)$/ );
909             for my $name ( split( m/\ +/, $names ) ) {
910             $conf3{'PropertyNames'}->{$name}->{sort_length} = $len;
911             }
912             }
913             }
914             elsif ( $k eq 'PreSortedIndex' ) {
915             for my $line (@args) {
916             for my $name ( split( m/\ +/, $line ) ) {
917             $conf3{'PropertyNames'}->{$name}->{sort} = 1;
918             }
919             }
920             }
921             elsif ( $k eq 'StoreDescription' ) {
922             for my $line (@args) {
923             my ( $parser_type, $tag, $len )
924             = ( $line =~ m/^(XML|HTML|TXT)[2\*]? +<(.+?)> ?(\d*)$/ );
925             if ( !$tag ) {
926             warn "unparsed config2 line for StoreDescription: $line";
927             next;
928             }
929             $conf3{'PropertyNames'}->{$tag}->{alias_for}
930             = 'swishdescription';
931             }
932             }
933              
934             elsif ( $k eq 'IndexContents' ) {
935             for my $line (@args) {
936             my ( $parser_type, $file_ext )
937             = ( $line =~ m/^(XML|HTML|TXT)[2\*]? +(.+)$/ );
938              
939             if ( !exists $parser_map{$parser_type} ) {
940             warn "Unsupported Parser type: $parser_type\n";
941             next;
942             }
943              
944             for my $ext ( split( m/\ +/, $file_ext ) ) {
945             $ext =~ s/^\.//;
946             my $mime = $s3->get_mime("null.$ext")
947             || $parser_map{$parser_type};
948             if ( exists $conf3{Parsers}->{$parser_type}
949             and exists $conf3{Parsers}->{$parser_type}->{$mime} )
950             {
951             warn
952             "parser type $parser_type already defined for $mime\n";
953             next;
954             }
955             if ( exists $parser_map{$parser_type}
956             and $parser_map{$parser_type} eq $mime )
957             {
958              
959             # already a default
960             next;
961             }
962             $conf3{Parsers}->{$parser_type}->{$mime} = $ext;
963             if ( exists $conf3{MIME}->{$ext} ) {
964             warn "file extension '$ext' already defined\n";
965             next;
966             }
967             $conf3{MIME}->{$ext} = $mime;
968             }
969             }
970             }
971             elsif ( $k eq 'DefaultContents' ) {
972             my $parser = $args[0];
973             $conf3{Parsers}->{default}->{$parser} = $parser;
974             }
975             elsif ( exists $remap{$k} ) {
976             push( @{ $conf3{ $remap{$k} } }, @args );
977             }
978             elsif ( $k =~ m/^Index(\w+)/ ) {
979             my $tag = $1;
980             push( @{ $conf3{'Index'}->{$tag} }, join( ' ', @args ) );
981             }
982              
983             else {
984             push( @{ $conf3{$k} }, @args );
985             }
986              
987             }
988              
989             # now convert %conf3 to XML
990              
991             # deal with these special cases separately
992             my $metas = delete $conf3{'MetaNames'};
993             my $props = delete $conf3{'PropertyNames'};
994             my $index = delete $conf3{'Index'};
995             my $mimes = delete $conf3{'MIME'};
996             my $parsers = delete $conf3{'Parsers'};
997             my $tag_alias = delete $conf3{'TagAlias'};
998              
999             for my $k ( sort keys %conf3 ) {
1000             my $key = to_utf8($k);
1001             for my $v ( @{ $conf3{$k} } ) {
1002             my $val = $XML->escape( to_utf8($v) );
1003             my $note = '';
1004              
1005             # $key fails to register in exists() below under 5.10
1006             if ( exists $unsupported{$k} ) {
1007             $note = $disclaimer;
1008             $note =~ s/CONFIG/$key/;
1009             }
1010             $xml .= " $note<$key>$val</$key>\n";
1011             }
1012             }
1013              
1014             if ( keys %$metas ) {
1015             $xml .= " <MetaNames>\n";
1016             for my $name ( sort keys %$metas ) {
1017             my $uname = to_utf8($name);
1018             if ( exists $ReservedFieldNames{$uname} ) {
1019             warn
1020             "'$uname' is a reserved field name and may clash at search time\n";
1021             }
1022             $xml .= sprintf( " <%s />\n",
1023             $self->_make_tag( $uname, $metas->{$name} ) );
1024             }
1025             $xml .= " </MetaNames>\n";
1026             }
1027             if ( keys %$props ) {
1028             $xml .= " <PropertyNames>\n";
1029             for my $name ( sort keys %$props ) {
1030             my $uname = to_utf8($name);
1031             if ( exists $ReservedFieldNames{$uname} ) {
1032             warn
1033             "'$uname' is a reserved field name and may clash at search time\n";
1034             }
1035             $xml .= sprintf( " <%s />\n",
1036             $self->_make_tag( $uname, $props->{$name} ) );
1037             }
1038             $xml .= " </PropertyNames>\n";
1039             }
1040              
1041             $xml .= " <Index>\n";
1042             for my $tag ( sort keys %$index ) {
1043             for my $val ( @{ $index->{$tag} } ) {
1044             $xml .= sprintf( " <%s>%s</%s>\n", $tag, $XML->escape($val),
1045             $tag );
1046             }
1047             }
1048             if ( $conf3{FuzzyIndexingMode} ) {
1049             $debug
1050             and warn "got FuzzyIndexingMode: $conf3{FuzzyIndexingMode}->[0]";
1051             $xml .= sprintf(
1052             " <%s>%s</%s>\n",
1053             "Stemmer",
1054             $XML->escape(
1055             $self->get_stemmer_lang( $conf3{FuzzyIndexingMode}->[0] )
1056             ),
1057             "Stemmer"
1058             );
1059             }
1060             $xml .= " </Index>\n";
1061              
1062             if ( keys %$mimes ) {
1063             $xml .= " <MIME>\n";
1064             for my $ext ( sort keys %$mimes ) {
1065             my $mime = $mimes->{$ext};
1066             $xml .= sprintf( " <%s>%s</%s>\n",
1067             $XML->tag_safe($ext),
1068             $XML->escape($mime), $XML->tag_safe($ext) );
1069             }
1070             $xml .= " </MIME>\n";
1071             }
1072              
1073             if ( keys %$parsers ) {
1074             $xml .= " <Parsers>\n";
1075             for my $parser ( sort keys %$parsers ) {
1076             for my $mime ( sort keys %{ $parsers->{$parser} } ) {
1077             $xml .= sprintf( " <%s>%s</%s>\n",
1078             $XML->tag_safe($parser),
1079             $XML->escape($mime), $XML->tag_safe($parser) );
1080             }
1081             }
1082             $xml .= " </Parsers>\n";
1083             }
1084              
1085             if ( keys %$tag_alias ) {
1086             $xml .= " <TagAlias>\n";
1087             for my $alias ( sort keys %$tag_alias ) {
1088             my $name = $tag_alias->{$alias};
1089             $xml .= sprintf( " <%s>%s</%s>\n",
1090             $XML->tag_safe($alias),
1091             $XML->escape($name), $XML->tag_safe($alias) );
1092             }
1093             $xml .= " </TagAlias>\n";
1094             }
1095              
1096             $xml .= "</swish>\n";
1097              
1098             return $xml;
1099              
1100             }
1101              
1102             sub _make_tag {
1103             my ( $self, $tag, $attrs ) = @_;
1104             return $XML->tag_safe($tag) . $XML->attr_safe($attrs);
1105             }
1106              
1107             =head2 get_stemmer_lang([ I<fuzzymode> ])
1108              
1109             Returns the 2-letter language code for the Snowball stemmer
1110             corresponding to I<fuzzymode>. If I<fuzzymode> is not defined,
1111             calls FuzzyIndexingMode() method on the config object.
1112              
1113             =cut
1114              
1115             sub get_stemmer_lang {
1116             my $self = shift;
1117             my $lang = shift || $self->FuzzyIndexingMode;
1118             $self->debug and warn "get_stemmer_lang for '$lang'";
1119             if ( $lang and $lang =~ m/^Stemming_(\w\w)/ ) {
1120             return $1;
1121             }
1122             return 'none';
1123             }
1124              
1125             __PACKAGE__->meta->make_immutable;
1126              
1127             1;
1128              
1129             __END__
1130              
1131             =head1 CAVEATS
1132              
1133             IgnoreTotalWordCountWhenRanking defaults to 0
1134             which is B<not> the default in Swish-e 2.x.
1135              
1136             =head1 AUTHOR
1137              
1138             Peter Karman, E<lt>perl@peknet.comE<gt>
1139              
1140             =head1 BUGS
1141              
1142             Please report any bugs or feature requests to C<bug-swish-prog at rt.cpan.org>, or through
1143             the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Dezi-App>.
1144             I will be notified, and then you'll
1145             automatically be notified of progress on your bug as I make changes.
1146              
1147             =head1 SUPPORT
1148              
1149             You can find documentation for this module with the perldoc command.
1150              
1151             perldoc Dezi
1152              
1153              
1154             You can also look for information at:
1155              
1156             =over 4
1157              
1158             =item * Mailing list
1159              
1160             L<http://lists.swish-e.org/listinfo/users>
1161              
1162             =item * RT: CPAN's request tracker
1163              
1164             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Dezi-App>
1165              
1166             =item * AnnoCPAN: Annotated CPAN documentation
1167              
1168             L<http://annocpan.org/dist/Dezi-App>
1169              
1170             =item * CPAN Ratings
1171              
1172             L<http://cpanratings.perl.org/d/Dezi-App>
1173              
1174             =item * Search CPAN
1175              
1176             L<http://search.cpan.org/dist/Dezi-App/>
1177              
1178             =back
1179              
1180             =head1 COPYRIGHT AND LICENSE
1181              
1182             Copyright 2006-2009 by Peter Karman
1183              
1184             This library is free software; you can redistribute it and/or modify
1185             it under the same terms as Perl itself.
1186              
1187             =head1 SEE ALSO
1188              
1189             L<http://swish-e.org/>