| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | #====================================================================== | 
| 2 |  |  |  |  |  |  | package DBD::SQLite::VirtualTable::FileContent; | 
| 3 |  |  |  |  |  |  | #====================================================================== | 
| 4 | 2 |  |  | 2 |  | 145696 | use strict; | 
|  | 2 |  |  |  |  | 7 |  | 
|  | 2 |  |  |  |  | 67 |  | 
| 5 | 2 |  |  | 2 |  | 11 | use warnings; | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 87 |  | 
| 6 | 2 |  |  | 2 |  | 12 | use base 'DBD::SQLite::VirtualTable'; | 
|  | 2 |  |  |  |  | 5 |  | 
|  | 2 |  |  |  |  | 1309 |  | 
| 7 |  |  |  |  |  |  |  | 
| 8 |  |  |  |  |  |  | my %option_ok = map {($_ => 1)} qw/source content_col path_col | 
| 9 |  |  |  |  |  |  | expose root get_content/; | 
| 10 |  |  |  |  |  |  |  | 
| 11 |  |  |  |  |  |  | my %defaults = ( | 
| 12 |  |  |  |  |  |  | content_col => "content", | 
| 13 |  |  |  |  |  |  | path_col    => "path", | 
| 14 |  |  |  |  |  |  | expose      => "*", | 
| 15 |  |  |  |  |  |  | get_content => "DBD::SQLite::VirtualTable::FileContent::get_content", | 
| 16 |  |  |  |  |  |  | ); | 
| 17 |  |  |  |  |  |  |  | 
| 18 |  |  |  |  |  |  |  | 
| 19 |  |  |  |  |  |  | #---------------------------------------------------------------------- | 
| 20 |  |  |  |  |  |  | # object instanciation | 
| 21 |  |  |  |  |  |  | #---------------------------------------------------------------------- | 
| 22 |  |  |  |  |  |  |  | 
| 23 |  |  |  |  |  |  | sub NEW { | 
| 24 | 3 |  |  | 3 | 1 | 12 | my $class = shift; | 
| 25 |  |  |  |  |  |  |  | 
| 26 | 3 |  |  |  |  | 17 | my $self  = $class->_PREPARE_SELF(@_); | 
| 27 |  |  |  |  |  |  |  | 
| 28 | 3 |  |  |  |  | 7 | local $" = ", "; # for array interpolation in strings | 
| 29 |  |  |  |  |  |  |  | 
| 30 |  |  |  |  |  |  | # initial parameter check | 
| 31 | 3 | 50 |  |  |  | 35 | !@{$self->{columns}} | 
|  | 3 |  |  |  |  | 18 |  | 
| 32 | 0 |  |  |  |  | 0 | or die "${class}->NEW(): illegal options: @{$self->{columns}}"; | 
| 33 |  |  |  |  |  |  | $self->{options}{source} | 
| 34 | 3 | 50 |  |  |  | 12 | or die "${class}->NEW(): missing (source=...)"; | 
| 35 | 3 |  |  |  |  | 5 | my @bad_options = grep {!$option_ok{$_}} keys %{$self->{options}}; | 
|  | 9 |  |  |  |  | 28 |  | 
|  | 3 |  |  |  |  | 20 |  | 
| 36 |  |  |  |  |  |  | !@bad_options | 
| 37 | 3 | 50 |  |  |  | 22 | or die "${class}->NEW(): bad options: @bad_options"; | 
| 38 |  |  |  |  |  |  |  | 
| 39 |  |  |  |  |  |  | # defaults ... tempted to use //= but we still want to support perl 5.8 :-( | 
| 40 | 3 |  |  |  |  | 10 | foreach my $k (keys %defaults) { | 
| 41 |  |  |  |  |  |  | defined $self->{options}{$k} | 
| 42 | 12 | 100 |  |  |  | 39 | or $self->{options}{$k} = $defaults{$k}; | 
| 43 |  |  |  |  |  |  | } | 
| 44 |  |  |  |  |  |  |  | 
| 45 |  |  |  |  |  |  | # get list of columns from the source table | 
| 46 | 3 |  |  |  |  | 9 | my $src_table  = $self->{options}{source}; | 
| 47 | 3 |  |  |  |  | 12 | my $sql        = "PRAGMA table_info($src_table)"; | 
| 48 | 3 |  |  |  |  | 6 | my $dbh        = ${$self->{dbh_ref}}; # can't use method ->dbh, not blessed yet | 
|  | 3 |  |  |  |  | 8 |  | 
| 49 | 3 |  |  |  |  | 58 | my $src_info   = $dbh->selectall_arrayref($sql, {Slice => [1, 2]}); | 
| 50 | 3 | 50 |  |  |  | 454 | @$src_info | 
| 51 |  |  |  |  |  |  | or die "${class}->NEW(source=$src_table): no such table in database"; | 
| 52 |  |  |  |  |  |  |  | 
| 53 |  |  |  |  |  |  | # associate each source colname with its type info or " " (should eval true) | 
| 54 | 3 |  | 50 |  |  | 9 | my %src_col = map  { ($_->[0] => $_->[1] || " ") } @$src_info; | 
|  | 8 |  |  |  |  | 41 |  | 
| 55 |  |  |  |  |  |  |  | 
| 56 |  |  |  |  |  |  |  | 
| 57 |  |  |  |  |  |  | # check / complete the exposed columns | 
| 58 | 3 |  |  |  |  | 7 | my @exposed_cols; | 
| 59 | 3 | 50 |  |  |  | 13 | if ($self->{options}{expose} eq '*') { | 
| 60 | 0 |  |  |  |  | 0 | @exposed_cols = map {$_->[0]} @$src_info; | 
|  | 0 |  |  |  |  | 0 |  | 
| 61 |  |  |  |  |  |  | } | 
| 62 |  |  |  |  |  |  | else { | 
| 63 | 3 |  |  |  |  | 23 | @exposed_cols = split /\s*,\s*/, $self->{options}{expose}; | 
| 64 | 3 |  |  |  |  | 8 | my @bad_cols  = grep { !$src_col{$_} } @exposed_cols; | 
|  | 5 |  |  |  |  | 27 |  | 
| 65 | 3 | 50 |  |  |  | 19 | die "table $src_table has no column named @bad_cols" if @bad_cols; | 
| 66 |  |  |  |  |  |  | } | 
| 67 | 3 |  |  |  |  | 10 | for (@exposed_cols) { | 
| 68 |  |  |  |  |  |  | die "$class: $self->{options}{content_col} cannot be both the " | 
| 69 | 5 | 50 |  |  |  | 15 | . "content_col and an exposed col" if $_ eq $self->{options}{content_col}; | 
| 70 |  |  |  |  |  |  | } | 
| 71 |  |  |  |  |  |  |  | 
| 72 |  |  |  |  |  |  | # build the list of columns for this table | 
| 73 |  |  |  |  |  |  | $self->{columns} = [ "$self->{options}{content_col} TEXT", | 
| 74 | 3 |  |  |  |  | 12 | map {"$_ $src_col{$_}"} @exposed_cols ]; | 
|  | 5 |  |  |  |  | 22 |  | 
| 75 |  |  |  |  |  |  |  | 
| 76 |  |  |  |  |  |  | # acquire a coderef to the get_content() implementation, which | 
| 77 |  |  |  |  |  |  | # was given as a symbolic reference in %options | 
| 78 | 2 |  |  | 2 |  | 25 | no strict 'refs'; | 
|  | 2 |  |  |  |  | 5 |  | 
|  | 2 |  |  |  |  | 1092 |  | 
| 79 | 3 |  |  |  |  | 8 | $self->{get_content} = \ &{$self->{options}{get_content}}; | 
|  | 3 |  |  |  |  | 15 |  | 
| 80 |  |  |  |  |  |  |  | 
| 81 | 3 |  |  |  |  | 33 | bless $self, $class; | 
| 82 |  |  |  |  |  |  | } | 
| 83 |  |  |  |  |  |  |  | 
| 84 |  |  |  |  |  |  | sub _build_headers { | 
| 85 | 3 |  |  | 3 |  | 7 | my $self = shift; | 
| 86 |  |  |  |  |  |  |  | 
| 87 | 3 |  |  |  |  | 30 | my $cols = $self->sqlite_table_info; | 
| 88 |  |  |  |  |  |  |  | 
| 89 |  |  |  |  |  |  | # headers : names of columns, without type information | 
| 90 | 3 |  |  |  |  | 638 | $self->{headers} = [ map {$_->{name}} @$cols ]; | 
|  | 8 |  |  |  |  | 38 |  | 
| 91 |  |  |  |  |  |  | } | 
| 92 |  |  |  |  |  |  |  | 
| 93 |  |  |  |  |  |  |  | 
| 94 |  |  |  |  |  |  | #---------------------------------------------------------------------- | 
| 95 |  |  |  |  |  |  | # method for initiating a search | 
| 96 |  |  |  |  |  |  | #---------------------------------------------------------------------- | 
| 97 |  |  |  |  |  |  |  | 
| 98 |  |  |  |  |  |  | sub BEST_INDEX { | 
| 99 | 10 |  |  | 10 | 1 | 907 | my ($self, $constraints, $order_by) = @_; | 
| 100 |  |  |  |  |  |  |  | 
| 101 | 10 | 100 |  |  |  | 46 | $self->_build_headers if !$self->{headers}; | 
| 102 |  |  |  |  |  |  |  | 
| 103 | 10 |  |  |  |  | 20 | my @conditions; | 
| 104 | 10 |  |  |  |  | 19 | my $ix = 0; | 
| 105 | 10 |  |  |  |  | 36 | foreach my $constraint (grep {$_->{usable}} @$constraints) { | 
|  | 5 |  |  |  |  | 24 |  | 
| 106 | 5 |  |  |  |  | 16 | my $col     = $constraint->{col}; | 
| 107 |  |  |  |  |  |  |  | 
| 108 |  |  |  |  |  |  | # if this is the content column, skip because we can't filter on it | 
| 109 | 5 | 100 |  |  |  | 15 | next if $col == 0; | 
| 110 |  |  |  |  |  |  |  | 
| 111 |  |  |  |  |  |  | # for other columns, build a fragment for SQL WHERE on the underlying table | 
| 112 | 4 | 100 |  |  |  | 14 | my $colname = $col == -1 ? "rowid" : $self->{headers}[$col]; | 
| 113 | 4 |  |  |  |  | 15 | push @conditions, "$colname $constraint->{op} ?"; | 
| 114 | 4 |  |  |  |  | 11 | $constraint->{argvIndex} = $ix++; | 
| 115 | 4 |  |  |  |  | 11 | $constraint->{omit}      = 1;     # SQLite doesn't need to re-check the op | 
| 116 |  |  |  |  |  |  | } | 
| 117 |  |  |  |  |  |  |  | 
| 118 |  |  |  |  |  |  | # TODO : exploit $order_by to add ordering clauses within idxStr | 
| 119 |  |  |  |  |  |  |  | 
| 120 | 10 |  |  |  |  | 64 | my $outputs = { | 
| 121 |  |  |  |  |  |  | idxNum           => 1, | 
| 122 |  |  |  |  |  |  | idxStr           => join(" AND ", @conditions), | 
| 123 |  |  |  |  |  |  | orderByConsumed  => 0, | 
| 124 |  |  |  |  |  |  | estimatedCost    => 1.0, | 
| 125 |  |  |  |  |  |  | estimatedRows    => undef, | 
| 126 |  |  |  |  |  |  | }; | 
| 127 |  |  |  |  |  |  |  | 
| 128 | 10 |  |  |  |  | 15820 | return $outputs; | 
| 129 |  |  |  |  |  |  | } | 
| 130 |  |  |  |  |  |  |  | 
| 131 |  |  |  |  |  |  |  | 
| 132 |  |  |  |  |  |  | #---------------------------------------------------------------------- | 
| 133 |  |  |  |  |  |  | # method for preventing updates | 
| 134 |  |  |  |  |  |  | #---------------------------------------------------------------------- | 
| 135 |  |  |  |  |  |  |  | 
| 136 |  |  |  |  |  |  | sub _SQLITE_UPDATE { | 
| 137 | 0 |  |  | 0 |  | 0 | my ($self, $old_rowid, $new_rowid, @values) = @_; | 
| 138 |  |  |  |  |  |  |  | 
| 139 | 0 |  |  |  |  | 0 | die "attempt to update a readonly virtual table"; | 
| 140 |  |  |  |  |  |  | } | 
| 141 |  |  |  |  |  |  |  | 
| 142 |  |  |  |  |  |  |  | 
| 143 |  |  |  |  |  |  | #---------------------------------------------------------------------- | 
| 144 |  |  |  |  |  |  | # file slurping function (not a method!) | 
| 145 |  |  |  |  |  |  | #---------------------------------------------------------------------- | 
| 146 |  |  |  |  |  |  |  | 
| 147 |  |  |  |  |  |  | sub get_content { | 
| 148 | 78 |  |  | 78 | 1 | 158 | my ($path, $root) = @_; | 
| 149 |  |  |  |  |  |  |  | 
| 150 | 78 | 50 |  |  |  | 290 | $path = "$root/$path" if $root; | 
| 151 |  |  |  |  |  |  |  | 
| 152 | 78 |  |  |  |  | 153 | my $content = ""; | 
| 153 | 78 | 50 |  |  |  | 3624 | if (open my $fh, "<", $path) { | 
| 154 | 78 |  |  |  |  | 468 | local $/;          # slurp the whole file into a scalar | 
| 155 | 78 |  |  |  |  | 10990 | $content = <$fh>; | 
| 156 | 78 |  |  |  |  | 1125 | close $fh; | 
| 157 |  |  |  |  |  |  | } | 
| 158 |  |  |  |  |  |  | else { | 
| 159 | 0 |  |  |  |  | 0 | warn "can't open $path"; | 
| 160 |  |  |  |  |  |  | } | 
| 161 |  |  |  |  |  |  |  | 
| 162 | 78 |  |  |  |  | 1469 | return $content; | 
| 163 |  |  |  |  |  |  | } | 
| 164 |  |  |  |  |  |  |  | 
| 165 |  |  |  |  |  |  |  | 
| 166 |  |  |  |  |  |  |  | 
| 167 |  |  |  |  |  |  | #====================================================================== | 
| 168 |  |  |  |  |  |  | package DBD::SQLite::VirtualTable::FileContent::Cursor; | 
| 169 |  |  |  |  |  |  | #====================================================================== | 
| 170 | 2 |  |  | 2 |  | 24 | use strict; | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 57 |  | 
| 171 | 2 |  |  | 2 |  | 16 | use warnings; | 
|  | 2 |  |  |  |  | 5 |  | 
|  | 2 |  |  |  |  | 78 |  | 
| 172 | 2 |  |  | 2 |  | 13 | use base "DBD::SQLite::VirtualTable::Cursor"; | 
|  | 2 |  |  |  |  | 5 |  | 
|  | 2 |  |  |  |  | 1654 |  | 
| 173 |  |  |  |  |  |  |  | 
| 174 |  |  |  |  |  |  |  | 
| 175 |  |  |  |  |  |  | sub FILTER { | 
| 176 | 57 |  |  | 57 |  | 169 | my ($self, $idxNum, $idxStr, @values) = @_; | 
| 177 |  |  |  |  |  |  |  | 
| 178 | 57 |  |  |  |  | 105 | my $vtable = $self->{vtable}; | 
| 179 |  |  |  |  |  |  |  | 
| 180 |  |  |  |  |  |  | # build SQL | 
| 181 | 57 |  |  |  |  | 130 | local $" = ", "; | 
| 182 | 57 |  |  |  |  | 97 | my @cols = @{$vtable->{headers}}; | 
|  | 57 |  |  |  |  | 134 |  | 
| 183 | 57 |  |  |  |  | 111 | $cols[0] = 'rowid';                 # replace the content column by the rowid | 
| 184 | 57 |  |  |  |  | 138 | push @cols, $vtable->{options}{path_col}; # path col in last position | 
| 185 | 57 |  |  |  |  | 208 | my $sql  = "SELECT @cols FROM $vtable->{options}{source}"; | 
| 186 | 57 | 100 |  |  |  | 258 | $sql .= " WHERE $idxStr" if $idxStr; | 
| 187 |  |  |  |  |  |  |  | 
| 188 |  |  |  |  |  |  | # request on the index table | 
| 189 | 57 |  |  |  |  | 163 | my $dbh = $vtable->dbh; | 
| 190 | 57 | 50 |  |  |  | 290 | $self->{sth} = $dbh->prepare($sql) | 
| 191 |  |  |  |  |  |  | or die DBI->errstr; | 
| 192 | 57 |  |  |  |  | 1023 | $self->{sth}->execute(@values); | 
| 193 | 57 |  |  |  |  | 685 | $self->{row} = $self->{sth}->fetchrow_arrayref; | 
| 194 |  |  |  |  |  |  |  | 
| 195 | 57 |  |  |  |  | 366 | return; | 
| 196 |  |  |  |  |  |  | } | 
| 197 |  |  |  |  |  |  |  | 
| 198 |  |  |  |  |  |  |  | 
| 199 |  |  |  |  |  |  | sub EOF { | 
| 200 | 82 |  |  | 82 |  | 198 | my ($self) = @_; | 
| 201 |  |  |  |  |  |  |  | 
| 202 | 82 |  |  |  |  | 25516 | return !$self->{row}; | 
| 203 |  |  |  |  |  |  | } | 
| 204 |  |  |  |  |  |  |  | 
| 205 |  |  |  |  |  |  | sub NEXT { | 
| 206 | 25 |  |  | 25 |  | 240 | my ($self) = @_; | 
| 207 |  |  |  |  |  |  |  | 
| 208 | 25 |  |  |  |  | 391 | $self->{row} = $self->{sth}->fetchrow_arrayref; | 
| 209 |  |  |  |  |  |  | } | 
| 210 |  |  |  |  |  |  |  | 
| 211 |  |  |  |  |  |  | sub COLUMN { | 
| 212 | 160 |  |  | 160 |  | 387 | my ($self, $idxCol) = @_; | 
| 213 |  |  |  |  |  |  |  | 
| 214 | 160 | 100 |  |  |  | 33416 | return $idxCol == 0 ? $self->file_content : $self->{row}[$idxCol]; | 
| 215 |  |  |  |  |  |  | } | 
| 216 |  |  |  |  |  |  |  | 
| 217 |  |  |  |  |  |  | sub ROWID { | 
| 218 | 75 |  |  | 75 |  | 177 | my ($self) = @_; | 
| 219 |  |  |  |  |  |  |  | 
| 220 | 75 |  |  |  |  | 695 | return $self->{row}[0]; | 
| 221 |  |  |  |  |  |  | } | 
| 222 |  |  |  |  |  |  |  | 
| 223 |  |  |  |  |  |  | sub file_content { | 
| 224 | 78 |  |  | 78 |  | 135 | my ($self) = @_; | 
| 225 |  |  |  |  |  |  |  | 
| 226 | 78 |  |  |  |  | 181 | my $root = $self->{vtable}{options}{root}; | 
| 227 | 78 |  |  |  |  | 140 | my $path = $self->{row}[-1]; | 
| 228 | 78 |  |  |  |  | 128 | my $get_content_func = $self->{vtable}{get_content}; | 
| 229 |  |  |  |  |  |  |  | 
| 230 | 78 |  |  |  |  | 171 | return $get_content_func->($path, $root); | 
| 231 |  |  |  |  |  |  | } | 
| 232 |  |  |  |  |  |  |  | 
| 233 |  |  |  |  |  |  |  | 
| 234 |  |  |  |  |  |  | 1; | 
| 235 |  |  |  |  |  |  |  | 
| 236 |  |  |  |  |  |  | __END__ | 
| 237 |  |  |  |  |  |  |  | 
| 238 |  |  |  |  |  |  |  | 
| 239 |  |  |  |  |  |  | =head1 NAME | 
| 240 |  |  |  |  |  |  |  | 
| 241 |  |  |  |  |  |  | DBD::SQLite::VirtualTable::FileContent -- virtual table for viewing file contents | 
| 242 |  |  |  |  |  |  |  | 
| 243 |  |  |  |  |  |  |  | 
| 244 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 245 |  |  |  |  |  |  |  | 
| 246 |  |  |  |  |  |  | Within Perl : | 
| 247 |  |  |  |  |  |  |  | 
| 248 |  |  |  |  |  |  | $dbh->sqlite_create_module(fcontent => "DBD::SQLite::VirtualTable::FileContent"); | 
| 249 |  |  |  |  |  |  |  | 
| 250 |  |  |  |  |  |  | Then, within SQL : | 
| 251 |  |  |  |  |  |  |  | 
| 252 |  |  |  |  |  |  | CREATE VIRTUAL TABLE tbl USING fcontent( | 
| 253 |  |  |  |  |  |  | source      = src_table, | 
| 254 |  |  |  |  |  |  | content_col = content, | 
| 255 |  |  |  |  |  |  | path_col    = path, | 
| 256 |  |  |  |  |  |  | expose      = "path, col1, col2, col3", -- or "*" | 
| 257 |  |  |  |  |  |  | root        = "/foo/bar" | 
| 258 |  |  |  |  |  |  | get_content = Foo::Bar::read_from_file | 
| 259 |  |  |  |  |  |  | ); | 
| 260 |  |  |  |  |  |  |  | 
| 261 |  |  |  |  |  |  | SELECT col1, path, content FROM tbl WHERE ...; | 
| 262 |  |  |  |  |  |  |  | 
| 263 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 264 |  |  |  |  |  |  |  | 
| 265 |  |  |  |  |  |  | A "FileContent" virtual table is bound to some underlying I<source | 
| 266 |  |  |  |  |  |  | table>, which has a column containing paths to files.  The virtual | 
| 267 |  |  |  |  |  |  | table behaves like a database view on the source table, with an added | 
| 268 |  |  |  |  |  |  | column which exposes the content from those files. | 
| 269 |  |  |  |  |  |  |  | 
| 270 |  |  |  |  |  |  | This is especially useful as an "external content" to some | 
| 271 |  |  |  |  |  |  | fulltext table (see L<DBD::SQLite::Fulltext_search>) : the index | 
| 272 |  |  |  |  |  |  | table stores some metadata about files, and then the fulltext engine | 
| 273 |  |  |  |  |  |  | can index both the metadata and the file contents. | 
| 274 |  |  |  |  |  |  |  | 
| 275 |  |  |  |  |  |  | =head1 PARAMETERS | 
| 276 |  |  |  |  |  |  |  | 
| 277 |  |  |  |  |  |  | Parameters for creating a C<FileContent> virtual table are | 
| 278 |  |  |  |  |  |  | specified within the C<CREATE VIRTUAL TABLE> statement, just | 
| 279 |  |  |  |  |  |  | like regular column declarations, but with an '=' sign. | 
| 280 |  |  |  |  |  |  | Authorized parameters are : | 
| 281 |  |  |  |  |  |  |  | 
| 282 |  |  |  |  |  |  | =over | 
| 283 |  |  |  |  |  |  |  | 
| 284 |  |  |  |  |  |  | =item C<source> | 
| 285 |  |  |  |  |  |  |  | 
| 286 |  |  |  |  |  |  | The name of the I<source table>. | 
| 287 |  |  |  |  |  |  | This parameter is mandatory. All other parameters are optional. | 
| 288 |  |  |  |  |  |  |  | 
| 289 |  |  |  |  |  |  | =item C<content_col> | 
| 290 |  |  |  |  |  |  |  | 
| 291 |  |  |  |  |  |  | The name of the virtual column exposing file contents. | 
| 292 |  |  |  |  |  |  | The default is C<content>. | 
| 293 |  |  |  |  |  |  |  | 
| 294 |  |  |  |  |  |  | =item C<path_col> | 
| 295 |  |  |  |  |  |  |  | 
| 296 |  |  |  |  |  |  | The name of the column in C<source> that contains paths to files. | 
| 297 |  |  |  |  |  |  | The default is C<path>. | 
| 298 |  |  |  |  |  |  |  | 
| 299 |  |  |  |  |  |  | =item C<expose> | 
| 300 |  |  |  |  |  |  |  | 
| 301 |  |  |  |  |  |  | A comma-separated list (within double quotes) of source column names | 
| 302 |  |  |  |  |  |  | to be exposed by the virtual table. The default is C<"*">, which means | 
| 303 |  |  |  |  |  |  | all source columns. | 
| 304 |  |  |  |  |  |  |  | 
| 305 |  |  |  |  |  |  | =item C<root> | 
| 306 |  |  |  |  |  |  |  | 
| 307 |  |  |  |  |  |  | An optional root directory that will be prepended to the I<path> column | 
| 308 |  |  |  |  |  |  | when opening files. | 
| 309 |  |  |  |  |  |  |  | 
| 310 |  |  |  |  |  |  | =item C<get_content> | 
| 311 |  |  |  |  |  |  |  | 
| 312 |  |  |  |  |  |  | Fully qualified name of a Perl function for reading file contents. | 
| 313 |  |  |  |  |  |  | The default implementation just slurps the entire file into a string; | 
| 314 |  |  |  |  |  |  | but this hook can point to more sophisticated implementations, like for | 
| 315 |  |  |  |  |  |  | example a function that would remove html tags. The hooked function is | 
| 316 |  |  |  |  |  |  | called like this : | 
| 317 |  |  |  |  |  |  |  | 
| 318 |  |  |  |  |  |  | $file_content = $get_content->($path, $root); | 
| 319 |  |  |  |  |  |  |  | 
| 320 |  |  |  |  |  |  | =back | 
| 321 |  |  |  |  |  |  |  | 
| 322 |  |  |  |  |  |  | =head1 AUTHOR | 
| 323 |  |  |  |  |  |  |  | 
| 324 |  |  |  |  |  |  | Laurent Dami E<lt>dami@cpan.orgE<gt> | 
| 325 |  |  |  |  |  |  |  | 
| 326 |  |  |  |  |  |  | =head1 COPYRIGHT AND LICENSE | 
| 327 |  |  |  |  |  |  |  | 
| 328 |  |  |  |  |  |  | Copyright Laurent Dami, 2014. | 
| 329 |  |  |  |  |  |  |  | 
| 330 |  |  |  |  |  |  | This library is free software; you can redistribute it and/or modify | 
| 331 |  |  |  |  |  |  | it under the same terms as Perl itself. | 
| 332 |  |  |  |  |  |  |  | 
| 333 |  |  |  |  |  |  | =cut |