# $Id$ $VERSION{''.__FILE__} = '$Revision$'; # # >>Title:: SGML Format Driver # # >>Copyright:: # Copyright (c) 1992-1997, Ian Clatworthy (ianc@mincom.com). # You may distribute under the terms specified in the LICENSE file. # # >>History:: # ----------------------------------------------------------------------- # Date Who Change # 14-Aug-97 ianc SDF 2.000 # ----------------------------------------------------------------------- # # >>Purpose:: # This library provides an [[SDF_DRIVER]] which generates # [[SGML]] files. # # >>Description:: # # >>Limitations:: # Cross-references and URLs aren't there yet. # # Indented tables within a bulleted list don't work yet. # # Tagged lists aren't mapped as well as they could be. # # Special character support still needs some work (for dagger and # doubledagger, if not a few others). # # 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. # # >>Resources:: # # >>Implementation:: # ##### Constants ##### # These are the tags which don't have/need a closing tag %_SGML_NOENDTAG = ( 'title', 1, 'author', 1, 'date', 1, 'sect', 1, 'sect1', 1, 'sect2', 1, 'sect3', 1, 'sect4', 1, 'sect5', 1, 'p', 1, ); # Mapping table for characters %_SGML_CHAR = ( 'bullet', '.', 'c', '©', 'cent', '¢', 'dagger', '^', 'doubledagger', '#', 'emdash', '—', 'endash', '–', 'emspace', ' ', 'enspace', ' ', 'lbrace', '{', 'lbracket', '[', 'nbdash', '-', 'nbspace', ' ', 'nl', '&nl;', 'pound', '£', 'r', '®', 'rbrace', '}', 'rbracket', ']', 'tab', ' ', 'tm', '', # not sure about this 'yen', '¥', ); # Directive mapping table %_SGML_HANDLER = ( 'tuning', '_SgmlHandlerTuning', 'endtuning', '_SgmlHandlerEndTuning', 'table', '_SgmlHandlerTable', 'row', '_SgmlHandlerRow', 'cell', '_SgmlHandlerCell', 'endtable', '_SgmlHandlerEndTable', 'import', '_SgmlHandlerImport', 'inline', '_SgmlHandlerInline', 'output', '_SgmlHandlerOutput', 'object', '_SgmlHandlerObject', 'stylesheet', '', ); # Phrase directive mapping table %_SGML_PHRASE_HANDLER = ( 'char', '_SgmlPhraseHandlerChar', 'import', '_SgmlPhraseHandlerImport', 'inline', '_SgmlPhraseHandlerInline', 'variable', '_SgmlPhraseHandlerVariable', ); # Table states $_SGML_INTABLE = 1; $_SGML_INROW = 2; $_SGML_INCELL = 3; ##### Variables ##### # Table states @_sgml_tbl_state = (); @_sgml_tbl_endtokens = (); @_sgml_tbl_previndent = (); @_sgml_tbl_title = (); ##### Routines ##### # # >>Description:: # {{Y:SgmlFormat}} is an SDF driver which outputs SGML. # sub SgmlFormat { local(*data) = @_; local(@result); # Format the paragraphs @result = &_SgmlFormatSection(*data); # Build the final result. @result = &_SgmlFinalise(*result); # Return the result return @result; } # # >>_Description:: # {{Y:_SgmlFormatSection}} formats a set of SDF paragraphs into SGML. # sub _SgmlFormatSection { local(*data) = @_; local(@result); local($prev_tag, $prev_indent); local($para_tag, $para_text, %para_attrs); local($directive); # Process the paragraphs @result = (); $prev_tag = ''; $prev_indent = ''; while (($para_text, $para_tag, %para_attrs) = &SdfNextPara(*data)) { # handle directives if ($para_tag =~ /^__(\w+)$/) { $directive = $_SGML_HANDLER{$1}; if (defined &$directive) { &$directive(*result, $para_text, %para_attrs); } else { &AppTrace("sgml", 5, "ignoring internal directive '$1'"); } next; } # Add the paragraph &_SgmlParaAdd(*result, $para_tag, $para_text, *para_attrs, $prev_tag, $prev_indent); } # Do this stuff before starting next loop iteration continue { $prev_tag = $para_tag; $prev_indent = $para_attrs{'in'}; } # Return result return @result; } # # >>_Description:: # {{Y:_SgmlParaAdd}} adds a paragraph. # sub _SgmlParaAdd { local(*result, $para_tag, $para_text, *para_attrs, $prev_tag, $prev_indent) = @_; # local(); local($is_example); local($para_fmt); local($para_override); local($para); local($hdg_level); local($label); local($indent); local($list_tag); # Get the example flag $is_example = $SDF_USER'parastyles_category{$para_tag} eq 'example'; # Enumerated lists are the same as list paragraphs at the previous level, # except that we bold the text if ($para_tag =~ /^LI(\d)$/) { $para_tag = $1 > 1 ? "L" . ($1 - 1) : 'N'; $para_attrs{'bold'} = 1; } # Get the target format name $para_fmt = $SDF_USER'parastyles_to{$para_tag}; $para_fmt = $is_example ? 'tscreen' : 'p' if $para_fmt eq ''; # Map the attributes &SdfAttrMap(*para_attrs, 'sgml', *SDF_USER'paraattrs_to, *SDF_USER'paraattrs_map, *SDF_USER'paraattrs_attrs, $SDF_USER'parastyles_attrs{$para_tag}); # Handle headings if ($para_tag =~ /^[HAP](\d)$/) { $hdg_level = $1 - 1; $para_fmt = $hdg_level ? "sect$hdg_level" : "sect"; } # Handle lists elsif ($para_tag =~ /^(L[FUN]?)(\d)$/) { $para_attrs{'in'} = $2; if ($1 eq 'LU') { $para_fmt = 'itemize'; } elsif ($1 eq 'L') { $para_fmt = 'list'; } else { $para_fmt = 'enum'; } } # Prepend the label, if any (replacing tabs with spaces) $label = $para_attrs{'label'}; $label = 'Note: ' if ($para_tag eq 'Note' || $para_tag eq 'NB') && $label eq ''; $label =~ s/\\t/ /g; $para_text = "{{2:$label}}$para_text" if $label ne ''; # Indent examples, if necessary if ($is_example && $para_attrs{'in'}) { $para_text = " " x ($para_attrs{'in'} * 4) . $para_text; delete $para_attrs{'in'}; } # Format the paragraph body if ($para_attrs{'verbatim'}) { $para = &_SgmlEscape($para_text); delete $para_attrs{'verbatim'}; } else { $para = &_SgmlParaText($para_text); } ## Examples with change bars currently come out as separate ## paragraphs - this fixes the problem, for now #delete $para_attrs{'changed'} if $para_attrs{'changed'}; # Build result $indent = $para_attrs{'in'}; #if ($is_example && $para_tag eq $prev_tag && !%para_attrs) { if ($is_example && $para_tag eq $prev_tag) { &_SgmlParaAppend(*result, $para); } elsif ($indent && $prev_indent != 0) { $item = &_SgmlElement($para_fmt, $para, %para_attrs); &_SgmlItemAppend(*result, $item, $indent, $prev_indent, $para_tag, $prev_tag, *para_attrs); } # Plain paragraphs inside tables are not preceded by
elsif (@_sgml_tbl_state && $para_fmt eq 'p') { push(@result, $para); } else { # After a heading, make sure the next entity is another heading # or a plain paragraph if ($prev_tag =~ /^[HAP]\d$/ && $para_fmt ne 'p' && $para_fmt !~ /^sect\d?$/) { push(@result, '
');
}
# Add this element, handling lists which begin at an indent
# greater than 1
$para = &_SgmlElement($para_fmt, $para, %para_attrs);
$list_tag = $para_fmt;
while (--$indent > 0) {
$para = "<$list_tag>$para$list_tag>";
}
push(@result, $para);
}
}
#
# >>_Description::
# {{Y:_SgmlParaText}} converts SDF paragraph text into SGML.
#
sub _SgmlParaText {
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);
# 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 .= &_SgmlEscape($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 = &_SgmlEscape($text);
# Expand non-breaking spaces, if necessary
if ($char_tag eq 'S') {
$text =~ s/ /~/g;
}
# Add hypertext stuff
$added_anchors = &_SgmlAddAnchors(*text, *sect_attrs);
# Process formatting attributes
&SdfAttrMap(*sect_attrs, 'sgml', *SDF_USER'phraseattrs_to,
*SDF_USER'phraseattrs_map, *SDF_USER'phraseattrs_attrs,
$SDF_USER'phrasestyles_attrs{$char_tag});
# Map the font
$char_font = $SDF_USER'phrasestyles_to{$char_tag};
$char_font = 'em' if $char_font eq '' && !$added_anchors;
# Add the text for this phrase
push(@char_fonts, $char_font);
if ($char_font ne '' && $char_font !~ /^SDF/) {
$para .= "<$char_font>$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 = $_SGML_PHRASE_HANDLER{$char_tag};
if (defined &$directive) {
&$directive(*para, $text, %sect_attrs);
}
else {
&AppMsg("warning", "ignoring special phrase '$1' in SGML driver");
}
}
else {
&AppMsg("warning", "unknown section type '$sect_type' in SGML driver");
}
}
# Return result
return $para;
}
#
# >>_Description::
# {{Y:_SgmlFinalise}} generates the final SGML file.
#
sub _SgmlFinalise {
local(*body) = @_;
# local(@result);
local(@head);
# Build the preamble
my $dtd = $var{'SGML_DTD'} || 'linuxdoc';
my @head = (
"",
'',
'";
$end_tokens .= "
";
}
}
else {
$posn = -$indent * 7;
}
if ($posn < 0) {
$end_tokens .= substr($outbuffer[$#outbuffer], $posn);
substr($outbuffer[$#outbuffer], $posn) = $begin_tokens;
}
else {
push(@outbuffer, $begin_tokens);
}
}
# Update the state
push(@_sgml_tbl_state, $_SGML_INTABLE);
push(@_sgml_tbl_endtokens, $end_tokens);
push(@_sgml_tbl_previndent, $indent);
push(@_sgml_tbl_title, $attr{'title'});
# Build the layout
my @col_aligns = split(//, 'l' x $columns);
my @user_aligns = split(/,/, $attr{'colaligns'});
my $i;
for ($i = 0; $i <= $#user_aligns; $i++) {
$col_aligns[$i] = lc(substr($user_aligns[$i], 0, 1));
}
my $col_sep = $attr{'style'} eq 'plain' ? '' : "|";
my $layout = join($col_sep, @col_aligns);
# Update the output buffer
push(@outbuffer, "", "
");
# Terminate the list, if any
push(@outbuffer, $end_tokens);
# Restore the previous indent. We do this by hacking the
# %para_attrs hash dynamically scoped in &SgmlFormatSection. :-(
$para_attrs{'in'} = pop(@_sgml_tbl_previndent);
}
#
# >>_Description::
# {{Y:_SgmlHandlerImport}} handles the import directive.
#
sub _SgmlHandlerImport {
local(*outbuffer, $filepath, %attr) = @_;
# local();
local($para);
# Build the result
&_SgmlPhraseHandlerImport(*para, $filepath, %attr);
push(@outbuffer, $para);
}
#
# >>_Description::
# {{Y:_SgmlHandlerInline}} handles the inline directive.
#
sub _SgmlHandlerInline {
local(*outbuffer, $text, %attr) = @_;
# local();
# Check we can handle this format
my $target = $attr{'target'};
return unless $target eq 'sgml';
# Build the result
push(@outbuffer, $text);
}
#
# >>_Description::
# {{Y:_SgmlHandlerOutput}} handles the output directive.
#
sub _SgmlHandlerOutput {
local(*outbuffer, $text, %attr) = @_;
# local();
# do nothing
}
#
# >>_Description::
# {{Y:_SgmlHandlerObject}} handles the 'object' directive.
#
sub _SgmlHandlerObject {
local(*outbuffer, $text, %attrs) = @_;
# local();
# do nothing
}
#
# >>_Description::
# {{Y:_SgmlPhraseHandlerChar}} handles the 'char' phrase directive.
#
sub _SgmlPhraseHandlerChar {
local(*para, $text, %attr) = @_;
# local();
# Map the symbolic names
if (defined($_SGML_CHAR{$text})) {
$para .= $_SGML_CHAR{$text};
}
else {
# Numbers are ISO character codes
$para .= $text =~ /\D/ ? "&$text;" : "$text;";
}
}
#
# >>_Description::
# {{Y:_SgmlPhraseHandlerImport}} handles the 'import' phrase directive.
#
sub _SgmlPhraseHandlerImport {
local(*para, $filepath, %attr) = @_;
# local();
# Trim the extension off the filepath
$filepath =~ s/\.\w+$//;
# Build the result
$para .= "";
}
#
# >>_Description::
# {{Y:_SgmlPhraseHandlerInline}} handles the 'inline' phrase directive.
#
sub _SgmlPhraseHandlerInline {
local(*para, $text, %attr) = @_;
# local();
# Build the result
$para .= $text;
}
#
# >>_Description::
# {{Y:_SgmlPhraseHandlerVariable}} handles the 'variable' phrase directive.
#
sub _SgmlPhraseHandlerVariable {
local(*para, $text, %attr) = @_;
# local();
# do nothing
}
# package return value
1;