cdesktopenv/cde/programs/dtdocbook/doc2sdl/docbook.tcl

4874 lines
134 KiB
Tcl
Executable File

#!/usr/bin/tclsh
# set the global variables
set nextId 0 ;# gets incremented before each use
set errorCount 0 ;# number of user errors
set warningCount 0 ;# number of user warnings
set havePartIntro 0 ;# need to emit a hometopic
set firstPInBlock 0 ;# allows a different SSI for first P
set inBlock "" ;# holds "</BLOCK>\n" when in a BLOCK
set inVirpage "" ;# holds "</VIRPAGE>\n" when in a VIRPAGE
set needFData "" ;# holds "<FDATA>\n" if needed (starting a FORM)
set inP 0 ;# flag that we're in an SDL paragraph
set formStack {} ;# need to stack FORMs when they contain others
set listStack {} ;# holds type of list and spacing for ListItem
# create some constants for converting list count to ordered label
set ROMAN0 [list "" I II III IV V VI VII VIII IX]
set ROMAN10 [list "" X XX XXX XL L LX LXX LXXX XC]
set ROMAN100 [list "" C CC CCC CD D DC DCC DCCC CM]
set roman0 [list "" i ii iii iv v vi vii viii ix]
set roman10 [list "" x xx xxx xl l lx lxx lxxx xc]
set roman100 [list "" c cc ccc cd d dc dcc dccc cm]
set ALPHABET [list "" A B C D E F G H I J K L M N O P Q R S T U V W X Y Z]
set alphabet [list "" a b c d e f g h i j k l m n o p q r s t u v w x y z]
set DIGITS [list 0 1 2 3 4 5 6 7 8 9]
set NZDIGITS [list "" 1 2 3 4 5 6 7 8 9]
# specify the "level" value to be given to VIRPAGEs (based on SSI);
# the indexes for this associative array are also used to determine
# whether the closing of a DocBook Title should re-position the
# snbLocation (because the SNB follows HEADs, if any)
set virpageLevels(FOOTNOTE) 0
set virpageLevels(TITLE) 0
set virpageLevels(AUTHORGROUP) 0
set virpageLevels(ABSTRACT) 0
set virpageLevels(REVHISTORY) 0
set virpageLevels(LEGALNOTICE) 0
set virpageLevels(PARTINTRO) 1
set virpageLevels(CHAPTER) 2
set virpageLevels(APPENDIX) 2
set virpageLevels(BIBLIOGRAPHY) 2
set virpageLevels(GLOSSARY) 2
set virpageLevels(INDEX) 2
set virpageLevels(LOT) 2
set virpageLevels(PREFACE) 2
set virpageLevels(REFENTRY) 2
set virpageLevels(REFERENCE) 2
set virpageLevels(TOC) 2
set virpageLevels(SECT1) 3
set virpageLevels(SECT2) 4
set virpageLevels(SECT3) 5
set virpageLevels(SECT4) 6
set virpageLevels(SECT5) 7
# assume the first ID used is SDL-RESERVED1 - if we get a INDEXTERM
# before anything has started, default to the assumed ID
set mostRecentId "SDL-RESERVED1"
# a counter for use in pre-numbering footnotes - will create an
# associative array indexed by "FOOTNOTE ID=" values to hold
# the number of the FOOTNOTE for use by FOOTNOTEREF
set footnoteCounter 0
# the absolute byte offset into the output file where the SNB should be
# inserted by the second pass - the location and snb get saved at
# the end of each VIRPAGE with a little special handling for the
# SDLDOC SNB, the entire snb gets written to the .snb file at
# the close of the document after the saved locations get incremented
# by the size of the index
set snbLocation 0
# normally, we dafault paragraphs to no TYPE= attribute; when in an
# EXAMPLE, for instance, we need to default to TYPE="LITERAL"
set defaultParaType ""
# print internal error message and exit
proc InternalError {what} {
global errorInfo
error $what
}
# print a warning message
proc UserWarning {what location} {
global warningCount
puts stderr "DtDocBook User Warning: $what"
if {$location} {
PrintLocation
}
incr warningCount
}
# print an error message plus the location in the source file of the
# error; if we get more than 100 errors, quit
proc UserError {what location} {
global errorCount
puts stderr "DtDocBook User Error: $what"
if {$location} {
PrintLocation
}
if {[incr errorCount] >= 100} {
puts stderr "Too many errors - quitting"
exit 1
}
}
# set up a default output string routine so everything works even
# if run outside of instant(1)
if {[info commands OutputString] == ""} {
proc OutputString {string} {
puts -nonewline "$string"
}
}
# emit a string to the output stream
proc Emit {string} {
OutputString $string
}
# push an item onto a stack (a list); return item pushed
proc Push {stack item} {
upvar $stack s
lappend s $item
return $item
}
# pop an item from a stack (i.e., a list); return the popped item
proc Pop {stack} {
upvar $stack s
set top [llength $s]
if {!$top} {
InternalError "Stack underflow in Pop"
}
incr top -1
set item [lindex $s $top]
incr top -1
set s [lrange $s 0 $top]
return $item
}
# return the top of a stack (the stack is a list)
proc Peek {stack} {
upvar $stack s
set top [llength $s]
incr top -1
set item [lindex $s $top]
}
# replace the top of the stack with the new item; return the item
proc Poke {stack item} {
upvar $stack s
set top [llength $s]
incr top -1
set s [lreplace $s $top $top $item]
return $item
}
# emit an ID and save it for reference as the most recently emitted ID;
# the saved value will be used to mark locations for index entries
proc Id {name} {
global mostRecentId
set mostRecentId $name
return "ID=\"$name\""
}
# emit an ANCHOR into the SDL stream; if the passed id is empty, don't
# emit the anchor
proc Anchor {id} {
if {$id != ""} {
Emit "<ANCHOR [Id $id]>"
}
}
# emit an ANCHOR into the SDL stream; if the passed id is empty, don't
# emit the anchor; if we're not in an SDL P yet, start one and use
# the id there rather than emitting an SDL ANCHOR
proc AnchorInP {id} {
global inP
if {$id != ""} {
if {!$inP} {
StartParagraph $id "P" ""
} else {
Emit "<ANCHOR [Id $id]>"
}
}
}
# set up containers for the IDs of the blocks holding marks - clear
# on entry to each <virpage> but re-use within the <virpage> as much as
# possible; we need two each of the regular and loose versions because
# we need to alternate to avoid the <form> runtime code thinking we're
# trying to span columns
#
# specify a routine to (re-)initialize all the variables for use
# in ListItem
proc ReInitPerMarkInfo {} {
global validMarkArray
foreach mark [array names validMarkArray] {
global FIRSTTIGHT${mark}Id
set FIRSTTIGHT${mark}Id ""
global FIRSTLOOSE${mark}Id
set FIRSTLOOSE${mark}Id ""
global TIGHT${mark}Id0
set TIGHT${mark}Id0 ""
global TIGHT${mark}Id1
set TIGHT${mark}Id1 ""
global LOOSE${mark}Id0
set LOOSE${mark}Id0 ""
global LOOSE${mark}Id1
set LOOSE${mark}Id1 ""
global TIGHT${mark}num
set TIGHT${mark}num 1
global LOOSE${mark}num
set LOOSE${mark}num 1
}
}
# add a new mark to the mark array and initialize all the variables
# that depend on the mark; the index for the mark is just the mark
# itself with the square brackets removed and whitespace deleted;
# we've already guaranteed that the mark will be of the form
# "[??????]" (open-square, 6 characters, close-square) and that this
# mark isn't in the array already
proc AddToMarkArray {mark} {
global validMarkArray
set m [string range $mark 1 6]
set m [string trim $m]
set validMarkArray($m) $mark
global FIRSTTIGHT${m}Id
set FIRSTTIGHT${m}Id ""
global FIRSTLOOSE${m}Id
set FIRSTLOOSE${m}Id ""
global TIGHT${m}Id0
set TIGHT${m}Id0 ""
global TIGHT${m}Id1
set TIGHT${m}Id1 ""
global LOOSE${m}Id0
set LOOSE${m}Id0 ""
global LOOSE${m}Id1
set LOOSE${m}Id1 ""
global TIGHT${m}num
set TIGHT${m}num 1
global LOOSE${m}num
set LOOSE${m}num 1
return $m
}
# start a new paragraph; start a block if necessary
proc StartParagraph {id ssi type} {
global inBlock firstPInBlock inP defaultParaType
# close any open paragraph
if {$inP} { Emit "</P>\n" }
# if not in a BLOCK, open one
if {$inBlock == ""} { StartBlock "" "" "" 1 }
Emit "<P"
if {$id != ""} { Emit " [Id $id]" }
# don't worry about whether we're the first para if there's no SSI
if {$ssi != ""} {
set firstString ""
if {$firstPInBlock} {
if {$ssi == "P"} {
set firstString 1
}
set firstPInBlock 0
}
Emit " SSI=\"$ssi$firstString\""
}
if {$type == ""} {
Emit $defaultParaType
} else {
Emit " TYPE=\"$type\""
}
Emit ">"
set inP 1
set inBlock "</P>\n</BLOCK>\n"
}
# conditionally start a paragraph - that is, only start a new
# paragraph if we aren't in one already
proc StartParagraphMaybe {id ssi type} {
global inP
if {$inP} {
Anchor $id
} else {
StartParagraph $id $ssi $type
}
}
# start a compound paragraph - a compound paragraph is when a Para
# contains some other element that requires starting its own SDL
# BLOCK or FORM, e.g., VariableList; we need to create a FORM to hold
# the Para and its parts - put the id and ssi on the FORM rather than
# the contained Ps.
proc StartCompoundParagraph {id ssi type} {
global firstPInBlock
if {$ssi != ""} {
if {$firstPInBlock} {
set firstString 1
} else {
set firstString ""
}
PushForm "" $ssi$firstString $id
} else {
PushForm "" "" $id
}
StartParagraph "" "" ""
}
# given the path of parentage of an element, return its n'th ancestor
# (parent == 1), removing the child number (if any); e.g., convert
# "PART CHAPTER(0) TITLE" into "CHAPTER" if level is 2
proc Ancestor {path level} {
if {$level < 0} { return "_UNDERFLOW_" }
set last [llength $path]
incr last -1
if {$level > $last} { return "_OVERFLOW_" }
# invert "level" about "last" so we count from the end
set level [expr "$last - $level"]
set parent [lindex $path $level]
set parent [lindex [split $parent "("] 0] ;# remove child #
}
# start a HEAD element for the DocBook Title - use the parent's
# GI in the SSI= of the HEAD except that all titles to things in
# their own topic (VIRPAGE) will use an SSI of CHAPTER-TITLE;
# if we are in a topic with a generated id (e.g., _glossary or
# _title), we might have saved an id or two in savedId to be
# emitted in the HEAD
proc Title {id parent} {
global virpageLevels partID inP savedId
Emit "<HEAD"
if {$id != ""} {
Emit " ID=\"$id\""
}
# if we are the Title of a PartIntro, we'd like to emit the
# partID as an anchor so linking to the volume will succeed;
# add it to the list of saved ids to be emitted
if {$parent == "PARTINTRO"} {
lappend savedId $partID
}
# make the HEAD for all topics (VIRPAGE) have an SSI of
# "CHAPTER-HEAD", use LEVEL to distinguish between them
set topicNames [array names virpageLevels]
foreach name $topicNames {
if {$parent == $name} {
set parent CHAPTER
break
}
}
Emit " SSI=\"$parent-TITLE\">"
# being in a HEAD is equivalent to being in a P for content model
# but we use "incr" instead of setting inP directly so that if we
# are in a P->HEAD, we won't prematurely clear inP when leaving
# the HEAD
incr inP
if {[info exists savedId]} {
foreach id $savedId {
Anchor $id
}
unset savedId
}
}
# close a HEAD element for a DocBook Title - if the Title is one for
# a DocBook element that gets turned into an SDL VIRPAGE, set the
# location for the insertion of an SNB (if any) to follow the HEAD
proc CloseTitle {parent} {
global snbLocation virpageLevels inP
Emit "</HEAD>\n"
# we incremented inP on entry to the HEAD so decrement it here
incr inP -1
# get a list of DocBook elements that start VIRPAGEs
set names [array names virpageLevels]
# add the start of the help volume, PART, to the list
lappend names PART
# if our parent is a VIRPAGE creator or the start of the document,
# we must be dealing with the heading of a VIRPAGE or with the
# heading of the SDLDOC so move the spot where we want to include
# the SNB to immediately after this HEAD
foreach name $names {
if {$name == $parent} {
set snbLocation [tell stdout]
break
}
}
}
# open an SGML tag - add punctuation as guided by the class attribute
proc StartSgmlTag {id class} {
switch $class {
ELEMENT {set punct "&<"}
ATTRIBUTE {set punct ""}
GENENTITY {set punct "&&"}
PARAMENTITY {set punct "%"}
}
Emit $punct
}
# close an SGML tag - add punctuation as guided by the class attribute
proc EndSgmlTag {class} {
switch $class {
ELEMENT {set punct ">"}
ATTRIBUTE {set punct ""}
GENENTITY {set punct ";"}
PARAMENTITY {set punct ";"}
}
Emit $punct
}
# end a trademark, append a symbol if needed
proc EndTradeMark {class} {
switch $class {
SERVICE {set punct ""}
TRADE {set punct "<SPC NAME=\"\[trade \]\">"}
REGISTERED {set punct "<SPC NAME=\"\[reg \]\">"}
COPYRIGHT {set punct "<SPC NAME=\"\[copy \]\">"}
}
Emit "</KEY>$punct"
}
# handle the BridgeHead tag; emit a FORM to hold a HEAD and put the
# BridgeHead there - use the procedure Title to do all the work, the
# renderas attributre simply become the parent to Title
proc StartBridgeHead {id renderas} {
PushForm "" "" ""
# default renderas to CHAPTER - arbitrarily
if {$renderas == "OTHER"} {
set renderas CHAPTER
}
Title $id $renderas
}
# end a BridgeHead; we need to close out the SDL HEAD and close the
# FORM - use CloseTitle to close out the HEAD but give it a null
# parent so it doesn't try to save the SNB now
proc EndBridgeHead {} {
CloseTitle ""
PopForm
}
# end a paragraph
proc EndParagraph {} {
global inP inBlock
if {$inP} {
Emit "</P>\n"
}
# we set inBlock to </P></BLOCK> in StartParagraph so we need
# to remove the </P> here; if we're continuing a paragraph
# inBlock will have been set to "" when we closed the BLOCK to
# open the embedded FORM so we need to leave it empty to cause
# a new BLOCK to be opened
if {$inBlock != ""} {
set inBlock "</BLOCK>\n"
}
# and flag that we're not in a paragraph anymore
set inP 0
}
# continue a PARA that was interrupted by something from %object.gp;
# first pop the FORM that held the indent attributes for the object
# then start a new paragraph with an SSI that indicates we are
# continuing
proc ContinueParagraph {} {
PopForm
StartParagraph "" "P-CONT" ""
}
# start a new BLOCK element; close the old one, if any;
# return the ID in case we allocated one and someone else wants it
proc StartBlock {class ssi id enterInForm} {
global needFData inBlock formStack nextId firstPInBlock inP
# if we are the first BLOCK in a FORM, emit the FDATA tag
Emit $needFData; set needFData ""
# close any open block and flag that we're opening one
# but that we haven't seen a paragraph yet
Emit $inBlock
set inBlock "</BLOCK>\n"
set inP 0
# if a FORM is in progress, add our ID to the row vector,
# FROWVEC - create an ID if one wasn't provided
if {$enterInForm && [llength $formStack] != 0} {
if {$id == ""} { set id "SDL-RESERVED[incr nextId]" }
AddRowVec $id
}
# open the BLOCK
Emit "<BLOCK"
if {$id != ""} { Emit " [Id $id]" }
if {$class != ""} { Emit " CLASS=\"$class\"" }
if {$ssi != ""} { Emit " SSI=\"$ssi\"" }
Emit ">\n"
# and flag that the next paragraph is the first in a block
set firstPInBlock 1
return $id
}
# close any open BLOCK - no-op if not in a BLOCK otherwise emit the
# BLOCK etag or both BLOCK and P etags if there's an open paragraph
proc CloseBlock {} {
global inBlock inP
if {$inBlock != ""} {
Emit $inBlock ;# has been prefixed with </P> if needed
set inBlock ""
set inP 0
}
}
# add another FROWVEC element to the top of the form stack
proc AddRowVec {ids} {
global formStack
Push formStack "[Pop formStack]<FROWVEC CELLS=\"$ids\">\n"
}
# start a new FORM element within a THead, TBody or TFoot ("push"
# because they're recursive); return the ID in case we allocated one;
# do not enter the ID in the parent's FROWVEC, we'll do that later
# from the rowDope that we build to compute horizontal spans and
# vertical straddles
proc PushFormCell {ssi id} {
global needFData formStack nextId
Emit $needFData ;# in case we're the first in an old FORM
set needFData "<FDATA>\n" ;# and were now starting a new FORM
# close any open BLOCK
CloseBlock
# make sure we have an ID
if {$id == ""} { set id "SDL-RESERVED[incr nextId]" }
# add a new (empty) string to the formStack list (i.e., push)
Push formStack {}
Emit "<FORM"
if {$id != ""} { Emit " [Id $id]" }
Emit " CLASS=\"CELL\""
if {$ssi != ""} { Emit " SSI=\"$ssi\"" }
Emit ">\n"
return $id
}
# start a new FORM element ("push" because they're recursive);
# return the ID in case we allocated one
proc PushForm {class ssi id} {
global needFData formStack nextId
Emit $needFData ;# in case we're the first in an old FORM
set needFData "<FDATA>\n" ;# and were now starting a new FORM
# close any open BLOCK
CloseBlock
if {[llength $formStack] != 0} {
# there is a <form> in progress
if {$id == ""} { set id "SDL-RESERVED[incr nextId]" }
AddRowVec $id
}
# add a new (empty) string to the formStack list (i.e., push)
Push formStack {}
Emit "<FORM"
if {$id != ""} { Emit " [Id $id]" }
if {$class != ""} { Emit " CLASS=\"$class\"" }
if {$ssi != ""} { Emit " SSI=\"$ssi\"" }
Emit ">\n"
return $id
}
# start a new FORM element to hold a labeled list item ("push"
# because they're recursive), adding it to an already open two
# column FORM, if any; we assume the first ID is the block holding
# the label and always defined on entry but we return the second
# ID in case we allocated one
proc PushFormItem {ssi id1 id2} {
global needFData formStack nextId
Emit $needFData ;# in case we're the first in an old FORM
set needFData "<FDATA>\n" ;# and were now starting a new FORM
# close any open BLOCK
CloseBlock
if {$id2 == ""} { set id2 "SDL-RESERVED[incr nextId]" }
if {[llength $formStack] != 0} {
# there is a <form> in progress
if {$id2 == ""} { set id2 "SDL-RESERVED[incr nextId]" }
AddRowVec "$id1 $id2"
}
# add a new (empty) string to the formStack list (i.e., push)
Push formStack {}
Emit "<FORM [Id $id2] CLASS=\"ITEM\""
if {$ssi != ""} { Emit " SSI=\"$ssi\"" }
Emit ">\n"
return $id2
}
# close out a THead, TBody or TFoot; create the FROWVEC from the
# rowDope - save it if we aren't popping the FORM yet (which happens
# if no ColSpec elements were given at the THead or TFoot level and
# we're merging one, the other or both with the TBody), emit the
# saved ROWVEC, if any, and newly created one if we are popping the
# FORM in which case we also want to blow away the top of the
# formStack; we can also blow away the current rowDope here since
# we write or save the FROWVEC and we're done with the dope vector
proc PopTableForm {parent gi popForm} {
global formStack
# get the proper row descriptor(s) and number of columns
if {$parent == "ENTRYTBL"} {
upvar #0 entryTableRowDope rowDope
upvar #0 entryTableSavedFRowVec fRowVec
global entryTableAttributes
set nCols $entryTableAttributes(cols)
} else {
upvar #0 tableGroupRowDope rowDope
upvar #0 tableGroupSavedFRowVec fRowVec
global tableGroupAttributes
set nCols $tableGroupAttributes(cols)
}
# flush the unused formStack entry if we're actually popping
if {$popForm} {
Pop formStack
}
# determine whether we are a "header", i.e., inside a TFoot or
# THead
if {$gi == "TBODY"} {
set hdr ""
} else {
set hdr " HDR=\"YES\""
}
# if actually popping the FORM here (i.e., writing the FSTYLE),
# emit the FSTYLE wrapper
if {$popForm} {
Emit "</FDATA>\n<FSTYLE"
if {$nCols > 1} {
Emit " NCOLS=\"$nCols\""
}
Emit ">\n"
}
set currentRow 1
set nRows $rowDope(nRows)
while {$currentRow <= $nRows} {
append fRowVec "<FROWVEC$hdr CELLS=\""
append fRowVec $rowDope(row$currentRow)
append fRowVec "\">\n"
incr currentRow
}
unset rowDope
# if actually popping the FORM here (i.e., writing the FSTYLE),
# emit the FROWVEC elements, zero out the saved fRowVec and close
# the FSTYLE wrapper
if {$popForm} {
Emit $fRowVec
set fRowVec ""
Emit "</FSTYLE>\n</FORM>\n"
}
}
# close out one FORM on the stack; if there hasn't been a block added
# to the FORM, create an empty one to make it legal SDL
proc PopForm {} {
global formStack
if {[Peek formStack] == ""} {
# oops, empty FROWVEC means empty FORM so add an empty BLOCK
StartBlock "" "" "" 1
}
# close any open BLOCK
CloseBlock
# write out the saved FROWVEC information wrapped in an FSTYLE
set openStyle "</FDATA>\n<FSTYLE>\n"
set closeStyle "</FSTYLE>\n</FORM>"
Emit "$openStyle[Pop formStack]$closeStyle\n"
}
# close out one N columned FORM on the stack; if there hasn't been a
# block added to the FORM, create an empty one to make it legal SDL
proc PopFormN {nCols} {
global formStack
if {[Peek formStack] == ""} {
# oops, empty FROWVEC means empty FORM so add an empty BLOCK
# and bring this down to a single column FORM containing only
# the new BLOCK
StartBlock "" "" "" 1
set nCols 1
}
# close any open BLOCK
CloseBlock
# write out the saved FROWVEC information wrapped in an FSTYLE
set openStyle "</FDATA>\n<FSTYLE NCOLS=\"$nCols\">\n"
set closeStyle "</FSTYLE>\n</FORM>"
Emit "$openStyle[Pop formStack]$closeStyle\n"
}
# check the Role attribute on lists to verify that it's either "LOOSE"
# or "TIGHT"; return upper cased version of verified Role
proc CheckSpacing {spacing} {
set uSpacing [string toupper $spacing]
switch $uSpacing {
LOOSE -
TIGHT {return $uSpacing}
}
UserError "Bad value (\"$role\") for Role attribute in a list" yes
return LOOSE
}
# start a simple list - if Type is not INLINE, we need to save the
# Ids of the BLOCKs we create and lay them out in a HORIZONTAL or
# VERTICAL grid when we have them all
proc StartSimpleList {id type spacing parent} {
global listStack firstString
if {$type == "INLINE"} {
StartParagraphMaybe $id P ""
} else {
# if we are inside a Para, we need to issue a FORM to hang the
# indent attributes on
if {$parent == "PARA"} {
PushForm "" "INSIDE-PARA" ""
}
# insure "spacing" is upper case and valid (we use it in the SSI)
set spacing [CheckSpacing $spacing]
# save the list type and spacing for use by <Member>;
set listDope(type) simple
set listDope(spacing) $spacing
Push listStack [array get listDope]
PushForm LIST SIMPLE-$spacing $id
set firstString "FIRST-"
}
}
# end a simple list - if Type was INLINE, we're done, otherwise, we
# need to lay out the grid based on Type and Columns
proc EndSimpleList {columns type parent} {
global listStack lastList listMembers
if {$columns == 0} {
UserWarning "must have at least one column in a simple list" yes
set columns 1
}
if {$type != "INLINE"} {
# get the most recently opened list and remove it from the stack
array set lastList [Pop listStack]
# calculate the number of rows and lay out the BLOCK ids
# as per the type attribute
set length [llength $listMembers]
set rows [expr ($length + $columns - 1) / $columns]
set c 0
set r 0
set cols $columns
if {$type == "HORIZ"} {
incr cols -1
while {$r < $rows} {
set ids [lrange $listMembers $c [incr c $cols]]
AddRowVec $ids
incr c
incr r
}
} else {
set lastRowLength [expr $cols - (($rows * $cols) - $length)]
incr rows -1
while {$r <= $rows} {
set i $r
set ids ""
set c 0
if {$r == $rows} {
set cols $lastRowLength
}
while {$c < $cols} {
lappend ids [lindex $listMembers $i]
incr i $rows
if {$c < $lastRowLength} {
incr i
}
incr c
}
AddRowVec $ids
incr r
}
}
unset listMembers
# close the open FORM using the newly generated ROWVECs
PopFormN $columns
# if we are inside a Para, we need to close the FORM we issued for
# hanging the indent attributes
if {$parent == "PARA"} {
ContinueParagraph
}
}
}
# collect another Member of a SimpleList; if we're a Vert(ical) or
# Horiz(ontal) list, don't put the BLOCK's id on the list's FORM
# yet - we need to collect them all and lay them out afterward in
# EndSimpleList; if we're an Inline list, don't create a BLOCK, we'll
# add punctuation to separate them in EndMember
proc StartMember {id type} {
global nextId listStack firstString listMembers
if {$type == "INLINE"} {
Anchor $id
} else {
# put it in a BLOCK, make sure we have an id and add it to
# the list of members
if {$id == ""} {
set id SDL-RESERVED[incr nextId]
}
lappend listMembers $id
# get the current list info
array set listTop [Peek listStack]
set spacing $listTop(spacing)
# use an SSI of, e.g., FIRST-LOOSE-SIMPLE
StartBlock ITEM $firstString$spacing-SIMPLE $id 0
StartParagraph "" P ""
set firstString ""
}
}
# end a SimpleList Member; if it's an Inline list, emit the
# punctuation ("", ", " or "and") based on the position of the
# Member in the list - otherwise, do nothing and the StartBlock from
# the next Member or the PopFormN in EndSimpleList will close the
# current one out
proc EndMember {type punct} {
if {$type == "INLINE"} {
Emit $punct
}
}
# check the value of a ITEMIZEDLIST MARK - issue warning and default
# it to BULLET if it's unrecognized
proc ValidMark {mark} {
global validMarkArray
if {[string toupper $mark] == "PLAIN"} { return PLAIN }
# if an SDATA entity was used, it'll have spurious "\|" at the
# beginning and the end added by [n]sgmls
if {[string match {\\|????????\\|} $mark]} {
set mark [string range $mark 2 9]
}
if {![string match {\[??????\]} $mark]} {
UserError "Unknown list mark \"$mark\" specified, using PLAIN" yes
return PLAIN
} else {
foreach m [array names validMarkArray] {
if {$validMarkArray($m) == $mark} {return $m}
}
return [AddToMarkArray $mark]
}
}
# start an itemized list
proc ItemizedList {id mark spacing parent} {
global listStack firstString
# if we are inside a Para, we need to issue a FORM to hang the
# indent attributes on
if {$parent == "PARA"} {
PushForm "" "INSIDE-PARA" ""
}
# make sure we recognize the mark
set mark [ValidMark $mark]
# insure "spacing" is upper case and valid (we use it in the SSI)
set spacing [CheckSpacing $spacing]
# save the list type, mark and spacing for use by <ListItem>
set listDope(type) itemized
set listDope(spacing) $spacing
set listDope(mark) $mark
Push listStack [array get listDope]
# create a FORM to hold the list items
if {$mark == "PLAIN"} {
PushForm LIST "PLAIN-$spacing" $id
} else {
PushForm LIST "MARKED-$spacing" $id
}
set firstString "FIRST-"
}
# turn absolute item count into proper list number e.g., 2, B, or II
proc MakeOrder {numeration count} {
global ROMAN0 ROMAN10 ROMAN100
global roman0 roman10 roman100
global ALPHABET alphabet
global NZDIGITS DIGITS
if {$count == ""} { return "" }
if {$count > 999} { set count 999 } ;# list too big - cap it
# initialize the 3 digits of the result value
set c1 0
set c2 0
set c3 0
# first get the 3 digits in the proper base (26 or 10)
switch -exact $numeration {
UPPERALPHA -
LOWERALPHA {
set c3 [expr "$count % 26"]
if {$c3 == 0} { set c3 26 }
if {[set count [expr "$count / 26"]]} {
set c2 [expr "$count % 26"]
if {$c2 == 0} { set c2 26 }
set c1 [expr "$count / 26"]
}
}
UPPERROMAN -
LOWERROMAN -
default {
set c3 [expr "$count % 10"]
if {[set count [expr "$count / 10"]]} {
set c2 [expr "$count % 10"]
if {[set count [expr "$count / 10"]]} {
set c1 [expr "$count % 10"]
}
}
}
}
# then point to proper conversion list(s)
switch -exact $numeration {
UPPERALPHA {
set c1List $ALPHABET
set c2List $ALPHABET
set c3List $ALPHABET
}
LOWERALPHA {
set c1List $alphabet
set c2List $alphabet
set c3List $alphabet
}
UPPERROMAN {
set c3List $ROMAN0
set c2List $ROMAN10
set c1List $ROMAN100
}
LOWERROMAN {
set c3List $roman0
set c2List $roman10
set c1List $roman100
}
default {
set c1List $DIGITS
set c2List $DIGITS
set c3List $DIGITS
if {$c1 == 0} {
set c1List $NZDIGITS
if {$c2 == 0} {
set c2List $NZDIGITS
}
}
}
}
# and do the conversion
set string [lindex $c1List $c1]
append string [lindex $c2List $c2]
append string [lindex $c3List $c3]
append string .
return $string
}
# start an ordered list
proc OrderedList {id numeration inheritNum continue spacing parent} {
global listStack lastList firstString
# if we are inside a Para, we need to issue a FORM to hang the
# indent attributes on
if {$parent == "PARA"} {
PushForm "" "INSIDE-PARA" ""
}
# make sure the INHERIT param is compatible with enclosing list
if {$inheritNum == "INHERIT"} {
if {[llength $listStack] > 0} {
array set outerList [Peek listStack]
if {$outerList(type) != "ordered"} {
UserError "Can only inherit numbering from an ordered list" yes
set inheritNum IGNORE
}
} else {
UserError \
"Attempt to inherit a list number with no previous list" yes
set inheritNum IGNORE
}
}
# make sure the CONTINUE param is compatible with previous list;
# also inherit numeration here if unset (else error if different)
# and we're continuing
if {$continue == "CONTINUES"} {
if {![array exists lastList]} {
# nothing to inherit from
UserError "Attempt to continue a list with no previous list" yes
set continue RESTARTS
} elseif {$lastList(type) != "ordered"} {
UserError "Only ordered lists can be continued" yes
set continue RESTARTS
} elseif {$numeration == ""} {
set numeration $lastList(numeration)
} elseif {$lastList(numeration) != $numeration} {
UserError "Can't continue a list with different numeration" yes
set continue RESTARTS
}
}
# if no numeration specified, default to Arabic
if {$numeration == ""} {
set numeration ARABIC
}
set count 0 ;# assume we are restarting the item count
set inheritString "" ;# fill in later if set
if {$continue == "CONTINUES"} {
# continuing means use the old inherit string (if any) and
# pick up with the old count
set count $lastList(count)
if {($lastList(inheritString) != "") && ($inheritNum != "INHERIT")} {
UserError \
"Must continue inheriting if continuing list numbering" yes
set inheritNum INHERIT
}
}
if {$inheritNum == "INHERIT"} {
# inheriting a string to preface the current number - e.g., "A.1."
set inheritString $outerList(inheritString)
append inheritString \
[MakeOrder $outerList(numeration) $outerList(count)]
}
# insure "spacing" is upper case and valid (we use it in the SSI)
set spacing [CheckSpacing $spacing]
# save the list type and spacing for use by <ListItem>
set listDope(type) ordered
set listDope(spacing) $spacing
set listDope(numeration) $numeration
set listDope(inheritString) $inheritString
set listDope(count) $count
Push listStack [array get listDope]
# create a FORM to hold the list items
PushForm LIST "ORDER-$spacing" $id
set firstString "FIRST-"
}
# start a variable list (i.e., labeled list)
proc VariableList {id role parent} {
global listStack firstString
# if we are inside a Para, we need to issue a FORM to hang the
# indent attributes on
if {$parent == "PARA"} {
PushForm "" "INSIDE-PARA" ""
}
# parse out the possible role values (loose/tight and
# wrap/nowrap)
set role [split [string toupper $role]]
set role1 [lindex $role 0]
set role2 ""
set length [llength $role]
if {$length > 1} {
set role2 [lindex $role 1]
}
if {$length > 2} {
UserError "Too many values (> 2) for Role in a VARIABLELIST" yes
}
set spacing ""
set wrap ""
switch $role1 {
LOOSE -
TIGHT {set spacing $role1}
WRAP -
NOWRAP {set wrap $role1}
default {UserError "Bad value for Role ($role1) in a VARIABLELIST" yes
}
}
switch $role2 {
"" {#}
LOOSE -
TIGHT {if {$spacing == ""} {
set spacing $role2
} else {
UserError "Only specify LOOSE/TIGHT once per Role" yes
}
}
WRAP -
NOWRAP {if {$wrap == ""} {
set wrap $role2
} else {
UserError "Only specify WRAP/NOWRAP once per Role" yes
}
}
default {UserError "Bad value for Role ($role2) in a VARIABLELIST" yes
}
}
if {$spacing == ""} {
set spacing "LOOSE"
}
if {$wrap == ""} {
set wrap "NOWRAP"
}
# insure "spacing" is upper case and valid (we use it in the SSI)
set spacing [CheckSpacing $spacing]
# save the list type and spacing for use by <ListItem>;
# also save a spot for the current label ID
set listDope(type) variable
set listDope(spacing) $spacing
set listDope(labelId) $id
set listDope(wrap) $wrap
Push listStack [array get listDope]
# create a FORM to hold the list items
PushForm LIST "VARIABLE-$spacing" $id
set firstString "FIRST-"
}
# open a variable list entry - create a BLOCK to hold the term(s)
proc VarListEntry {id} {
global firstString listStack nextId
# get the list spacing, i.e., TIGHT or LOOSE
array set listDope [Peek listStack]
set spacing $listDope(spacing)
# make sure we have an ID for the label (it goes in a FORM)
# save the ID for use in PushFormItem
if {$id == ""} {
set id SDL-RESERVED[incr nextId]
}
array set listDope [Pop listStack]
set listDope(labelId) $id
Push listStack [array get listDope]
StartBlock ITEM "$firstString$spacing-TERMS" $id 0
}
# process a term in a variablelist
proc StartTerm {id} {
global listStack
# get the current list info
array set listTop [Peek listStack]
set wrap $listTop(wrap)
set lined ""
if {$wrap == "NOWRAP"} {
set lined "LINED"
}
StartParagraph $id "P" $lined
}
# process an item in an ordered, variable or itemized list
proc ListItem {id override} {
global listStack firstString nextId needFData validMarkArray
# get the current list info
array set listTop [Peek listStack]
set spacing $listTop(spacing)
# if it's an itemized list, are we overriding the mark?
if {$listTop(type) == "itemized"} {
if {$override == "NO"} {
set mark $listTop(mark)
} elseif {$override == ""} {
set mark PLAIN
} else {
set mark [ValidMark $override]
}
}
if {($listTop(type) == "itemized") && ($mark != "PLAIN")} {
# marked itemized list, try to reuse an existing mark <BLOCK>
if {$firstString == ""} {
# not a FIRST, calculate the next id index - we flip
# between 0 and 1 to avoid column span in viewer
set numName $spacing${mark}num ;# get index name
upvar #0 $numName idNum
set idNum [expr "-$idNum + 1"] ;# flip it
}
if {$firstString != ""} {
set idName FIRST$spacing${mark}Id
} else {
set idName $spacing${mark}Id$idNum
}
upvar #0 $idName labelId
if {$labelId == ""} {
# need to create a <BLOCK> and save the id
set labelId "SDL-RESERVED[incr nextId]"
Emit $needFData; set needFData ""
Emit "<BLOCK [Id $labelId] CLASS=\"ITEM\""
Emit " TIMING=\"ASYNC\" "
Emit "SSI=\"$firstString$spacing-MARKED\""
Emit ">\n<P SSI=\"P1\"><SPC NAME=\"$validMarkArray($mark)\""
Emit "></P>\n</BLOCK>\n"
}
}
# emit the SSI and label for an ordered list
if {$listTop(type) == "ordered"} {
# start a block for the label
set labelId "SDL-RESERVED[incr nextId]"
Emit $needFData; set needFData ""
Emit "<BLOCK [Id $labelId] CLASS=\"ITEM\" SSI=\""
# create, e.g., FIRST-LOOSE-ORDERED
Emit "$firstString$spacing-ORDERED\">\n"
# emit the label (inherit string followed by order string)
# and close the block
Emit "<P SSI=\"P1\">"
Emit $listTop(inheritString)
Emit [MakeOrder $listTop(numeration) [incr listTop(count)]]
Emit "</P>\n</BLOCK>\n"
# then update the top of the list stack
Poke listStack [array get listTop]
}
# or just get the label id for a variable (labeled) list - the
# label was emitted in another production
if {$listTop(type) == "variable"} {
set labelId $listTop(labelId)
}
# emit a one (for PLAIN) or two column FORM to wrap this list item
set ssi "$firstString$spacing"
if {($listTop(type) == "itemized") && ($mark == "PLAIN")} {
PushForm ITEM $ssi $id
} else {
PushFormItem $ssi $labelId $id
}
set firstString ""
}
# start a segmented list, e.g.,
# foo: fooItem1
# bar: barItem1
#
# foo: fooItem2
# bar: barItem2
proc SegmentedList {id spacing parent} {
global listStack firstString
# if we are inside a Para, we need to issue a FORM to hang the
# indent attributes on
if {$parent == "PARA"} {
PushForm "" "INSIDE-PARA" ""
}
# insure "spacing" is upper case and valid (we use it in the SSI)
set spacing [CheckSpacing $spacing]
# save the list type and spacing for use by <ListItem>;
set listDope(type) segmented
set listDope(spacing) $spacing
Push listStack [array get listDope]
# create a FORM to hold the list items
PushForm LIST "SEGMENTED-$spacing" $id
set firstString "FIRST-"
}
# emit the SegTitle elements, each in its own BLOCK - we'll reuse
# them on each Seg of each SegListItem
proc StartSegTitle {id} {
global firstString listStack segTitleList nextId
# get the list spacing, i.e., TIGHT or LOOSE
array set listDope [Peek listStack]
set spacing $listDope(spacing)
# make sure we have an ID for the label (it goes in a FORM)
# save the ID for use in PushFormItem
if {$id == ""} {
set id SDL-RESERVED[incr nextId]
}
lappend segTitleList $id
# start the block but don't put in on the FORM, we'll put this
# BLOCK and the one containing the SegListItem.Seg into a two
# column form later
StartBlock ITEM "$firstString$spacing-SEGTITLE" $id 0
set firstString ""
StartParagraph "" SEGTITLE ""
}
# start a SegListItem - save the id (if any) of the SegListItem to
# be emitted as an anchor in the first Seg
proc StartSegListItem {id} {
global segListItemNumber segListItemId firstString
set segListItemId $id
set segListItemNumber 0
set firstString "FIRST-"
}
# process a Seg in a SegListItem - get the corresponding SegTitle ID
# and create a BLOCK for the item then put the pair into the FORM that
# was created back in SegmentedList
proc StartSeg {id isLastSeg} {
global segTitleList segListItemNumber segListItemId firstString
global listStack nextId
set nTitles [llength $segTitleList]
if {$segListItemNumber >= $nTitles} {
UserError \
"More Seg than SegTitle elements in a SegmentedList" yes
return
}
if {$isLastSeg} {
if {[expr "$segListItemNumber" + 1] != $nTitles} {
UserError \
"More SegTitle than Seg elements in a SegmentedList" yes
}
}
# get the current list info
array set listTop [Peek listStack]
set spacing $listTop(spacing)
# open a BLOCK and P to hold the Seg content; put any user
# supplied Id on the BLOCK and the saved segListItem Id (if
# any) on the P.
set itemId $id
if {$id == ""} {
set itemId "SDL-RESERVED[incr nextId]"
}
StartBlock ITEM $firstString$spacing $itemId 0
set firstString ""
StartParagraph $segListItemId P ""
set segListItemId ""
# we've already guaranteed that we don't overflow the list
set titleId [lindex $segTitleList $segListItemNumber]
incr segListItemNumber
# add the title and item to a row vector (FROWVEC)
AddRowVec "$titleId $itemId"
}
# close a list
proc EndList {parent} {
global listStack lastList
# get the most recently opened list and remove it from the stack
array set lastList [Pop listStack]
if {($lastList(type) == "itemized") && ($lastList(mark) == "PLAIN") } {
PopForm
} else {
PopFormN 2
}
# if we are inside a Para, we need to close the FORM we issued for
# hanging the indent attributes
if {$parent == "PARA"} {
ContinueParagraph
}
}
# start a super- or sub-scripted phrase; if there's an ID, emit the
# anchor before the SPHRASE
proc StartSPhrase {id gi} {
Anchor $id
switch $gi {
SUPERSCRIPT {set type SUPER}
SUBSCRIPT {set type SUB}
}
Emit "<KEY CLASS=\"EMPH\" SSI=\"SUPER-SUB\"><SPHRASE CLASS=\"$type\">"
}
# end a super- or sub-scripted phrase
proc EndSPhrase {} {
Emit "</SPHRASE></KEY>"
}
# start an admonition (note/caution/warning/tip/important),
# emit a FORM to hold it and the HEAD for the icon (if any);
# if the admonition has no Title, emit one using the GI of the
# admonition; map Tip to Note and Important to Caution
proc StartAdmonition {id gi haveTitle} {
PushForm "" ADMONITION $id
# select the icon
switch $gi {
NOTE -
TIP {set icon "graphics/noteicon.pm"}
CAUTION -
IMPORTANT {set icon "graphics/cauticon.pm"}
WARNING {set icon "graphics/warnicon.pm"}
}
set snbId [AddToSNB GRAPHIC $icon]
# emit the icon wrapped in a head for placement
Emit "<HEAD SSI=\"ADMONITION-ICON\"><SNREF>"
Emit "<REFITEM RID=\"$snbId\" CLASS=\"ICON\"></REFITEM>\n"
Emit "</SNREF></HEAD>"
# emit a title if none provided
if {!$haveTitle} {
Emit "<HEAD SSI=\"ADMONITION-TITLE\">$gi</HEAD>\n"
}
}
# start a Procedure - emit a <FORM> to hold it
proc StartProcedure {id} {
PushForm "" PROCEDURE $id
}
# start a Step inside a Procedure, emit another FORM to hold it
proc StartStep {id} {
PushForm "" STEP $id
}
# start a SubStep inside a Stop, emit yet another FORM to hold it
proc StartSubStep {id} {
PushForm "" SUBSTEP $id
}
# start a Part; make the PARTGlossArray be the current glossary array
proc StartPart {id} {
global partID glossStack
set partID $id
# make sure the glossary array exists but is empty
Push glossStack PARTGlossArray
upvar #0 [Peek glossStack] currentGlossArray
set currentGlossArray(foo) ""
unset currentGlossArray(foo)
}
# end a Part; check for definitions for all glossed terms
proc EndPart {} {
global glossStack
# get a convenient handle on the glossary array
upvar #0 [Peek glossStack] currentGlossArray
# check that all the glossed terms have been defined
foreach name [array names currentGlossArray] {
if {[info exists currentGlossArray($name)]} {
if {[lindex $currentGlossArray($name) 1] != "defined"} {
set glossString [lindex $currentGlossArray($name) 2]
UserError "No glossary definition for \"$glossString\"" no
}
} else {
puts stderr "EndPart: currentGlossArray: index does not exist: '$name'"
}
}
# delete this glossary array
unset currentGlossArray
}
# create and populate a dummy home page title - if no Title was
# specified we want it to be "Home Topic"
proc SynthesizeHomeTopicTitle {} {
global partID
global localizedAutoGeneratedStringArray
Title $partID PARTINTRO
set message "Home Topic"
Emit $localizedAutoGeneratedStringArray($message)
CloseTitle PARTINTRO
}
# create and populate a dummy home page because there was no
# PartIntro in this document
proc SynthesizeHomeTopic {} {
global partID
global localizedAutoGeneratedStringArray
StartNewVirpage PARTINTRO ""
SynthesizeHomeTopicTitle
StartParagraph $partID P ""
set message "No home topic (PartIntro) was specified by the author."
Emit $localizedAutoGeneratedStringArray($message)
EndParagraph
}
# start a virpage for, e.g., a SECTn - close the previous first;
# compute the level rather than specifying it in the transpec to allow
# one specification to do for all SECTn elements; if level=2 and we
# haven't emitted a PartIntro (aka HomeTopic), emit one
proc StartNewVirpage {ssi id} {
global nextId virpageLevels inVirpage firstPInBlock
global indexLocation snbLocation savedSNB currentSNB
global lastList language charset docId havePartIntro partIntroId
global emptyCells
global manTitle manVolNum manDescriptor manNames manPurpose
# get the LEVEL= value for this VIRPAGE (makes for a shorter
# transpec to not have to specify level there)
if {[info exists virpageLevels($ssi)]} {
set level $virpageLevels($ssi)
} else {
set level 0
}
# if we are opening the PartIntro, use the generated ID (which
# may be the assigned ID, if present) and flag that we've seen
# the home topic
if {$ssi == "PARTINTRO"} {
set ssi CHAPTER
set id $partIntroId
set havePartIntro 1
}
# if we haven't seen a PartIntro but we're trying to create a
# level 2 VIRPAGE, emit a dummy PartInto
if {($level == 2) && !$havePartIntro} {
SynthesizeHomeTopic
}
if {[string match {SECT[1-5]} $ssi]} {
# make Chapter and all Sect? have an SSI of "CHAPTER", use LEVEL
# to distinguish between them
set ssi CHAPTER
} else {
# make Reference, RefEntry and all RefSect? have an SSI of
# "REFERENCE", use LEVEL to distinguish between them
if {$ssi == "REFENTRY"} {
set $ssi REFERENCE
} else {
if {[string match {REFSECT[1-3]} $ssi]} { set ssi REFERENCE }
}
}
if {($ssi == "REFERENCE") || ($ssi == "REFENTRY")} {
# assume no section, we'll get one in RefMeta.ManVolNum, if any
set manTitle ""
set manVolNum ""
set manDescriptor ""
set manNames ""
set manPurpose ""
}
# close an open BLOCK, if any
CloseBlock
# close any open VIRPAGE
Emit $inVirpage; set inVirpage "</VIRPAGE>\n"
# if the first paragraph on the page is a compound para, we want
# to emit a FORM with an SSI="P1" so set the first P flag
set firstPInBlock 1
# stash away the SNB for this VIRPAGE (or SDLDOC) - make an
# associative array of the file location and the SNB data so
# we can update the file location by adding the INDEX size before
# writing the .snb file
set names [array names currentSNB]
if {[llength $names] != 0} {
foreach name $names {
# split the name into the GI and xid of the SNB entry
set colonLoc [string first "::" $name]
set type [string range $name 0 [incr colonLoc -1]]
set data [string range $name [incr colonLoc 3] end]
# emit the entry
append tempSNB "<$type ID=\"$currentSNB($name)\" "
switch $type {
GRAPHIC -
AUDIO -
VIDEO -
ANIMATE -
CROSSDOC -
MAN-PAGE -
TEXTFILE { set command "XID" }
SYS-CMD { set command "COMMAND" }
CALLBACK { set command "DATA" }
}
append tempSNB "$command=\"$data\">\n"
}
set savedSNB($snbLocation) $tempSNB
unset currentSNB
}
if {[array exists lastList]} {
unset lastList ;# don't allow lists to continue across virpage
}
# delete the list of empty cells used for indefined Entries in
# tables - we can only re-use them on the same virpage
if {[array exists emptyCells]} {
unset emptyCells
}
# we have to create new BLOCKs to hold the marks on the new page
ReInitPerMarkInfo
if {$id == ""} { set id "SDL-RESERVED[incr nextId]" }
Emit "<VIRPAGE [Id $id] LEVEL=\"$level\" "
Emit "LANGUAGE=\"$language\" "
Emit "CHARSET=\"$charset\" "
Emit "DOC-ID=\"$docId\" "
Emit "SSI=\"$ssi\">\n"
set snbLocation [tell stdout] ;# assume no HEAD for now
}
# save the virpageLevels setting for this ssi (if any) and unset it
# then call StartNewVirpage; on return, restore the virpagelevels
# setting and continue - this will force the virpage to be a level 0
# virpage and not show up in the TOC
proc StartNewLevel0Virpage {ssi id} {
global virpageLevels
if {[info exists virpageLevels($ssi)]} {
set savedLevel $virpageLevels($ssi)
unset virpageLevels($ssi)
}
StartNewVirpage $ssi $id
if {[info exists savedLevel]} {
set virpageLevels($ssi) $savedLevel
}
}
# call StartNewVirpage, then if the user supplied ID is not same as
# the default ID for that topic, emit an empty paragragh to contain
# the user supplied ID; also, convert the ID of
# SDL-RESERVED-LEGALNOTICE to SDL-RESERVED-COPYRIGHT for backwards
# compatibility, preserve the original default ID so we're consistent
# on this release too
proc StartNewVirpageWithID {ssi id defaultID haveTitle} {
global savedId
# do we need to replace LEGALNOTICE with COPYRIGHT?
set legalNotice 0
if {[string toupper $defaultID] == "SDL-RESERVED-LEGALNOTICE"} {
set defaultID SDL-RESERVED-COPYRIGHT
set legalNotice 1
}
StartNewVirpage $ssi $defaultID
# if no user supplied ID but we changed the default, emit the
# original default on the empty paragraph
if {($id == "") && $legalNotice} {
set id SDL-RESERVED-LEGALNOTICE
set legalNotice 0
}
# id is either user supplied or the original default (if changed);
# if the VIRPAGE has a HEAD (Title), save this id (these ids) and
# emit it (them) there, otherwise, emit an empty paragraph with
# the id as its id
if {$id != ""} {
if {[string toupper $id] != [string toupper $defaultID]} {
if {$haveTitle} {
set savedId $id
if {$legalNotice} {
# had both a user supplied ID and we changed the default
lappend savedId SDL-RESERVED-LEGALNOTICE
}
} else {
StartParagraph $id "" ""
if {$legalNotice} {
# had both a user supplied ID and we changed the default
Anchor SDL-RESERVED-LEGALNOTICE
}
EndParagraph
}
}
}
}
# start a VIRPAGE for an appendix; if there's no ROLE=NOTOC, use the
# virpage level from the level array, otherwise, use level 0
proc StartAppendix {ssi id role} {
global virpageLevels
set uRole [string toupper $role]
if {$uRole == "NOTOC"} {
set saveAppendixLevel $virpageLevels(APPENDIX)
set virpageLevels(APPENDIX) 0
} elseif {$role != ""} {
UserError "Bad value (\"$role\") for Role attribute in Appendix" yes
}
StartNewVirpage $ssi $id
if {$uRole == "NOTOC"} {
set virpageLevels(APPENDIX) $saveAppendixLevel
}
}
# start a new VIRPAGE for a topic that may contain a glossary; if
# there is a glossary, start a new one and make it the current glossary,
# otherwise, make the parent's glossary the current one.
proc StartGlossedTopic {gi id haveGlossary} {
global glossStack
if {$haveGlossary} {
# save the glossary array name so we can get back here
# when this topic is done
Push glossStack ${gi}GlossArray
# start a new (empty) glossary array for this glossary
upvar #0 ${gi}GlossArray currentGlossArray
set currentGlossArray(foo) ""
unset currentGlossArray(foo)
}
StartNewVirpage $gi $id
}
# end a topic that may contain a glossary; if it did, check that all
# glossed terms have been defined and remove the array - restore the
# previous glossary array
proc EndGlossedTopic {haveGlossary} {
global glossStack
# get a convenient handle on the glossary array
upvar #0 [Peek glossStack] currentGlossArray
if {$haveGlossary} {
# check that all the glossed terms have been defined
foreach name [array names currentGlossArray] {
if {[lindex $currentGlossArray($name) 1] != "defined"} {
set glossString [lindex $currentGlossArray($name) 2]
UserError "No glossary definition for \"$glossString\"" no
}
}
# delete this glossary array and restore the previous one
unset currentGlossArray
Pop glossStack
}
}
# alternate OutputString routine for when in a glossed term - merely
# buffer the output rather than sending to the output stream; we'll
# emit the SDL when the whole term has been seen
proc GlossOutputString {string} {
global glossBuffer
append glossBuffer $string
}
# prepare to link a glossed term to its definition in the glossary -
# at this point, we simply divert the output into a buffer
proc StartAGlossedTerm {} {
global glossBuffer
set glossBuffer ""
rename OutputString SaveGlossOutputString
rename GlossOutputString OutputString
}
# strip any SDL markup from the string, upper case it and return
# the result; takes advantage of the fact that we never split
# start or end tags across lines (operates a line at a time)
proc StripMarkup {markup} {
set exp {(^|([^&]*))</?[A-Z]+[^>]*>}
set stripped ""
set mList [split $markup "\n"]; # split into a list of lines
set listLen [llength $mList]
while {[incr listLen -1] >= 0} {
set mString [lindex $mList 0]; # get the first line from the
set mList [lreplace $mList 0 0]; # list and delete it
if {[string length $mString] == 0} {
# empty line of pcdata (no markup)
append stripped "\n"
continue
}
# force to upper case and delete all start and end tags
set mString [string toupper $mString]
while {[regsub -all $exp $mString {\1} mString]} {#}
if {[string length $mString] == 0} {
# empty line after removing markup; skip it
continue
}
append stripped $mString "\n"; # concat this line to result
}
return $stripped
}
# done collecting a glossed term - if we're not NOGLOSS, emit the SDL
# wrapped in a LINK; save the term, baseform (if any) and the ID
# used in the link - we'll define the ID in the glossary itself
proc EndAGlossedTerm {id role} {
global glossBuffer nextId glossStack
# get a convenient handle on the glossary array
upvar #0 [Peek glossStack] currentGlossArray
# get the original output routine back
rename OutputString GlossOutputString
rename SaveGlossOutputString OutputString
set qualifier [string toupper [string range $role 0 8]]
if {$qualifier == "NOGLOSS"} {
Emit "<KEY CLASS=\"TERM\" SSI=\"GLOSSARY\">"
Emit $glossBuffer
Emit "</KEY>"
} else {
if {$qualifier == "BASEFORM="} {
set glossString [string range $role 9 end]
} else {
set glossString $glossBuffer
}
# trim whitespace from the front and back of the string to be
# glossed, also turn line feeds into spaces and compress out
# duplicate whitespace
set glossString [string trim $glossString]
set glossString [split $glossString '\n']
set tmpGlossString $glossString
set glossString [lindex $tmpGlossString 0]
foreach str [lrange $tmpGlossString 1 end] {
if {$str != ""} {
append glossString " " [string trim $str]
}
}
# upper case the glossary entry and strip it of markup to
# use as an index so we get a case insensitive match - we'll
# save the original string too for error messages; if there's
# no glossary entry yet, issue an ID - the second entry in
# the list will be set to "defined" when we see the definition
set glossIndex [StripMarkup $glossString]
if {[info exists currentGlossArray($glossIndex)]} {
set refId [lindex $currentGlossArray($glossIndex) 0]
} else {
set refId SDL-RESERVED[incr nextId]
set currentGlossArray($glossIndex) [list $refId "" $glossString]
}
# now we can emit the glossed term wrapped in a popup link
Emit "<LINK WINDOW=\"POPUP\" RID=\"$refId\">"
Emit "<KEY CLASS=\"TERM\" SSI=\"GLOSSARY\">"
Emit $glossBuffer
Emit "</KEY></LINK>"
}
}
# done collecting a term in a glossary - emit the anchor, if not
# already done; if we are to be followed by alternate names (i.e.,
# Abbrev and/or Acronym), emit the opening paren, otherwise, close
# the open KEY
proc EndATermInAGlossary {id} {
global glossBuffer nextId nGlossAlts glossStack
global strippedGlossIndex
# get a convenient handle on the glossary array
upvar #0 [Peek glossStack] currentGlossArray
# get the original output routine back
rename OutputString GlossOutputString
rename SaveGlossOutputString OutputString
# emit the user supplied ID
Anchor $id
# trim whitespace from the front and back of the string to be
# placed in the glossary, also turn line feeds into spaces and
# compress out duplicate whitespace
set glossString [split $glossBuffer '\n']
set tmpGlossString $glossString
set glossString [lindex $tmpGlossString 0]
foreach str [lrange $tmpGlossString 1 end] {
if {$str != ""} {
append glossString " " [string trim $str]
}
}
# create an upper cased version of the glossed string with markup
# removed to use as a case insensitive index to the array
set strippedGlossIndex [StripMarkup $glossString]
# get or create the generated ID; update the glossary array to
# reflect that we now have a definition
if {[info exists currentGlossArray($strippedGlossIndex)]} {
set id [lindex $currentGlossArray($strippedGlossIndex) 0]
set defined [lindex $currentGlossArray($strippedGlossIndex) 1]
if {$defined == "defined"} {
UserError \
"multiple definitions for glossary term \"$glossBuffer\"" yes
set id SDL-RESERVED[incr nextId]
}
} else {
set id SDL-RESERVED[incr nextId]
}
set currentGlossArray($strippedGlossIndex) \
[list $id defined $glossString "" ""]
# emit the generated ID
Anchor $id
Emit "<KEY CLASS=\"TERM\" SSI=\"GLOSSARY\">"
Emit "$glossBuffer"
if {$nGlossAlts != 0} {
Emit " ("
} else {
Emit "</KEY>"
unset nGlossAlts
}
}
proc EndAcronymInGlossary {id} {
global nGlossAlts
if {[incr nGlossAlts -1] != 0} {
Emit ", "
} else {
Emit ")</KEY>"
unset nGlossAlts
}
}
proc EndAbbrevInGlossary {id} {
global nGlossAlts
Emit ")"</KEY"
unset nGlossAlts
}
# start an entry in a glossary or glosslist; divert the output - we
# need to sort the terms before emitting them
proc StartGlossEntry {id nAlternates nDefs} {
global nGlossAlts nGlossDefs currentGlossDef
global glossEntryBuffer
# this helps when determining if a comma is needed after an alt
# (either an Abbrev or an Acronym)
set nGlossAlts $nAlternates
# this lets us know when to close the FORM holding the GlossDef+
set nGlossDefs $nDefs
set currentGlossDef 0
set glossEntryBuffer ""
rename OutputString SaveGlossEntryOutputString
rename GlossEntryOutputString OutputString
PushForm "" GLOSSENTRY $id
StartParagraph "" "" ""
}
# alternate OutputString routine for when in a GlossEntry - merely
# buffer the output rather than sending to the output stream; we'll
# save this text for emission when the entire GlossDiv, Glossary or
# GlossList has been processed and we've sorted the entries.
proc GlossEntryOutputString {string} {
global glossEntryBuffer
append glossEntryBuffer $string
}
# end an entry in a glossary or glosslist; save the entry in the
# glossarray so we can later sort it for output
proc EndGlossEntry {sortAs} {
global glossEntryBuffer strippedGlossIndex glossStack
PopForm
# get the original output routine back
rename OutputString GlossEntryOutputString
rename SaveGlossEntryOutputString OutputString
# get a convenient handle on the glossary array and element
upvar #0 [Peek glossStack] currentGlossArray
upvar 0 currentGlossArray($strippedGlossIndex) currentEntryList
# save any user supplied sort key and the content of this glossary
# entry for use when all entries are defined to sort them and emit
# them in the sorted order
set currentEntryList \
[lreplace $currentEntryList 3 4 $sortAs $glossEntryBuffer]
}
# the current batch of glossary entries (to a Glossary, GlossList or
# GlossDiv has been saved in the glossArray - we need to sort them
# based on the sortAs value if given (list index 3) or the index into
# the glossArray of no sortAs was provided; when sorted, we can emit
# entries (list index 4) in the new order and delete the emitted text
# so that we don't try to emit it again (we want to save the
# glossArray until, e.g., all GlossDiv elements are processed so we
# can tell if all glossed terms have been defined); do a PopForm
# when we're done if requested (for, e.g., GlossList)
proc SortAndEmitGlossary {popForm} {
global glossStack
# get a convenient handle on the glossary array
upvar #0 [Peek glossStack] currentGlossArray
# start with an empty sortArray
set sortArray(foo) ""
unset sortArray(foo)
set names [array names currentGlossArray]
foreach name $names {
# puts stderr "JET0: name: $name"
upvar 0 currentGlossArray($name) glossEntryList
# skip this array entry if we've already emitted it; mark as
# emitted after we've extracted the content for emission
if {[set content [lindex $glossEntryList 4]] == ""} {
continue; # already been processed
}
set glossEntryList [lreplace $glossEntryList 4 4 ""]
# sort by the GlossTerm content or sortAs, if provided
if {[set sortAs [lindex $glossEntryList 3]] == ""} {
set sortAs $name
}
# append the content in case we have equal sort values
append sortArray($sortAs) $content
}
set idxnames [lsort -dictionary [array names sortArray]]
foreach name $idxnames {
# puts stderr "JET1: name: $name"
if {[info exists sortArray($name)]} {
Emit $sortArray($name)
} else {
puts stderr "SortAndEmitGlossary: sortArray index does not exist: '$name'"
}
}
if {[string toupper $popForm] == "POPFORM"} {
PopForm
}
}
# start a "See ..." in a glossary; if there was an otherterm, duplicate
# its content and wrap it in a link to the GlossTerm holding the content
proc StartGlossSee {id otherterm} {
global localizedAutoGeneratedStringArray
StartBlock "" GLOSSSEE $id 1
StartParagraph "" "" ""
set message "See"
Emit $localizedAutoGeneratedStringArray($message)
Emit " "
if {$otherterm != ""} {
Emit "<LINK RID=\"$otherterm\">"
}
}
# check the target of an OtherTerm attribute in a GlossSee to verify
# that it is indeed the ID of a GlossTerm inside a GlossEntry
proc CheckOtherTerm {id gi parent} {
global glossType
set errorMess "Other term (\"$id\") referenced from a"
if {$gi != "GLOSSTERM"} {
UserError "$errorMess $glossType must be a GlossTerm" yes
} elseif {$parent != "GLOSSENTRY"} {
UserError "$errorMess GlossSee must be in a GlossEntry" yes
}
}
# start a definition in a glossary; we wrap a FORM around the whole
# group of GlossDef elements in the GlossEntry
proc StartGlossDef {id} {
global nGlossDefs currentGlossDef
if {$currentGlossDef == 0} {
PushForm "" GLOSSDEF $id
}
StartBlock "" "" $id 1
}
# end a definition in a glossary; if this is the last definition,
# close the FORM that holds the group
proc EndGlossDef {} {
global nGlossDefs currentGlossDef
if {[incr currentGlossDef] == $nGlossDefs} {
PopForm
unset nGlossDefs currentGlossDef
}
}
# start a "See Also ..." in a glossary definition; if there was an
# otherterm, duplicate its content and wrap it in a link to the
# GlossTerm holding the content
proc StartGlossSeeAlso {id otherterm} {
global localizedAutoGeneratedStringArray
StartBlock "" GLOSSSEE $id 1
StartParagraph "" "" ""
set message "See Also"
Emit $localizedAutoGeneratedStringArray($message)
Emit " "
if {$otherterm != ""} {
Emit "<LINK RID=\"$otherterm\">"
}
}
# end a "See ..." or a "See Also ..." in a glossary definition; if there
# was an otherterm, end the link to it
proc EndGlossSeeOrSeeAlso {otherterm} {
if {$otherterm != ""} {
Emit "</LINK>"
}
}
# alternate OutputString routine for when in IndexTerm - merely
# buffer the output rather than sending to the output stream (index
# entries get emitted into the index, not where they are defined)
proc IndexOutputString {string} {
global indexBuffer
append indexBuffer $string
}
# alternate Id routine for when in IndexTerm - merely
# return the string rather than also setting the "most recently used"
# variable. The markup inside the IndexTerm goes into the index
# not the current virpage so we don't want to use the ids here
proc IndexId {name} {
return "ID=\"$name\""
}
# start an index entry
proc StartIndexTerm {id} {
global indexBuffer inP inBlock
if {$id != ""} {
if {$inP} {
Anchor $id
} elseif {$inBlock != ""} {
StartParagraph "" "P" ""
Anchor $id
EndParagraph
}
}
# prepare to buffer the output while in IndexTerm
set indexBuffer ""
rename OutputString DefaultOutputString
rename IndexOutputString OutputString
rename Id DefaultId
rename IndexId Id
}
# add an index sub-entry
proc AddIndexEntry {loc} {
global indexBuffer indexVals indexArray
# trim superfluous whitespace at the beginning and end of the
# indexed term
set indexBuffer [string trim $indexBuffer]
# get an array index and determine whether 1st, 2nd or 3rd level
set index [join $indexVals ", "]
set level [llength $indexVals]
set value [lindex $indexVals [expr "$level - 1"]]
# look for the string we want to put into the index; if the string
# isn't there, add it - if it's there, verify that the content
# being indexed is marked up the same as the last time we saw it
# and that the primary/secondary/tertiary fields are split the
# same way (bad check for now, we really need to save the
# individual values) and add the location ID to the list of locs.
set names [array names indexArray]
if {$names == ""} {
set indexArray($index) [list $level $value $loc $indexBuffer]
} else {
foreach i $names {
set found 0
if {$i == $index} {
set thisIndex $indexArray($index)
if {$indexBuffer != [lindex $thisIndex 3]} {
UserError "Indexing same terms with different markup" yes
}
if {$level != [lindex $thisIndex 0]} {
UserError "Index botch: levels don't match" yes
}
if {$loc != ""} {
set locs [lindex $thisIndex 2]
if {$locs != ""} { append locs " " }
append locs "$loc"
set thisIndex [lreplace $thisIndex 2 2 $locs]
set indexArray($index) $thisIndex
}
set found 1
break
}
}
if {!$found} {
set indexArray($index) [list $level $value $loc $indexBuffer]
}
}
set indexBuffer ""
}
# end an index entry
proc EndIndexTerm {} {
global mostRecentId
AddIndexEntry $mostRecentId
# start emitting to output stream again
rename OutputString IndexOutputString
rename DefaultOutputString OutputString
rename Id IndexId
rename DefaultId Id
}
# start a primary index term
proc StartPrimaryIndexEntry {id cdata} {
global indexVals
set indexVals [list [string trim $cdata]]
}
# end a primary index term
proc EndPrimaryIndexEntry {} {
}
# start a secondary index term
proc StartSecondaryIndexEntry {id cdata} {
global indexVals
AddIndexEntry "" ;# make sure our primary is defined
lappend indexVals [string trim $cdata]
}
# end a secondary index term
proc EndSecondaryIndexEntry {} {
}
# start a tertiary index term
proc StartTertiaryIndexEntry {id cdata} {
global indexVals
AddIndexEntry "" ;# make sure our secondary is defined
lappend indexVals [string trim $cdata]
}
# end a tertiary index term
proc EndTertiaryIndexEntry {} {
}
# compute the proper string for LOCS= in an index entry - primarily,
# we want to avoid emitting the LOCS= if there are no locations
# defined for this entry
proc Locs {entry} {
set locs [lindex $entry 2]
if {$locs != ""} {
return " LOCS=\"$locs\""
}
return ""
}
# open a .idx file and write the index into it
proc WriteIndex {} {
global baseName indexArray
set file [open "${baseName}.idx" w]
# sort the index
set idxnames [lsort -dictionary [array names indexArray]]
if {[set length [llength $idxnames]]} {
set oldLevel 0
puts $file "<INDEX COUNT=\"$length\">"
foreach name $idxnames {
if {[info exists indexArray($name)]} {
set thisEntry $indexArray($name)
switch [lindex $thisEntry 0] {
1 { switch $oldLevel {
1 { puts $file "</ENTRY>" }
2 { puts $file "</ENTRY>\n</ENTRY>" }
3 { puts $file "</ENTRY>\n</ENTRY>\n</ENTRY>" }
}
}
2 { switch $oldLevel {
2 { puts $file "</ENTRY>" }
3 { puts $file "</ENTRY>\n</ENTRY>" }
}
}
3 { if {$oldLevel == 3} { puts $file "</ENTRY>" } }
}
puts -nonewline $file "<ENTRY[Locs $thisEntry]>"
puts -nonewline $file [lindex $thisEntry 3]
set oldLevel [lindex $thisEntry 0]
} else {
puts stderr "WriteIndex: index does not exist: '$name'"
}
}
switch $oldLevel {
1 { puts $file "</ENTRY>" }
2 { puts $file "</ENTRY>\n</ENTRY>" }
3 { puts $file "</ENTRY>\n</ENTRY>\n</ENTRY>" }
}
puts $file "</INDEX>"
}
close $file
}
# called at the beginning of CHAPTER on each FOOTNOTE element - save
# their numbering for use by FOOTNOTEREF and emit a VIRPAGE for each
# note
proc GatherFootnote {id} {
global footnoteArray footnoteCounter nextId
incr footnoteCounter
if {$id != ""} {
set footnoteArray($id) $footnoteCounter
} else {
set id SDL-RESERVED[incr nextId]
}
StartNewVirpage FOOTNOTE $id
}
# emit the footnote number of the id surrounded by a <LINK> so we can
# get to it; skip out if there's no id to reference
proc FootnoteRef {idref} {
global footnoteArray
if {$idref != ""} {
if {[info exists footnoteArray($idref)]} {
Emit "<LINK RID=\"$idref\" WINDOW=\"popup\">"
Emit "<KEY CLASS=\"EMPH\" SSI=\"FOOTNOTE\">"
Emit "$footnoteArray($idref)</KEY></LINK>"
}
}
}
# add an element to the current SNB - try to reuse an entry if
# possible
proc AddToSNB {stype data} {
global currentSNB nextId
set index "${stype}::${data}"
if {[info exists currentSNB($index)]} {
set snbId $currentSNB($index)
} else {
set snbId "SDL-RESERVED[incr nextId]"
set currentSNB($index) $snbId
}
return $snbId
}
# emit a DocBook Graphic element - create an SNB entry and point to
# it here
proc Graphic {id entityref fileref gi} {
global inP
if {$gi == "GRAPHIC"} {
set class FIGURE
} else {
set class IN-LINE
}
# if "entityref" is present, it overrides "fileref"
if {$entityref != ""} {
# need to remove "<OSFILE ASIS>" (or equivalent for different
# system identifiers) from the beginning of the entity name
# if nsgmls was used for the original parse; the regular
# expression below should work by simply ignoring any leading
# angle bracket delimited string
regsub {^(<.*>)(.*)$} $entityref {\2} entityref
set file $entityref
} else {
set file $fileref
}
if {$file == ""} {
UserError "No file name or entity specified for $gi" yes
}
# if not in a paragraph, start one
if {($gi == "GRAPHIC") && (!$inP)} { StartParagraph "" "P" "" }
set snbId [AddToSNB GRAPHIC $file]
Emit "<SNREF>"
Emit "<REFITEM RID=\"$snbId\" CLASS=\"$class\"></REFITEM>\n"
Emit "</SNREF>"
}
# emit a deferred link; we deferred it when we saw that it was first
# in a Para and that it contained only an InlineGraphic - we had
# to wait for the InlineGraphic to come along to see if it not only
# met the contextual constraints but also had a Remap=Graphic
# attribute
proc EmitDeferredLink {} {
global deferredLink
if {![array exists deferredLink]} return
switch $deferredLink(gi) {
LINK {StartLink "" $deferredLink(linkend) $deferredLink(type)}
OLINK {StartOLink "" $deferredLink(localinfo) $deferredLink(type)}
}
unset deferredLink
}
# emit an InlineGraphic that might be remapped to a Graphic (via
# Remap=) and might have text wrapped around it (if it's first in
# a Para or first in a [OU]Link that is itself first in a Para)
proc InFlowGraphic {id entityref fileref parent remap role} {
global deferredLink
# we only map InlineGraphic to Graphic if we're either the first
# thing in a Para or the only thing in a link which is itself
# the first thing in a Para
set ok 0
set haveDeferredLink [array exists deferredLink]
switch $parent {
PARA {set ok 1}
LINK -
OLINK -
ULINK {set ok $haveDeferredLink}
}
if {!$ok} {
Graphic $id $entityref $fileref INLINEGRAPHIC
return
}
set uRemap [string toupper $remap]
if {$uRemap == "GRAPHIC"} {
set uRole [string toupper $role]
switch $uRole {
LEFT -
"" {set role "LEFT"}
RIGHT {set role "RIGHT"}
default {
set badValMess "Bad value (\"$role\") for Role attribute"
UserError "$badValMess in InlineGraphic" yes
set role "LEFT"
}
}
if {$haveDeferredLink} {
set linkID " ID=\"$deferredLink(id)\""
if {$deferredLink(gi) == "ULINK"} {
unset deferredLink
set haveDeferredLink 0
}
} else {
set linkID ""
}
Emit "<HEAD$linkID SSI=\"GRAPHIC-$role\">"
if {$haveDeferredLink} {
EmitDeferredLink
}
Graphic $id $entityref $fileref GRAPHIC
if {$haveDeferredLink} {
EndLink
}
Emit "</HEAD>"
return
} elseif {$remap != ""} {
set badValMess "Bad value (\"$remap\") for Remap attribute"
UserError "$badValMess in InlineGraphic" yes
}
Graphic $id $entityref $fileref INLINEGRAPHIC
}
# start a figure; for now, ignore Role (as it was ignored in HelpTag)
# but make sure Role contains only legal values
proc StartFigure {id role} {
if {$role != ""} {
set uRole [string toupper $role]
switch $uRole {
LEFT -
CENTER -
RIGHT {set i 0}
default {
set badValMess "Bad value for Role (\"$role\") attribute"
UserError "$badValMess in Figure" yes
}
}
}
PushForm "" "FIGURE" $id
}
# emit a CiteTitle in a KEY with the SSI set to the PubWork attr.
proc CiteTitle {id type} {
Emit "<KEY CLASS=\"PUB-LIT\""
if {$id != ""} {
Emit " ID=\"$id\""
}
Emit " SSI=\"$type\">"
}
# start a KEY element - each parameter is optional (i.e, may be "")
proc StartKey {id class ssi} {
Emit "<KEY"
if {$id != ""} {
Emit " ID=\"$id\""
}
if {$class != ""} {
Emit " CLASS=\"$class\""
}
if {$ssi != ""} {
Emit " SSI=\"$ssi\""
}
Emit ">"
}
# start an emphasis with role=heading; want want a different ssi
# so we can make it bold rather than italic for use as a list
# heading
proc StartHeading {id role} {
set role [string toupper $role]
if {$role != "HEADING"} {
if {$role != ""} {
UserWarning "Bad value for Role (!= \"Heading\") in EMPHASIS" yes
}
set ssi EMPHASIS
} else {
set ssi LIST-HEADING
}
StartKey $id EMPH $ssi
}
# start an Example or InformalExample - we need to put ourselves
# in a mode where lines and spacing are significant
proc Example {id} {
global defaultParaType
set defaultParaType " TYPE=\"LITERAL\""
PushForm "" "EXAMPLE" $id
}
# close an Example or InformalExample - put ourselves back in
# the normal (non-literal) mode
proc CloseExample {} {
global defaultParaType
set defaultParaType ""
PopForm
}
# start a Table or InformalTable - save the global attributes and
# open a FORM to hold the table
proc StartTable {id colSep frame label rowSep} {
global tableAttributes
set tableAttributes(colSep) $colSep
set tableAttributes(label) $label
set tableAttributes(rowSep) $rowSep
PushForm TABLE "TABLE-$frame" $id
# create a list of ids of empty blocks to be used to fill in
# undefined table cells
}
# check the "char" attribute - we only support "." at this time;
# return "." if char="." and "" otherwise; issue warning if char
# is some character other than "."
proc CheckChar {char} {
if {($char != "") && ($char != ".")} {
UserError "Only \".\" supported for character alignment" yes
return ""
}
return $char
}
# start a TGROUP - prepare to build a list of column specifications
# and an array of span specifications to be accessed by name; a column
# specification may be numbered, in which case default (all #IMPLIED)
# column specifications will be inserted to come up to the specified
# number - if there are already more column specifications than the
# given number, it's an error; open a FORM to hold the TGroup
proc StartTGroup {id align char cols colSep rowSep nColSpecs} {
global tableGroupAttributes tableAttributes
global tableGroupColSpecs tableGroupSpanSpecs
global numberOfColSpecs colNames haveTFoot
global needTGroupTHeadForm needTFootForm
global tableGroupSavedFRowVec
set numberOfColSpecs $nColSpecs
# do a sanity check on the number of columns, there must be
# at least 1
if {$cols <= 0} {
UserError "Unreasonable number of columns ($cols) in TGroup" yes
set cols 1
}
# check for more COLSPECs than COLS - error if so
if {$nColSpecs > $cols} {
UserError "More ColSpecs defined than columns in the TGroup" yes
}
set tableGroupAttributes(align) $align
set tableGroupAttributes(char) [CheckChar $char]
set tableGroupAttributes(cols) $cols
if {$colSep == ""} {
set tableGroupAttributes(colSep) $tableAttributes(colSep)
} else {
set tableGroupAttributes(colSep) $colSep
}
if {$rowSep == ""} {
set tableGroupAttributes(rowSep) $tableAttributes(rowSep)
} else {
set tableGroupAttributes(rowSep) $rowSep
}
# make sure we have a blank colName array so we don't get errors
# if we try to read or delete it when there have been no named
# ColSpecs in this tableGroup - use a numeric key since that is
# not a NMTOKEN and so can never be a colName - note that all
# colNames share a common name space within each tGroup.
set colNames(0) ""
# create an empty column specification list for this TGroup;
# if no ColSpec definitions at this level, set them all to the
# defaults - take advantage of the fact that the function ColSpec
# will create default column specifications to fill out up to an
# explicitly set ColNum
set tableGroupColSpecs ""
if {$nColSpecs == 0} {
ColSpec "" TGROUP "" "" "" $cols "" "" ""
}
PushForm TABLE TGROUP $id
# set a flag to indicate that we haven't seen a TFoot yet; this
# flag is used in EndRow and StartCell to determine if a Row is
# the last row in this TGroup (the last row will be in the TFoot,
# if present, otherwise it will be in the TBody)
set haveTFoot 0
# initialize variables used to determine if we need separate FORM
# elements for THead or TFoot - if ColSpec elements are not given
# at those levels, they can go in the same FORM as the TBody and
# we can guarantee that the columns will line up
set needTGroupTHeadForm 0
set needTFootForm 0
# and initialize a variable to hold saved FROWVEC elements across
# THead, TBody and TFoot in case we are merging them into one or
# two FORM elements rather than putting each in its own
set tableGroupSavedFRowVec ""
}
# close a table group; delete the info arrays and lists and close the
# FORM
proc EndTGroup {} {
global tableGroupAttributes tableGroupColSpecs tableGroupSpanSpecs
global haveTFoot
unset tableGroupAttributes
unset tableGroupColSpecs
if {[info exists tableGroupSpanSpecs]} {
unset tableGroupSpanSpecs
}
PopForm
# see the explanation for this variable under StartTGroup
unset haveTFoot
}
# process one of a series of column specifications - use the parent GI
# to determine which column specifications we're dealing with; fill up
# to the specified column number with default COLSPECs, using the
# TGROUP, THEAD or TFOOT values as defaults; any COLSPEC values not
# specified in the parameter list should also be defaulted
proc ColSpec {grandparent parent align char colName colNum
colSep colWidth rowSep} {
# the number of currently defined colSpecs in this context
global numberOfColSpecs
global colNames
# get the proper list of ColSpecs for the current context
if {$grandparent == "ENTRYTBL"} {
set gpName entryTable
} else {
set gpName tableGroup
}
switch $parent {
THEAD { upvar #0 ${gpName}HeadColSpecs colSpecs }
TGROUP { upvar #0 tableGroupColSpecs colSpecs }
TFOOT { upvar #0 tableFootColSpecs colSpecs }
ENTRYTBL { upvar #0 entryTableColSpecs colSpecs }
}
# get the proper number of columns (either from TGroup or EntryTbl);
# a THead could be in either a TGroup or EntryTbl so we need
# to check the grandparent if we aren't at the top level
if {$parent == "TGROUP"} {
upvar #0 tableGroupAttributes attributes
} elseif {$parent == "ENTRYTBL"} {
upvar #0 entryTableAttributes attributes
} elseif {$grandparent == "ENTRYTBL"} {
upvar #0 entryTableAttributes attributes
} else {
upvar #0 tableGroupAttributes attributes
}
set nCols $attributes(cols)
# check for more COLSPECs than COLS - we've already issued an error if so
append colSpecs ""
set currentLength [llength $colSpecs]
if {$currentLength >= $nCols} {
return
}
# create a default ColSpec
set thisColSpec(align) $attributes(align)
set thisColSpec(char) $attributes(char)
set thisColSpec(colName) ""
set thisColSpec(colSep) $attributes(colSep)
set thisColSpec(colWidth) "1*"
set thisColSpec(rowSep) $attributes(rowSep)
# back fill with default COLSPECs if given an explicit COLNUM and
# it's greater than our current position
incr currentLength
if {($colNum != "")} {
if {($colNum < $currentLength)} {
set badValMess1 "Explicit colNum ($colNum) less than current"
set badValMess2 "number of ColSpecs ($currentLength)"
UserError "$badValMess1 $badValMess2" yes
return
} else {
while {$currentLength < $colNum} {
set thisColSpec(colNum) $currentLength
lappend colSpecs [array get thisColSpec]
incr currentLength
}
}
}
set colNum $currentLength
# set this COLSPEC, we've already set the defaults
if {$align != ""} {
set thisColSpec(align) $align
}
if {$char != ""} {
set thisColSpec(char) [CheckChar $char]
}
set thisColSpec(colName) $colName
if {$colName != ""} {
# save name to num mapping for later lookup by Entry
set colNames($colName) $colNum
}
set thisColSpec(colNum) $colNum
if {$colSep != ""} {
set thisColSpec(colSep) $colSep
}
if {$colWidth != ""} {
set thisColSpec(colWidth) $colWidth
}
if {$rowSep != ""} {
set thisColSpec(rowSep) $rowSep
}
if {$colNum == $nCols} {
set thisColSpec(colSep) 0; # ignore COLSEP on last column
}
lappend colSpecs [array get thisColSpec]
# fill out to the number of columns if we've run out of COLSPECs
if {[incr numberOfColSpecs -1] <= 0} {
# restore the default COLSPEC
set thisColSpec(align) $attributes(align)
set thisColSpec(char) $attributes(char)
set thisColSpec(colName) ""
set thisColSpec(colSep) $attributes(colSep)
set thisColSpec(colWidth) "1*"
set thisColSpec(rowSep) $attributes(rowSep)
while {$colNum < $nCols} {
incr colNum
set thisColSpec(colNum) $colNum
if {$colNum == $nCols} {
set thisColSpec(colSep) 0; # ignore on last column
}
lappend colSpecs [array get thisColSpec]
}
}
}
# process a SpanSpec - we can't take defaults yet because the Namest
# and Nameend attributes may refer to ColSpecs that don't get defined
# until a TFoot or THead
proc SpanSpec {parent align char colSep nameEnd nameSt rowSep spanName} {
if {$parent == "TGROUP"} {
upvar #0 tableGroupSpanSpecs spanSpecs
} else {
upvar #0 entryTableSpanSpecs spanSpecs
}
set thisSpanSpec(align) $align
set thisSpanSpec(char) [CheckChar $char]
set thisSpanSpec(colSep) $colSep
set thisSpanSpec(nameEnd) $nameEnd
set thisSpanSpec(nameSt) $nameSt
set thisSpanSpec(rowSep) $rowSep
if {[info exists spanSpecs($spanName)]} {
UserError "duplicate span name \"$spanName\"" yes
return
}
set spanSpecs($spanName) [array get thisSpanSpec]
}
# make a list of empty strings for use as an empty Row
proc MakeEmptyRow {nCols} {
set thisList ""
while {$nCols > 0} {
lappend thisList ""
incr nCols -1
}
return $thisList
}
# given a ColSpec list, compute a COLW= vector for SDL;
# the idea is to assume the page is 9360 units wide - that's
# 6.5 inches in points at approximately 1/72 in. per point,
# subtract all the absolute widths and divide the remnant by
# the number of proportional width values then re-add the absolute
# widths back in to the proper columns; this technique should
# make pages that are exactly 6.5 in. in printing surface look just
# right and then go proportional from there
proc ComputeCOLW {colSpecList} {
set nCols [llength $colSpecList]
# build lists of just the ColWidth specs - one for the proporional
# values and one for the absolutes
set index 0
set totalProps 0
set totalAbs 0
while {$index < $nCols} {
array set thisColSpec [lindex $colSpecList $index]
set colWidth $thisColSpec(colWidth)
set colWidth [string trimleft $colWidth]
set colWidth [string trimright $colWidth]
set colWidth [string tolower $colWidth]
set widths [split $colWidth '+']
set nWidths [llength $widths]
set propWidth 0
set absWidth 0
set wIndex 0
while {$wIndex < $nWidths} {
set thisWidth [lindex $widths $wIndex]
if {[scan $thisWidth "%f%s" val qual] != 2} {
UserError "Malformed ColWidth \"$thisWidth\"" yes
incr wIndex
continue
}
set thisProp 0
set thisAbs 0
switch -exact $qual {
* {set thisProp $val}
pt {set thisAbs [expr "$val * 1 * 20"]}
pi {set thisAbs [expr "$val * 12 * 20"]}
cm {set thisAbs [expr "$val * 28 * 20"]}
mm {set thisAbs [expr "$val * 3 * 20"]}
in {set thisAbs [expr "$val * 72 * 20"]}
}
set propWidth [expr "$propWidth + $thisProp"]
set absWidth [expr "$absWidth + $thisAbs"]
incr wIndex
}
lappend propWidths $propWidth
lappend absWidths $absWidth
set totalProps [expr "$totalProps + $propWidth"]
set totalAbs [expr "$totalAbs + $absWidth"]
incr index
}
if {$totalProps == 0} {
# we need at least some proportionality; assume each cell
# had been set to 1* to distribute evenly
set totalProps $nCols
set index 0
if {[info exists propWidths]} {
unset propWidths
}
while {$index < $nCols} {
lappend propWidths 1
incr index
}
}
set tableWidth 9360
if {$totalAbs > $tableWidth} {
set tableWidth $totalAbs
}
set propAvail [expr "$tableWidth - $totalAbs"]
set oneProp [expr "$propAvail / $totalProps"]
# now we know what a 1* is worth and we know the absolute size
# requests, create a ColWidth by adding the product of the
# proportional times a 1* plus any absolute request; we'll allow
# 20% growth and shrinkage
set index 0
set space ""
while {$index < $nCols} {
set thisAbs [lindex $absWidths $index]
set thisProp [lindex $propWidths $index]
set thisWidth [expr "$thisAbs + ($thisProp * $oneProp)"]
set thisSlop [expr "$thisWidth * 0.2"]
# make thisWidth an integer
set dotIndex [string last "." $thisWidth]
if {$dotIndex == 0} {
set thisWidth 0
} elseif {$dotIndex > 0} {
incr dotIndex -1
set thisWidth [string range $thisWidth 0 $dotIndex]
}
# make thisSlop an integer
set dotIndex [string last "." $thisSlop]
if {$dotIndex == 0} {
set thisSlop 0
} elseif {$dotIndex > 0} {
incr dotIndex -1
set thisSlop [string range $thisSlop 0 $dotIndex]
}
append returnValue "$space$thisWidth,$thisSlop"
set space " "
incr index
}
return $returnValue
}
# given a ColSpec list, compute a COLJ= vector for SDL;
proc ComputeCOLJ {colSpecList} {
set nCols [llength $colSpecList]
set space ""
set index 0
while {$index < $nCols} {
array set thisColSpec [lindex $colSpecList $index]
switch -exact $thisColSpec(align) {
LEFT -
JUSTIFY -
"" { set thisColJ l}
CENTER { set thisColJ c}
RIGHT { set thisColJ r}
CHAR { set thisColJ d}
}
append returnValue "$space$thisColJ"
set space " "
incr index
}
return $returnValue
}
# given a ColSpec, create the COLW= and COLJ= attributes; check the
# list of current TOSS entries to see if one matches - if so, return
# its SSI= else add it and create an SSI= to return
proc CreateOneTOSS {ssi vAlign colSpec} {
global newTOSS nextId
set colW [ComputeCOLW $colSpec]
set colJ [ComputeCOLJ $colSpec]
set names [array names newTOSS]
foreach name $names {
array set thisTOSS $newTOSS($name)
if {[string compare $colW $thisTOSS(colW)]} {
if {[string compare $colJ $thisTOSS(colJ)]} {
if {[string compare $vAlign $thisTOSS(vAlign)]} {
return $name
}
}
}
}
# no matching colW,colJ, add an entry
if {$ssi == ""} {
set ssi HBF-SDL-RESERVED[incr nextId]
}
set thisTOSS(colW) $colW
set thisTOSS(colJ) $colJ
set thisTOSS(vAlign) $vAlign
set newTOSS($ssi) [array get thisTOSS]
return $ssi
}
# save values from TFoot, we'll actually process TFoot after TBody
# but we need to know whether we have a TFoot and whether that TFoot
# has ColSpec elements in order to push/pop a FORM for the TBody if
# so
proc PrepForTFoot {nColSpecs} {
global haveTFoot needTFootForm
set haveTFoot 1
set needTFootForm [expr "$nColSpecs > 0"]
}
# start a table header, footer or body - create a FORM to hold the rows;
# create an empty row to be filled in by the Entry elements - set the
# current row and number of rows to 1
proc StartTHeadTFootTBody {parent gi haveTHead id vAlign nRows nColSpecs} {
global numberOfColSpecs haveTFoot
global needTFootForm
if {$parent == "ENTRYTBL"} {
upvar #0 entryTableRowDope rowDope
upvar #0 needEntryTblTHeadForm needTHeadForm
global entryTableAttributes
set nCols $entryTableAttributes(cols)
set entryTableAttributes(vAlign) $vAlign
set entryTableAttributes(rows) $nRows
} else {
upvar #0 tableGroupRowDope rowDope
upvar #0 needTGroupTHeadForm needTHeadForm
global tableGroupAttributes
set nCols $tableGroupAttributes(cols)
set tableGroupAttributes(vAlign) $vAlign
set tableGroupAttributes(rows) $nRows
}
set numberOfColSpecs $nColSpecs
# get the proper list of ColSpecs for the current context
if {$parent == "ENTRYTBL"} {
set parentName entryTable
} else {
set parentName tableGroup
}
switch $gi {
THEAD {upvar #0 ${parentName}HeadColSpecs colSpecs}
TBODY {upvar #0 ${parentName}ColSpecs colSpecs}
TFOOT {upvar #0 tableFootColSpecs colSpecs }
}
# if no ColSpec definitions at this level, copy the parent's
# ColSpec definition to here
if {$nColSpecs == 0} {
switch $gi {
THEAD {upvar #0 ${parentName}ColSpecs parentColSpecs}
TFOOT {upvar #0 tableGroupColSpecs parentColSpecs}
}
if {$gi != "TBODY"} {
set colSpecs $parentColSpecs
}
}
# if we have ColSpec elements on a THead, we'll need to put it
# in its own FORM; we saved this value for TFoot earlier
# because TFoot precedes TBody in the content model but doesn't
# get processed until after TBody (as EndText: to TGroup)
if {$gi == "THEAD"} {
set needTHeadForm [expr "$nColSpecs > 0"]
}
# determine whether we need to push a new FORM here - we always
# have to push a FORM for a THead, we only push one for TBody
# if THead needed its own or there was no THead and we only push
# one for TFoot if it needs its own
if {!$haveTHead} {
set needTBodyForm 1
} else {
set needTBodyForm $needTHeadForm
}
set doit 0
switch $gi {
THEAD {set doit 1}
TBODY {set doit $needTBodyForm}
TFOOT {set doit $needTFootForm}
}
# and push it, if so
if {$doit} {
set ssi [CreateOneTOSS $id "" $colSpecs]
PushForm TABLE "$ssi" $id
}
set rowDope(nRows) 0
set rowDope(currentRow) 0
}
# end a table header footer or body - delete the global row
# information and close the FORM; also delete the ColSpec info for
# this THead or TFoot (TBody always uses the parent's)
proc EndTHeadTFootTBody {parent gi} {
global numberOfColSpecs needTFootForm haveTFoot
if {$parent == "ENTRYTBL"} {
upvar #0 needEntryTblTHeadForm needTHeadForm
} else {
upvar #0 needTGroupTHeadForm needTHeadForm
}
# determine whether we want to terminate this FORM here - we
# only terminate the THead FORM if it needed its own, we only
# terminate the TBody FORM if the TFoot needs its own or there
# is no TFoot and we always terminate the FORM for TFoot
if {($parent == "ENTRYTBL") || !$haveTFoot} {
set needTBodyForm 1
} else {
set needTBodyForm $needTFootForm
}
set doit 0
switch $gi {
THEAD {set doit $needTHeadForm}
TBODY {set doit $needTBodyForm}
TFOOT {set doit 1}
}
PopTableForm $parent $gi $doit
# blow away the list of ColSpecs for the current context
switch $gi {
THEAD { if {$parent == "ENTRYTBL"} {
global entryTableHeadColSpecs
unset entryTableHeadColSpecs
} else {
global tableGroupHeadColSpecs
unset tableGroupHeadColSpecs
}
}
TFOOT { global tableFootColSpecs
unset tableFootColSpecs
}
}
}
# start a table row - save the attribute values for when we
# actually emit the entries of this row; when we emit the first
# entry we'll emit the ID on the rowSep FORM that we create for each
# Entry and set the ID field to "" so we only emit the ID once
proc StartRow {grandparent parent id rowSep vAlign} {
if {$grandparent == "ENTRYTBL"} {
upvar #0 entryTableRowDope rowDope
global entryTableAttributes
set nCols $entryTableAttributes(cols)
if {$vAlign == ""} {
set vAlign $entryTableAttributes(vAlign)
}
} else {
upvar #0 tableGroupRowDope rowDope
global tableGroupAttributes
set nCols $tableGroupAttributes(cols)
if {$vAlign == ""} {
set vAlign $tableGroupAttributes(vAlign)
}
}
upvar 0 rowDope(currentRow) currentRow
upvar 0 rowDope(nRows) nRows
set rowDope(id) $id
set rowDope(rowSep) $rowSep
set rowDope(vAlign) $vAlign
incr currentRow
if {![info exists rowDope(row$currentRow)]} {
set rowDope(row$currentRow) [MakeEmptyRow $nCols]
incr nRows
}
}
# a debugging procedure
proc DumpRowDope {rowDopeName} {
upvar 1 $rowDopeName rowDope
puts stderr "rowDope:"
set index 0
while {[incr index] <= $rowDope(nRows)} {
puts stderr \
" $index: ([llength $rowDope(row$index)]) $rowDope(row$index)"
}
}
# end a table row
proc EndRow {grandparent parent} {
global emptyCells nextId haveTFoot
# this row could be in a TGroup or an EntryTbl
if {$grandparent == "ENTRYTBL"} {
upvar #0 entryTableRowDope rowDope
global entryTableAttributes
set nCols $entryTableAttributes(cols)
set nRowDefs $entryTableAttributes(rows)
} else {
upvar #0 tableGroupRowDope rowDope
global tableGroupAttributes
set nCols $tableGroupAttributes(cols)
set nRowDefs $tableGroupAttributes(rows)
}
# get the proper list of ColSpecs for the current context
switch $parent {
THEAD { if {$grandparent == "ENTRYTBL"} {
upvar #0 entryTableHeadColSpecs colSpecs
} else {
upvar #0 tableGroupHeadColSpecs colSpecs
}
}
TBODY { if {$grandparent == "ENTRYTBL"} {
upvar #0 entryTableColSpecs colSpecs
} else {
upvar #0 tableGroupColSpecs colSpecs
}
}
TFOOT { upvar #0 tableFootColSpecs colSpecs }
}
# go over the row filing empty cells with an empty FORM containing
# an empty BLOCK. The FORM SSI= is chosen to give a RowSep based
# upon the current ColSpec and rowDope, if we are on the last row
# we want to set the RowSep to 0 unless there were more rows
# created via the MoreRows attribute of Entry or EntryTbl forcing
# the table to be longer than the number of Rows specified in which
# case we want to fill in all those rows too and only force RowSep
# to 0 on the last one; the inner BLOCK SSI= is chosen to give a
# ColSep based upon the current ColSpec and Row definition - if
# the column is the last one in the row, the ColSep is set to 0
set currentRow $rowDope(currentRow)
if {$currentRow == $nRowDefs} {
set moreRows [expr "$rowDope(nRows) - $nRowDefs"]
} else {
set moreRows 0
}
upvar 0 rowDope(row$currentRow) thisRow
upvar 0 rowDope(row[expr "$currentRow - 1"]) prevRow
while {$moreRows >= 0} {
set colIndex 0
while {$colIndex < $nCols} {
set thisCellId [lindex $thisRow $colIndex]
if {$thisCellId == ""} {
array set thisColSpec [lindex $colSpecs $colIndex]
set desiredCell(colSep) $thisColSpec(colSep)
set desiredCell(rowSep) $thisColSpec(rowSep)
if {$rowDope(rowSep) != ""} {
set desiredCell(rowSep) $rowDope(rowSep)
}
if {$colIndex == $nCols} {
set desiredCell(colSep) 0
}
if {($moreRows == 0) && ($currentRow == $nRowDefs)} {
if {($parent == "TFOOT") ||
(($parent == "TBODY") && (!$haveTFoot))} {
set desiredCell(rowSep) 0
}
}
if {$desiredCell(colSep) == ""} {
set desiredCell(colSep) 1
}
if {$desiredCell(rowSep) == ""} {
set desiredCell(rowSep) 1
}
set found 0
foreach id [array names emptyCells] {
array set thisCell $emptyCells($id)
if {$thisCell(colSep) != $desiredCell(colSep)} {
continue
}
if {$thisCell(rowSep) != $desiredCell(rowSep)} {
continue
}
if {$currentRow > 1} {
if {[lindex $prevRow $colIndex] == $id} {
continue
}
}
if {$colIndex > 0} {
if {$lastCellId == $id} {
continue
}
}
set thisCellId $id
set found 1
break
}
if {!$found} {
if {$desiredCell(rowSep)} {
set ssi BORDER-BOTTOM
} else {
set ssi BORDER-NONE
}
set id [PushFormCell $ssi ""]
if {$desiredCell(colSep)} {
set ssi ENTRY-NONE-YES-NONE
} else {
set ssi ENTRY-NONE-NO-NONE
}
StartBlock CELL $ssi "" 1
PopForm
set emptyCells($id) [array get desiredCell]
set thisCellId $id
}
Replace thisRow $colIndex 1 $thisCellId
}
set lastCellId $thisCellId
incr colIndex
}
incr moreRows -1
incr currentRow 1
upvar 0 thisRow prevRow
upvar 0 rowDope(row$currentRow) thisRow
}
# blow away the variables that get reset on each row
unset rowDope(id)
unset rowDope(rowSep)
unset rowDope(vAlign)
}
# given a row list, an id and start and stop columns, replace the
# entries in the list from start to stop with id - use "upvar" on
# the row list so we actually update the caller's row
proc Replace {callersRow start length id} {
upvar $callersRow row
# length will be 0 if there was an error on the row
if {$length <= 0} {
return
}
# make a list of ids long enough to fill the gap
set i 1
set ids $id; # we pad all the others with a starting space
while {$i < $length} {
append ids " " $id
incr i
}
# do the list replacement - need to "eval" because we want the
# ids to be seen a individual args, not a list so we need to
# evaluate the command twice
set stop [expr "$start + $length - 1"]
set command "set row \[lreplace \$row $start $stop $ids\]"
eval $command
}
# process a table cell (Entry or EntryTbl); attributes are inherited
# in the following fashion:
#
# ColSpec
# SpanSpec
# Row
# Entry/EntryTbl
#
# with later values (going down the list) overriding earlier ones;
# Table, TGroup, etc., values have already been propagated to the
# ColSpecs
proc StartCell {ancestor grandparent gi id align char colName cols
colSep moreRows nameEnd nameSt rowSep spanName
vAlign nColSpecs nTBodies} {
global colNames tableGroupAttributes entryTableAttributes
global numberOfColSpecs entryTableColSpecs nextId haveTFoot
global needEntryTblTHeadForm entryTableSavedFRowVec
# get the appropriate SpanSpec list, if any; also get the row
# row dope vector which also contains the current row number
# and number of rows currently allocated (we might get ahead
# of ourselves due to a vertical span via MOREROWS=)
if {$ancestor == "TGROUP"} {
upvar #0 tableGroupSpanSpecs spanSpecs
upvar #0 tableGroupRowDope rowDope
set nCols $tableGroupAttributes(cols)
set nRowDefs $tableGroupAttributes(rows)
} else {
upvar #0 entryTableSpanSpecs spanSpecs
upvar #0 entryTableRowDope rowDope
set nCols $entryTableAttributes(cols)
set nRowDefs $entryTableAttributes(rows)
}
# get the proper list of ColSpecs for the current context
switch $grandparent {
THEAD { if {$ancestor == "ENTRYTBL"} {
upvar #0 entryTableHeadColSpecs colSpecs
} else {
upvar #0 tableGroupHeadColSpecs colSpecs
}
}
TBODY { if {$ancestor == "ENTRYTBL"} {
upvar #0 entryTableColSpecs colSpecs
} else {
upvar #0 tableGroupColSpecs colSpecs
}
}
TFOOT { upvar #0 tableFootColSpecs colSpecs }
}
# check for a span
if {$spanName != ""} {
if {[info exists spanSpecs($spanName)]} {
array set thisSpan $spanSpecs($spanName)
# SpanSpec column names win over explicit ones
set nameSt $thisSpan(nameSt)
set nameEnd $thisSpan(nameEnd)
} else {
UserError "Attempt to use undefined SpanSpec \"$spanName\"" yes
}
}
# nameSt, whether explicit or from a span, wins over colName
if {$nameSt != ""} {
set colName $nameSt
}
# get the row information - use upvar so we can update rowDope
upvar 0 rowDope(currentRow) currentRow
upvar 0 rowDope(row$currentRow) thisRow
upvar 0 rowDope(nRows) nRows
# by now, if no colName we must have neither colName, nameSt nor
# a horizontal span - find the next open spot in this row
if {$colName != ""} {
if {[info exists colNames($colName)]} {
set startColNum $colNames($colName)
if {$startColNum > $nCols} {
UserError "Attempt to address column outside of table" yes
set colName ""
} else {
incr startColNum -1 ;# make the column number 0 based
}
} else {
UserError "Attempt to use undefined column name \"$colName\"" yes
set colName ""
}
}
if {$colName == ""} {
set index 0
while {[lindex $thisRow $index] != ""} {
incr index
}
if {$index == $nCols} {
UserError "More entries defined than columns in this row" yes
set index -1
}
set startColNum $index
}
# if we have a nameEnd, it was either explicit or via a span -
# get the stop column number; else set the stop column to the
# start column, i.e., a span of 1
if {$nameEnd == ""} {
set stopColNum $startColNum
} else {
if {[info exists colNames($nameEnd)]} {
set stopColNum $colNames($nameEnd)
if {$stopColNum > $nCols} {
UserError "Attempt to address column outside of table" yes
set stopColNum $nCols
}
incr stopColNum -1 ;# make the column number 0 based
if {$startColNum > $stopColNum} {
UserError "End of column span is before the start" yes
set stopColNum $startColNum
}
} else {
UserError "Attempt to use undefined column name \"$nameEnd\"" yes
set stopColNum $startColNum
}
}
# create an empty set of attributes for the cell - we'll fill
# them in from the ColSpec, SpanSpec, Row and Entry or EntryTbl
# defined values, if any, in that order
set cellAlign ""
set cellColSep 1
set cellRowSep 1
set cellVAlign ""
# initialize the cell description with the ColSpec data
# Table, TGroup and EntryTable attributes have already
# percolated to the ColSpec
if {$startColNum >= 0} {
array set thisColSpec [lindex $colSpecs $startColNum]
if {$thisColSpec(colSep) != ""} {
set cellColSep $thisColSpec(colSep)
}
if {$thisColSpec(rowSep) != ""} {
set cellRowSep $thisColSpec(rowSep)
}
}
# overlay any attributes defined on the span, that is, SpanSpec
# attributes win over ColSpec ones
if {[info exists thisSpan]} {
if {$thisSpan(align) != ""} {
set cellAlign $thisSpan(align)
}
if {$thisSpan(colSep) != ""} {
set cellColSep $thisSpan(colSep)
}
if {$thisSpan(rowSep) != ""} {
set cellRowSep $thisSpan(rowSep)
}
}
# overlay any attributes defined on the Row
if {$rowDope(rowSep) != ""} {
set cellRowSep $rowDope(rowSep)
}
if {$rowDope(vAlign) != ""} {
set cellVAlign $rowDope(vAlign)
}
# check for a char other than "" or "."; just a check, we don't
# do anything with char
set char [CheckChar $char]
# overlay any attributes defined on the Entry or EntryTbl - these
# win over all
if {$align != ""} {
set cellAlign $align
}
if {$colSep != ""} {
set cellColSep $colSep
}
if {$rowSep != ""} {
set cellRowSep $rowSep
}
if {$vAlign != ""} {
set cellVAlign $vAlign
}
# if this cell is the first on the row, feed it the (possible)
# Row ID and set the Row ID to ""
if {[set cellId $rowDope(id)] == ""} {
set cellId SDL-RESERVED[incr nextId]
} else {
set rowDope(id) ""
}
# now put the cell into the rowDope vector - if there's a
# span, we'll put the cell in several slots; if there's a
# vertical straddle, we may need to add more rows to rowDope
if {$startColNum >= 0} {
set stopRowNum [expr "$currentRow + $moreRows"]
set spanLength [expr "($stopColNum - $startColNum) + 1"]
set rowIndex $currentRow
while {$rowIndex <= $stopRowNum} {
if {![info exists rowDope(row$rowIndex)]} {
set rowDope(row$rowIndex) [MakeEmptyRow $nCols]
incr nRows
}
upvar 0 rowDope(row$rowIndex) thisRow
set colIndex $startColNum
while {$colIndex <= $stopColNum} {
if {[lindex $thisRow $colIndex] != ""} {
set badValMess1 "Multiple definitions for column"
set badValMess2 "of row $rowIndex"
UserError \
"$badValMess1 [expr $colIndex + 1] $badValMess2" yes
set stopColNum 0
set stopRowNum 0
set spanLength 0
}
incr colIndex
}
Replace thisRow $startColNum $spanLength $cellId
incr rowIndex
}
}
# on the last column, the column separator should be 0; on the
# last row, the row separator should be 0 - the table frame will
# set the border on the right and bottom sides
if {$stopColNum == $nCols} {
set cellColSep 0
}
if {$currentRow == $nRowDefs} {
if {($grandparent == "TFOOT") ||
(($grandparent == "TBODY") && (!$haveTFoot))} {
set cellRowSep 0
}
}
# push a form to hold the RowSep
if {$cellRowSep == 1} {
set ssi "BORDER-BOTTOM"
} else {
set ssi "BORDER-NONE"
}
PushFormCell $ssi $cellId
# build the SSI= for the cell and push a form to hold it
if {$gi == "ENTRY"} {
set ssi "ENTRY-"
} else {
set ssi "ENTRYTBL-"
}
switch $cellAlign {
"" { append ssi "NONE-" }
LEFT { append ssi "LEFT-" }
RIGHT { append ssi "RIGHT-" }
CENTER { append ssi "CENTER-" }
JUSTIFY { append ssi "LEFT-" }
CHAR { append ssi "CHAR-" }
}
switch $cellColSep {
0 { append ssi "NO-" }
1 { append ssi "YES-" }
}
switch $cellVAlign {
"" -
NONE { append ssi "NONE" }
TOP { append ssi "TOP" }
MIDDLE { append ssi "MIDDLE" }
BOTTOM { append ssi "BOTTOM" }
}
PushForm CELL $ssi $id
# if we are in an Entry, open a paragraph in case all that's in
# the Entry are inline objects - this may end up in an empty P
# if the Entry contains paragraph level things, e.g., admonitions,
# lists or paragraphs; if we are an EntryTbl, set up the defaults
# for the recursive calls to, e.g., THead or TBody
if {$gi == "ENTRY"} {
StartParagraph "" "" ""
} else {
# the syntax would allow multiple TBODY in an ENTRYTBL but
# we (and the rest of the SGML community, e.g., SGML/Open)
# don't allow more than one - the transpec will keep us from
# seeing the extras but we need to flag the error to the user
if {$nTBodies != 1} {
UserError "More than one TBODY in an ENTRYTBL" yes
}
set entryTableAttributes(align) $align
set entryTableAttributes(char) [CheckChar $char]
# do a sanity check on the number of columns, there must be
# at least 1
if {$cols <= 0} {
UserError "Unreasonable number of columns ($cols) in EntryTbl" yes
set cols 1
}
set entryTableAttributes(cols) $cols
if {$colSep == ""} {
set entryTableAttributes(colSep) 1
} else {
set entryTableAttributes(colSep) $colSep
}
if {$rowSep == ""} {
set entryTableAttributes(rowSep) 1
} else {
set entryTableAttributes(rowSep) $rowSep
}
# check for more COLSPECs than COLS - error if so
if {$nColSpecs > $cols} {
UserError \
"More ColSpecs defined than columns in an EntryTbl" yes
}
set numberOfColSpecs $nColSpecs
set entryTableColSpecs ""
# if no ColSpec definitions at this level, set them all to the
# defaults - take advantage of the fact that the function ColSpec
# will create default column specifications to fill out up to an
# explicitly set ColNum
if {$nColSpecs == 0} {
ColSpec "" ENTRYTBL "" "" "" $cols "" "" ""
}
# initialize a variable used to determine if we need a separate
# FORM element for THead - if ColSpec elements are not given
# at that level, it can go in the same FORM as the TBody and
# we can guarantee that the columns will line up
set needEntryTblTHeadForm 0
# and initialize a variable to hold saved FROWVEC elements
# across THead into TBody in case we are merging them into
# one FORM element rather than putting each in its own
set entryTableSavedFRowVec ""
}
}
# end a table Entry - pop the form holding the cell
# attributes and the form holding the RowSep
proc EndEntry {} {
PopForm
PopForm
}
# end a table EntryTbl - pop the form holding the cell
# attributes and the form holding the RowSep and clean up the
# global variables
proc EndEntryTbl {} {
global entryTableSpanSpecs numberOfColSpecs entryTableColSpecs
PopForm
PopForm
if {[info exists entryTableSpanSpecs]} {
unset entryTableSpanSpecs
}
unset entryTableColSpecs
}
######################################################################
######################################################################
#
# RefEntry
#
######################################################################
######################################################################
# change the OutputString routine into one that will save the content
# of this element for use as the man-page title, e.g., the "cat"
# in "cat(1)"; this name may be overridden by RefDescriptor in
# RefNameDiv if the sort name is different (e.g., "memory" for
# "malloc")
proc DivertOutputToManTitle {} {
rename OutputString SaveManTitleOutputString
rename ManTitleOutputString OutputString
}
# change the output stream back to the OutputString in effect at the
# time of the call to DivertOutputToManTitle
proc RestoreOutputStreamFromManTitle {} {
rename OutputString ManTitleOutputString
rename SaveManTitleOutputString OutputString
}
# a routine to buffer the output into the string "manTitle" for later
# use in the top corners of man-pages
proc ManTitleOutputString {string} {
global manTitle
append manTitle $string
}
# change the OutputString routine into one that will save the content
# of this element for use as the man-page volume number, e.g., the "1"
# in "cat(1)"
proc DivertOutputToManVolNum {} {
rename OutputString SaveManVolNumOutputString
rename ManVolNumOutputString OutputString
}
# change the output stream back to the OutputString in effect at the
# time of the call to DivertOutputToManVolNum
proc RestoreOutputStreamFromManVolNum {} {
rename OutputString ManVolNumOutputString
rename SaveManVolNumOutputString OutputString
}
# a routine to buffer the output into the string "manVolNum" for later
# use in the top corners of man-pages
proc ManVolNumOutputString {string} {
global manVolNum
append manVolNum $string
}
# start a reference name division; nothing to emit now, just save
# the number of names defined in this division and initialize the
# current name count to 1
proc StartRefNameDiv {nNames} {
global numManNames currentManName
set numManNames $nNames
set currentManName 1
}
# end a reference name division; we can now emit the HEAD elements to
# create the titles in the upper corners and the "NAME" section of the
# man-page
proc EndRefNameDiv {id} {
global manTitle manVolNum manDescriptor manNames manPurpose
global localizedAutoGeneratedStringArray
set manPageName $manTitle
if {$manDescriptor != ""} {
set manPageName $manDescriptor
}
# emit the titles in the upper left and right corners
Emit "<HEAD TYPE=\"LITERAL\" SSI=\"MAN-PAGE-TITLE-LEFT\">"
Emit "${manPageName}($manVolNum)"
Emit "</HEAD>\n"
Emit "<HEAD TYPE=\"LITERAL\" SSI=\"MAN-PAGE-TITLE-RIGHT\">"
Emit "${manPageName}($manVolNum)"
Emit "</HEAD>\n"
# and the NAME section
PushForm "" "" $id
Emit "<HEAD TYPE=\"LITERAL\" SSI=\"MAN-PAGE-DIVISION-NAME\">"
set message "NAME"
Emit $localizedAutoGeneratedStringArray($message)
Emit "</HEAD>\n"
StartBlock "" "MAN-PAGE-DIVISION" "" 1
StartParagraph "" "" ""
Emit "$manNames - $manPurpose"
PopForm
}
# change the OutputString routine into one that will save the content
# of this element for use as the man-page descriptor, e.g., the
# "string" in "string(3C)"
proc DivertOutputToManDescriptor {} {
rename OutputString SaveManDescriptorOutputString
rename ManDescriptorOutputString OutputString
}
# change the output stream back to the OutputString in effect at the
# time of the call to DivertOutputToManDescriptor
proc RestoreOutputStreamFromManDescriptor {} {
rename OutputString ManDescriptorOutputString
rename SaveManDescriptorOutputString OutputString
}
# a routine to buffer the output into the string "manDescriptor" for
# later use in the top corners of man-pages
proc ManDescriptorOutputString {string} {
global manDescriptor
append manDescriptor $string
}
# change the OutputString routine into one that will save the content
# of this element for use as the man-page command or function name,
# e.g., the "cat" in "cat(1)"
proc DivertOutputToManNames {} {
rename OutputString SaveManNamesOutputString
rename ManNamesOutputString OutputString
}
# change the output stream back to the OutputString in effect at the
# time of the call to DivertOutputToManNames
proc RestoreOutputStreamFromManNames {} {
rename OutputString ManNamesOutputString
rename SaveManNamesOutputString OutputString
}
# a routine to buffer the output into the string "manNames" for
# later use in the top corners of man-pages
proc ManNamesOutputString {string} {
global manNames
append manNames $string
}
# collect RefName elements into a single string; start diversion to
# the string on the first man name
proc StartAManName {} {
global numManNames currentManName
if {$currentManName == 1} {
DivertOutputToManNames
}
}
# end diversion on the last man name; append "(), " to each name but
# the last to which we only append "()"
proc EndAManName {} {
global numManNames currentManName manDescriptor manNames
if {($currentManName == 1) && ($manDescriptor == "")} {
set manDescriptor $manNames
}
if {$currentManName < $numManNames} {
Emit ", "
} elseif {$currentManName == $numManNames} {
RestoreOutputStreamFromManNames
}
incr currentManName
}
# change the OutputString routine into one that will save the content
# of this element for use as the man-page purpose; this string will
# follow the function or command name(s) separated by a "-"
proc DivertOutputToManPurpose {} {
rename OutputString SaveManPurposeOutputString
rename ManPurposeOutputString OutputString
}
# change the output stream back to the OutputString in effect at the
# time of the call to DivertOutputToManPurpose
proc RestoreOutputStreamFromManPurpose {} {
rename OutputString ManPurposeOutputString
rename SaveManPurposeOutputString OutputString
}
# a routine to buffer the output into the string "manPurpose" for
# later use in the NAME section of man-pages
proc ManPurposeOutputString {string} {
global manPurpose
append manPurpose $string
}
# start a reference synopsis division - create a FORM to hold the
# division and, potentially, any RefSect2-3; if there is a Title on
# RefSynopsisDiv, use it, else default to "SYNOPSIS"
proc StartRefSynopsisDiv {id haveTitle nSynopses} {
global remainingSynopses
global localizedAutoGeneratedStringArray
set remainingSynopses $nSynopses
PushForm "" "" $id
if {!$haveTitle} {
StartManPageDivisionTitle ""
set message "SYNOPSIS"
Emit $localizedAutoGeneratedStringArray($message)
EndManPageDivisionTitle
}
}
# the user provided a title for this section, use it
proc StartManPageDivisionTitle {id} {
if {$id != ""} {
set id " ID=\"$id\""
}
Emit "<HEAD$id TYPE=\"LITERAL\" SSI=\"MAN-PAGE-DIVISION-NAME\">"
}
# the user provided a title for this section, we need to open a form
# to hold the section now
proc EndManPageDivisionTitle {} {
Emit "</HEAD>\n"
PushForm "" "MAN-PAGE-DIVISION" ""
}
# begin a Synopsis - if this is the first of any of the synopses, emit
# a FORM to hold them all
proc StartSynopsis {id linespecific} {
if {$linespecific == ""} {
set type LINED
} else {
set type ""
}
StartParagraph id "" $type
}
# end any of Synopsis, CmdSynopsis or FuncSynopsis - close out the
# form if it's the last one
proc EndSynopses {parent} {
global remainingSynopses
Emit "\n"
if {($parent == "REFSYNOPSISDIV") && ([incr remainingSynopses -1] == 0)} {
PopForm
}
}
# begin a CmdSynopsis
proc StartCmdSynopsis {id} {
StartParagraph id "" ""
}
# start a man-page argument - surround the arg in a KEY element
proc StartArg {id choice separator} {
# mark this spot if there's a user supplied ID
Anchor $id
# emit nothing at start of list, v-bar inside of Group else space
Emit $separator
Emit "<KEY CLASS=\"NAME\" SSI=\"MAN-PAGE-ARG\">"
if {$choice == "OPT"} {
Emit "\["
} elseif {$choice == "REQ"} {
Emit "\{"
}
}
# end a man-page argument - if choice is not "plain", emit the proper
# close character for the choice; if repeat is "repeat", emit an
# ellipsis after the arg
proc EndArg {choice repeat} {
if {$choice == "OPT"} {
Emit "\]"
} elseif {$choice == "REQ"} {
Emit "\}"
}
if {$repeat == "REPEAT"} {
Emit "<SPC NAME=\"\[hellip\]\">"
}
Emit "</KEY>"
}
# start an argument, filename, etc., group in a man-page command
# synopsis
proc StartGroup {id choice separator} {
# mark this spot if there's a user supplied ID
Anchor $id
# emit nothing at start of list, v-bar inside of Group else space
Emit $separator
# clean up optmult/reqmult since, for example, req+repeat == reqmult,
# optmult and reqmult are redundant
if {$choice == "OPTMULT"} {
set choice OPT
} elseif {$choice == "REQMULT"} {
set choice REQ
}
if {$choice == "OPT"} {
Emit "\["
} elseif {$choice == "REQ"} {
Emit "\{"
}
}
# end an argument, filename, etc., group in a man-page command
# synopsis
proc EndGroup {choice repeat} {
# clean up optmult/reqmult since, for example, req+repeat == reqmult,
# optmult and reqmult are redundant
if {$choice == "OPTMULT"} {
set choice OPT
set repeat REPEAT
} elseif {$choice == "REQMULT"} {
set choice "REQ"
set repeat REPEAT
}
if {$choice == "OPT"} {
Emit "\]"
} elseif {$choice == "REQ"} {
Emit "\}"
}
if {$repeat == "REPEAT"} {
Emit "<SPC NAME=\"\[hellip\]\">"
}
}
# start a command name in a man-page command synopsis
proc StartCommand {id separator} {
# mark this spot if there's a user supplied ID
Anchor $id
# emit nothing at start of synopsis else space
Emit $separator
Emit "<KEY CLASS=\"NAME\" SSI=\"MAN-PAGE-COMMAND\">"
}
# begin a FuncSynopsis
proc StartFuncSynopsis {id} {
}
# check that the GI of the element pointed to by a SynopFragmentRef
# is really a SynopFragment
proc CheckSynopFragmentRef {gi id} {
if {$gi != "SYNOPFRAGMENT"} {
set badValMess1 "SynopFragmentRef LinkEnd=$id"
set badValMess2 "must refer to a SynopFragment"
UserError "$badValMess1 $badValMess2" yes
}
}
# begin a FuncSynopsisInfo - emit a P to hold it
proc StartFuncSynopsisInfo {id linespecific} {
if {$linespecific == "LINESPECIFIC"} {
set type " TYPE=\"LINED\""
} else {
set type ""
}
StartParagraph $id "FUNCSYNOPSISINFO" $type
}
# begin a FuncDef - emit a P to hold it
proc StartFuncDef {id} {
StartParagraph $id "FUNCDEF" ""
}
# end a FuncDef, emit the open paren in preparation for the args
proc EndFuncDef {} {
Emit "("
}
# handle Void or Varargs in a FuncSynopsis - wrap it in a KEY and
# emit the string "VOID" or "VARARGS"
proc DoVoidOrVarargs {gi id} {
# mark this spot if there's a user supplied ID
Anchor $id
Emit "<KEY CLASS=\"NAME\" SSI=\"FUNCDEF-ARGS\">"
Emit $gi
Emit "</KEY>"
Emit ")"
}
# start a ParamDef - just emit an anchor, if needed, for now
proc StartParamDef {id} {
# mark this spot if there's a user supplied ID
Anchor $id
}
# end of a ParamDef - emit either the ", " for the next one or, if the
# last, emit the closing ")"
proc EndParamDef {separator} {
Emit $separator
}
# start a FuncParams - just emit an anchor, if needed, for now
proc StartFuncParams {id} {
# mark this spot if there's a user supplied ID
Anchor $id
}
# end of a FuncParams - emit either the ", " for the next one or, if the
# last, emit the closing ")"
proc EndFuncParams {separator} {
Emit $separator
}
######################################################################
######################################################################
#
# links
#
######################################################################
######################################################################
# open an intradocument link
proc StartLink {id linkend type} {
StartParagraphMaybe "" "P" $id
Emit "<LINK RID=\"$linkend\""
if {$type != ""} {
set type [string toupper $type]
switch $type {
JUMPNEWVIEW {Emit " WINDOW=\"NEW\""}
DEFINITION {Emit " WINDOW=\"POPUP\""}
}
}
Emit ">"
Anchor $id
}
# defer a Link at the start of a Para until we see if the following
# InlineGraphic has Role=graphic and we want it in a HEAD
proc DeferLink {id linkend type} {
global deferredLink
set deferredLink(gi) LINK
set deferredLink(id) $id
set deferredLink(linkend) $linkend
set deferredLink(type) $type
}
# open an interdocument link; this link will require an SNB entry
proc StartOLink {id localInfo type} {
StartParagraphMaybe "" "P" $id
set type [string toupper $type]
set linkType CURRENT
switch $type {
JUMP {set linkType CURRENT}
JUMPNEWVIEW {set linkType NEW}
MAN -
DEFINITION {set linkType POPUP}
}
set snbType CROSSDOC
switch $type {
EXECUTE {set snbType SYS-CMD}
APP-DEFINED {set snbType CALLBACK}
MAN {set snbType MAN-PAGE}
}
set snbId [AddToSNB $snbType $localInfo]
Emit "<LINK RID=\"$snbId\""
if {$linkType != "CURRENT"} {
Emit " WINDOW=\"$linkType\""
}
Emit ">"
}
# defer an OLink at the start of a Para until we see if the following
# InlineGraphic has Role=graphic and we want it in a HEAD
proc DeferOLink {id localInfo type} {
global deferredLink
set deferredLink(gi) OLINK
set deferredLink(id) $id
set deferredLink(localinfo) $localinfo
set deferredLink(type) $type
}
# defer a ULink at the start of a Para until we see if the following
# InlineGraphic has Role=graphic and we want it in a HEAD
proc DeferULink {id} {
global deferredLink
set deferredLink(gi) ULINK
set deferredLink(id) $id
}
# close a link
proc EndLink {} {
Emit "</LINK>"
}
######################################################################
######################################################################
#
# character formatting
#
######################################################################
######################################################################
# open a Quote; we'll emit two open single quotes wrapped in a
# key with a style that will put them in a proportional font so they
# fit together and look like an open double quote
proc StartQuote {id} {
Emit "<KEY CLASS=\"QUOTE\" SSI=\"PROPORTIONAL\">"
Anchor $id
Emit "``</KEY>"
}
# close a Quote; we'll emit two close single quotes wrapped in a
# key with a style that will put them in a proportional font so they
# fit together and look like a close double quote
proc EndQuote {} {
Emit "<KEY CLASS=\"QUOTE\" SSI=\"PROPORTIONAL\">''</KEY>"
}
######################################################################
######################################################################
#
# end of document stuff
#
######################################################################
######################################################################
# write out the .snb file - first update the file location for
# insertion of the SNB by the second pass to reflect the addition
# of the INDEX; also incorporate the INDEX and update the TOSS to
# reflect any additions necessary to support tables
proc WriteSNB {} {
global savedSNB indexLocation tossLocation baseName
# get a handle for the index file and the existing .sdl file;
# prepare to write the updated .sdl file and the .snb file by
# blowing away the current names so the second open of the .sdl
# file is creating a new file and we don't have leftover .snb
# or .idx files laying around
close stdout
set sdlInFile [open "${baseName}.sdl" r]
set sdlSize [file size "${baseName}.sdl"]
#
set idxFile [open "${baseName}.idx" r]
set idxSize [file size "${baseName}.idx"]
#
exec rm -f ${baseName}.sdl ${baseName}.idx ${baseName}.snb
set sdlOutFile [open "${baseName}.sdl" w]
# create any additional TOSS entries made necessary by COLW and
# COLJ settings for TGroup or EntryTbl elements.
set toss [CreateTableTOSS]
set tossSize [string length $toss]
# get a list of the byte offsets into the .sdl file for the
# .snb entries
set snbLocations [lsort -integer [array names savedSNB]]
# and write out the .snb file updating the locations as we go
if {[llength $snbLocations] > 0} {
set snbFile [open "${baseName}.snb" w]
foreach location $snbLocations {
puts $snbFile [expr "$location + $idxSize + $tossSize"]
puts -nonewline $snbFile $savedSNB($location)
}
close $snbFile
}
# now update the toss and include the index file into the sdl file
# by copying the old .sdl file to the new up to the location of
# the first FORMSTYLE in the TOSS and emitting the new TOSS
# entries then continue copying the old .sdl file up to the index
# location and copying the .idx file to the new .sdl file followed
# by the rest of the old .sdl file (the old .sdl and .idx files
# have already been deleted from the directory), finally, close
# the output file
#
# 1: copy the sdl file up to the first FORMSTYLE element or, if
# none, to just after the open tag for the TOSS
set location $tossLocation
set readSize 1024
while {$location > 0} {
if {$location < $readSize} { set readSize $location }
puts -nonewline $sdlOutFile [read $sdlInFile $readSize]
incr location -$readSize
}
# 2: emit the TOSS updates, if any
puts -nonewline $sdlOutFile $toss
# 3: copy the sdl file up to the index location
set location [expr "$indexLocation - $tossLocation"]
set readSize 1024
while {$location > 0} {
if {$location < $readSize} { set readSize $location }
puts -nonewline $sdlOutFile [read $sdlInFile $readSize]
incr location -$readSize
}
# 4: copy over the index file
set location $idxSize
set readSize 1024
while {$location > 0} {
if {$location < $readSize} { set readSize $location }
puts -nonewline $sdlOutFile [read $idxFile $readSize]
incr location -$readSize
}
# 5: and copy over the rest of the sdl file
set location [expr "$sdlSize - $indexLocation"]
set readSize 1024
while {$location > 0} {
if {$location < $readSize} { set readSize $location }
puts -nonewline $sdlOutFile [read $sdlInFile $readSize]
incr location -$readSize
}
# 6: close the output
close $sdlOutFile
}
# read the global variable newTOSS and use the information to create
# TOSS entries for THead, TBody and TFoot; these entries will contain
# the justification and width information for the table sub-components;
# return the new TOSS elements
proc CreateTableTOSS {} {
global newTOSS
set returnValue ""
foreach ssi [array names newTOSS] {
array set thisTOSSdata $newTOSS($ssi)
set vAlign $thisTOSSdata(vAlign)
switch $vAlign {
NONE -
"" { set vJust "" }
TOP { set vJust "TOP" }
MIDDLE { set vJust "CENTER" }
BOTTOM { set vJust "BOTTOM" }
}
append returnValue "<FORMSTYLE\n"
append returnValue " CLASS=\"TABLE\"\n"
append returnValue " SSI=\"$ssi\"\n"
append returnValue \
" PHRASE=\"TGroup, THead or TBody specification\"\n"
append returnValue " COLW=\"$thisTOSSdata(colW)\"\n"
append returnValue " COLJ=\"$thisTOSSdata(colJ)\"\n"
if {$vJust != ""} {
append returnValue " VJUST=\"${vJust}-VJUST\"\n"
}
append returnValue ">\n"
}
return $returnValue
}
# try to open a file named docbook.tss either in our current
# directory or on TOSS_PATH - if it exists, copy it to
# the output file as the TOSS - when the first line containing
# "<FORMSTYLE" is seen, save the location so we can include the
# updates to the TOSS necessary due to needing FORMSTYLE entries for
# tables with the appropriate COLJ and COLW values
proc IncludeTOSS {} {
global tossLocation TOSS_PATH
set tossLocation -1
set foundToss 0
# look for docbook.tss in the current directory first, then on the path
set path ". [split $TOSS_PATH :]"
foreach dir $path {
set tssFileName $dir/docbook.tss
if {[file exists $tssFileName]} {
set foundToss 1
break;
}
}
if {$foundToss} {
if {[file readable $tssFileName]} {
set tssFile [open $tssFileName r]
set eof [gets $tssFile line]
while {$eof != -1} {
if {[string match "*<FORMSTYLE*" [string toupper $line]]} {
set tossLocation [tell stdout]
}
puts $line
set eof [gets $tssFile line]
}
close $tssFile
} else {
UserError "$tssFileName exists but is not readable" no
}
} else {
UserWarning "Could not find docbook.tss - continuing with null TOSS" no
}
if {$tossLocation == -1} {
set tossLocation [tell stdout]
}
}
proc GetLocalizedAutoGeneratedStringArray {filename} {
global localizedAutoGeneratedStringArray
set buffer [ReadLocaleStrings $filename]
set regExp {^(".*")[ ]*(".*")$} ;# look for 2 quoted strings
set stringList [split $buffer \n]
set listLength [llength $stringList]
set index 0
while {$listLength > 0} {
set line [lindex $stringList $index]
set line [string trim $line]
if {([string length $line] > 0) && ([string index $line 0] != "#")} {
if {[regexp $regExp $line match match1 match2]} {
set match1 [string trim $match1 \"]
set match2 [string trim $match2 \"]
set localizedAutoGeneratedStringArray($match1) $match2
} else {
UserError \
"Malformed line in $filename line [expr $index + 1]" no
}
}
incr index
incr listLength -1
}
set message "Home Topic"
if {![info exists localizedAutoGeneratedStringArray($message)]} {
set localizedAutoGeneratedStringArray($message) $message
}
set message "No home topic (PartIntro) was specified by the author."
if {![info exists localizedAutoGeneratedStringArray($message)]} {
set localizedAutoGeneratedStringArray($message) $message
}
set message "See"
if {![info exists localizedAutoGeneratedStringArray($message)]} {
set localizedAutoGeneratedStringArray($message) $message
}
set message "See Also"
if {![info exists localizedAutoGeneratedStringArray($message)]} {
set localizedAutoGeneratedStringArray($message) $message
}
set message "NAME"
if {![info exists localizedAutoGeneratedStringArray($message)]} {
set localizedAutoGeneratedStringArray($message) $message
}
set message "SYNOPSIS"
if {![info exists localizedAutoGeneratedStringArray($message)]} {
set localizedAutoGeneratedStringArray($message) $message
}
}
# start - initialize variables and write the preamble
proc OpenDocument {host base date} {
global docId baseName indexLocation snbLocation
global validMarkArray partIntroId nextId
global NO_UNIQUE_ID LOCALE_STRING_DIR
global language charset
# NO_UNIQUE_ID will be set to YES for test purposes so we don't
# get spurious mismatches from the timestamp of from the system on
# which the document was processed.
if {[string toupper $NO_UNIQUE_ID] == "YES"} {
set docId TEST
set timeStamp 0
} else {
set docId $host
set timeStamp $date
}
GetLocalizedAutoGeneratedStringArray ${LOCALE_STRING_DIR}/strings
# split out the language and charset info from LOCALE_STRING_DIR
# first, remove any directory information
set languageAndCharset [lindex [split $LOCALE_STRING_DIR /] end]
# then split the language and charset at the dot
set languageAndCharset [split $languageAndCharset .]
# and extract the values from the resulting list
set language [lindex $languageAndCharset 0]
set charset [lindex $languageAndCharset 1]
set baseName $base
# set up the validMarkArray values
ReInitPerMarkInfo
# if we have a PartIntro element, use its ID as the first-page
# attribute - if no ID, assign one; if no PartIntro, assign an
# ID and we'll dummy in a hometopic when we try to emit the first
# level 1 virpage
if {![info exists partIntroId]} {
set partIntroId ""
}
if {$partIntroId == ""} {
# set partIntroId SDL-RESERVED[incr nextId]
set partIntroId SDL-RESERVED-HOMETOPIC
}
# open the document
Emit "<SDLDOC PUB-ID=\"CDE 2.1\""
Emit " DOC-ID=\"$docId\""
Emit " LANGUAGE=\"$language\""
Emit " CHARSET=\"$charset\""
Emit " FIRST-PAGE=\"$partIntroId\""
Emit " TIMESTMP=\"$timeStamp\""
Emit " SDLDTD=\"1.1.1\">\n"
# and create the VSTRUCT - the INDEX goes in it, the SNB goes after
# it; if there's a Title later, it'll reset the SNB location;
# we also need to read in docbook.tss (if any) and to create an
# empty TOSS to cause the second pass to replace docbook.tss with
# <src file name>.tss (if any) in the new .sdl file
Emit "<VSTRUCT DOC-ID=\"$docId\">\n"
Emit "<LOIDS>\n</LOIDS>\n<TOSS>\n"
IncludeTOSS
Emit "</TOSS>\n"
set indexLocation [tell stdout]
Emit "</VSTRUCT>\n"
set snbLocation [tell stdout]
}
# done - write the index and close the document
proc CloseDocument {} {
global inVirpage errorCount warningCount
global snbLocation savedSNB currentSNB
# close any open block and the current VIRPAGE
CloseBlock
Emit $inVirpage; set inVirpage ""
# if the last VIRPAGE in the document had any system notation
# block references, we need to add them to the saved snb array
# before writing it out
set names [array names currentSNB]
if {[llength $names] != 0} {
foreach name $names {
# split the name into the GI and xid of the SNB entry
set colonLoc [string first "::" $name]
set type [string range $name 0 [incr colonLoc -1]]
set data [string range $name [incr colonLoc 3] end]
# emit the entry
append tempSNB "<$type ID=\"$currentSNB($name)\" "
switch $type {
GRAPHIC -
AUDIO -
VIDEO -
ANIMATE -
CROSSDOC -
MAN-PAGE -
TEXTFILE { set command "XID" }
SYS-CMD { set command "COMMAND" }
CALLBACK { set command "DATA" }
}
append tempSNB "$command=\"$data\">\n"
}
set savedSNB($snbLocation) $tempSNB
unset currentSNB
}
# close the document and write out the stored index and system
# notation block
Emit "</SDLDOC>\n"
WriteIndex
WriteSNB
if {$errorCount || $warningCount} {
puts stderr "DtDocBook total user errors: $errorCount"
puts stderr "DtDocBook total user warnings: $warningCount"
}
if {$errorCount > 0} {
exit 1
}
if {$warningCount > 0} {
exit -1
}
}