| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | # Listman.pm - this file is part of the CGI::Listman distribution | 
| 2 |  |  |  |  |  |  | # | 
| 3 |  |  |  |  |  |  | # CGI::Listman is Copyright (C) 2002 iScream multimédia | 
| 4 |  |  |  |  |  |  | # | 
| 5 |  |  |  |  |  |  | # This package is free software; you can redistribute it and/or | 
| 6 |  |  |  |  |  |  | # modify it under the same terms as Perl itself. | 
| 7 |  |  |  |  |  |  | # | 
| 8 |  |  |  |  |  |  | # Author: Wolfgang Sourdeau | 
| 9 |  |  |  |  |  |  |  | 
| 10 |  |  |  |  |  |  | # For a schematic description of the classes implemented in this file, | 
| 11 |  |  |  |  |  |  | # have a look at the file "schema.txt". | 
| 12 |  |  |  |  |  |  |  | 
| 13 |  |  |  |  |  |  | package CGI::Listman; | 
| 14 |  |  |  |  |  |  |  | 
| 15 | 1 |  |  | 1 |  | 4458 | use strict; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 25 |  | 
| 16 |  |  |  |  |  |  |  | 
| 17 | 1 |  |  | 1 |  | 4 | use Carp; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 40 |  | 
| 18 | 1 |  |  | 1 |  | 1282 | use DBI; | 
|  | 1 |  |  |  |  | 16696 |  | 
|  | 1 |  |  |  |  | 62 |  | 
| 19 |  |  |  |  |  |  |  | 
| 20 | 1 |  |  | 1 |  | 8 | use vars qw($VERSION); | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 2092 |  | 
| 21 |  |  |  |  |  |  |  | 
| 22 |  |  |  |  |  |  | $VERSION = '0.02'; | 
| 23 |  |  |  |  |  |  |  | 
| 24 |  |  |  |  |  |  | sub new { | 
| 25 | 0 |  |  | 0 | 1 |  | my $class = shift; | 
| 26 |  |  |  |  |  |  |  | 
| 27 | 0 |  |  |  |  |  | my $self = {}; | 
| 28 | 0 |  |  |  |  |  | $self->{'dbi_backend'} = shift; | 
| 29 | 0 |  |  |  |  |  | $self->{'list_name'} = shift; | 
| 30 | 0 |  |  |  |  |  | $self->{'list_dir'} = shift; | 
| 31 | 0 |  |  |  |  |  | $self->{'table_name'} = $self->{'list_name'}; | 
| 32 | 0 |  |  |  |  |  | $self->{'db_name'} = undef; | 
| 33 | 0 |  |  |  |  |  | $self->{'db_uname'} = undef; | 
| 34 | 0 |  |  |  |  |  | $self->{'db_passwd'} = undef; | 
| 35 | 0 |  |  |  |  |  | $self->{'db_host'} = undef; | 
| 36 | 0 |  |  |  |  |  | $self->{'db_port'} = undef; | 
| 37 |  |  |  |  |  |  |  | 
| 38 | 0 |  |  |  |  |  | $self->{'list'} = undef; | 
| 39 | 0 |  |  |  |  |  | $self->{'_dbi_params'} = undef; | 
| 40 | 0 |  |  |  |  |  | $self->{'_dictionary'} = undef; | 
| 41 | 0 |  |  |  |  |  | $self->{'_last_line_number'} = 0; | 
| 42 | 0 |  |  |  |  |  | $self->{'_loading_list'} = undef; | 
| 43 |  |  |  |  |  |  |  | 
| 44 | 0 |  |  |  |  |  | bless $self, $class; | 
| 45 |  |  |  |  |  |  | } | 
| 46 |  |  |  |  |  |  |  | 
| 47 |  |  |  |  |  |  | sub set_backend { | 
| 48 | 0 |  |  | 0 | 1 |  | my ($self, $backend) = @_; | 
| 49 |  |  |  |  |  |  |  | 
| 50 | 0 | 0 |  |  |  |  | if (defined $self->{'dbi_backend'}) { | 
| 51 |  |  |  |  |  |  | print STDERR "A backend is already defined (" | 
| 52 | 0 |  |  |  |  |  | .$self->{'dbi_backend'}.") for this CGI::Listman instance.\n" | 
| 53 |  |  |  |  |  |  | } else { | 
| 54 | 0 |  |  |  |  |  | eval "use DBD::".$backend.";"; | 
| 55 | 0 | 0 |  |  |  |  | die "This backend is not available:\n".$@ if ($@); | 
| 56 | 0 |  |  |  |  |  | $self->{'dbi_backend'} = $backend; | 
| 57 |  |  |  |  |  |  | } | 
| 58 |  |  |  |  |  |  | } | 
| 59 |  |  |  |  |  |  |  | 
| 60 |  |  |  |  |  |  | sub set_list_name { | 
| 61 | 0 |  |  | 0 | 1 |  | my ($self, $list_name) = @_; | 
| 62 |  |  |  |  |  |  |  | 
| 63 | 0 | 0 |  |  |  |  | if (defined $self->{'list_name'}) { | 
| 64 |  |  |  |  |  |  | print STDERR "A list name is already defined (" | 
| 65 | 0 |  |  |  |  |  | .$self->{'list_name'}.") for this instance of CGI::Listman.\n"; | 
| 66 |  |  |  |  |  |  | } else { | 
| 67 | 0 |  |  |  |  |  | $self->{'list_name'} = $list_name; | 
| 68 |  |  |  |  |  |  | $self->{'table_name'} = $list_name | 
| 69 | 0 | 0 |  |  |  |  | unless (defined $self->{'table_name'}); | 
| 70 |  |  |  |  |  |  | } | 
| 71 |  |  |  |  |  |  | } | 
| 72 |  |  |  |  |  |  |  | 
| 73 |  |  |  |  |  |  | sub set_table_name { | 
| 74 | 0 |  |  | 0 | 1 |  | my ($self, $table_name) = @_; | 
| 75 |  |  |  |  |  |  |  | 
| 76 | 0 | 0 |  |  |  |  | if (defined $self->{'table_name'}) { | 
| 77 | 0 |  |  |  |  |  | $self->{'table_name'} = $table_name; | 
| 78 |  |  |  |  |  |  | } | 
| 79 |  |  |  |  |  |  | } | 
| 80 |  |  |  |  |  |  |  | 
| 81 |  |  |  |  |  |  | sub dictionary { | 
| 82 | 0 |  |  | 0 | 1 |  | my $self = shift; | 
| 83 |  |  |  |  |  |  |  | 
| 84 | 0 | 0 |  |  |  |  | unless (defined $self->{'_dictionary'}) { | 
| 85 |  |  |  |  |  |  | die "List directory not defined for this instance of CGI::Listman.\n" | 
| 86 | 0 | 0 |  |  |  |  | unless (defined $self->{'list_dir'}); | 
| 87 |  |  |  |  |  |  | die "List filename not defined for this instance of CGI::Listman.\n" | 
| 88 | 0 | 0 |  |  |  |  | unless (defined $self->{'list_name'}); | 
| 89 |  |  |  |  |  |  |  | 
| 90 | 0 |  |  |  |  |  | my $path = $self->{'list_dir'}.'/'.$self->{'list_name'}.'.dict'; | 
| 91 | 0 | 0 |  |  |  |  | die "No dictionary ('".$self->{'list_name'}.".dict')\n" | 
| 92 |  |  |  |  |  |  | unless (-f $path); | 
| 93 |  |  |  |  |  |  |  | 
| 94 | 0 |  |  |  |  |  | my $dictionary = CGI::Listman::dictionary->new ($path); | 
| 95 |  |  |  |  |  |  |  | 
| 96 | 0 |  |  |  |  |  | $self->{'_dictionary'} = $dictionary; | 
| 97 |  |  |  |  |  |  | } | 
| 98 |  |  |  |  |  |  |  | 
| 99 | 0 |  |  |  |  |  | return $self->{'_dictionary'}; | 
| 100 |  |  |  |  |  |  | } | 
| 101 |  |  |  |  |  |  |  | 
| 102 |  |  |  |  |  |  | sub seek_line_by_num { | 
| 103 | 0 |  |  | 0 | 1 |  | my ($self, $number) = @_; | 
| 104 |  |  |  |  |  |  |  | 
| 105 | 0 | 0 |  |  |  |  | $self->load_lines () unless (defined $self->{'list'}); | 
| 106 |  |  |  |  |  |  |  | 
| 107 | 0 |  |  |  |  |  | my $ret_line = undef; | 
| 108 | 0 |  |  |  |  |  | my $list_ref = $self->{'list'}; | 
| 109 |  |  |  |  |  |  |  | 
| 110 | 0 |  |  |  |  |  | foreach my $line (@$list_ref) { | 
| 111 | 0 | 0 |  |  |  |  | if ($line->number () == $number) { | 
| 112 | 0 |  |  |  |  |  | $ret_line = $line; | 
| 113 | 0 |  |  |  |  |  | last; | 
| 114 |  |  |  |  |  |  | } | 
| 115 |  |  |  |  |  |  | } | 
| 116 |  |  |  |  |  |  |  | 
| 117 | 0 |  |  |  |  |  | return $ret_line; | 
| 118 |  |  |  |  |  |  | } | 
| 119 |  |  |  |  |  |  |  | 
| 120 |  |  |  |  |  |  | sub _dbi_setup { | 
| 121 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 122 |  |  |  |  |  |  |  | 
| 123 | 0 | 0 |  |  |  |  | unless (defined $self->{'_dbi_params'}) { | 
| 124 |  |  |  |  |  |  | die "No backend specified for this instance of CGI::Listman.\n" | 
| 125 | 0 | 0 |  |  |  |  | unless (defined $self->{'dbi_backend'}); | 
| 126 | 0 | 0 |  |  |  |  | if ($self->{'dbi_backend'} eq 'CSV') { | 
| 127 | 0 |  |  |  |  |  | $self->{'_dbi_params'} = ":f_dir=".$self->{'list_dir'}; | 
| 128 | 0 | 0 |  |  |  |  | unless (-f $self->{'list_dir'}.'/'.$self->{'table_name'}.'.csv') { | 
| 129 |  |  |  |  |  |  | open my $list_file, '>' | 
| 130 | 0 |  |  |  |  |  | .$self->{'list_dir'}.'/'.$self->{'table_name'}.'.csv'; | 
| 131 | 0 |  |  |  |  |  | close $list_file; | 
| 132 |  |  |  |  |  |  | } | 
| 133 |  |  |  |  |  |  | } else { | 
| 134 |  |  |  |  |  |  | die "Sorry, this DBI backend \"".$self->{'dbi_backend'} | 
| 135 |  |  |  |  |  |  | ."\" is not handled at this time.\n" | 
| 136 | 0 | 0 |  |  |  |  | unless ($self->{'dbi_backend'} eq 'mysql'); | 
| 137 | 0 |  |  |  |  |  | my $dbi_params = ":database=".$self->{'db_name'}; | 
| 138 |  |  |  |  |  |  | $dbi_params .= ":host=".$self->{'db_host'} | 
| 139 | 0 | 0 | 0 |  |  |  | if (defined $self->{'db_host'} && $self->{'db_host'} ne ''); | 
| 140 |  |  |  |  |  |  | $dbi_params .= ":port=".$self->{'db_port'} | 
| 141 | 0 | 0 | 0 |  |  |  | if (defined $self->{'db_port'} && $self->{'db_port'} ne ''); | 
| 142 | 0 |  |  |  |  |  | $self->{'_dbi_params'} = $dbi_params; | 
| 143 |  |  |  |  |  |  | } | 
| 144 |  |  |  |  |  |  | } | 
| 145 |  |  |  |  |  |  | } | 
| 146 |  |  |  |  |  |  |  | 
| 147 |  |  |  |  |  |  | sub _db_fields_setup { | 
| 148 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 149 |  |  |  |  |  |  |  | 
| 150 | 0 | 0 |  |  |  |  | unless (defined $self->{'_db_fields'}) { | 
| 151 | 0 |  |  |  |  |  | my @fields = ('number', 'timestamp', 'seen', 'exported'); | 
| 152 | 0 |  |  |  |  |  | my $dictionary = $self->dictionary (); | 
| 153 | 0 |  |  |  |  |  | my $dict_terms = $dictionary->terms (); | 
| 154 |  |  |  |  |  |  |  | 
| 155 | 0 |  |  |  |  |  | foreach my $term (@$dict_terms) { | 
| 156 | 0 |  |  |  |  |  | push @fields, $term->{'key'}; | 
| 157 |  |  |  |  |  |  | } | 
| 158 | 0 |  |  |  |  |  | $self->{'_db_fields'} = \@fields; | 
| 159 |  |  |  |  |  |  | } | 
| 160 |  |  |  |  |  |  | } | 
| 161 |  |  |  |  |  |  |  | 
| 162 |  |  |  |  |  |  | sub _db_connect { | 
| 163 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 164 |  |  |  |  |  |  |  | 
| 165 | 0 | 0 |  |  |  |  | unless (defined $self->{'_db_connection'}) { | 
| 166 | 0 |  |  |  |  |  | $self->_dbi_setup (); | 
| 167 | 0 |  |  |  |  |  | $self->_db_fields_setup (); | 
| 168 |  |  |  |  |  |  | my $dbh = DBI->connect ("DBI:" | 
| 169 |  |  |  |  |  |  | .$self->{'dbi_backend'} | 
| 170 |  |  |  |  |  |  | .$self->{'_dbi_params'}, | 
| 171 |  |  |  |  |  |  | $self->{'db_uname'}, | 
| 172 | 0 | 0 |  |  |  |  | $self->{'db_passwd'}) | 
| 173 |  |  |  |  |  |  | or die DBI->errstr; | 
| 174 | 0 | 0 |  |  |  |  | if ($self->{'dbi_backend'} eq 'CSV') { | 
| 175 |  |  |  |  |  |  | $dbh->{'csv_tables'}->{$self->{'table_name'}} = | 
| 176 |  |  |  |  |  |  | {'col_names' => $self->{'_db_fields'}, | 
| 177 | 0 |  |  |  |  |  | 'file' => $self->{'table_name'}.".csv"}; | 
| 178 |  |  |  |  |  |  | } | 
| 179 | 0 |  |  |  |  |  | $self->{'_db_connection'} = $dbh; | 
| 180 |  |  |  |  |  |  | } | 
| 181 |  |  |  |  |  |  | } | 
| 182 |  |  |  |  |  |  |  | 
| 183 |  |  |  |  |  |  | sub _get_line_numbers { | 
| 184 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 185 |  |  |  |  |  |  |  | 
| 186 | 0 |  |  |  |  |  | my @numbers; | 
| 187 |  |  |  |  |  |  |  | 
| 188 | 0 | 0 |  |  |  |  | if (defined $self->{'list'}) { | 
| 189 | 0 |  |  |  |  |  | my $list_ref = $self->{'list'}; | 
| 190 |  |  |  |  |  |  |  | 
| 191 | 0 |  |  |  |  |  | foreach my $line (@$list_ref) { | 
| 192 | 0 |  |  |  |  |  | push @numbers, $line->number (); | 
| 193 |  |  |  |  |  |  | } | 
| 194 |  |  |  |  |  |  | } | 
| 195 |  |  |  |  |  |  |  | 
| 196 | 0 |  |  |  |  |  | return @numbers; | 
| 197 |  |  |  |  |  |  | } | 
| 198 |  |  |  |  |  |  |  | 
| 199 |  |  |  |  |  |  | sub add_line { | 
| 200 | 0 |  |  | 0 | 0 |  | my ($self, $line) = @_; | 
| 201 |  |  |  |  |  |  |  | 
| 202 |  |  |  |  |  |  | $self->load_lines () | 
| 203 |  |  |  |  |  |  | unless (defined $self->{'list'} | 
| 204 | 0 | 0 | 0 |  |  |  | || defined $self->{'_loading_list'}); | 
| 205 |  |  |  |  |  |  |  | 
| 206 |  |  |  |  |  |  | $line->{'number'} = $self->{'_last_line_number'} + 1 | 
| 207 | 0 | 0 |  |  |  |  | unless ($line->{'number'}); | 
| 208 |  |  |  |  |  |  |  | 
| 209 | 0 |  |  |  |  |  | my @numbers = $self->_get_line_numbers (); | 
| 210 |  |  |  |  |  |  | croak "This instance's list of lines already contains a line with" | 
| 211 | 0 | 0 |  |  |  |  | ." this number (".$line->{'number'}.").\n" | 
| 212 |  |  |  |  |  |  | if (grep (m/$line->{'number'}/, @numbers)); | 
| 213 |  |  |  |  |  |  |  | 
| 214 | 0 |  |  |  |  |  | $self->{'_last_line_number'} = $line->{'number'}; | 
| 215 |  |  |  |  |  |  |  | 
| 216 | 0 | 0 |  |  |  |  | unless (defined $self->{'list'}) { | 
| 217 | 0 |  |  |  |  |  | my @new_list; | 
| 218 | 0 |  |  |  |  |  | $self->{'list'} = \@new_list; | 
| 219 |  |  |  |  |  |  | } | 
| 220 |  |  |  |  |  |  |  | 
| 221 | 0 |  |  |  |  |  | my $list_ref = $self->{'list'}; | 
| 222 | 0 |  |  |  |  |  | push @$list_ref, $line; | 
| 223 |  |  |  |  |  |  | } | 
| 224 |  |  |  |  |  |  |  | 
| 225 |  |  |  |  |  |  | sub load_lines { | 
| 226 | 0 |  |  | 0 | 1 |  | my $self = shift; | 
| 227 |  |  |  |  |  |  |  | 
| 228 | 0 |  |  |  |  |  | $self->{'_loading_list'} = 1; | 
| 229 | 0 |  |  |  |  |  | $self->_db_connect (); | 
| 230 |  |  |  |  |  |  |  | 
| 231 | 0 |  |  |  |  |  | my $dbh = $self->{'_db_connection'}; | 
| 232 |  |  |  |  |  |  |  | 
| 233 |  |  |  |  |  |  | my $row_list = | 
| 234 | 0 | 0 |  |  |  |  | $dbh->selectall_arrayref ("SELECT * FROM ".$self->{'table_name'}) | 
| 235 |  |  |  |  |  |  | or die $dbh->errstr; | 
| 236 |  |  |  |  |  |  |  | 
| 237 |  |  |  |  |  |  | # die $row_list->[0]; | 
| 238 | 0 | 0 |  |  |  |  | delete $self->{'list'} if (defined $self->{'list'}); | 
| 239 |  |  |  |  |  |  |  | 
| 240 | 0 | 0 |  |  |  |  | if (defined $row_list) { | 
| 241 | 0 |  |  |  |  |  | foreach my $row (@$row_list) { | 
| 242 | 0 |  |  |  |  |  | my $line = CGI::Listman::line->new (); | 
| 243 | 0 |  |  |  |  |  | $line->_build_from_listman_data ($row); | 
| 244 | 0 |  |  |  |  |  | $self->add_line ($line); | 
| 245 |  |  |  |  |  |  | } | 
| 246 |  |  |  |  |  |  | } | 
| 247 |  |  |  |  |  |  |  | 
| 248 | 0 |  |  |  |  |  | $self->{'_loading_list'} = undef; | 
| 249 |  |  |  |  |  |  | } | 
| 250 |  |  |  |  |  |  |  | 
| 251 |  |  |  |  |  |  | sub list_contents { | 
| 252 | 0 |  |  | 0 | 1 |  | my $self = shift; | 
| 253 |  |  |  |  |  |  |  | 
| 254 | 0 |  |  |  |  |  | my $contents_ref = undef; | 
| 255 | 0 | 0 |  |  |  |  | if (defined $self->{'list'}) { | 
| 256 | 0 |  |  |  |  |  | my @filt_contents; | 
| 257 | 0 |  |  |  |  |  | my $old_cref = $self->{'list'}; | 
| 258 | 0 |  |  |  |  |  | foreach my $line (@$old_cref) { | 
| 259 |  |  |  |  |  |  | push @filt_contents, $line | 
| 260 | 0 | 0 |  |  |  |  | if (!$line->{'_deleted'}); | 
| 261 |  |  |  |  |  |  | } | 
| 262 | 0 |  |  |  |  |  | $contents_ref = \@filt_contents; | 
| 263 |  |  |  |  |  |  | } else { | 
| 264 | 0 |  |  |  |  |  | $self->load_lines (); | 
| 265 | 0 |  |  |  |  |  | $contents_ref = $self->{'list'}; | 
| 266 |  |  |  |  |  |  | } | 
| 267 |  |  |  |  |  |  |  | 
| 268 | 0 |  |  |  |  |  | return $contents_ref; | 
| 269 |  |  |  |  |  |  | } | 
| 270 |  |  |  |  |  |  |  | 
| 271 |  |  |  |  |  |  | # Check the validity of received parameters and returns two refs against | 
| 272 |  |  |  |  |  |  | # the missing mandatory values and the unknown fields. | 
| 273 |  |  |  |  |  |  | sub check_params { | 
| 274 | 0 |  |  | 0 | 0 |  | my ($self, $param_hash_ref) = @_; | 
| 275 |  |  |  |  |  |  |  | 
| 276 | 0 |  |  |  |  |  | my $dictionary = $self->dictionary (); | 
| 277 |  |  |  |  |  |  |  | 
| 278 | 0 |  |  |  |  |  | my @missing; | 
| 279 |  |  |  |  |  |  | my @unknown; | 
| 280 |  |  |  |  |  |  |  | 
| 281 | 0 |  |  |  |  |  | foreach my $key (keys %$param_hash_ref) { | 
| 282 | 0 |  |  |  |  |  | my $term = $dictionary->get_term ($key); | 
| 283 | 0 | 0 |  |  |  |  | push @unknown, $key | 
| 284 |  |  |  |  |  |  | unless (defined $term); | 
| 285 |  |  |  |  |  |  | } | 
| 286 |  |  |  |  |  |  |  | 
| 287 | 0 |  |  |  |  |  | my $dict_terms = $dictionary->terms (); | 
| 288 |  |  |  |  |  |  |  | 
| 289 | 0 |  |  |  |  |  | foreach my $term (@$dict_terms) { | 
| 290 | 0 |  |  |  |  |  | my $key = $term->{'key'}; | 
| 291 |  |  |  |  |  |  | push @missing, $term->definition_or_key () | 
| 292 |  |  |  |  |  |  | if ($term->{'mandatory'} | 
| 293 |  |  |  |  |  |  | && (!defined $param_hash_ref->{$key} | 
| 294 | 0 | 0 | 0 |  |  |  | || $param_hash_ref->{$key} eq '')); | 
|  |  |  | 0 |  |  |  |  | 
| 295 |  |  |  |  |  |  | } | 
| 296 |  |  |  |  |  |  |  | 
| 297 | 0 |  |  |  |  |  | return (\@missing, \@unknown); | 
| 298 |  |  |  |  |  |  | } | 
| 299 |  |  |  |  |  |  |  | 
| 300 |  |  |  |  |  |  | sub _prepare_record { | 
| 301 | 0 |  |  | 0 |  |  | my ($self, $line) = @_; | 
| 302 |  |  |  |  |  |  |  | 
| 303 | 0 |  |  |  |  |  | my $fields_ref = $line->line_fields (); | 
| 304 | 0 |  |  |  |  |  | my @records; | 
| 305 | 0 |  |  |  |  |  | push @records, ($line->{'timestamp'}, $line->{'seen'}, $line->{'exported'}); | 
| 306 | 0 |  |  |  |  |  | push @records, @$fields_ref; | 
| 307 |  |  |  |  |  |  |  | 
| 308 | 0 |  |  |  |  |  | my $record_line = "'".$line->{'number'}."'"; | 
| 309 | 0 |  |  |  |  |  | foreach my $record (@records) { | 
| 310 | 0 | 0 |  |  |  |  | $record = '' unless (defined $record); | 
| 311 | 0 |  |  |  |  |  | $record_line .= ", '".$record."'"; | 
| 312 |  |  |  |  |  |  | } | 
| 313 |  |  |  |  |  |  |  | 
| 314 |  |  |  |  |  |  | # if we don't untaint $record_line, we get a stange error regarding | 
| 315 |  |  |  |  |  |  | # DBD::SQL::Statement::HASH_ref... | 
| 316 | 0 |  |  |  |  |  | $record_line =~ m/(.*)/; | 
| 317 | 0 |  |  |  |  |  | $record_line = $1; | 
| 318 |  |  |  |  |  |  |  | 
| 319 | 0 |  |  |  |  |  | return $record_line; | 
| 320 |  |  |  |  |  |  | } | 
| 321 |  |  |  |  |  |  |  | 
| 322 |  |  |  |  |  |  | sub commit { | 
| 323 | 0 |  |  | 0 | 1 |  | my $self = shift; | 
| 324 |  |  |  |  |  |  |  | 
| 325 |  |  |  |  |  |  | die "Commit again?\n" | 
| 326 | 0 | 0 |  |  |  |  | if (defined $self->{'_commit'}); | 
| 327 |  |  |  |  |  |  |  | 
| 328 | 0 | 0 |  |  |  |  | if (defined $self->{'list'}) { | 
| 329 | 0 |  |  |  |  |  | $self->_db_connect (); | 
| 330 | 0 |  |  |  |  |  | my $dbh = $self->{'_db_connection'}; | 
| 331 | 0 |  |  |  |  |  | my $list_ref = $self->{'list'}; | 
| 332 | 0 |  |  |  |  |  | foreach my $line (@$list_ref) { | 
| 333 | 0 | 0 |  |  |  |  | if ($line->{'_updated'}) { | 
| 334 | 0 | 0 | 0 |  |  |  | next if ($line->{'_deleted'} && $line->{'_new_line'}); | 
| 335 | 0 | 0 |  |  |  |  | if ($line->{'_deleted'}) { | 
|  |  | 0 |  |  |  |  |  | 
| 336 |  |  |  |  |  |  | $dbh->do ("DELETE FROM ".$self->{'table_name'}. | 
| 337 |  |  |  |  |  |  | "       WHERE number = ".$line->{'number'}) | 
| 338 |  |  |  |  |  |  | or die "An DBI error occured while deleting line " | 
| 339 | 0 | 0 |  |  |  |  | .$line->{'number'}." from ".$self->{'table_name'} | 
| 340 |  |  |  |  |  |  | .":\n".$dbh->errstr; | 
| 341 |  |  |  |  |  |  | } elsif ($line->{'_new_line'}) { | 
| 342 |  |  |  |  |  |  | $line->{'timestamp'} = time () | 
| 343 | 0 | 0 |  |  |  |  | unless ($line->{'timestamp'}); | 
| 344 | 0 |  |  |  |  |  | my $record = $self->_prepare_record ($line); | 
| 345 |  |  |  |  |  |  | my $sth = $dbh->do ("INSERT INTO ".$self->{'table_name'}. | 
| 346 |  |  |  |  |  |  | "       VALUES (".$record.")") | 
| 347 |  |  |  |  |  |  | or die "An DBI error occured while inserting...\n".$record. | 
| 348 | 0 | 0 |  |  |  |  | "... into ".$self->{'table_name'}.":\n".$dbh->errstr; | 
| 349 |  |  |  |  |  |  | } else { | 
| 350 |  |  |  |  |  |  | $dbh->do ("DELETE FROM ".$self->{'table_name'}. | 
| 351 |  |  |  |  |  |  | "       WHERE number = ".$line->{'number'}) | 
| 352 |  |  |  |  |  |  | or die "An DBI error occured while deleting line " | 
| 353 | 0 | 0 |  |  |  |  | .$line->{'number'}." from ".$self->{'table_name'} | 
| 354 |  |  |  |  |  |  | .":\n".$dbh->errstr; | 
| 355 | 0 |  |  |  |  |  | my $record = $self->_prepare_record ($line); | 
| 356 |  |  |  |  |  |  | my $sth = $dbh->do ("INSERT INTO ".$self->{'table_name'}. | 
| 357 |  |  |  |  |  |  | "       VALUES (".$record.")") | 
| 358 |  |  |  |  |  |  | or die "An DBI error occured while inserting...\n".$record. | 
| 359 | 0 | 0 |  |  |  |  | "... into ".$self->{'table_name'}.":\n".$dbh->errstr; | 
| 360 |  |  |  |  |  |  | } | 
| 361 |  |  |  |  |  |  | } | 
| 362 |  |  |  |  |  |  | } | 
| 363 | 0 |  |  |  |  |  | $dbh->disconnect (); | 
| 364 |  |  |  |  |  |  | } | 
| 365 |  |  |  |  |  |  |  | 
| 366 | 0 |  |  |  |  |  | $self->{'_commit'} = 1; | 
| 367 |  |  |  |  |  |  | } | 
| 368 |  |  |  |  |  |  |  | 
| 369 |  |  |  |  |  |  | sub delete_line { | 
| 370 | 0 |  |  | 0 | 1 |  | my ($self, $line) = @_; | 
| 371 |  |  |  |  |  |  |  | 
| 372 |  |  |  |  |  |  | die "Cannot delete a line with number equal to 0.\n" | 
| 373 | 0 | 0 |  |  |  |  | unless ($line->{'number'}); | 
| 374 |  |  |  |  |  |  |  | 
| 375 | 0 |  |  |  |  |  | my $list_ref = $self->{'list'}; | 
| 376 | 0 | 0 |  |  |  |  | die "List empty.\n" unless (defined $list_ref); | 
| 377 |  |  |  |  |  |  |  | 
| 378 |  |  |  |  |  |  | # delete the line from the list in memory... | 
| 379 | 0 |  |  |  |  |  | my $count; | 
| 380 | 0 |  |  |  |  |  | for ($count = 0; $count < @$list_ref; $count++) { | 
| 381 | 0 | 0 |  |  |  |  | if ($list_ref->[$count] == $line) { | 
| 382 | 0 |  |  |  |  |  | $line->{'_updated'} = 1; | 
| 383 | 0 |  |  |  |  |  | $line->{'_deleted'} = 1; | 
| 384 | 0 |  |  |  |  |  | last; | 
| 385 |  |  |  |  |  |  | } | 
| 386 |  |  |  |  |  |  | } | 
| 387 |  |  |  |  |  |  |  | 
| 388 | 0 | 0 |  |  |  |  | die "Line not found in list." | 
| 389 |  |  |  |  |  |  | if ($count == @$list_ref); | 
| 390 |  |  |  |  |  |  | } | 
| 391 |  |  |  |  |  |  |  | 
| 392 |  |  |  |  |  |  | sub delete_selection { | 
| 393 | 0 |  |  | 0 | 1 |  | my ($self, $selection) = @_; | 
| 394 |  |  |  |  |  |  |  | 
| 395 | 0 |  |  |  |  |  | my $list_ref = $selection->{'list'}; | 
| 396 | 0 | 0 |  |  |  |  | die "Selection is empty.\n" unless ($list_ref); | 
| 397 | 0 |  |  |  |  |  | foreach my $line (@$list_ref) { | 
| 398 | 0 |  |  |  |  |  | $self->delete_line ($line); | 
| 399 |  |  |  |  |  |  | } | 
| 400 |  |  |  |  |  |  | } | 
| 401 |  |  |  |  |  |  |  | 
| 402 |  |  |  |  |  |  |  | 
| 403 |  |  |  |  |  |  | package CGI::Listman::line; | 
| 404 |  |  |  |  |  |  |  | 
| 405 | 1 |  |  | 1 |  | 6 | use strict; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 409 |  | 
| 406 |  |  |  |  |  |  |  | 
| 407 |  |  |  |  |  |  | # line format: (number, timestamp, seen, exported, fields...) | 
| 408 |  |  |  |  |  |  | sub new { | 
| 409 | 0 |  |  | 0 |  |  | my $class = shift; | 
| 410 |  |  |  |  |  |  |  | 
| 411 | 0 |  |  |  |  |  | my $self = {}; | 
| 412 | 0 |  |  |  |  |  | $self->{'number'} = 0; | 
| 413 | 0 |  |  |  |  |  | $self->{'timestamp'} = 0; | 
| 414 | 0 |  |  |  |  |  | $self->{'seen'} = 0; | 
| 415 | 0 |  |  |  |  |  | $self->{'exported'} = 0; | 
| 416 | 0 |  |  |  |  |  | $self->{'data'} = shift; | 
| 417 |  |  |  |  |  |  |  | 
| 418 | 0 |  |  |  |  |  | $self->{'_updated'} = 1; | 
| 419 | 0 |  |  |  |  |  | $self->{'_new_line'} = 1; | 
| 420 | 0 |  |  |  |  |  | $self->{'_deleted'} = 0; | 
| 421 |  |  |  |  |  |  |  | 
| 422 | 0 |  |  |  |  |  | bless $self, $class; | 
| 423 |  |  |  |  |  |  | } | 
| 424 |  |  |  |  |  |  |  | 
| 425 |  |  |  |  |  |  | sub mark_seen { | 
| 426 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 427 |  |  |  |  |  |  |  | 
| 428 | 0 |  |  |  |  |  | $self->{'seen'} = 1; | 
| 429 | 0 |  |  |  |  |  | $self->{'_updated'} = 1; | 
| 430 |  |  |  |  |  |  | } | 
| 431 |  |  |  |  |  |  |  | 
| 432 |  |  |  |  |  |  | sub mark_exported { | 
| 433 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 434 |  |  |  |  |  |  |  | 
| 435 | 0 |  |  |  |  |  | $self->{'exported'} = 1; | 
| 436 | 0 |  |  |  |  |  | $self->{'_updated'} = 1; | 
| 437 |  |  |  |  |  |  | } | 
| 438 |  |  |  |  |  |  |  | 
| 439 |  |  |  |  |  |  | sub number { | 
| 440 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 441 |  |  |  |  |  |  |  | 
| 442 | 0 |  |  |  |  |  | return $self->{'number'}; | 
| 443 |  |  |  |  |  |  | } | 
| 444 |  |  |  |  |  |  |  | 
| 445 |  |  |  |  |  |  | sub set_fields { | 
| 446 | 0 |  |  | 0 |  |  | my ($self, $fields_ref) = @_; | 
| 447 |  |  |  |  |  |  |  | 
| 448 |  |  |  |  |  |  | die "Fields already defined for line.\n" | 
| 449 | 0 | 0 |  |  |  |  | if (defined $self->{'data'}); | 
| 450 |  |  |  |  |  |  |  | 
| 451 | 0 |  |  |  |  |  | $self->{'data'} = $fields_ref; | 
| 452 | 0 |  |  |  |  |  | $self->{'_updated'} = 1; | 
| 453 |  |  |  |  |  |  | } | 
| 454 |  |  |  |  |  |  |  | 
| 455 |  |  |  |  |  |  | sub update_fields { | 
| 456 | 0 |  |  | 0 |  |  | my ($self, $fields_ref) = @_; | 
| 457 |  |  |  |  |  |  |  | 
| 458 |  |  |  |  |  |  | delete $self->{'data'} | 
| 459 | 0 | 0 |  |  |  |  | if (defined $self->{'data'}); | 
| 460 |  |  |  |  |  |  |  | 
| 461 | 0 |  |  |  |  |  | $self->{'data'} = $fields_ref; | 
| 462 | 0 |  |  |  |  |  | $self->{'_updated'} = 1; | 
| 463 |  |  |  |  |  |  | } | 
| 464 |  |  |  |  |  |  |  | 
| 465 |  |  |  |  |  |  | sub line_fields { | 
| 466 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 467 |  |  |  |  |  |  |  | 
| 468 | 0 |  |  |  |  |  | return $self->{'data'}; | 
| 469 |  |  |  |  |  |  | } | 
| 470 |  |  |  |  |  |  |  | 
| 471 |  |  |  |  |  |  | # internals only | 
| 472 |  |  |  |  |  |  | sub _build_from_listman_data { | 
| 473 | 0 |  |  | 0 |  |  | my ($self, $listman_data_ref) = @_; | 
| 474 |  |  |  |  |  |  |  | 
| 475 | 0 |  |  |  |  |  | my @backend_data = @$listman_data_ref; | 
| 476 |  |  |  |  |  |  |  | 
| 477 | 0 |  |  |  |  |  | my $number = shift @backend_data; | 
| 478 | 0 |  |  |  |  |  | $number =~ m/^([0-9]*)$/; | 
| 479 | 0 | 0 |  |  |  |  | $number = $1 or die 'Wrong number ("'.$number | 
| 480 |  |  |  |  |  |  | .'") containing non-digit characters'."\n"; | 
| 481 |  |  |  |  |  |  |  | 
| 482 | 0 |  |  |  |  |  | $self->{'number'} = $number; | 
| 483 | 0 |  |  |  |  |  | $self->{'timestamp'} = shift @backend_data; | 
| 484 | 0 |  |  |  |  |  | $self->{'seen'} = shift @backend_data; | 
| 485 | 0 |  |  |  |  |  | $self->{'exported'} = shift @backend_data; | 
| 486 | 0 |  |  |  |  |  | $self->{'data'} = \@backend_data; | 
| 487 |  |  |  |  |  |  |  | 
| 488 | 0 |  |  |  |  |  | $self->{'_updated'} = 0; | 
| 489 | 0 |  |  |  |  |  | $self->{'_new_line'} = 0; | 
| 490 |  |  |  |  |  |  | } | 
| 491 |  |  |  |  |  |  |  | 
| 492 |  |  |  |  |  |  |  | 
| 493 |  |  |  |  |  |  | package CGI::Listman::exporter; | 
| 494 |  |  |  |  |  |  |  | 
| 495 | 1 |  |  | 1 |  | 6 | use strict; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 18 |  | 
| 496 | 1 |  |  | 1 |  | 1019 | use Text::CSV_XS; | 
|  | 1 |  |  |  |  | 16919 |  | 
|  | 1 |  |  |  |  | 538 |  | 
| 497 |  |  |  |  |  |  |  | 
| 498 |  |  |  |  |  |  | sub new { | 
| 499 | 0 |  |  | 0 |  |  | my $class = shift; | 
| 500 |  |  |  |  |  |  |  | 
| 501 | 0 |  |  |  |  |  | my $self = {}; | 
| 502 |  |  |  |  |  |  |  | 
| 503 | 0 |  |  |  |  |  | my @lines; | 
| 504 | 0 |  |  |  |  |  | $self->{'file_name'} = shift; | 
| 505 | 0 |  | 0 |  |  |  | $self->{'separator'} = shift || ','; | 
| 506 | 0 |  |  |  |  |  | $self->{'lines'} = \@lines; | 
| 507 | 0 |  |  |  |  |  | $self->{'_csv'} = Text::CSV_XS->new ({sep_char => $self->{'separator'}, | 
| 508 |  |  |  |  |  |  | binary => 1}); | 
| 509 | 0 |  |  |  |  |  | $self->{'_file_read'} = 0; | 
| 510 |  |  |  |  |  |  |  | 
| 511 | 0 |  |  |  |  |  | bless $self, $class; | 
| 512 | 0 | 0 |  |  |  |  | $self->_read_file () if (defined $self->{'file_name'}); | 
| 513 |  |  |  |  |  |  |  | 
| 514 | 0 |  |  |  |  |  | return $self; | 
| 515 |  |  |  |  |  |  | } | 
| 516 |  |  |  |  |  |  |  | 
| 517 |  |  |  |  |  |  | sub set_file_name { | 
| 518 | 0 |  |  | 0 |  |  | my ($self, $file_name) = @_; | 
| 519 |  |  |  |  |  |  |  | 
| 520 |  |  |  |  |  |  | die "A file name is already defined for this instance" | 
| 521 |  |  |  |  |  |  | ." of CGI::Listman::exporter.\n" | 
| 522 | 0 | 0 |  |  |  |  | if (defined $self->{'file_name'}); | 
| 523 | 0 |  |  |  |  |  | $self->{'file_name'} = $file_name; | 
| 524 | 0 |  |  |  |  |  | $self->_read_file (); | 
| 525 |  |  |  |  |  |  | } | 
| 526 |  |  |  |  |  |  |  | 
| 527 |  |  |  |  |  |  | sub set_separator { | 
| 528 | 0 |  |  | 0 |  |  | my ($self, $sep) = @_; | 
| 529 |  |  |  |  |  |  |  | 
| 530 | 0 |  |  |  |  |  | $self->{'separator'} = $sep; | 
| 531 |  |  |  |  |  |  | } | 
| 532 |  |  |  |  |  |  |  | 
| 533 |  |  |  |  |  |  | sub add_line { | 
| 534 | 0 |  |  | 0 |  |  | my ($self, $line) = @_; | 
| 535 |  |  |  |  |  |  |  | 
| 536 | 0 |  |  |  |  |  | my $csv = $self->{'_csv'}; | 
| 537 |  |  |  |  |  |  |  | 
| 538 | 0 |  |  |  |  |  | my $data_ref = $line->{'data'}; | 
| 539 | 0 |  |  |  |  |  | my @columns = @$data_ref; | 
| 540 | 0 |  |  |  |  |  | $csv->combine (@columns); | 
| 541 | 0 |  |  |  |  |  | my $csv_line = $csv->string (); | 
| 542 | 0 |  |  |  |  |  | my $lines_ref = $self->{'lines'}; | 
| 543 | 0 |  |  |  |  |  | push @$lines_ref, $csv_line; | 
| 544 | 0 |  |  |  |  |  | $line->mark_exported (); | 
| 545 |  |  |  |  |  |  | } | 
| 546 |  |  |  |  |  |  |  | 
| 547 |  |  |  |  |  |  | sub add_selection { | 
| 548 | 0 |  |  | 0 |  |  | my ($self, $selection) = @_; | 
| 549 |  |  |  |  |  |  |  | 
| 550 | 0 |  |  |  |  |  | my $sel_list_ref = $selection->{'list'}; | 
| 551 | 0 |  |  |  |  |  | foreach my $line (@$sel_list_ref) { | 
| 552 | 0 |  |  |  |  |  | $self->add_line ($line); | 
| 553 |  |  |  |  |  |  | } | 
| 554 |  |  |  |  |  |  | } | 
| 555 |  |  |  |  |  |  |  | 
| 556 |  |  |  |  |  |  | sub file_contents { | 
| 557 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 558 |  |  |  |  |  |  |  | 
| 559 | 0 |  |  |  |  |  | my $contents = undef; | 
| 560 | 0 |  |  |  |  |  | my $lines_ref = $self->{'lines'}; | 
| 561 | 0 |  |  |  |  |  | foreach my $line (@$lines_ref) { | 
| 562 | 0 |  |  |  |  |  | $contents .= $line."\r\n"; | 
| 563 |  |  |  |  |  |  | } | 
| 564 |  |  |  |  |  |  |  | 
| 565 | 0 |  |  |  |  |  | return $contents; | 
| 566 |  |  |  |  |  |  | } | 
| 567 |  |  |  |  |  |  |  | 
| 568 |  |  |  |  |  |  | sub save_file { | 
| 569 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 570 |  |  |  |  |  |  |  | 
| 571 | 0 |  |  |  |  |  | print STDERR "saving to ".$self->{'file_name'}."\n"; | 
| 572 |  |  |  |  |  |  | die "No file to export to.\n" | 
| 573 | 0 | 0 |  |  |  |  | unless (defined $self->{'file_name'}); | 
| 574 | 0 |  |  |  |  |  | my $contents = $self->file_contents (); | 
| 575 |  |  |  |  |  |  |  | 
| 576 |  |  |  |  |  |  | open EFOUT, '>'.$self->{'file_name'} | 
| 577 |  |  |  |  |  |  | or die "Could not open export file (\"" | 
| 578 | 0 | 0 |  |  |  |  | .$self->{'file_name'}."\") for writing.\n"; | 
| 579 | 0 |  |  |  |  |  | print EFOUT $contents; | 
| 580 | 0 |  |  |  |  |  | close EFOUT; | 
| 581 |  |  |  |  |  |  | } | 
| 582 |  |  |  |  |  |  |  | 
| 583 |  |  |  |  |  |  | sub _read_file { | 
| 584 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 585 |  |  |  |  |  |  |  | 
| 586 | 0 | 0 |  |  |  |  | if (-f $self->{'file_name'}) { | 
| 587 |  |  |  |  |  |  | open EFIN, $self->{'file_name'} | 
| 588 | 0 | 0 |  |  |  |  | or die "Could not open export file ('".$self->{'file_name'}."').\n"; | 
| 589 |  |  |  |  |  |  |  | 
| 590 | 0 |  |  |  |  |  | my $lines_ref = $self->{'lines'}; | 
| 591 | 0 |  |  |  |  |  | while () { | 
| 592 | 0 |  |  |  |  |  | my $line = $_; | 
| 593 | 0 |  |  |  |  |  | chomp $line; | 
| 594 | 0 |  |  |  |  |  | push @$lines_ref, $line; | 
| 595 |  |  |  |  |  |  | } | 
| 596 | 0 |  |  |  |  |  | close EFIN; | 
| 597 |  |  |  |  |  |  |  | 
| 598 | 0 |  |  |  |  |  | $self->{'_file_read'} = 1; | 
| 599 |  |  |  |  |  |  | } | 
| 600 |  |  |  |  |  |  | } | 
| 601 |  |  |  |  |  |  |  | 
| 602 |  |  |  |  |  |  |  | 
| 603 |  |  |  |  |  |  | package CGI::Listman::selection; | 
| 604 |  |  |  |  |  |  |  | 
| 605 | 1 |  |  | 1 |  | 8 | use strict; | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 1297 |  | 
| 606 |  |  |  |  |  |  |  | 
| 607 |  |  |  |  |  |  | sub new { | 
| 608 | 0 |  |  | 0 |  |  | my $class = shift; | 
| 609 |  |  |  |  |  |  |  | 
| 610 | 0 |  |  |  |  |  | my $self = {}; | 
| 611 | 0 |  |  |  |  |  | my @selection_list; | 
| 612 | 0 |  |  |  |  |  | $self->{'list'} = \@selection_list; | 
| 613 |  |  |  |  |  |  |  | 
| 614 | 0 |  |  |  |  |  | bless $self, $class; | 
| 615 |  |  |  |  |  |  | } | 
| 616 |  |  |  |  |  |  |  | 
| 617 |  |  |  |  |  |  | sub add_line { | 
| 618 | 0 |  |  | 0 |  |  | my ($self, $line) = @_; | 
| 619 |  |  |  |  |  |  |  | 
| 620 | 0 |  |  |  |  |  | my $list_ref = $self->{'list'}; | 
| 621 | 0 |  |  |  |  |  | push @$list_ref, $line; | 
| 622 |  |  |  |  |  |  | } | 
| 623 |  |  |  |  |  |  |  | 
| 624 |  |  |  |  |  |  | sub add_line_by_number { | 
| 625 | 0 |  |  | 0 |  |  | my ($self, $listman, $number) = @_; | 
| 626 |  |  |  |  |  |  |  | 
| 627 | 0 |  |  |  |  |  | my $line = $listman->seek_line_by_num ($number); | 
| 628 | 0 | 0 |  |  |  |  | die "Line number ".$number." not found.\n" | 
| 629 |  |  |  |  |  |  | unless (defined $line); | 
| 630 | 0 |  |  |  |  |  | $self->add_line ($line); | 
| 631 |  |  |  |  |  |  | } | 
| 632 |  |  |  |  |  |  |  | 
| 633 |  |  |  |  |  |  | sub add_lines_by_number { | 
| 634 | 0 |  |  | 0 |  |  | my ($self, $listman, $numbers) = @_; | 
| 635 |  |  |  |  |  |  |  | 
| 636 | 0 |  |  |  |  |  | foreach my $number (@$numbers) { | 
| 637 | 0 |  |  |  |  |  | $self->add_line_by_number ($listman, $number); | 
| 638 |  |  |  |  |  |  | } | 
| 639 |  |  |  |  |  |  | } | 
| 640 |  |  |  |  |  |  |  | 
| 641 |  |  |  |  |  |  |  | 
| 642 |  |  |  |  |  |  | package CGI::Listman::dictionary; | 
| 643 |  |  |  |  |  |  |  | 
| 644 |  |  |  |  |  |  | sub new { | 
| 645 | 0 |  |  | 0 |  |  | my $class = shift; | 
| 646 |  |  |  |  |  |  |  | 
| 647 | 0 |  |  |  |  |  | my $self = {}; | 
| 648 | 0 |  |  |  |  |  | $self->{'filename'} = shift; | 
| 649 |  |  |  |  |  |  |  | 
| 650 | 0 |  |  |  |  |  | $self->{'_terms'} = undef; | 
| 651 | 0 |  |  |  |  |  | $self->{'_loading'} = 0; | 
| 652 |  |  |  |  |  |  |  | 
| 653 | 0 |  |  |  |  |  | bless $self, $class; | 
| 654 |  |  |  |  |  |  | } | 
| 655 |  |  |  |  |  |  |  | 
| 656 |  |  |  |  |  |  | sub _load { | 
| 657 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 658 |  |  |  |  |  |  |  | 
| 659 | 0 | 0 |  |  |  |  | return if $self->{'_loading'}; | 
| 660 |  |  |  |  |  |  |  | 
| 661 | 0 |  |  |  |  |  | $self->{'_loading'} = 1; | 
| 662 |  |  |  |  |  |  | die "No dictionary filename.\n" | 
| 663 | 0 | 0 |  |  |  |  | unless (defined $self->{'filename'}); | 
| 664 |  |  |  |  |  |  |  | 
| 665 |  |  |  |  |  |  | open DINF, $self->{'filename'} | 
| 666 | 0 | 0 |  |  |  |  | or die "Could not open dictionary (\"".$self->{'filename'}."\").\n"; | 
| 667 |  |  |  |  |  |  |  | 
| 668 | 0 |  |  |  |  |  | my @terms; | 
| 669 | 0 |  |  |  |  |  | while () { | 
| 670 | 0 |  |  |  |  |  | my $line = $_; | 
| 671 | 0 |  |  |  |  |  | chomp $line; | 
| 672 | 0 |  |  |  |  |  | $line =~ m/([^:]*)(:([^:]+)?(:([!]))?)?/; | 
| 673 |  |  |  |  |  |  |  | 
| 674 | 0 |  |  |  |  |  | my $key = $1; | 
| 675 | 0 |  | 0 |  |  |  | my $definition = $3 || ''; | 
| 676 | 0 |  | 0 |  |  |  | my $mandatory = (defined $5 && $5 eq '!'); | 
| 677 |  |  |  |  |  |  |  | 
| 678 | 0 | 0 |  |  |  |  | die "Dictionary entry \"".$key."\" is duplicated." | 
| 679 |  |  |  |  |  |  | if (defined $self->get_term ($key)); | 
| 680 |  |  |  |  |  |  |  | 
| 681 |  |  |  |  |  |  | my $term_object = CGI::Listman::dictionary::term->new ($key, | 
| 682 |  |  |  |  |  |  | $definition, | 
| 683 |  |  |  |  |  |  | $mandatory, | 
| 684 | 0 |  |  |  |  |  | $self->{'count'}); | 
| 685 | 0 |  |  |  |  |  | push @terms, $term_object; | 
| 686 |  |  |  |  |  |  | } | 
| 687 | 0 |  |  |  |  |  | close DINF; | 
| 688 |  |  |  |  |  |  |  | 
| 689 | 0 |  |  |  |  |  | $self->{'_terms'} = \@terms; | 
| 690 | 0 |  |  |  |  |  | $self->{'_loading'} = 0; | 
| 691 |  |  |  |  |  |  | } | 
| 692 |  |  |  |  |  |  |  | 
| 693 |  |  |  |  |  |  | sub add_term { | 
| 694 | 0 |  |  | 0 |  |  | my ($self, $term) = @_; | 
| 695 |  |  |  |  |  |  |  | 
| 696 | 0 |  |  |  |  |  | my $terms_ref = $self->terms (); | 
| 697 | 0 |  |  |  |  |  | push @$terms_ref, $term; | 
| 698 |  |  |  |  |  |  | } | 
| 699 |  |  |  |  |  |  |  | 
| 700 |  |  |  |  |  |  | sub get_term { | 
| 701 | 0 |  |  | 0 |  |  | my ($self, $key) = @_; | 
| 702 |  |  |  |  |  |  |  | 
| 703 | 0 |  |  |  |  |  | my $terms_ref = $self->terms (); | 
| 704 |  |  |  |  |  |  |  | 
| 705 | 0 |  |  |  |  |  | my $term_object = undef; | 
| 706 |  |  |  |  |  |  |  | 
| 707 | 0 | 0 |  |  |  |  | if (defined $terms_ref) { | 
| 708 | 0 |  |  |  |  |  | foreach my $term (@$terms_ref) { | 
| 709 | 0 | 0 |  |  |  |  | next if ($term->{'key'} ne $key); | 
| 710 | 0 |  |  |  |  |  | $term_object = $term; | 
| 711 |  |  |  |  |  |  | } | 
| 712 |  |  |  |  |  |  | } | 
| 713 |  |  |  |  |  |  |  | 
| 714 | 0 |  |  |  |  |  | return $term_object; | 
| 715 |  |  |  |  |  |  | } | 
| 716 |  |  |  |  |  |  |  | 
| 717 |  |  |  |  |  |  | sub terms { | 
| 718 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 719 |  |  |  |  |  |  |  | 
| 720 | 0 | 0 |  |  |  |  | $self->_load () unless (defined $self->{'_terms'}); | 
| 721 | 0 |  |  |  |  |  | my $terms_ref = $self->{'_terms'}; | 
| 722 |  |  |  |  |  |  |  | 
| 723 | 0 |  |  |  |  |  | return $terms_ref; | 
| 724 |  |  |  |  |  |  | } | 
| 725 |  |  |  |  |  |  |  | 
| 726 |  |  |  |  |  |  | sub term_pos_in_list { | 
| 727 | 0 |  |  | 0 |  |  | my ($self, $term) = @_; | 
| 728 |  |  |  |  |  |  |  | 
| 729 | 0 |  |  |  |  |  | my $number = 0; | 
| 730 | 0 |  |  |  |  |  | my $terms_ref = $self->terms (); | 
| 731 | 0 |  |  |  |  |  | foreach my $comp_term (@$terms_ref) { | 
| 732 | 0 | 0 |  |  |  |  | last if ($comp_term == $term); | 
| 733 | 0 |  |  |  |  |  | $number++; | 
| 734 |  |  |  |  |  |  | } | 
| 735 |  |  |  |  |  |  |  | 
| 736 | 0 |  |  |  |  |  | return $number; | 
| 737 |  |  |  |  |  |  | } | 
| 738 |  |  |  |  |  |  |  | 
| 739 |  |  |  |  |  |  | sub reposition_term { | 
| 740 | 0 |  |  | 0 |  |  | my ($self, $term, $delta) = @_; | 
| 741 |  |  |  |  |  |  |  | 
| 742 | 0 |  |  |  |  |  | my $curr_pos = $self->term_pos_in_list ($term); | 
| 743 | 0 |  |  |  |  |  | my $new_pos = $curr_pos + $delta; | 
| 744 | 0 |  |  |  |  |  | my $terms_ref = $self->{'_terms'}; | 
| 745 |  |  |  |  |  |  |  | 
| 746 | 0 | 0 | 0 |  |  |  | unless ($new_pos > scalar (@$terms_ref) | 
|  |  |  | 0 |  |  |  |  | 
| 747 |  |  |  |  |  |  | || $new_pos < 0 | 
| 748 |  |  |  |  |  |  | || $delta == 0) { | 
| 749 | 0 |  |  |  |  |  | my @new_terms_list; | 
| 750 |  |  |  |  |  |  |  | 
| 751 | 0 |  |  |  |  |  | for (my $count = 0; $count < @$terms_ref; $count++) { | 
| 752 | 0 | 0 |  |  |  |  | if ($delta > 0) { | 
| 753 | 0 | 0 | 0 |  |  |  | push @new_terms_list, $terms_ref->[$count + 1] | 
| 754 |  |  |  |  |  |  | if ($count < $new_pos && $count >= $curr_pos); | 
| 755 |  |  |  |  |  |  | } else { | 
| 756 | 0 | 0 | 0 |  |  |  | push @new_terms_list, $terms_ref->[$count - 1] | 
| 757 |  |  |  |  |  |  | if ($count > $new_pos && $count <= $curr_pos); | 
| 758 |  |  |  |  |  |  | } | 
| 759 | 0 | 0 | 0 |  |  |  | push @new_terms_list, $terms_ref->[$count] | 
|  |  |  | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
| 760 |  |  |  |  |  |  | if (($count < $new_pos && $count < $curr_pos) | 
| 761 |  |  |  |  |  |  | || ($count > $new_pos && $count > $curr_pos)); | 
| 762 | 0 | 0 |  |  |  |  | push @new_terms_list, $term | 
| 763 |  |  |  |  |  |  | if ($count == $new_pos); | 
| 764 |  |  |  |  |  |  | } | 
| 765 |  |  |  |  |  |  |  | 
| 766 | 0 |  |  |  |  |  | delete $self->{'_terms'}; | 
| 767 | 0 |  |  |  |  |  | $self->{'_terms'} = \@new_terms_list; | 
| 768 |  |  |  |  |  |  | } | 
| 769 |  |  |  |  |  |  | } | 
| 770 |  |  |  |  |  |  |  | 
| 771 |  |  |  |  |  |  | sub increase_term_pos { | 
| 772 | 0 |  |  | 0 |  |  | my ($self, $term, $increment) = @_; | 
| 773 |  |  |  |  |  |  |  | 
| 774 | 0 | 0 |  |  |  |  | $increment = 1 unless (defined $increment); | 
| 775 |  |  |  |  |  |  |  | 
| 776 | 0 |  |  |  |  |  | $self->reposition_term ($term, $increment); | 
| 777 |  |  |  |  |  |  | } | 
| 778 |  |  |  |  |  |  |  | 
| 779 |  |  |  |  |  |  | sub decrease_term_pos { | 
| 780 | 0 |  |  | 0 |  |  | my ($self, $term, $decrement) = @_; | 
| 781 |  |  |  |  |  |  |  | 
| 782 | 0 | 0 |  |  |  |  | $decrement = 1 unless (defined $decrement); | 
| 783 |  |  |  |  |  |  |  | 
| 784 | 0 |  |  |  |  |  | $self->reposition_term ($term, -$decrement); | 
| 785 |  |  |  |  |  |  | } | 
| 786 |  |  |  |  |  |  |  | 
| 787 |  |  |  |  |  |  | sub increase_term_pos_by_key { | 
| 788 | 0 |  |  | 0 |  |  | my ($self, $key, $increment) = @_; | 
| 789 |  |  |  |  |  |  |  | 
| 790 | 0 |  |  |  |  |  | my $term = $self->get_term ($key); | 
| 791 | 0 |  |  |  |  |  | $self->increase_term_pos ($term, $increment); | 
| 792 |  |  |  |  |  |  | } | 
| 793 |  |  |  |  |  |  |  | 
| 794 |  |  |  |  |  |  | sub decrease_term_pos_by_key { | 
| 795 | 0 |  |  | 0 |  |  | my ($self, $key, $decrement) = @_; | 
| 796 |  |  |  |  |  |  |  | 
| 797 | 0 |  |  |  |  |  | my $term = $self->get_term ($key); | 
| 798 | 0 |  |  |  |  |  | $self->decrease_term_pos ($term, $decrement); | 
| 799 |  |  |  |  |  |  | } | 
| 800 |  |  |  |  |  |  |  | 
| 801 |  |  |  |  |  |  | sub save { | 
| 802 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 803 |  |  |  |  |  |  |  | 
| 804 |  |  |  |  |  |  | open DOUTF, '>'.$self->{'filename'} | 
| 805 |  |  |  |  |  |  | or die "Could not open dictionary (\"" | 
| 806 | 0 | 0 |  |  |  |  | .$self->{'filename'}."\" for writing).\n"; | 
| 807 | 0 |  |  |  |  |  | my $terms_ref = $self->{'_terms'}; | 
| 808 | 0 |  |  |  |  |  | foreach my $term (@$terms_ref) { | 
| 809 | 0 |  |  |  |  |  | my $line = $term->{'key'}; | 
| 810 | 0 |  |  |  |  |  | my $definition = $term->definition (); | 
| 811 | 0 | 0 | 0 |  |  |  | $line .= ':'.$definition if (defined $definition && $definition ne ''); | 
| 812 | 0 | 0 |  |  |  |  | if ($term->{'mandatory'}) { | 
| 813 | 0 | 0 | 0 |  |  |  | $line .= (defined $definition && $definition ne '') ? ':!' : '::!'; | 
| 814 |  |  |  |  |  |  | } | 
| 815 | 0 |  |  |  |  |  | print DOUTF $line."\n"; | 
| 816 |  |  |  |  |  |  | } | 
| 817 | 0 |  |  |  |  |  | close DOUTF; | 
| 818 |  |  |  |  |  |  | } | 
| 819 |  |  |  |  |  |  |  | 
| 820 |  |  |  |  |  |  | package CGI::Listman::dictionary::term; | 
| 821 |  |  |  |  |  |  |  | 
| 822 |  |  |  |  |  |  | sub new { | 
| 823 | 0 |  |  | 0 |  |  | my $class = shift; | 
| 824 |  |  |  |  |  |  |  | 
| 825 | 0 |  |  |  |  |  | my $self = {}; | 
| 826 | 0 |  |  |  |  |  | $self->{'key'} = shift; | 
| 827 | 0 |  |  |  |  |  | $self->{'_definition'} = shift; | 
| 828 | 0 |  | 0 |  |  |  | $self->{'mandatory'} = shift || 0; | 
| 829 |  |  |  |  |  |  |  | 
| 830 | 0 |  |  |  |  |  | bless $self, $class; | 
| 831 |  |  |  |  |  |  | } | 
| 832 |  |  |  |  |  |  |  | 
| 833 |  |  |  |  |  |  | sub set_key { | 
| 834 | 0 |  |  | 0 |  |  | my ($self, $key) = @_; | 
| 835 |  |  |  |  |  |  |  | 
| 836 | 0 | 0 | 0 |  |  |  | die "Bad key name.\n" unless (defined $key && $key ne ''); | 
| 837 |  |  |  |  |  |  | die 'This term already has a key name ("'.$self->{'key'}."\n" | 
| 838 | 0 | 0 |  |  |  |  | if (defined $self->{'key'}); | 
| 839 | 0 |  |  |  |  |  | $self->{'key'} = $key; | 
| 840 |  |  |  |  |  |  | } | 
| 841 |  |  |  |  |  |  |  | 
| 842 |  |  |  |  |  |  | sub set_definition { | 
| 843 | 0 |  |  | 0 |  |  | my ($self, $definition) = @_; | 
| 844 |  |  |  |  |  |  |  | 
| 845 | 0 | 0 | 0 |  |  |  | $definition = undef if (defined $definition | 
| 846 |  |  |  |  |  |  | && ($definition =~ m/^\s+$/)); | 
| 847 | 0 |  |  |  |  |  | $self->{'_definition'} = $definition; | 
| 848 |  |  |  |  |  |  | } | 
| 849 |  |  |  |  |  |  |  | 
| 850 |  |  |  |  |  |  | sub set_mandatory { | 
| 851 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 852 |  |  |  |  |  |  |  | 
| 853 | 0 |  |  |  |  |  | $self->{'mandatory'} = 1; | 
| 854 |  |  |  |  |  |  | } | 
| 855 |  |  |  |  |  |  |  | 
| 856 |  |  |  |  |  |  | sub definition { | 
| 857 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 858 |  |  |  |  |  |  |  | 
| 859 | 0 |  |  |  |  |  | my $definition = $self->{'_definition'}; | 
| 860 |  |  |  |  |  |  |  | 
| 861 | 0 |  |  |  |  |  | return $definition; | 
| 862 |  |  |  |  |  |  | } | 
| 863 |  |  |  |  |  |  |  | 
| 864 |  |  |  |  |  |  | sub definition_or_key { | 
| 865 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 866 |  |  |  |  |  |  |  | 
| 867 | 0 |  | 0 |  |  |  | my $definition = $self->definition () || $self->{'key'}; | 
| 868 |  |  |  |  |  |  |  | 
| 869 | 0 |  |  |  |  |  | return $definition; | 
| 870 |  |  |  |  |  |  | } | 
| 871 |  |  |  |  |  |  |  | 
| 872 |  |  |  |  |  |  | 1; | 
| 873 |  |  |  |  |  |  | __END__ |