#!/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 "\n" when in a BLOCK set inVirpage "" ;# holds "\n" when in a VIRPAGE set needFData "" ;# holds "\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 "" } } # 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 "" } } } # set up containers for the IDs of the blocks holding marks - clear # on entry to each but re-use within the as much as # possible; we need two each of the regular and loose versions because # we need to alternate to avoid the
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 "

\n" } # if not in a BLOCK, open one if {$inBlock == ""} { StartBlock "" "" "" 1 } Emit "" set inP 1 set inBlock "

\n\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 "" # 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 "\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 ""} REGISTERED {set punct ""} COPYRIGHT {set punct ""} } Emit "$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 "

\n" } # we set inBlock to

in StartParagraph so we need # to remove the

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 "\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 "\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 "\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

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]\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 "\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 "\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 "\n" ;# and were now starting a new FORM # close any open BLOCK CloseBlock if {[llength $formStack] != 0} { # there is a 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 "\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 "\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 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 "\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 "\n 1} { Emit " NCOLS=\"$nCols\"" } Emit ">\n" } set currentRow 1 set nRows $rowDope(nRows) while {$currentRow <= $nRows} { 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 "\n\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 "\n\n" set closeStyle "\n" 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 "\n\n" set closeStyle "\n" 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 ; 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 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 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 ; # 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 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 and save the id set labelId "SDL-RESERVED[incr nextId]" Emit $needFData; set needFData "" Emit "\n

\n
\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 "\n" # emit the label (inherit string followed by order string) # and close the block Emit "

" Emit $listTop(inheritString) Emit [MakeOrder $listTop(numeration) [incr listTop(count)]] Emit "

\n
\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 ; 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 "" } # end a super- or sub-scripted phrase proc EndSPhrase {} { Emit "" } # 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 "" Emit "\n" Emit "" # emit a title if none provided if {!$haveTitle} { Emit "$gi\n" } } # start a Procedure - emit a
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 "\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 "\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 {(^|([^&]*))]*>} 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 "" Emit $glossBuffer Emit "" } 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 "" Emit "" Emit $glossBuffer Emit "" } } # 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 "" Emit "$glossBuffer" if {$nGlossAlts != 0} { Emit " (" } else { Emit "" unset nGlossAlts } } proc EndAcronymInGlossary {id} { global nGlossAlts if {[incr nGlossAlts -1] != 0} { Emit ", " } else { Emit ")" unset nGlossAlts } } proc EndAbbrevInGlossary {id} { global nGlossAlts Emit ")"" } } # 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 "" } } # 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 "" } } # 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 "" foreach name $idxnames { if {[info exists indexArray($name)]} { set thisEntry $indexArray($name) switch [lindex $thisEntry 0] { 1 { switch $oldLevel { 1 { puts $file "" } 2 { puts $file "\n" } 3 { puts $file "\n\n" } } } 2 { switch $oldLevel { 2 { puts $file "" } 3 { puts $file "\n" } } } 3 { if {$oldLevel == 3} { puts $file "" } } } puts -nonewline $file "" 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 "" } 2 { puts $file "\n" } 3 { puts $file "\n\n" } } puts $file "" } 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 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 "" Emit "" Emit "$footnoteArray($idref)" } } } # 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 "" (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 "" Emit "\n" Emit "" } # 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 "" if {$haveDeferredLink} { EmitDeferredLink } Graphic $id $entityref $fileref GRAPHIC if {$haveDeferredLink} { EndLink } Emit "" 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 "" } # start a KEY element - each parameter is optional (i.e, may be "") proc StartKey {id class 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 "" Emit "${manPageName}($manVolNum)" Emit "\n" Emit "" Emit "${manPageName}($manVolNum)" Emit "\n" # and the NAME section PushForm "" "" $id Emit "" set message "NAME" Emit $localizedAutoGeneratedStringArray($message) Emit "\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 "" } # the user provided a title for this section, we need to open a form # to hold the section now proc EndManPageDivisionTitle {} { Emit "\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 "" 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 "" } Emit "" } # 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 "" } } # 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 "" } # 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 "" Emit $gi Emit "" 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 "" 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 "" } # 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 "" } ###################################################################### ###################################################################### # # 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 "" Anchor $id Emit "``" } # 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 "''" } ###################################################################### ###################################################################### # # 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 "\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 # " 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 "\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 # .tss (if any) in the new .sdl file Emit "\n" Emit "\n\n\n" IncludeTOSS Emit "\n" set indexLocation [tell stdout] Emit "\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 "\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 } }