4874 lines
134 KiB
Tcl
Executable File
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
|
|
}
|
|
}
|