+ my ($repeat_no_enclosing_tags, $repeat_enclosing_tags);
+
+ if ($repeat) {
+ if (ref ($repeat) eq 'ARRAY' ) {
+ $repeat_no_enclosing_tags = $repeat;
+ } else {
+ $repeat_enclosing_tags = $repeat;
+ }
+ }
+
+ if ($repeat_enclosing_tags) {
+ while ( my ($tag, $tag_tables) = each %$repeat_enclosing_tags ) {
+ if ( $letter->{content} =~ m!<$tag>(.*)</$tag>!s ) {
+ my $subcontent = $1;
+ my @lines = map {
+ my %subletter = ( title => '', content => $subcontent );
+ _substitute_tables( \%subletter, $_ );
+ $subletter{content};
+ } @$tag_tables;
+ $letter->{content} =~ s!<$tag>.*</$tag>!join( "\n", @lines )!se;
+ }
+ }
+ }
+
+ if ($tables) {
+ _substitute_tables( $letter, $tables );
+ }
+
+ if ($repeat_no_enclosing_tags) {
+ if ( $letter->{content} =~ m/[^\n]*<<.*>>[^\n]*/so ) {
+ my $line = $&;
+ my $i = 1;
+ my @lines = map {
+ my $c = $line;
+ $c =~ s/<<count>>/$i/go;
+ foreach my $field ( keys %{$_} ) {
+ $c =~ s/(<<[^\.]+.$field>>)/$_->{$field}/;
+ }
+ $i++;
+ $c;
+ } @$repeat_no_enclosing_tags;
+
+ my $replaceby = join( "\n", @lines );
+ $letter->{content} =~ s/\Q$line\E/$replaceby/s;
+ }
+ }
+
+ $letter->{content} =~ s/<<\S*>>//go; #remove any stragglers
+# $letter->{content} =~ s/<<[^>]*>>//go;
+
+ return $letter;
+}
+
+sub _substitute_tables {
+ my ( $letter, $tables ) = @_;
+ while ( my ($table, $param) = each %$tables ) {
+ next unless $param;
+
+ my $ref = ref $param;
+
+ my $values;
+ if ($ref && $ref eq 'HASH') {
+ $values = $param;
+ }
+ else {
+ my @pk;
+ my $sth = _parseletter_sth($table);
+ unless ($sth) {
+ warn "_parseletter_sth('$table') failed to return a valid sth. No substitution will be done for that table.";
+ return;
+ }
+ $sth->execute( $ref ? @$param : $param );
+
+ $values = $sth->fetchrow_hashref;
+ }
+
+ _parseletter ( $letter, $table, $values );
+ }
+}
+
+my %handles = ();
+sub _parseletter_sth {