summaryrefslogtreecommitdiff
path: root/fml/lib/Mail/HTML
diff options
context:
space:
mode:
authorfukachan <fukachan>2002-03-31 03:39:13 +0000
committerfukachan <fukachan>2002-03-31 03:39:13 +0000
commita249a796d278345c1ac2718affd2dbb47346f399 (patch)
tree859d1967bdece97795dc5c109cac9a2297f47376 /fml/lib/Mail/HTML
parent78e8e804b05c1667173fe118c488744f34a1e07a (diff)
downloadfml8-a249a796d278345c1ac2718affd2dbb47346f399.tar.gz
fml8-a249a796d278345c1ac2718affd2dbb47346f399.tar.bz2
fml8-a249a796d278345c1ac2718affd2dbb47346f399.zip
rename class: Mail::HTML::Lite -> Mail::Message::ToHTML
Diffstat (limited to 'fml/lib/Mail/HTML')
-rw-r--r--fml/lib/Mail/HTML/Lite.pm2391
1 files changed, 0 insertions, 2391 deletions
diff --git a/fml/lib/Mail/HTML/Lite.pm b/fml/lib/Mail/HTML/Lite.pm
deleted file mode 100644
index 3a1fab3e..00000000
--- a/fml/lib/Mail/HTML/Lite.pm
+++ /dev/null
@@ -1,2391 +0,0 @@
-#-*- perl -*-
-#
-# Copyright (C) 2001,2002 Ken'ichi Fukamachi
-# All rights reserved. This program is free software; you can
-# redistribute it and/or modify it under the same terms as Perl itself.
-#
-# $FML: Lite.pm,v 1.39 2002/02/01 12:04:02 fukachan Exp $
-#
-
-package Mail::HTML::Lite;
-use strict;
-use vars qw(@ISA @EXPORT @EXPORT_OK $AUTOLOAD);
-use Carp;
-
-my $debug = 0;
-my $URL = "<A HREF=\"http://www.fml.org/software/\">Mail::HTML::Lite</A>";
-
-my $version = q$FML: Lite.pm,v 1.39 2002/02/01 12:04:02 fukachan Exp $;
-if ($version =~ /,v\s+([\d\.]+)\s+/) {
- $version = "$URL $1";
-}
-
-=head1 NAME
-
-Mail::HTML::Lite - convert text format mail to HTML format
-
-=head1 SYNOPSIS
-
- ... lock by something ...
-
- use Mail::HTML::Lite;
- my $obj = new Mail::HTML::Lite {
- charset => "euc-jp",
- directory => "/var/www/htdocs/ml/elena",
- };
-
- $obj->htmlfy_rfc822_message({
- id => 1,
- src => "/var/spool/ml/elena/spool/1",
- });
-
- ... unlock by something ...
-
-This module itself provides no lock function.
-please use flock() built in perl or CPAN lock modules for it.
-
-=head1 DESCRIPTION
-
-=head2 Message structure created as HTML
-
-HTML-fied message has following structure.
-something() below is method name.
-
- for example
- -------------------------------------------------------------------
- html_begin() <HTML><HEAD> ... </HEAD><BODY>
- mhl_preamble() <!-- comment used by this module -->
- mhl_separator() <HR>
-
- message header
- From: ...
- Subject: ...
-
- mhl_separator() <HR>
-
- message body
-
- mhl_separator() <HR>
- mhl_footer() <!-- comment used by this module -->
- html_end() </BODY></HTML>
-
-=head1 METHODS
-
-=head2 C<new($args)>
-
- $args = {
- directory => $directory,
- };
-
-C<$directory> is top level directory where html-fied articles are
-stored.
-
-=cut
-
-
-# Descriptions: constructor.
-# Arguments: OBJ($self) HASH_REF($args)
-# Side Effects: none
-# Return Value: OBJ
-sub new
-{
- my ($self, $args) = @_;
- my ($type) = ref($self) || $self;
- my $me = {};
-
- $me->{ _html_base_directory } = $args->{ directory };
- $me->{ _charset } = $args->{ charset } || 'us-ascii';
- $me->{ _is_attachment } = defined($args->{ attachment }) ? 1 : 0;
- $me->{ _db_type } = $args->{ db_type };
- $me->{ _args } = $args;
- $me->{ _num_attachment } = 0; # for child process
-
- return bless $me, $type;
-}
-
-
-=head2 C<htmlfy_rfc822_message($args)>
-
-convert mail to html.
-
- $args = {
- id => $id,
- path => $path,
- };
-
-where C<$path> is file path.
-
-=cut
-
-
-# Descriptions: top level entrance to convert mail to html
-# Arguments: OBJ($self) HASH_REF($args)
-# $args = { id => $id, path => $path };
-# $id identifier (e.g. "1" (article id))
-# $path file path (e.g. "/some/where/1");
-# Side Effects: none
-# Return Value: none
-sub htmlfy_rfc822_message
-{
- my ($self, $args) = @_;
-
- # initialize basic information
- # $id = article id
- # $src = source file
- # $dst = destination file (target html)
- my ($id, $src, $dst) = $self->_init_htmlfy_rfc822_message($args);
-
- # target html exists already.
- if (-f $dst) {
- $self->{ _ignore_list }->{ $id } = 1; # ignore flag
- warn("html file for $id already exists") if $debug;
- return undef;
- }
-
- use Mail::Message;
- use FileHandle;
- my $rh = new FileHandle $src;
- my $msg = Mail::Message->parse( { fd => $rh } );
- my $hdr = $msg->whole_message_header;
- my $body = $msg->whole_message_body;
-
- # save information for index.html and thread.html
- $self->cache_message_info($msg, { id => $id,
- src => $src,
- dst => $dst,
- } );
-
- # prepare output channel
- my $wh = $self->_set_output_channel( { dst => $dst } );
- unless (defined $wh) {
- croak("cannot open output file\n");
- }
-
- # before main message
- $self->html_begin($wh, { message => $msg });
- $self->mhl_preamble($wh);
-
- # analyze $msg, chain of Mail::Message objects.
- # See Mail::Message class for more detail.
- # XXX we use $m->{ next } here, but we should avoid this style and
- # XXX prepare access method for it in Mail::Message class.
- my ($m, $type, $attach);
- CHAIN:
- for ($m = $msg; defined($m) ; $m = $m->{ 'next' }) {
- $type = $m->data_type;
-
- last CHAIN if $type eq 'multipart.close-delimiter'; # last of multipart
- next CHAIN if $type =~ /^multipart/; # multipart type is special.
-
- unless ($type =~ /^\w+\/[-\w\d\.]+$/) {
- warn("invalid type={$type}");
- next CHAIN;
- }
-
- # header (Mail::Message object uses this special type)
- if ($type eq 'text/rfc822-headers') {
- $self->mhl_separator($wh);
- my $charset = $self->{ _charset };
- my $header = $self->_format_safe_header($msg);
- _print_raw_str($wh, $header, $charset);
- $self->mhl_separator($wh);
- }
- # message/rfc822 case (attached rfc822 message)
- elsif ($type eq 'message/rfc822') {
- $attach++;
-
- my $tmpf = $self->_create_temporary_file_in_raw_mode($m);
- if (defined $tmpf && -f $tmpf) {
- # write attachement into a separete file
- my $outf = _gen_attachment_filename($dst, $attach, 'html');
- my $args = $self->{ _args };
- $args->{ attachment } = 1; # clarify not top level content.
- my $text = new Mail::HTML::Lite $args;
- $text->htmlfy_rfc822_message({
- parent_id => $id,
- src => $tmpf,
- dst => $outf,
- });
-
- # show inline <HREF> link,
- # which appears in parent html ( == $wh channel ).
- $self->_print_inline_object_link({
- fh => $wh, # file descriptor
- type => $type, # XXX derived from input message
- num => $attach, # number
- file => $outf, # temporary file name
- });
-
- unlink $tmpf;
- }
- }
- # text/plain case.
- # XXX inline expansion.
- elsif ($type eq 'text/plain') {
- $self->_text_safe_print({
- fh => $wh, # parent html
- data => $m->message_text(),
- });
- }
- # create a separete file for attachment
- else {
- $attach++;
-
- # write attachement into a separete file
- my $outf = _gen_attachment_filename($dst, $attach, $type);
- my $enc = $m->encoding_mechanism;
- my $msginfo = { message => $m };
-
- # e.g. text/xxx case (e.g. text/html case)
- if ($type =~ /^text/) {
- # 1. firstly saved to temporary file $tmpf in "raw" mode
- my $tmpf = $self->_create_temporary_filename();
- $msginfo->{ file } = $tmpf;
-
- # once create temporary file
- _PRINT_DEBUG("attachment: type=$type attach=$attach enc=$enc");
- if ($enc) {
- $self->_binary_print($msginfo); # XXX raw mode
- }
- else {
- $self->_text_raw_print($msginfo); # XXX raw mode
- }
-
- # 2. secondary convert $tmpf to real target $outf with
- # some modification e.g. metachars escaping, ...
- # disable html tag in file saved in raw mode.
- if (-f $tmpf) {
- $msginfo->{ description } = "(HTML TAGs are disabled)";
- _disable_html_tag_in_file($tmpf, $outf);
- unlink $tmpf;
- }
- }
- # e.g. image/gif not text/* nor message/*
- else {
- $msginfo->{ file } = $outf;
- $self->_binary_print($msginfo);
- }
-
- # show inline <HREF> link appeared in parent html.
- $self->_print_inline_object_link({
- inline => 1,
- fh => $wh,
- type => $type,
- num => $attach,
- file => $outf,
- info => $msginfo,
- });
- }
- }
-
- # show navigation bar et.al. after message itself
- $self->mhl_separator($wh);
- $self->mhl_footer($wh);
- $self->html_end($wh);
-}
-
-
-# Descriptions: copy $inf file to $outf file with disabling HTML tag
-# by _print_safe_buf().
-# Arguments: STR($inf) STR($outf)
-# Side Effects: create $outf file
-# Return Value: none
-sub _disable_html_tag_in_file
-{
- my ($inf, $outf) = @_;
-
- use FileHandle;
- my $rh = new FileHandle $inf;
- my $wh = new FileHandle "> $outf";
- if (defined $rh) {
- my $buf = '';
- while (<$rh>) { $buf .= $_;}
- _print_safe_buf($wh, $buf);
- $wh->close;
- $rh->close;
- }
-}
-
-
-# Descriptions: return HTML filename
-# Arguments: OBJ($self) NUM($id)
-# Side Effects: none
-# Return Value: STR or UNDEF
-sub html_filename
-{
- my ($self, $id) = @_;
-
- if (defined($id) && ($id > 0)) {
- return "msg${id}.html";
- }
- else {
- return undef;
- }
-}
-
-
-# Descriptions: return HTML file path
-# Arguments: OBJ($self) NUM($id)
-# Side Effects: none
-# Return Value: STR
-sub html_filepath
-{
- my ($self, $id) = @_;
- my $html_base_dir = $self->{ _html_base_directory };
-
- if (defined($id) && ($id > 0)) {
- return "$html_base_dir/msg$id.html";
- }
- else {
- return undef;
- }
-}
-
-
-# Descriptions: parse $args and return file id, name, path.
-# Arguments: OBJ($self) HASH_REF($args)
-# Side Effects: none
-# Return Value: ARRAY(NUM, STR, STR)
-sub _init_htmlfy_rfc822_message
-{
- my ($self, $args) = @_;
- my ($id, $src, $dst);
-
- if (defined $args->{ src }) {
- $src = $args->{ src };
- }
- else {
- croak("htmlfy_rfc822_message: \$src is mandatory\n");
- }
-
- if (defined $args->{ id }) {
- my $html_base_dir = $self->{ _html_base_directory };
- $id = $args->{ id };
- $dst = $self->html_filepath($id);
- }
- # this object is an attachment if parent_id is specified.
- elsif (defined $args->{ parent_id }) {
- $self->{ _num_attachment }++;
- $id = $args->{ parent_id } .'.'. $self->{ _num_attachment };
- $dst = $args->{ dst };
- }
- # last resort: give unique identifier
- elsif (defined $args->{ dst }) {
- $id = time.".".$$;
- $dst = $args->{ dst };
- }
- # oops ;) wrong call of this function
- else {
- croak("htmlfy_rfc822_message: specify \$id or \$dst\n");
- }
-
- $self->{ _id } = $id;
-
- return ($id, $src, $dst);
-}
-
-
-# Descriptions: show html header + file title in <BODY>
-# Arguments: OBJ($self) HANDLE($wh) HASH_REF($args)
-# Side Effects: none
-# Return Value: none
-sub html_begin
-{
- my ($self, $wh, $args) = @_;
- my ($msg, $hdr, $title);
-
- if (defined $args->{ title }) {
- $title = $args->{ title };
- }
- elsif (defined $args->{ message }) {
- $msg = $args->{ message };
- $hdr = $msg->whole_message_header;
- $title = $self->_decode_mime_string( $hdr->get('subject') );
- }
-
- print $wh
- q{<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">};
- print $wh "\n";
- print $wh "<HTML>\n";
- print $wh "<HEAD>\n";
-
- if (defined $self->{ _charset }) {
- my $charset = $self->{ _charset };
- print $wh "<META http-equiv=\"Content-Type\"\n";
- print $wh " content=\"text/html; charset=${charset}\">\n";
- }
-
- if (defined $self->{ _stylsheet }) {
- my $css = $self->{ _stylsheet };
- print $wh "<LINK rel=\"stylesheet\"\n";
- print $wh " type=\"text/css\" href=\"fml.css\">\n";
- }
-
- if (defined $title) {
- print $wh "<title>";
- _print_safe_str($wh, $title);
- print $wh "</title>\n";
- }
-
- print $wh "</HEAD>\n";
- print $wh "<BODY>\n";
- print $wh "<CENTER>";
- _print_safe_str($wh, $title);
- print $wh "</CENTER>\n";
-}
-
-
-# Descriptions: show html closing
-# Arguments: OBJ($self) HANDLE($wh)
-# Side Effects: none
-# Return Value: none
-sub html_end
-{
- my ($self, $wh) = @_;
- print $wh "</BODY>";
- print $wh "</HTML>\n";
-}
-
-
-# Descriptions: show html separetor, we use <HR> now.
-# Arguments: OBJ($self) HANDLE($wh)
-# Side Effects: none
-# Return Value: none
-sub mhl_separator
-{
- my ($self, $wh) = @_;
- print $wh "<HR>\n";
-}
-
-
-my $preamble_begin = "<!-- __PREAMBLE_BEGIN__ by Mail::HTML::Lite -->";
-my $preamble_end = "<!-- __PREAMBLE_END__ by Mail::HTML::Lite -->";
-my $footer_begin = "<!-- __FOOTER_BEGIN__ by Mail::HTML::Lite -->";
-my $footer_end = "<!-- __FOOTER_END__ by Mail::HTML::Lite -->";
-
-
-# Descriptions: prepare information area before main message appears.
-# Later, this area is replaced with useful information
-# e.g. thread link.
-# Arguments: OBJ($self) HANDLE($wh)
-# Side Effects: none
-# Return Value: none
-sub mhl_preamble
-{
- my ($self, $wh) = @_;
- print $wh $preamble_begin, "\n";
- print $wh $preamble_end, "\n";
-}
-
-
-# Descriptions: prepare information area after main message appears.
-# Later, this area is replaced with useful information
-# e.g. thread link.
-# Arguments: OBJ($self) HANDLE($wh)
-# Side Effects: none
-# Return Value: none
-sub mhl_footer
-{
- my ($self, $wh) = @_;
- print $wh $footer_begin, "\n";
- print $wh $footer_end, "\n";
-}
-
-
-# Descriptions: prepare write handle
-# Arguments: OBJ($self) HASH_REF($args)
-# Side Effects: create $args->{ dst } file if needed
-# Return Value: HANDLE
-sub _set_output_channel
-{
- my ($self, $args) = @_;
- my $dst = $args->{ dst };
- my $wh;
-
- if (defined $dst) {
- $wh = new FileHandle "> $dst";
- }
- else {
- $wh = \*STDOUT;
- }
-
- return $wh;
-}
-
-
-# Descriptions: return temporary file path.
-# XXX temporary file is created under $db_dir not public space
-# Arguments: OBJ($self) HASH_REF($args)
-# Side Effects: none
-# Return Value: STR
-sub _create_temporary_filename
-{
- my ($self, $msg) = @_;
- my $db_dir = $self->{ _html_base_directory };
-
- return "$db_dir/tmp$$";
-}
-
-
-# Descriptions: create a temporary file with the content $msg
-# Arguments: OBJ($self) HASH_REF($args)
-# Side Effects: create $tmpf file
-# Return Value: STR
-sub _create_temporary_file_in_raw_mode
-{
- my ($self, $msg) = @_;
- my $tmpf = $self->_create_temporary_filename();
-
- use FileHandle;
- my $wh = new FileHandle "> $tmpf";
- if (defined $wh) {
- $wh->autoflush(1);
-
- my $buf = $msg->message_text();
- $wh->print($buf);
- $wh->close;
-
- return ($tmpf);
- }
-
- return undef;
-}
-
-
-# Descriptions: convert $file filepath to relative path
-# XXX UNIX specific ???
-# Arguments: OBJ($self) STR($file)
-# Side Effects: none
-# Todo: UNIX specific
-# Return Value: STR
-sub _relative_path
-{
- my ($self, $file) = @_;
- my $html_base_dir = $self->{ _html_base_directory };
- $file =~ s/$html_base_dir//;
- $file =~ s@^/@@;
- return $file;
-}
-
-
-# Descriptions: print inline link as html for attachments e.g.
-# images, files et. al.
-# Arguments: OBJ($self) HASH_REF($args)
-# Side Effects: none
-# Return Value: none
-sub _print_inline_object_link
-{
- my ($self, $args) = @_;
- my $wh = $args->{ fh };
- my $type = $args->{ type };
- my $num = $args->{ num };
- my $file = $self->_relative_path($args->{ file });
- my $desc = '';
- my $inline = defined( $args->{ inline } ) ? 1 : 0;
-
- if (defined $args->{ info }->{ description }) {
- $desc = $args->{ info }->{ description };
- }
-
- if ($inline && $type =~ /image/) {
- print $wh "<BR><IMG SRC=\"$file\">$desc\n";
- }
- else {
- my $t = $file;
- print $wh "<BR><A HREF=\"$file\" TARGET=\"$t\"> $type $num </A>";
- print $wh "$desc<BR>\n";
- }
-}
-
-
-# Descriptions: return attachment filename
-# Arguments: OBJ($self) STR($attach) STR($suffix)
-# Side Effects: none
-# Todo: UNIX specific
-# Return Value: STR
-sub _gen_attachment_filename
-{
- my ($dst, $attach, $suffix) = @_;
- my $outf = $dst;
- if ($suffix =~ m@/@) { $suffix =~ s@.*/@@;}
-
- $outf =~s/\.html$//;
- $outf = "$outf.$attach.$suffix";
- return $outf;
-}
-
-
-# default header to show
-my @header_field = qw(From To Cc Subject Date
- X-ML-Name X-Mail-Count X-Sequence);
-
-
-# Descriptions: format header of $msg with escaping HTML metachars
-# and disabling special HTML tags.
-# See _sprintf_safe_str() for how to escape.
-# Arguments: OBJ($self) OBJ($msg)
-# Side Effects: none
-# XXX $buf is printed out later in raw mode.
-# Return Value: STR
-sub _format_safe_header
-{
- my ($self, $msg) = @_;
- my ($buf);
- my $hdr = $msg->whole_message_header;
- my $header_field = \@header_field;
-
- # header
- $buf .= "<SPAN CLASS=mailheaders>\n";
- for my $field (@$header_field) {
- if (defined($hdr->get($field))) {
- $buf .= "<SPAN CLASS=${field}>\n";
- $buf .= "${field}: ";
- $buf .= "</SPAN>\n";
-
- my $xbuf = $hdr->get($field);
- $xbuf = $self->_decode_mime_string($xbuf) if $xbuf =~ /=\?iso/i;
- $buf .= "<SPAN CLASS=${field}-value>\n";
- $buf .= _sprintf_safe_str($xbuf);
- $buf .= "</SPAN>\n";
- $buf .= "<BR>\n";
- }
- }
- $buf .= "</SPAN>\n";
-
- return($buf);
-}
-
-
-# Descriptions: show link to indexes as navigation
-# Arguments: none
-# Side Effects: none
-# Return Value: none
-sub _format_index_navigator
-{
- my $str = qq{
-<A HREF=\"index.html\">[ID Index]</A>
-<A HREF=\"thread.html\">[Thread Index]</A>
-<A HREF=\"monthly_index.html\">[Monthly ID Index]</A>
-};
-
-return $str;
-}
-
-
-# Descriptions: print out text data with escaping by _print_safe_buf()
-# Arguments: OBJ($self) HASH_REF($args)
-# Side Effects: none
-# Return Value: STR
-sub _text_safe_print
-{
- my ($self, $args) = @_;
- my $buf = $args->{ data };
- my $fh = $args->{ fh } || \*STDOUT;
-
- if (defined $buf) {
- use Jcode;
- &Jcode::convert(\$buf, 'euc');
- }
-
- _print_safe_buf($fh, $buf);
-}
-
-
-# Descriptions: print out message without escaping
-# Arguments: OBJ($self) HASH_REF($args)
-# Side Effects: create $outf
-# Return Value: none
-sub _text_raw_print
-{
- my ($self, $args) = @_;
- my $msg = $args->{ message }; # Mail::Message object
- my $type = $msg->data_type;
- my $enc = $msg->encoding_mechanism;
- my $buf = $msg->message_text();
-
- if (defined( $args->{ file } )) {
- my $outf = $args->{ file };
- use FileHandle;
- my $fh = new FileHandle "> $outf";
-
- if (defined $buf) {
- use Jcode;
- &Jcode::convert(\$buf, 'euc');
- }
- print $fh $buf, "\n";
- $fh->close();
- }
-}
-
-
-# Descriptions: print out binary with MIME encoding or
-# text with escaping
-# Arguments: OBJ($self) HASH_REF($args)
-# Side Effects: create $outf file
-# Return Value: none
-sub _binary_print
-{
- my ($self, $args) = @_;
- my $msg = $args->{ message }; # Mail::Message object
- my $type = $msg->data_type;
- my $enc = $msg->encoding_mechanism;
-
- if (defined( $args->{ file } )) {
- my $outf = $args->{ file };
- use FileHandle;
- my $fh = new FileHandle "> $outf";
-
- if (defined $fh) {
- $fh->autoflush(1);
- binmode($fh);
-
- if ($enc eq 'base64') {
- eval q{
- use MIME::Base64;
- print $fh decode_base64( $msg->message_text() );
- };
- }
- elsif ($enc eq 'quoted-printable') {
- eval q{
- use MIME::QuotedPrint;
- print $fh decode_qp( $msg->message_text() );
- };
- }
- elsif ($enc eq '7bit') {
- _print_safe_str($fh, $msg->message_text());
- }
- else {
- croak("unknown MIME encoding enc=$enc");
- }
-
- $fh->close();
- }
- }
-}
-
-
-=head2 C<is_ignore($id)>
-
-we should not process this C<$id>
-
-=cut
-
-
-# Descriptions: check whether article $id is ignored
-# Arguments: OBJ($self) STR($id)
-# Side Effects: none
-# Return Value: 1 or 0
-sub is_ignore
-{
- my ($self, $id) = @_;
-
- return defined($self->{ _ignore_list }->{ $id }) ? 1 : 0;
-}
-
-
-
-=head1 METHODS for index and thread
-
-=head2 C<cache_message_info($msg, $args)>
-
-save information into DB.
-See section C<Internal Data Presentation> for more detail.
-
-=cut
-
-
-# Descriptions: update database on message header, thread relation
-# et. al.
-# Arguments: OBJ($self) OBJ($msg) HASH_REF($args)
-# Side Effects: update database
-# Return Value: none
-sub cache_message_info
-{
- my ($self, $msg, $args) = @_;
- my $hdr = $msg->whole_message_header;
- my $id = $args-> { id };
- my $dst = $args-> { dst };
-
- $self->_db_open();
- my $db = $self->{ _db };
-
- # XXX we should not update max_id when our target is an attachment.
- # XXX update max_id only under the top level operation
- unless ($self->{ _is_attachment }) {
- if (defined $db->{ _info }->{ id_max }) {
- $db->{_info}->{id_max} =
- $db->{_info}->{id_max} < $id ? $id : $db->{_info}->{id_max};
- }
- else {
- $db->{_info}->{id_max} = $id;
- }
- _PRINT_DEBUG(" parent");
- _PRINT_DEBUG(" update id_max = $db->{_info }->{id_max}");
- }
- else {
- _PRINT_DEBUG(" child");
- }
-
- _PRINT_DEBUG(" cache_message_info( id=$id ) running");
-
- $db->{ _filename }->{ $id } = $self->html_filename($id);
- $db->{ _filepath }->{ $id } = $dst;
-
- # HASH { $id => Date: }
- $db->{ _date }->{ $id } = $hdr->get('date');
-
- use Time::ParseDate;
- my $unixtime = parsedate( $hdr->get('date') );
- $db->{ _unixtime }->{ $id } = $unixtime;
- my ($sec,$min,$hour,$mday,$mon,$year,$wday) = localtime( $unixtime );
- my $month = sprintf("%04d/%02d", 1900 + $year, $mon + 1);
-
- # HASH { $id => YYYY/MM }
- $db->{ _month }->{ $id } = $month;
-
- # HASH { YYYY/MM => (id1 id2 id3 ..) }
- __add_value_to_array($db, '_monthly_idlist', $month, $id);
-
- # HASH { $id => Subject: }
- $db->{ _subject }->{ $id } =
- $self->_decode_mime_string( $hdr->get('subject') );
-
- # HASH { $id => From: }
- my $ra = _address_clean_up( $hdr->get('from') );
- $db->{ _from }->{ $id } = $ra->[0];
- $db->{ _who }->{ $id } = $self->_who_of_address( $hdr->get('from') );
-
- # HASH { $id => Message-Id: }
- # HASH { Message-Id: => $id }
- # HASH { $id => list of $id ... }
- $ra = _address_clean_up( $hdr->get('message-id') );
- my $mid = $ra->[0];
- if ($mid) {
- $db->{ _message_id }->{ $id } = $mid;
- $db->{ _msgidref }->{ $mid } = $id;
- $db->{ _idref }->{ $id } = $id;
- }
-
- # Thread Information by In-Reply-To: and References
- {
- my $irt_ra = _address_clean_up( $hdr->get('in-reply-to') );
- my $in_reply_to = $irt_ra->[0];
-
- _PRINT_DEBUG("In-Reply-To: $in_reply_to") if defined $in_reply_to;
-
- # save message-id(s) within In-Reply-To: field into database
- for my $mid (@$irt_ra) {
- # { message-id => (id1 id2 id3 ...)
- __add_value_to_array($db, '_msgidref', $mid, $id);
-
- # idp (pointer to id) by { message-id => id }
- my $idp = _list_head($db->{ _msgidref }->{ $mid });
-
- # { idp => (id1 id2 id3 ...) }
- __add_value_to_array($db, '_idref', $idp, $id) if defined $idp;
- }
-
- # apply the same logic as above for all message-id's in References:
- my $ref_ra = _address_clean_up( $hdr->get('references') );
- my %uniq = ();
- MSGID_SEARCH:
- for my $mid (@$ref_ra) {
- next MSGID_SEARCH unless defined $mid;
- next MSGID_SEARCH if $uniq{$mid};
- $uniq{$mid} = 1; # ensure uniqueness
-
- _PRINT_DEBUG("References: $mid");
- __add_value_to_array($db, '_msgidref', $mid, $id);
- my $idp = _list_head($db->{ _msgidref }->{ $mid });
- __add_value_to_array($db, '_idref', $idp, $id) if defined $idp;
- }
-
- # 0. ok. go to speculate prev/next links
- # 1. If In-Reply-To: is found, use it as "pointer to previous id"
- my $idp = 0;
- if (defined $in_reply_to) {
- # XXX idp (id pointer) = id1 by _list_head( (id1 id2 id3 ...)
- $idp = _list_head( $db->{ _msgidref }->{ $in_reply_to } );
- }
- # 2. if not found, try to use References: "in reverse order"
- elsif (@$ref_ra) {
- my (@rra) = reverse(@$ref_ra);
- $idp = $rra[0];
- }
- # 3. no prev/next link
- else {
- $idp = 0;
- }
-
- if (defined($idp) && $idp && $idp =~ /^\d+$/) {
- if ($idp != $id) {
- $db->{ _prev_id }->{ $id } = $idp;
- _PRINT_DEBUG("\$db->{ _prev_id }->{ $id } = $idp");
- }
- else {
- _PRINT_DEBUG("no \$db->{ _prev_id }");
- }
-
- # XXX we should not overwrite " id => next_id " assinged already.
- # XXX we preserve the first " id => next_id " value.
- # XXX but we overwride it if "id => id (itself)", wrong link.
- unless ((defined $db->{ _next_id }->{ $idp }) &&
- ($db->{ _next_id }->{ $idp } != $idp)) {
- $db->{ _next_id }->{ $idp } = $id;
- _PRINT_DEBUG("override \$db->{ _next_id }->{ $idp } = $id");
- }
- else {
- my $thread_head_id = _thread_head( $db, $id );
- _PRINT_DEBUG("no \$db->{ _next_id }->{ $idp } override");
- _PRINT_DEBUG(" = $db->{ _next_id }->{ $idp }");
- }
- }
- else {
- _PRINT_DEBUG("no prev/next thread link (id=$id)");
- warn("no prev/next thread link (id=$id)\n") if $debug;
- }
- }
-
- $self->_db_close();
-}
-
-
-# Descriptions: convert space-separeted string to array
-# Arguments: STR($str)
-# Side Effects: none
-# Return Value: ARRAY_REF
-sub __str2array
-{
- my ($str) = @_;
-
- return undef unless defined $str;
-
- $str =~ s/^\s*//;
- $str =~ s/\s*$//;
- my (@a) = split(/\s+/, $str);
- return \@a;
-}
-
-
-# Descriptions: add { key => value } of database $dbname.
-# value is "x y z ..." form, space separated string.
-# Arguments: HASH_REF($db) STR($dbname) STR($key) STR($value)
-# Side Effects: update database
-# Return Value: none
-sub __add_value_to_array
-{
- my ($db, $dbname, $key, $value) = @_;
- my $found = 0;
- my $ra = __str2array($db->{ $dbname }->{ $key });
-
- # ensure uniqueness
- for (@$ra) {
- $found = 1 if ($value =~ /^\d+$/) && ($_ == $value);
- $found = 1 if ($value !~ /^\d+$/) && ($_ eq $value);
- }
-
- # add if the value is a new comer.
- unless ($found) {
- $db->{ $dbname }->{ $key } .= " $value";
- }
-}
-
-
-# Descriptions: speculate head of thread list,
-# traced back from $id.
-# Arguments: HASH_REF($db) STR($id)
-# Side Effects: none
-# Return Value: NUM
-sub _thread_head
-{
- my ($db, $id) = @_;
- my $max = 128;
- my $head_id = $id;
-
- # track back id list to search the thread head
- while ($max-- > 0) {
- my $prev_id = $db->{ _prev_id }->{ $head_id };
- last unless $prev_id;
- $head_id = $prev_id;
- }
-
- return $head_id;
-}
-
-
-# Descriptions: speculate head of the next thread list.
-# Arguments: HASH_REF($db) STR($id)
-# Side Effects: none
-# Return Value: STR
-sub _search_default_next_thread_id
-{
- my ($db, $id) = @_;
- my $list = __str2array( $db->{ _thread_list }->{ $id } );
- my (@ra, @c0, @c1) = ();
- @ra = reverse @$list if defined $list;
-
- for (1 .. 10) { push(@c0, $id + $_);}
-
- # prepare thread list to search
- # 1. thread includes $id
- # 2. thread(s) begining at each id in thread 1.
- # 3. last resort: thread includes ($id+1),
- # thread includes ($id+2), ...
- for my $xid ($id, @ra, @c0) {
- my $default = __search_default_next_id_in_thread($db, $xid);
- return $default if defined $default;
- }
-}
-
-
-# Descriptions: speculate the next id of $id.
-# Arguments: HASH_REF($db) STR($id)
-# Side Effects: none
-# Return Value: STR
-sub __search_default_next_id_in_thread
-{
- my ($db, $id) = @_;
- my $list = [];
- my $prev = 0;
-
- # thread_list HASH { $id => $id1 $id2 $id3 ... }
- if (defined $db->{ _thread_list }->{ $id }) {
- $list = __str2array( $db->{ _thread_list }->{ $id } );
- return undef unless $#$list > 1;
-
- # thread_list HASH { $id => $id1 $id2 $id3 ... $id $prev ... }
- # <---- search ---
- SEARCH:
- for my $xid (reverse @$list) {
- last SEARCH if $xid == $id;
- $prev = $xid;
- }
- }
-
- # found
- # XXX we use $prev in reverse order, so this $prev means "next"
- if ($prev > 0) {
- _PRINT_DEBUG("default thread: $id => $prev (@$list)");
- return $prev;
- }
- else {
- _PRINT_DEBUG("default thread: $id => none (@$list)");
- return undef;
- }
-}
-
-
-=head2 C<update_relation($id)>
-
-update link relation around C<$id>.
-
-=cut
-
-
-# Descriptions: top level dispatcher to update database.
-# _update_relation() has real function for updating.
-# Arguments: OBJ($self) STR($id)
-# Side Effects: update databse
-# Return Value: none
-sub update_relation
-{
- my ($self, $id) = @_;
- my $args = $self->evaluate_relation($id);
- my $list = $self->{ _affected_idlist } = [];
-
- if ($self->is_ignore($id)) {
- warn("not update relation around $id") if $debug;
- return undef;
- }
-
- # update target itself, of course
- $self->_update_relation($id);
- push(@$list, $id);
-
- # rewrite links of files for
- # prev/next id (article id) and
- # prev/next by thread
- my $db = $self->{ _db };
- my %uniq = ( $id => 1 );
-
- UPDATE:
- for my $id (qw(prev_id next_id prev_thread_id next_thread_id)) {
- if (defined $args->{ $id }) {
- next UPDATE if $uniq{ $args->{$id} }; $uniq{ $args->{$id} } = 1;
-
- $self->_update_relation( $args->{ $id });
- push(@$list, $args->{ $id });
- }
- }
-
- if (defined $db->{ _thread_list }->{ $id } ) {
- my $thread_list = __str2array( $db->{ _thread_list }->{ $id } );
-
- # update link relation for all articles in this thread.
- for my $id (@$thread_list) {
- next UPDATE if $uniq{ $id}; $uniq{ $id } = 1;
- $self->_update_relation( $id );
- push(@$list, $id);
- }
- }
-}
-
-
-# Descriptions: update link at preamble and footer of HTML-ified message.
-# Arguments: OBJ($self) STR($id)
-# Side Effects: rewrite index file
-# Return Value: none
-sub _update_relation
-{
- my ($self, $id) = @_;
- my $args = $self->evaluate_relation($id);
- my $preamble = $self->evaluate_safe_preamble($args);
- my $footer = $self->evaluate_safe_footer($args);
- my $code = _charset_to_code($self->{ _charset });
-
- my $pat_preamble_begin = quotemeta($preamble_begin);
- my $pat_preamble_end = quotemeta($preamble_end);
- my $pat_footer_begin = quotemeta($footer_begin);
- my $pat_footer_end = quotemeta($footer_end);
-
- _PRINT_DEBUG("_update_relation $id");
-
- use FileHandle;
- my $file = $args->{ file };
- my ($old, $new) = ($file, "$file.new.$$");
- my $rh = new FileHandle $old;
- my $wh = new FileHandle "> $new";
- if (defined $rh && defined $wh) {
- while (<$rh>) {
- if (/^$pat_preamble_begin/ .. /^$pat_preamble_end/) {
- _print_raw_str($wh, $preamble, $code) if /^$pat_preamble_end/;
- next;
- }
- if (/^$pat_footer_begin/ .. /^$pat_footer_end/) {
- _print_raw_str($wh, $footer, $code) if /^$pat_footer_end/;
- next;
- }
-
- # just copy (rewrite only $preamble and $footer not message)
- _print_raw_str($wh, $_, $code);
- }
- $rh->close;
- $wh->close;
-
- unless (rename($new, $old)) {
- croak("rename($new, $old) fail (id=$id)\n");
- }
- }
- else {
- warn("cannot open $old (id=$id)\n") unless defined $rh;
- warn("cannot create $new (id=$id)\n") unless defined $wh;
- }
-}
-
-
-# Descriptions: return thread link relation info et.al. for $id
-# Arguments: OBJ($self) HASH_REF($args)
-# Side Effects: none
-# Return Value: HASH_REF
-sub evaluate_relation
-{
- my ($self, $id) = @_;
-
- $self->_db_open();
- my $db = $self->{ _db };
- my $file = $db->{ _filepath }->{ $id };
-
- my $next_file = $self->html_filepath( $id + 1 );
- my $prev_id = $id > 1 ? $id - 1 : undef;
- my $next_id = $id + 1 if -f $next_file;
- my $prev_thread_id = $db->{ _prev_id }->{ $id } || undef;
- my $next_thread_id = $db->{ _next_id }->{ $id } || undef;
-
- # diagnostic
- if ($prev_thread_id) {
- undef $prev_thread_id if $prev_thread_id == $id;
- }
- if ($next_thread_id) {
- undef $next_thread_id if $next_thread_id == $id;
- }
- else {
- my $xid = _search_default_next_thread_id($db, $id);
- if ($xid && ($xid != $id)) {
- $next_thread_id = $xid;
- _PRINT_DEBUG("override next_thread_id = $next_thread_id");
- }
- }
-
- my $link_prev_id = $self->html_filename($prev_id);
- my $link_next_id = $self->html_filename($next_id);
- my $link_prev_thread_id = $self->html_filename($prev_thread_id);
- my $link_next_thread_id = $self->html_filename($next_thread_id);
-
- my $subject = {};
- if (defined $prev_id) {
- $subject->{ prev_id } = $db->{ _subject }->{ $prev_id };
- }
- if (defined $next_id) {
- $subject->{ next_id } = $db->{ _subject }->{ $next_id };
- }
- if (defined $prev_thread_id) {
- $subject->{ prev_thread_id } = $db->{ _subject }->{ $prev_thread_id };
- }
- if (defined $next_thread_id) {
- $subject->{ next_thread_id } = $db->{ _subject }->{ $next_thread_id };
- }
-
- my $args = {
- id => $id,
- file => $file,
- prev_id => $prev_id,
- next_id => $next_id,
- prev_thread_id => $prev_thread_id,
- next_thread_id => $next_thread_id,
- link_prev_id => $link_prev_id,
- link_next_id => $link_next_id,
- link_prev_thread_id => $link_prev_thread_id,
- link_next_thread_id => $link_next_thread_id,
- subject => $subject,
- };
- _PRINT_DEBUG_DUMP_HASH( $args );
-
- $self->_db_close();
-
- return $args;
-}
-
-
-# Descriptions: return preamble without metachars
-# Arguments: OBJ($self) HASH_REF($args)
-# Side Effects: none
-# Return Value: STR
-sub evaluate_safe_preamble
-{
- my ($self, $args) = @_;
- my $link_prev_id = $args->{ link_prev_id };
- my $link_next_id = $args->{ link_next_id };
- my $link_prev_thread_id = $args->{ link_prev_thread_id };
- my $link_next_thread_id = $args->{ link_next_thread_id };
-
- my $preamble = $preamble_begin. "\n";
-
- if (defined($link_prev_id)) {
- $preamble .= "<A HREF=\"$link_prev_id\">[Prev by ID]</A>\n";
- }
- else {
- $preamble .= "[No Prev ID]\n";
- }
-
- if (defined($link_next_id)) {
- $preamble .= "<A HREF=\"$link_next_id\">[Next by ID]</A>\n";
- }
- else {
- $preamble .= "[No Next ID]\n";
- }
-
- if (defined $link_prev_thread_id) {
- $preamble .= "<A HREF=\"$link_prev_thread_id\">[Prev by Thread]</A>\n";
- }
- else {
- if (defined $link_prev_id) {
- $preamble .= "<A HREF=\"$link_prev_id\">[Prev by Thread]</A>\n";
- }
- else {
- $preamble .= "[No Prev Thread]\n";
- }
- }
-
- if (defined $link_next_thread_id) {
- $preamble .= "<A HREF=\"$link_next_thread_id\">[Next by Thread]</A>\n";
- }
- else {
- if (defined $link_next_id) {
- $preamble .= "<A HREF=\"$link_next_id\">[Next by Thread]</A>\n";
- }
- else {
- $preamble .= "[No Next Thread]\n";
- }
- }
-
- $preamble .= _format_index_navigator();
- $preamble .= $preamble_end. "\n";;
-
- return $preamble;
-}
-
-
-# Descriptions: return footer without metachars
-# Arguments: OBJ($self) HASH_REF($args)
-# Side Effects: none
-# Return Value: STR
-sub evaluate_safe_footer
-{
- my ($self, $args) = @_;
- my $link_prev_id = $args->{ link_prev_id };
- my $link_next_id = $args->{ link_next_id };
- my $link_prev_thread_id = $args->{ link_prev_thread_id };
- my $link_next_thread_id = $args->{ link_next_thread_id };
- my $subject = $args->{ subject };
-
- my $footer = $footer_begin. "\n";;
-
- if (defined($link_prev_id)) {
- $footer .= "<BR>\n";
- $footer .= "<A HREF=\"$link_prev_id\">Prev by ID: ";
- $footer .= _sprintf_safe_str( $subject->{ prev_id } );
- $footer .= "</A>\n";
- }
-
- if (defined($link_next_id)) {
- $footer .= "<BR>\n";
- $footer .= "<A HREF=\"$link_next_id\">Next by ID: ";
- $footer .= _sprintf_safe_str( $subject->{ next_id } );
- $footer .= "</A>\n";
- }
-
- if (defined $link_prev_thread_id) {
- $footer .= "<BR>\n";
- $footer .= "<A HREF=\"$link_prev_thread_id\">Prev by Thread: ";
- $footer .= _sprintf_safe_str($subject->{ prev_thread_id });
- $footer .= "</A>\n";
- }
-
- if (defined $link_next_thread_id) {
- $footer .= "<BR>\n";
- $footer .= "<A HREF=\"$link_next_thread_id\">Next by Thread: ";
- $footer .= _sprintf_safe_str($subject->{ next_thread_id });
- $footer .= "</A>\n";
- }
-
- $footer .= qq{<BR>\n};
- $footer .= _format_index_navigator();
- $footer .= $footer_end. "\n";;
-
- return $footer;
-}
-
-
-=head1 Internal Data Presentation
-
-=head2 Hashes for Database
-
- name hash content
- ----------------------------
- from id => From: header field
- date id => Date: header field
- subject id => Subject: header field
- message_id id => Message-Id: header field
- references id => References: header field
- filepath id => file location ( /some/where/YYYY/MM/DD/xxx.html )
- idref id => id(myself) refered-by-id1 refered-by-id2 ...
- msgidref message-id => id(myself) refered-by-id1 refered-by-id2 ...
-
-We need several information to speculate thread relation rapidly.
-At least we need two relations:
-
-1. to speculate [Next by Thread]
-
- message-id => ( id1 id2 id3 ... )
-
-where C<id1> is the message itself.
-
-2. to speculate [Prev by Thread]
-
- id => message-id of replied message (e.g. In-Reply-To:)
-
-hashes.
-
-BTW, the end message of the thread has no next message,
-and the top of the thread has no previous message.
-We arrange apporopviate link to another thread.
-Also we need this relation for C<thread.html>.
-
-To resolve this problem, we need ID or Date ordered thread (top id of
-th thread) list ?
-
- thread followup relation in the thread
- -----------------------------
- id1 id1 - id2 - id4
- id3 id3 - id5 - id6
- |
- - id7 - id10
- id8 id8 - id9 - id11
- id12 id12 ...
-
-=head2 Usage
-
-For example, you can set { $key => $value } for C<from> data in this way:
-
- $self->{ _db }->{ _from }->{ $key } = $value;
-
-=cut
-
-my @kind_of_databases = qw(from date subject message_id references
- msgidref idref next_id prev_id
- filename filepath
- unixtime month monthly_idlist
- thread_list
- who info);
-
-
-# 1. Hmm, what database is needed for
-# {Prev,Next} by Article ID
-# {Prev,Next} by Thread
-#
-# 2. each message needs ?
-#
-# Subject:
-# From:
-#
-
-
-# Descriptions: open database
-# Arguments: OBJ($self) HASH_REF($args)
-# Side Effects: tied with $self->{ _db }
-# Todo: we should use IO::Adapter ?
-# Return Value: none
-sub _db_open
-{
- my ($self, $args) = @_;
- my $db_type = $args->{ db_type } || $self->{ _db_type } || 'AnyDBM_File';
- my $db_dir = $self->{ _html_base_directory };
-
- _PRINT_DEBUG("_db_open( type = $db_type )");
-
- eval qq{ use $db_type; use Fcntl;};
- unless ($@) {
- for my $db (@kind_of_databases) {
- my $file = "$db_dir/.ht_mhl_${db}";
- my $str = qq{
- my \%$db = ();
- tie \%$db, \$db_type, \$file, O_RDWR|O_CREAT, 0644;
- \$self->{ _db }->{ _$db } = \\\%$db;
- };
- eval $str;
- croak($@) if $@;
- }
- }
- else {
- croak("cannot use $db_type");
- }
-}
-
-
-# Descriptions: close database
-# Arguments: OBJ($self) HASH_REF($args)
-# Side Effects: untie $self->{ _db }
-# Todo: we should use IO::Adapter ?
-# Return Value: none
-sub _db_close
-{
- my ($self, $args) = @_;
- my $db_type = $args->{ db_type } || $self->{ _db_type } || 'AnyDBM_File';
- my $db_dir = $self->{ _html_base_directory };
-
- _PRINT_DEBUG("_db_close()");
-
- for my $db (@kind_of_databases) {
- my $str = qq{
- my \$${db} = \$self->{ _db }->{ _$db };
- untie \%\$${db};
- };
- eval $str;
- croak($@) if $@;
- }
-}
-
-
-=head2 C<update_id_index($args)>
-
-update index.html.
-
-=cut
-
-
-# Descriptions: print navigation bar et.al. at upper half of indexes
-# Arguments: OBJ($self) HASH_REF($args)
-# Side Effects: create $new html
-# Return Value: none
-sub _print_index_begin
-{
- my ($self, $args) = @_;
- my $old = $args->{ old };
- my $new = $args->{ new };
- my $title = $args->{ title };
- my $code = _charset_to_code($self->{ _charset });
-
- use FileHandle;
- my $wh = new FileHandle "> $new";
- $args->{ wh } = $wh;
-
- $self->html_begin($wh, { title => $title });
-
- _print_raw_str($wh, _format_index_navigator(), $code);
- $self->mhl_separator($wh);
-}
-
-
-# Descriptions: print navigation bar et.al. at the end of indexes.
-# Arguments: OBJ($self) HASH_REF($args)
-# Side Effects: create $new html
-# Return Value: none
-sub _print_index_end
-{
- my ($self, $args) = @_;
- my $wh = $args->{ wh };
- my $old = $args->{ old };
- my $new = $args->{ new };
- my $title = $args->{ title };
- my $code = $args->{ code };
-
- $self->mhl_separator($wh);
- _print_raw_str($wh, _format_index_navigator(), $code);
-
- # append version information
- _print_raw_str($wh, "<BR>Genereated by $version\n", $code);
-
- $self->html_end($wh);
-
- unless (rename($new, $old)) {
- croak("rename($new, $old) fail\n");
- }
-}
-
-
-# Descriptions: update index.html
-# Arguments: OBJ($self) HASH_REF($args)
-# Side Effects: rewrite index.html
-# Return Value: none
-sub update_id_index
-{
- my ($self, $args) = @_;
- my $html_base_dir = $self->{ _html_base_directory };
- my $code = _charset_to_code($self->{ _charset });
- my $htmlinfo = {
- title => defined($args->{ title }) ? $args->{ title } : "ID Index",
- old => "$html_base_dir/index.html",
- new => "$html_base_dir/index.html.new.$$",
- code => $code,
- };
-
- if ($self->is_ignore($args->{id})) {
- warn("not update index.html around $args->{id}") if $debug;
- return undef;
- }
-
- $self->_print_index_begin( $htmlinfo );
- my $wh = $htmlinfo->{ wh };
-
- $self->_db_open();
- my $db = $self->{ _db };
- my $id_max = $db->{ _info }->{ id_max };
-
- $self->_print_ul($wh, $db, $code);
- for my $id ( 1 .. $id_max ) {
- $self->_print_li_filename($wh, $db, $id, $code);
- }
- $self->_print_end_of_ul($wh, $db, $code);
-
- $self->_db_close();
- $self->_print_index_end( $htmlinfo );
-}
-
-
-=head2 C<update_id_monthly_index($args)>
-
-=cut
-
-
-# Descriptions: update monthly index
-# Arguments: OBJ($self) HASH_REF($args)
-# Side Effects: rewrite monthly index
-# Return Value: none
-sub update_id_monthly_index
-{
- my ($self, $args) = @_;
- my $affected_list = $self->{ _affected_idlist };
-
- if ($self->is_ignore($args->{id})) {
- warn("not update index.html around $args->{id}") if $debug;
- return undef;
- }
-
- # open databaes
- $self->_db_open();
- my $db = $self->{ _db };
-
- my %month_update = ();
-
- IDLIST:
- for my $id (@$affected_list) {
- next IDLIST unless $id =~ /^\d+$/;
- my $month = $db->{ _month }->{ $id };
- $month_update{ $month } = 1;
- }
-
- # todo list
- for my $month (sort keys %month_update) {
- my $this_month = $month; # yyyy/mm
- my $suffix = $month; $suffix =~ s@/@@g; # yyyymm
-
- $self->_update_id_monthly_index($args, {
- this_month => $this_month,
- suffix => $suffix,
- });
- }
-
- # update monthly_index.html
- $self->_update_id_montly_index_master($args);
-}
-
-
-# Descriptions: update monthly index master
-# Arguments: OBJ($self) HASH_REF($args)
-# Side Effects: rewrite monthly_index.html
-# Return Value: none
-sub _update_id_montly_index_master
-{
- my ($self, $args) = @_;
- my $html_base_dir = $self->{ _html_base_directory };
- my $code = _charset_to_code($self->{ _charset });
- my $htmlinfo = {
- title => defined($args->{ title }) ? $args->{ title } : "ID Index",
- old => "$html_base_dir/monthly_index.html",
- new => "$html_base_dir/monthly_index.html.new.$$",
- code => $code,
- };
-
- $self->_print_index_begin( $htmlinfo );
- my $wh = $htmlinfo->{ wh };
-
- $self->_db_open();
- my $db = $self->{ _db };
- my $mlist = $db->{ _monthly_idlist };
- my (@list) = sort __sort_yyyymm keys %$mlist;
- my ($years) = _yyyy_range(\@list);
-
- _print_raw_str($wh, "<TABLE>", $code);
-
- for my $year (@$years) {
- _print_raw_str($wh, "<TR>", $code);
-
- for my $month (1 .. 12) {
- _print_raw_str($wh, "<TR>", $code) if $month == 7;
-
- my $id = sprintf("%04d/%02d", $year, $month); # YYYY/MM
- my $xx = sprintf("%04d%02d", $year, $month); # YYYYMM
- my $fn = "month.$xx.html";
-
- use File::Spec;
- my $file = File::Spec->catfile($html_base_dir, $fn);
- if (-f $file) {
- _print_raw_str($wh, "<TD><A HREF=\"$fn\"> $id </A>", $code);
- }
- else {
- _print_raw_str($wh, "<TD>", $code);
- }
- }
- }
- _print_raw_str($wh, "</TABLE>", $code);
-
- $self->_db_close();
- $self->_print_index_end( $htmlinfo );
-}
-
-
-# Descriptions: return list of YYYY/MM format
-# Arguments: ARRAY_REF($list)
-# Side Effects: none
-# Return Value: ARRAY_REF
-sub _yyyy_range
-{
- my ($list) = @_;
- my ($yyyy) = {};
-
- for (@$list) {
- if (/^(\d{4})\/(\d{2})/) {
- $yyyy->{ $1 } = $1;
- }
- }
-
- my (@yyyy) = keys %$yyyy;
- return( \@yyyy );
-}
-
-
-# Descriptions: sort YYYY/MM formt strings
-# Arguments: none
-# Side Effects: none
-# Return Value: NUM
-sub __sort_yyyymm
-{
- my ($xa, $xb) = ($a, $b);
- $xa =~ s@/@@;
- $xb =~ s@/@@;
- if ($xa eq '') { $xa = 0;}
- if ($xb eq '') { $xb = 0;}
-
- $xa <=> $xb;
-}
-
-
-# Descriptions: update month.YYYYMM.html
-# Arguments: OBJ($self) HASH_REF($args) HASH_REF($monthlyinfo)
-# Side Effects: update month.YYYYMM.html
-# Return Value: none
-sub _update_id_monthly_index
-{
- my ($self, $args, $monthlyinfo) = @_;
- my $html_base_dir = $self->{ _html_base_directory };
- my $code = _charset_to_code($self->{ _charset });
- my $this_month = $monthlyinfo->{ this_month }; # yyyy/mm
- my $suffix = $monthlyinfo->{ suffix }; # yyyymm
- my $htmlinfo = {
- title => "ID Monthly Index $this_month",
- old => "$html_base_dir/month.${suffix}.html",
- new => "$html_base_dir/month.${suffix}.html.new.$$",
- code => $code,
- };
-
- $self->_print_index_begin( $htmlinfo );
- my $wh = $htmlinfo->{ wh };
-
- $self->_db_open();
- my $db = $self->{ _db };
- my $id_max = $db->{ _info }->{ id_max };
-
- # oops, this list may be " a b c d e " string, nuke \s* to avoid warning.
- $db->{ _monthly_idlist }->{ $this_month } =~ s/^\s*//;
- $db->{ _monthly_idlist }->{ $this_month } =~ s/\s*$//;
- my (@list) = split(/\s+/, $db->{ _monthly_idlist }->{ $this_month });
-
- $self->_print_ul($wh, $db, $code);
- for my $id (sort {$a <=> $b} @list) {
- next unless $id =~ /^\d+$/;
- $self->_print_li_filename($wh, $db, $id, $code);
- }
- $self->_print_end_of_ul($wh, $db, $code);
-
- $self->_db_close();
- $self->_print_index_end( $htmlinfo );
-}
-
-
-=head2 C<update_thread_index($args)>
-
-update thread.html.
-
-=cut
-
-
-# Descriptions: update thread.html
-# Arguments: OBJ($self) HASH_REF($args)
-# Side Effects: rewrite thread.html
-# Return Value: none
-sub update_thread_index
-{
- my ($self, $args) = @_;
- my $html_base_dir = $self->{ _html_base_directory };
- my $code = _charset_to_code($self->{ _charset });
- my $htmlinfo = {
- title => defined($args->{ title }) ? $args->{ title } : "Thread Index",
- old => "$html_base_dir/thread.html",
- new => "$html_base_dir/thread.html.new.$$",
- code => $code,
- };
-
- if ($self->is_ignore($args->{id})) {
- warn("not update thread.html around $args->{id}") if $debug;
- return undef;
- }
-
- $self->_print_index_begin( $htmlinfo );
- my $wh = $htmlinfo->{ wh };
-
- $self->_db_open();
- my $db = $self->{ _db };
- my $id_max = $db->{ _info }->{ id_max };
-
- # initialize negagtive cache to ensure uniquness
- delete $self->{ _uniq };
-
- $self->_print_ul($wh, $db, $code);
- for my $id ( 1 .. $id_max ) {
- # head of the thread (not referenced yet)
- unless (defined $self->{ _uniq }->{ $id }) {
- $self->_print_thread($wh, $db, $id, $code);
- }
- }
- $self->_print_end_of_ul($wh, $db, $code);
-
- $self->_db_close();
- $self->_print_index_end( $htmlinfo );
-}
-
-
-# Descriptions: check whether $id has next or previous link.
-# Arguments: OBJ($self) HASH_REF($db) NUM($id)
-# Side Effects: none
-# Return Value: 1 or 0
-sub _has_link
-{
- my ($self, $db, $id) = @_;
-
- if (defined( $db->{ _next_id }->{ $id } ) ||
- defined( $db->{ _prev_id }->{ $id } )) {
- return 1;
- }
- else {
- return 0;
- }
-}
-
-
-# Descriptions: print thread array of (head_id id2 id3 ...)
-# Arguments: OBJ($self) HANDLE($wh) HASH_REF($db) STR($head_id) STR($code)
-# Side Effects: none
-# Return Value: none
-sub _print_thread
-{
- my ($self, $wh, $db, $head_id, $code) = @_;
- my $saved_stack_level = $self->{ _stack };
- my $uniq = $self->{ _uniq };
-
- # debug information (it is useful not to remove this ?)
- _print_raw_str($wh, "<!-- thread head=$head_id -->\n", $code);
-
- # get id list: @idlist = ( $head_id id2 id3 ... )
- my $buf = $db->{ _idref }->{ $head_id };
-
- if (defined $buf) {
- my $ra = __str2array($buf);
- my (@idlist) = @$ra;
-
- IDLIST:
- for my $id (@idlist) {
- # save $id => " @idlist " for further use
- # XXX override occurs but select latest information (no reason;)
- if ($#idlist > 1) {
- $db->{ _thread_list }->{ $id } = $buf;
- _PRINT_DEBUG("\$db->{ _thread_list }->{ $id } = $buf");
- }
-
- # @idlist = (number's)
- _print_raw_str($wh, "<!-- thread (@idlist) -->\n", $code);
-
- next IDLIST if $uniq->{ $id };
- $uniq->{ $id } = 1;
-
- $self->_print_ul($wh, $db, $code);
-
- # oops, we should ignore head of the thread ( myself ;-)
- if (($id != $head_id) && $self->_has_link($db, $id)) {
- $self->_print_li_filename($wh, $db, $id, $code);
- $self->_print_thread($wh, $db, $id, $code);
- }
- else {
- $self->_print_li_filename($wh, $db, $id, $code);
- }
- }
- }
-
- while ($self->{ _stack } > $saved_stack_level) {
- $self->_print_end_of_ul($wh, $db, $code);
- }
-}
-
-
-=head2 internal utility functions for IO
-
-=cut
-
-
-# Descriptions: cnvert charset to code e.g. iso-2022-jp => jis
-# Arguments: STR($charset)
-# Side Effects: none
-# Return Value: STR
-sub _charset_to_code
-{
- my ($charset) = @_;
-
- if (defined $charset) {
- $charset =~ tr/A-Z/a-z/;
- if ($charset eq 'euc-jp') {
- return 'euc';
- }
- elsif ($charset eq 'iso-2022-jp') {
- return 'jis';
- }
- else {
- return $charset; # may be wrong, but I hope it works well:-)
- }
- }
- else {
- return 'euc'; # euc-jp by default
- }
-}
-
-
-# Descriptions: print raw $str to $wh channel
-# Arguments: HANDLE($wh) STR($str) STR($code)
-# Side Effects: none
-# Return Value: none
-sub _print_raw_str
-{
- my ($wh, $str, $code) = @_;
- $code = defined($code) ? $code : 'euc'; # euc-jp by default
-
- if (defined $str) {
- use Jcode;
- &Jcode::convert( \$str, $code);
- }
-
- print $wh $str;
-}
-
-
-# Descriptions: print safe $str to $wh channel
-# XXX text2html($str, urls => 1, pre => 0)
-# Arguments: HANDLE($wh) STR($str) STR($code)
-# Side Effects: none
-# Return Value: none
-sub _print_safe_str
-{
- my ($wh, $str, $code) = @_;
- __print_safe_str(0, $wh, $str, $code);
-}
-
-
-# Descriptions: print safe $str to $wh channel
-# XXX text2html($str, urls => 1, pre => 1)
-# Arguments: HANDLE($wh) STR($str) STR($code)
-# Side Effects: none
-# Return Value: none
-sub _print_safe_buf
-{
- my ($wh, $str, $code) = @_;
- __print_safe_str(1, $wh, $str, $code);
-}
-
-
-# Descriptions: print safe $str to $wh channel
-# Arguments: NUM($attr_pre) HANDLE($wh) STR($str) STR($code)
-# Side Effects: none
-# Return Value: none
-sub __print_safe_str
-{
- my ($attr_pre, $wh, $str, $code) = @_;
- my $p = __sprintf_safe_str($attr_pre, $wh, $str, $code);
- print $wh $p if defined $p;
- print $wh "\n";
-}
-
-
-# Descriptions: return safe $str
-# Arguments: STR($str) STR($code)
-# Side Effects: none
-# Return Value: STR
-sub _sprintf_safe_str
-{
- my ($str, $code) = @_;
- return __sprintf_safe_str(0, undef, $str, $code);
-}
-
-
-# Descriptions: return safe $str modified by text2html().
-# $str language code is modified by Jcode if needed.
-# Arguments: NUM($attr_pre) HANDLE($wh) STR($str) STR($code)
-# Side Effects: none
-# Return Value: STR or UNDEF
-sub __sprintf_safe_str
-{
- my ($attr_pre, $wh, $str, $code) = @_;
- my $rbuf = '';
-
- if (defined($str) && defined($code)) {
- use Jcode;
- &Jcode::convert(\$str, $code);
- }
-
- if (defined $str) {
- # $url$trailor => $url $trailor for text2html() incomplete regexp
- $str =~ s#(http://\S+[\w\d/])#_separete_url($1)#ge;
-
- use HTML::FromText;
- return text2html($str, urls => 1, pre => $attr_pre);
- }
- else {
- return undef;
- }
-}
-
-
-# Descriptions: extract URL syntax in $url string.
-# $url$trailor => $url $trailor for text2html()
-# XXX incomplete regexp, we should correct it.
-# Arguments: STR($url)
-# Side Effects: none
-# History: based on fml 4.0-current (2001/10/28)
-# Return Value: STR
-sub _separete_url
-{
- my ($url) = @_;
- my ($re_euc_c) = '[\241-\376][\241-\376]';
- my ($re_euc_s) = "($re_euc_c)+";
- my $trailor = '';
-
- # remove prepended/appended EUC strings
- if ($url =~ /($re_euc_s)+$/) {
- $trailor = $1;
- $url =~ s/$trailor//;
- }
-
- # incomplete but may be effective ?
- # RFC2068 says these special char's are not used.
- # we should not include these char's in URL.
- # reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+"
- # unsafe = CTL | SP | <"> | "#" | "%" | "<" | ">"
- if ($url =~ /(\&\w{2}\;|\;|\?|\:|\@|\&|\=|\+|\#|\%|\<|\>|\")+$/) {
- my $pat = $1;
- $trailor = $pat . $trailor;
- $url =~ s/${pat}$//;
- }
-
- return "$url $trailor";
-}
-
-
-# Descriptions: debug
-# Arguments: STR($str)
-# Side Effects: none
-# Return Value: none
-sub _PRINT_DEBUG
-{
- my ($str) = @_;
- print STDERR "(debug) $str\n" if $debug;
-}
-
-
-# Descriptions: debug, print out hash
-# Arguments: HASH_REF($hash)
-# Side Effects: none
-# Return Value: none
-sub _PRINT_DEBUG_DUMP_HASH
-{
- my ($hash) = @_;
- my ($k,$v);
-
- if ($debug) {
- while (($k, $v) = each %$hash) {
- printf STDERR "%-30s => %s\n", $k, $v;
- }
- }
-}
-
-
-=head2 internal utility functions for HTML TAGS
-
-C<_print_something()> internal function provides wrapper to print HTML
-tags et.al.
-
-=cut
-
-
-# Descriptions: print <UL> with proper indentation
-# Arguments: OBJ($self) HANDLE($wh) HASH_REF($db) STR($code)
-# Side Effects: none
-# Return Value: none
-sub _print_ul
-{
- my ($self, $wh, $db, $code) = @_;
-
- $self->{ _stack }++;
-
- my $padding = " " x $self->{ _stack };
- _print_raw_str($wh, "${padding}<UL>\n", $code);
-}
-
-
-# Descriptions: print </UL> with proper indentation
-# Arguments: OBJ($self) HANDLE($wh) HASH_REF($db) STR($code)
-# Side Effects: none
-# Return Value: none
-sub _print_end_of_ul
-{
- my ($self, $wh, $db, $code) = @_;
-
- return unless $self->{ _stack } > 0;
-
- my $padding = " " x $self->{ _stack };
- _print_raw_str($wh, "${padding}</UL>\n", $code);
-
- $self->{ _stack }--;
-}
-
-
-# Descriptions: print <LI> filename ... with proper indentation
-# Arguments: OBJ($self) HANDLE($wh) HASH_REF($db) STR($code)
-# Side Effects: none
-# Return Value: none
-sub _print_li_filename
-{
- my ($self, $wh, $db, $id, $code) = @_;
- my $filename = $db->{ _filename }->{ $id };
- my $subject = $db->{ _subject }->{ $id };
- my $who = $db->{ _who }->{ $id };
-
- if (defined $filename && $filename) {
- _print_raw_str($wh, "<!-- LI id=$id -->\n", $code);
-
- _print_raw_str($wh, "<LI>\n", $code);
- _print_raw_str($wh, "<A HREF=\"$filename\">\n", $code);
- _print_safe_str($wh, $subject, $code);
- _print_raw_str($wh, ",\n", $code);
- _print_safe_str($wh, "$who\n", $code);
- _print_raw_str($wh, "</A>\n", $code);
- }
-}
-
-
-=head2 misc
-
-=cut
-
-
-# Descriptions: clean up email address by Mail::Address.
-# return clean-up'ed address list.
-# Arguments: STR($addr)
-# Side Effects: none
-# Return Value: ARRAY_REF
-sub _address_clean_up
-{
- my ($addr) = @_;
- my (@r);
-
- use Mail::Address;
- my (@addrs) = Mail::Address->parse($addr);
-
- my $i = 0;
- LIST:
- for my $addr (@addrs) {
- my $xaddr = $addr->address();
- next LIST unless $xaddr =~ /\@/;
- push(@r, $xaddr);
- }
-
- return \@r;
-}
-
-
-# Descriptions: extrace gecos field in $address
-# Arguments: OBJ($self) STR($address)
-# Side Effects: none
-# Return Value: STR
-sub _who_of_address
-{
- my ($self, $address) = @_;
- my ($user);
-
- use Mail::Address;
- my (@addrs) = Mail::Address->parse($address);
-
- for my $addr (@addrs) {
- if (defined( $addr->phrase() )) {
- my $phrase = $self->_decode_mime_string( $addr->phrase() );
-
- if ($phrase) {
- return($phrase);
- }
- }
-
- $user = $addr->user();
- }
-
- return( $user ? "$user\@xxx.xxx.xxx.xxx" : $address );
-}
-
-
-# Descriptions: head of array (space separeted string)
-# Arguments: STR($buf)
-# Side Effects: none
-# Return Value: STR
-sub _list_head
-{
- my ($buf) = @_;
- $buf =~ s/^\s*//;
- $buf =~ s/\s*$//;
- return (split(/\s+/, $buf))[0];
-}
-
-
-# Descriptions: decode MIME-encoded $str
-# Arguments: OBJ($self) STR($str) HASH_REF($options)
-# Side Effects: none
-# Return Value: STR
-sub _decode_mime_string
-{
- my ($self, $str, $options) = @_;
- my $charset = $options->{ 'charset' } || $self->{ _charset };
- my $code = _charset_to_code($charset);
-
- # If looks Japanese and $code is specified as Japanese, decode !
- if (defined($str) &&
- ($str =~ /=\?ISO\-2022\-JP\?[BQ]\?/i) &&
- ($code eq 'euc' || $code eq 'jis')) {
- if ($str =~ /=\?ISO\-2022\-JP\?B\?(\S+\=*)\?=/i) {
- eval q{ use MIME::Base64; };
- $str =~ s/=\?ISO\-2022\-JP\?B\?(\S+\=*)\?=/decode_base64($1)/gie;
- }
-
- if ($str =~ /=\?ISO\-2022\-JP\?Q\?(\S+\=*)\?=/i) {
- eval q{ use MIME::QuotedPrint;};
- $str =~ s/=\?ISO\-2022\-JP\?Q\?(\S+\=*)\?=/decode_qp($1)/gie;
- }
-
- if (defined $str) {
- eval q{ use Jcode;};
- my $icode = &Jcode::getcode(\$str);
- &Jcode::convert(\$str, $code, $icode);
- }
- }
-
- return $str;
-}
-
-
-=head1 useful functions as entrance
-
-=head2 C<htmlify_file($file, $args)>
-
-try to convert rfc822 message C<$file> to HTML.
-
- $args = {
- directory => "destination directory",
- };
-
-=head2 C<htmlify_dir($dir, $args)>
-
-try to convert all rfc822 messages to HTML in C<$dir> directory.
-
- $args = {
- directory => "destination directory",
- };
-
-=cut
-
-
-# Descriptions: convert $file to HTML
-# Arguments: STR($file) HASH_REF($args)
-# Side Effects: none
-# Return Value: none
-sub htmlify_file
-{
- my ($file, $args) = @_;
- my $dst_dir = $args->{ directory };
-
- use File::Basename;
- my $id = basename($file);
- my $html = new Mail::HTML::Lite {
- charset => "euc-jp",
- directory => $dst_dir,
- };
-
- if ($debug) {
- printf STDERR "htmlify_file( id=%-6s src=%s )\n", $id, $file;
- }
-
- $html->htmlfy_rfc822_message({
- id => $id,
- src => $file,
- });
-
- $html->update_relation( $id );
- $html->update_id_monthly_index({ id => $id });
- $html->update_id_index({ id => $id });
- $html->update_thread_index({ id => $id });
-
- # no more action for old files
- if ($html->is_ignore($id)) {
- warn("not process $id (already exists)") if $debug;
- }
- else {
- printf STDERR " converted( id=%-6s src=%s )\n", $id, $file;
- }
-}
-
-
-# Descriptions: convert all articles in specified directory
-# Arguments: STR($src_dir) HASH_REF($args)
-# Side Effects: none
-# Return Value: none
-sub htmlify_dir
-{
- my ($src_dir, $args) = @_;
- my $dst_dir = $args->{ directory };
- my $max = 0;
-
- use DirHandle;
- my $dh = new DirHandle $src_dir;
- if (defined $dh) {
- FILE:
- for my $file ( $dh->read() ) {
- next FILE unless $file =~ /^\d+$/;
- $max = $max < $file ? $file : $max;
- }
- }
-
- for my $id ( 1 .. $max ) {
- use File::Spec;
- my $file = File::Spec->catfile($src_dir, $id);
- htmlify_file($file, { directory => $dst_dir });
- }
-}
-
-
-#
-# debug
-#
-if ($0 eq __FILE__) {
- my $dir = "/tmp/htdocs";
-
- eval q{
- for my $x (@ARGV) {
- if (-f $x) {
- htmlify_file($x, { directory => $dir });
- }
- elsif (-d $x) {
- htmlify_dir($x, { directory => $dir });
- }
- }
- };
- croak($@) if $@;
-}
-
-
-=head1 TODO
-
- expiration
-
- sub directory?
-
-=head1 AUTHOR
-
-Ken'ichi Fukamachi
-
-=head1 COPYRIGHT
-
-Copyright (C) 2001,2002 Ken'ichi Fukamachi
-
-All rights reserved. This program is free software; you can
-redistribute it and/or modify it under the same terms as Perl itself.
-
-=head1 HISTORY
-
-Mail::HTML::Lite appeared in fml5 mailing list driver package.
-See C<http://www.fml.org/> for more details.
-
-=cut
-
-
-1;