# $Id$
$VERSION{''.__FILE__} = '$Revision$';
#
# >>Title:: HTML Format Driver
#
# >>Copyright::
# Copyright (c) 1992-1999, Ian Clatworthy (ianc@mincom.com).
# You may distribute under the terms specified in the LICENSE file.
#
# >>History::
# -----------------------------------------------------------------------
# Date Who Change
# 29-Feb-96 ianc SDF 2.000
# -----------------------------------------------------------------------
#
# >>Purpose::
# This library provides an [[SDF_DRIVER]] which generates
# [[HTML]] files.
#
# >>Description::
#
# >>Limitations::
# Lists which have ordered items, then unordered items, then
# ordered items all at the same level are output as three
# separate lists. As a result, the numbering in the third list
# restarts even if you don't want it to.
#
# After hypertext jumps have been added throughout a paragraph,
# we should go back over the paragraph and unnest any nested jumps.
#
# >>Resources::
#
# >>Implementation::
#
##### Constants #####
# Mapping table for characters
%_HTML_CHAR = (
'bullet', '.',
'c', '© ',
'cent', '¢ ',
'dagger', '^',
'doubledagger', '#',
'emdash', '--',
'endash', '-',
'emspace', ' ',
'enspace', ' ',
'lbrace', '{',
'lbracket', '[',
'nbdash', '-',
'nbspace', ' ',
'nl', '
',
'pound', '£ ',
'r', '® ',
'rbrace', '}',
'rbracket', ']',
'tab', ' ',
'tm', ' ', # not sure about this
'yen', '¥ ',
);
# Directive mapping table
%_HTML_HANDLER = (
'tuning', '_HtmlHandlerTuning',
'endtuning', '_HtmlHandlerEndTuning',
'table', '_HtmlHandlerTable',
'row', '_HtmlHandlerRow',
'cell', '_HtmlHandlerCell',
'endtable', '_HtmlHandlerEndTable',
'import', '_HtmlHandlerImport',
'inline', '_HtmlHandlerInline',
'output', '_HtmlHandlerOutput',
'object', '_HtmlHandlerObject',
'stylesheet', '_HtmlHandlerStyleSheet',
'div', '_HtmlHandlerDiv',
'enddiv', '_HtmlHandlerEndDiv',
);
# Phrase directive mapping table
%_HTML_PHRASE_HANDLER = (
'char', '_HtmlPhraseHandlerChar',
'import', '_HtmlPhraseHandlerImport',
'inline', '_HtmlPhraseHandlerInline',
'variable', '_HtmlPhraseHandlerVariable',
);
# Table states
$_HTML_INTABLE = 1;
$_HTML_INROW = 2;
$_HTML_INCELL = 3;
# Attribute types - this is used to decide if an attribute is legal,
# and if it is, whether to quote the value (string) or not.
# 'class' types get made into a class attribute.
%_HTML_ATTR_TYPES = (
'align', 'string',
'alt', 'string',
'border', 'integer',
'class', 'string',
'changed', 'class',
);
# Attributes mapping to styles
%_HTML_STYLE_MAP = (
'family', 'font-family: %s',
'size', 'font-size: %s',
'bold', 'font-weight: bold',
'italics', 'font-style: italic',
'underline','text-decoration: underline',
'color', 'color: %s',
'bgcolor', 'background-color: %s',
# 'align', 'text-align: %s',
'first', 'text-indent: %s',
'left', 'margin-left: %s',
'right', 'margin-right: %s',
);
##### Variables #####
# Table/cell states
@_html_tbl_state = ();
@_html_tbl_endtokens = ();
@_html_tbl_previndent = ();
$_html_cell_paracnt = ();
# Stack of topic file offsets and filenames
@_html_topic_offset = ();
@_html_topic_file = ();
# Current topic and level
$_html_topic = '';
$_html_topic_level = 0;
# File/text combinations which start a new topic
%_html_topic_start = ();
# File/text lookup to a jump target
%_html_jump_id = ();
## Ordered list state
#$_html_in_olist = 0;
# Topic counter for building derived topic names
$_html_topic_cntr = 0;
# Meta information for this file
@_html_meta = ();
# Links this file
@_html_links = ();
# Stylesheet for this file
@_html_stylesheet = ();
# Counts for each class attribute
%_html_class_count = ();
# Division stack (contents is the name (i.e. class) of opened divisions)
@_html_divs = ();
# Title division text
@_html_title_div = ();
##### Routines #####
#
# >>Description::
# {{Y:HtmlFormat}} is an SDF driver which outputs HTML.
#
sub HtmlFormat {
local(*data) = @_;
local(@result);
local(@contents);
local(@data2, @contents2, %var2, @result2);
local($msg_cursor, %msg_counts);
local($main);
local(@topics_table, @jumps_table);
# Init global data
$_html_topic = '';
$_html_topic_level = 0;
$_html_topic_cntr = 0;
%_html_topic_start = ();
%_html_jump_id = ();
@_html_meta = ();
@_html_links = ();
@_html_stylesheet = ();
%_html_class_count = ();
@_html_divs = ();
@_html_title_div = ();
# If we're building topics, save the data for a second pass later
if ($SDF_USER'var{'HTML_TOPICS_MODE'}) {
@data2 = @data;
# Get the current message cursor - we skip the second pass
# if errors are found
$msg_cursor = &AppMsgNextIndex();
}
# Format the paragraphs
@contents = ();
@result = &_HtmlFormatSection(*data, *contents);
# Save away any unclosed topics
while (@_html_topic_file) {
&_HtmlHandlerOutput(*result, '-');
}
# Build the final result.
## Note that we must do this AFTER the subtopics stuff in order
## to get the next/previous topic data needed for the default
## header/footer.
@result = &_HtmlFinalise(*result, *contents);
# If there were no problems in the first pass,
# build the sub-topics, if requested
%msg_counts = &AppMsgCounts($msg_cursor);
if ($msg_counts{'error'} || $msg_counts{'abort'} || $msg_counts{'fatal'} ) {
# do nothing
}
elsif ($SDF_USER'var{'HTML_TOPICS_MODE'}) {
$main = $SDF_USER'var{'DOC_BASE'};
@topics_table = ();
@jumps_table = ();
&_HtmlBuildTopicsData($main, *topics_table, *jumps_table);
# Save the topics and jump data, so users can (eventually) rebuild
# just a single topic.
if ($SDF_USER'var{'HTML_SDJ'}) {
&_HtmlSaveTopicsData($main, *topics_table, *jumps_table);
}
# Initialise things ready for the next pass
%var2 = %convert_var; # get the original set of variables
$var2{'HTML_MAIN_TITLE'} = $SDF_USER'var{'DOC_TITLE'};
$var2{'HTML_URL_CONTENTS'} = $SDF_USER'var{'DOC_BASE'} . ".html";
$var2{'HTML_TOPICS_MODE'} = 0;
$var2{'HTML_SUBTOPICS_MODE'} = 1;
&SdfInit(*var2);
&SDF_USER'topics_Filter(*topics_table, 'data', 1);
&SDF_USER'jumps_Filter(*jumps_table, 'data', 1);
# Build the sub-topics
@contents2 = ();
#printf "DATA2:\n%s\nENDDATA2\n", join("\n", @data2);
@result2 = &_HtmlFormatSection(*data2, *contents2);
# Save away any unclosed topics
while (@_html_topic_file) {
&_HtmlHandlerOutput(*result2, '-');
}
}
# Return the result
return @result;
}
#
# >>_Description::
# {{Y:_HtmlBuildTopicsData}} builds the topics data
# needed for sub-topic building.
#
sub _HtmlBuildTopicsData {
local($main, *topics_table, *jumps_table) = @_;
# local();
local($topic, $level, $label, $next, $prev, $up, %last_at);
local($jump, $physical);
# Ensure that the main topic is first and that it has the highest level
#if ($SDF_USER'topics[0] eq $main) {
# $SDF_USER'levels[0] = 0;
#}
#else {
# unshift(@SDF_USER'topics, pop(@SDF_USER'topics));
# pop(@SDF_USER'levels);
# unshift(@SDF_USER'levels, 0);
#}
unshift(@SDF_USER'topics, $main);
unshift(@SDF_USER'levels, 0);
# Build the topics table
@topics_table = ("Topic|Label|Level|Next|Prev|Up");
$prev = $SDF_USER'topics[$#SDF_USER'topics];
%last_at = ();
for ($i = 0; $i <= $#SDF_USER'topics; $i++) {
$topic = $SDF_USER'topics[$i];
$level = $SDF_USER'levels[$i];
$label = $SDF_USER'topic_label{$topic};
$next = $i < $#SDF_USER'topics ? $SDF_USER'topics[$i + 1] : $SDF_USER'topics[0];
$up = $last_at{$level - 1};
push(@topics_table, "$topic|$label|$level|$next|$prev|$up");
# Save state for later iterations
$prev = $topic;
$last_at{$level} = $topic;
}
# Build the jumps table
@jumps_table = ("Jump|Physical");
for $jump (sort keys %SDF_USER'jump) {
$physical = $SDF_USER'jump{$jump};
push(@jumps_table, "$jump|$physical");
}
}
#
# >>_Description::
# {{Y:_HtmlSaveTopicsData}} dumps topic and jump data to a file.
#
sub _HtmlSaveTopicsData {
local($main, *topics_table, *jumps_table) = @_;
# local();
local($file);
# Save the topic and jump data
$file = &NameJoin('', $main, 'sdj');
unless (open(SDM, ">$file")) {
&AppMsg("warning", "unable to update topics file '$file'");
}
else {
# Output a warning message at the top
print SDM "# WARNING: This file is automatically generated\n";
print SDM "# by SDF, so any changes you make will be lost!\n";
# Dump the topics data
print SDM "\n";
print SDM "!block topics; data\n";
print SDM join("\n", @topics_table), "\n";
print SDM "!endblock\n";
# Dump the jumps data
print SDM "\n";
print SDM "!block jumps\n";
print SDM join("\n", @jumps_table), "\n";
print SDM "!endblock\n";
# Close the file
close(SDM);
}
}
#
# >>_Description::
# {{Y:_HtmlFormatSection}} formats a set of SDF paragraphs into HTML.
# If a parameter is passed to contents, then that array is populated
# with a generated Table of Contents. If {{division}} is set, the
# result is placed in a DIV element with that class.
#
sub _HtmlFormatSection {
local(*data, *contents, $division) = @_;
local(@result);
local($prev_tag, $prev_indent);
local($para_tag, $para_text, %para_attrs);
local($directive);
## Reset the ordered list state. I'm not absolutely sure that
## this is the best place to do this, but TJH had it here
## and I trust him (most of the time :-)
#$_html_in_olist = 0;
# Process the paragraphs
@result = $division eq '' ? () : ("
and
as # Netscape then outputs too much whitespace. elsif (@_html_tbl_state && $_html_cell_paracnt++ == 0 && $para_fmt eq 'P') { push(@result, $para); } else { $para = &_HtmlElement($para_fmt, $para, %para_attrs); # Handle lists which begin at an indent greater than 1 $list_tag = substr($para, 1, 2) if $indent; while (--$indent > 0) { $para = "<$list_tag>$para$list_tag>"; } # Prepend the table of contents jump id, if necessary if ($toc_jump =~ /^#HDR\d+$/) { $para = " \n$para"; } push(@result, $para); } } # # >>_Description:: # {{Y:_HtmlParaText}} converts SDF paragraph text into HTML. # sub _HtmlParaText { local($para_text) = @_; local($para); local($state); local($sect_type, $char_tag, $text, %sect_attrs); local($url); local($added_anchors); local(@char_fonts); local($char_font); local($directive); local($char_attrs); # Process the text $para = ''; $state = 0; while (($sect_type, $text, $char_tag, %sect_attrs) = &SdfNextSection(*para_text, *state)) { # Build the paragraph if ($sect_type eq 'string') { $para .= &_HtmlEscape($text); } elsif ($sect_type eq 'phrase') { # Expand out link phrases if ($char_tag eq 'L') { ($text, $url) = &SDF_USER'ExpandLink($text); $sect_attrs{'jump'} = $url; } # Escape any special characters $text = &_HtmlEscape($text); # Expand non-breaking spaces, if necessary if ($char_tag eq 'S') { $text =~ s/ / /g; } # Empty cells look ugly so the hack below # puts a space in empty phrases inside cells. # Unfortunately, this means truly empty phrases # inside cells are not handled. Is this an issue? $text = ' ' if $text eq '' && @_html_tbl_state; # If this is a jump, ignore the style (i.e. make it 'as-is') #$char_tag = 'A' if $sect_attrs{'jump'} ne ''; # Add hypertext stuff $added_anchors = &_HtmlAddAnchors(*text, *sect_attrs); # Process formatting attributes &SdfAttrMap(*sect_attrs, 'html', *SDF_USER'phraseattrs_to, *SDF_USER'phraseattrs_map, *SDF_USER'phraseattrs_attrs, $SDF_USER'phrasestyles_attrs{$char_tag}); $char_attrs = &_HtmlAttr(*sect_attrs); #print STDERR "char_attrs is $char_attrs<\n"; # Map the font $char_font = $SDF_USER'phrasestyles_to{$char_tag}; $char_font = $char_tag if $char_font eq '' && !$added_anchors; # If attributes are specified for an SDF font, use a SPAN if ($char_font =~ /^SDF/ && $char_attrs ne '') { $char_font = 'SPAN'; } # Add the text for this phrase push(@char_fonts, $char_font); if ($char_font ne '' && $char_font !~ /^SDF/) { $para .= "<$char_font$char_attrs>$text"; } else { $para .= $text; } } elsif ($sect_type eq 'phrase_end') { $char_font = pop(@char_fonts); $para .= "$char_font>" if $char_font ne '' && $char_font !~ /^SDF/; } elsif ($sect_type eq 'special') { $directive = $_HTML_PHRASE_HANDLER{$char_tag}; if (defined &$directive) { &$directive(*para, $text, %sect_attrs); } else { &AppMsg("warning", "ignoring special phrase '$1' in HTML driver"); } } else { &AppMsg("warning", "unknown section type '$sect_type' in HTML driver"); } } # Return result return $para; } # # >>_Description:: # {{Y:_HtmlFinalise}} generates the final HTML file. # sub _HtmlFinalise { local(*body, *contents) = @_; # local(@result); local($title, @sdf_title, @title); local($version, @head); local($body); # Build the BODY opening stuff $body = "BODY"; $body .= sprintf(' BACKGROUND="%s"', $SDF_USER'var{"HTML_BG_IMAGE"}) if defined($SDF_USER'var{"HTML_BG_IMAGE"}); $body .= sprintf(' BGPROPERTIES="FIXED"') if $SDF_USER'var{"HTML_BG_FIXED"}; $body .= sprintf(' BGCOLOR="%s"', $SDF_USER'var{"HTML_BG_COLOR"}) if defined($SDF_USER'var{"HTML_BG_COLOR"}); $body .= sprintf(' TEXT="%s"', $SDF_USER'var{"HTML_TEXT_COLOR"}) if defined($SDF_USER'var{"HTML_TEXT_COLOR"}); $body .= sprintf(' LINK="%s"', $SDF_USER'var{"HTML_LINK_COLOR"}) if defined($SDF_USER'var{"HTML_LINK_COLOR"}); $body .= sprintf(' VLINK="%s"', $SDF_USER'var{"HTML_VLINK_COLOR"}) if defined($SDF_USER'var{"HTML_VLINK_COLOR"}); # Convert the title, if any, to HTML $title = $SDF_USER'var{'HTML_TITLE'}; $title = $SDF_USER'var{'DOC_TITLE'} if !defined($title); if ($title) { @sdf_title = ("TITLE:$title"); @title = &_HtmlFormatSection(*sdf_title, *dummy); } else { @title = (); } # Prepend some useful things to the stylesheet, if applicable if ($_html_class_count{'changed'}) { my $changed_color = $SDF_USER'var{'HTML_CHANGED_COLOR'}; unshift(@_html_stylesheet, ".changed {background-color: $changed_color}"); } # Build the HEAD element (and append BODY opening) $version = $SDF_USER'var{'SDF_VERSION'}; @head = ( '', '', '', '', "', '', '', ); push(@head, @title) if @title; push(@head, @_html_meta) if @_html_meta; push(@head, @_html_links) if @_html_links; if (@_html_stylesheet) { push(@head, '', ); } push(@head, '', "<$body>", ''); # Build the body contents, unless we're generating an input file for # the HTMLDOC package unless ($SDF_USER'var{'HTMLDOC'}) { &_HtmlFinaliseBodyContents(*body, *contents); } # Return result push(@body, '', '', '