summaryrefslogtreecommitdiff
path: root/html/dbtable.dsl
diff options
context:
space:
mode:
Diffstat (limited to 'html/dbtable.dsl')
-rw-r--r--html/dbtable.dsl494
1 files changed, 494 insertions, 0 deletions
diff --git a/html/dbtable.dsl b/html/dbtable.dsl
new file mode 100644
index 0000000..91387b0
--- /dev/null
+++ b/html/dbtable.dsl
@@ -0,0 +1,494 @@
+;; $Id: dbtable.dsl,v 1.6 2003/02/11 01:20:04 adicarlo Exp $
+;;
+;; This file is part of the Modular DocBook Stylesheet distribution.
+;; See ../README or http://docbook.sourceforge.net/projects/dsssl/
+;;
+;; Table support completely reimplemented by norm 15/16 Nov 1997.
+;; Adapted from print support.
+;;
+;; ======================================================================
+;;
+;; This code is intended to implement the SGML Open Exchange Table Model
+;; (http://www.sgmlopen.org/sgml/docs/techpubs.htm) as far as is possible
+;; in HTML. There are a few areas where this code probably fails to
+;; perfectly implement the model:
+;;
+;; - Mixed column width units (4*+2pi) are not supported.
+;; - The behavior that results from mixing relative units with
+;; absolute units has not been carefully considered.
+;;
+;; ======================================================================
+;;
+;; My goal in reimplementing the table model was to provide correct
+;; formatting in tables that use MOREROWS. The difficulty is that
+;; correct formatting depends on calculating the column into which
+;; an ENTRY will fall.
+;;
+;; This is a non-trivial problem because MOREROWS can hang down from
+;; preceding rows and ENTRYs may specify starting columns (skipping
+;; preceding ones).
+;;
+;; A simple, elegant recursive algorithm exists. Unfortunately it
+;; requires calculating the column number of every preceding cell
+;; in the entire table. Without memoization, performance is unacceptable
+;; even in relatively small tables (5x5, for example).
+;;
+;; In order to avoid recursion, the algorithm used below is one that
+;; works forward from the beginning of the table and "passes along"
+;; the relevant information (column number of the preceding cell and
+;; overhang from the MOREROWS in preceding rows).
+;;
+;; Unfortunately, this means that element construction rules
+;; can't always be used to fire the appropriate rule. Instead,
+;; each TGROUP has to process each THEAD/BODY/FOOT explicitly.
+;; And each of those must process each ROW explicitly, then each
+;; ENTRY/ENTRYTBL explicitly.
+;;
+;; ----------------------------------------------------------------------
+;;
+;; I attempted to simplify this code by relying on inheritence from
+;; table-column flow objects, but that wasn't entirely successful.
+;; Horizontally spanning cells didn't seem to inherit from table-column
+;; flow objects that didn't specify equal spanning. There seemed to
+;; be other problems as well, but they could have been caused by coding
+;; errors on my part.
+;;
+;; Anyway, by the time I understood how I could use table-column
+;; flow objects for inheritence, I'd already implemented all the
+;; machinery below to "work it out by hand".
+;;
+;; ======================================================================
+;; NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE
+;; ----------------------------------------------------------------------
+;; A fairly large chunk of this code is in dbcommon.dsl!
+;; ======================================================================
+
+;; Default for COLSEP/ROWSEP if unspecified
+(define %cals-rule-default% "0")
+
+;; Default for VALIGN if unspecified
+(define %cals-valign-default% "TOP")
+
+;; ======================================================================
+;; Convert colwidth units into table-unit measurements
+
+(define (colwidth-length lenstr)
+ (if (string? lenstr)
+ (let ((number (length-string-number-part lenstr))
+ (units (length-string-unit-part lenstr)))
+ (if (or (string=? units "*") (string=? number ""))
+ ;; relative units or no number, give up
+ 0pt
+ (if (string=? units "")
+ ;; no units, default to pixels
+ (* (string->number number) 1px)
+ (let* ((unum (string->number number))
+ (uname (case-fold-down units)))
+ (case uname
+ (("mm") (* unum 1mm))
+ (("cm") (* unum 1cm))
+ (("in") (* unum 1in))
+ (("pi") (* unum 1pi))
+ (("pt") (* unum 1pt))
+ (("px") (* unum 1px))
+ ;; unrecognized units; use pixels
+ (else (* unum 1px)))))))
+ ;; lenstr is not a string...probably #f
+ 0pt))
+
+(define (cals-relative-colwidth? colwidth)
+ (if (string? colwidth)
+ (let ((strlen (string-length colwidth)))
+ (if (string=? colwidth "*")
+ #t
+ (string=? (substring colwidth (- strlen 1) strlen) "*")))
+ #f))
+
+(define (cals-relative-colwidth colwidth)
+ (let ((number (length-string-number-part colwidth))
+ (units (length-string-unit-part colwidth)))
+ (if (string=? units "*")
+ (if (string=? number "")
+ 1
+ (string->number number))
+ 0)))
+
+(define (cell-relative-colwidth cell relative)
+ ;; given a cell and a relative width, work out the HTML cell WIDTH attribute
+ (let* ((tgroup (find-tgroup cell))
+ (pgwide? (equal? (attribute-string (normalize "pgwide") (parent tgroup)) "1")))
+ (if (and (not pgwide?) %html40%)
+ ;; html4 allows widths like "1*", we don't wanna use 50% if pgwide is not on
+ (string-append (number->string relative) "*")
+ (let loop ((colspecs (select-elements (children tgroup)
+ (normalize "colspec")))
+ (reltotal 0))
+ (if (not (node-list-empty? colspecs))
+ (loop (node-list-rest colspecs)
+ (+ reltotal (cals-relative-colwidth
+ (colspec-colwidth
+ (node-list-first colspecs)))))
+ (if (equal? reltotal 0)
+ ""
+ (string-append (number->string (round (* (/ relative reltotal) 100))) "%")))))))
+
+(define (cell-colwidth cell colnum)
+ ;; return the width of a cell, or "" if not specified
+ (let* ((entry (ancestor-member cell (list (normalize "entry")
+ (normalize "entrytbl"))))
+ (colspec (find-colspec-by-number colnum))
+ (colwidth (colspec-colwidth colspec))
+ (width (round (/ (colwidth-length colwidth) 1px))))
+ (if (node-list-empty? colspec)
+ ""
+ (if (and (equal? (hspan entry) 1) colwidth)
+ (if (cals-relative-colwidth? colwidth)
+ (cell-relative-colwidth cell (cals-relative-colwidth colwidth))
+ (number->string width))
+ ""))))
+
+;; ======================================================================
+
+(define (cell-align cell colnum)
+ ;; horizontal alignment for the cell, or "" if not set; efficiency
+ ;; here is important
+ (let* ((entry (ancestor-member cell (list (normalize "entry")
+ (normalize "entrytbl"))))
+ (spanname (attribute-string (normalize "spanname") entry)))
+ (if (attribute-string (normalize "align") entry)
+ (attribute-string (normalize "align") entry)
+ (if (and spanname (spanspec-align (find-spanspec spanname)))
+ (spanspec-align (find-spanspec spanname))
+ (if %html40%
+ ;; no need to set align explictly, let COL do the work
+ ""
+ (if (colspec-align (find-colspec-by-number colnum))
+ (colspec-align (find-colspec-by-number colnum))
+ (let ((tgroup (find-tgroup entry)))
+ (if (tgroup-align tgroup)
+ (tgroup-align tgroup)
+ (normalize "left")))))))))
+
+(define (cell-valign cell colnum)
+ ;; vertical alignment for the cell, or "" if not set; efficiency
+ ;; here is important
+ (let ((entry (ancestor-member cell (list (normalize "entry")
+ (normalize "entrytbl")))))
+ (if (attribute-string (normalize "valign") entry)
+ (attribute-string (normalize "valign") entry)
+ "")))
+
+(define ($table-frame$ table)
+ ;; determine the proper setting for the html 4 FRAME attribute
+ (let* ((wrapper (parent (current-node)))
+ (frameattr (attribute-string (normalize "frame") wrapper)))
+ (if (and %html40% frameattr)
+ (cond
+ ((equal? frameattr (normalize "all"))
+ (list (list "FRAME" "border")))
+ ((equal? frameattr (normalize "bottom"))
+ (list (list "FRAME" "below")))
+ ((equal? frameattr (normalize "none"))
+ (list (list "FRAME" "void")))
+ ((equal? frameattr (normalize "sides"))
+ (list (list "FRAME" "vsides")))
+ ((equal? frameattr (normalize "top"))
+ (list (list "FRAME" "above")))
+ ((equal? frameattr (normalize "topbot"))
+ (list (list "FRAME" "hsides")))
+ (else '()))
+ '())))
+
+(define ($table-border$ table)
+ ;; determine the proper setting for the html 4 BORDER attribute (cell frames)
+ ;; FIXME: rules can be overriden by COLSPEC elements, use "group" value?
+ (let* ((wrapper (parent (current-node)))
+ (rowsepattr (or (attribute-string (normalize "rowsep") (current-node))
+ (attribute-string (normalize "rowsep") wrapper)))
+ (colsepattr (or (attribute-string (normalize "colsep") (current-node))
+ (attribute-string (normalize "colsep") wrapper))))
+ (if (and %html40% (or rowsepattr colsepattr))
+ ;; remember there are actually 3 possible values: 0, 1, unset
+ (cond
+ ((and (equal? colsepattr "1") (equal? rowsepattr "1"))
+ (list (list "RULES" "all")))
+ ((and (equal? colsepattr "0") (equal? rowsepattr "0"))
+ (list (list "RULES" "none")))
+ ((and (equal? colsepattr "1") (equal? rowsepattr "0"))
+ (list (list "RULES" "cols")))
+ ((and (equal? colsepattr "0") (equal? rowsepattr "1"))
+ (list (list "RULES" "rows")))
+ (else '())) ; if rowsep set but not colsep, ignore it
+ '())))
+
+;; ======================================================================
+;; Element rules
+
+(element tgroup
+ (let* ((wrapper (parent (current-node)))
+ (frameattr (attribute-string (normalize "frame") wrapper))
+ (pgwide (attribute-string (normalize "pgwide") wrapper)))
+ (make element gi: "TABLE"
+ attributes: (append
+ (if (equal? frameattr (normalize "none"))
+ '(("BORDER" "0"))
+ '(("BORDER" "1")))
+ ($table-frame$ (current-node))
+ ($table-border$ (current-node))
+ (if (equal? pgwide "1")
+ (list (list "WIDTH" ($table-width$)))
+ '())
+ (if %cals-table-class%
+ (list (list "CLASS" %cals-table-class%))
+ '()))
+ ($process-colspecs$ (current-node))
+ (process-node-list (select-elements (children (current-node)) (normalize "thead")))
+ (process-node-list (select-elements (children (current-node)) (normalize "tbody")))
+ (process-node-list (select-elements (children (current-node)) (normalize "tfoot")))
+ (make-table-endnotes))))
+
+(element entrytbl ;; sortof like a tgroup...
+ (let* ((wrapper (parent (parent (parent (parent (current-node))))))
+ ;; table tgroup tbody row
+ (frameattr (attribute-string (normalize "frame") wrapper))
+ (tgrstyle (attribute-string (normalize "tgroupstyle"))))
+ (make element gi: "TABLE"
+ attributes: (append
+ (if (and (or (equal? frameattr (normalize "none"))
+ (equal? tgrstyle (normalize "noborder")))
+ (not (equal? tgrstyle (normalize "border"))))
+ '(("BORDER" "0"))
+ '(("BORDER" "1")))
+ ($table-frame$ (current-node))
+ ($table-border$ (current-node))
+ (if %cals-table-class%
+ (list (list "CLASS" %cals-table-class%))
+ '()))
+ ($process-colspecs$ (current-node))
+ (process-node-list (select-elements (children (current-node)) (normalize "thead")))
+ (process-node-list (select-elements (children (current-node)) (normalize "tbody"))))))
+
+(element colspec
+ ;; now handled by $process-colspecs$
+ (empty-sosofo))
+
+(element spanspec
+ (empty-sosofo))
+
+(element thead
+ ;; note that colspec/spanspec in thead isn't supported by HTML table model
+ (if %html40%
+ (make element gi: "THEAD"
+ ($process-table-body$ (current-node)))
+ ($process-table-body$ (current-node))))
+
+(element tfoot
+ ;; note that colspec/spanspec in tfoot isn't supported by HTML table model
+ (if %html40%
+ (make element gi: "TFOOT"
+ ($process-table-body$ (current-node)))
+ ($process-table-body$ (current-node))))
+
+(element tbody
+ (if %html40%
+ (make element gi: "TBODY"
+ attributes: (if (attribute-string (normalize "valign"))
+ (list (list "VALIGN" (attribute-string (normalize "valign"))))
+ '())
+ ($process-table-body$ (current-node)))
+ ($process-table-body$ (current-node))))
+
+(element row
+ (empty-sosofo)) ;; this should never happen, they're processed explicitly
+
+(element entry
+ (empty-sosofo)) ;; this should never happen, they're processed explicitly
+
+;; ======================================================================
+;; Functions that handle processing of table bodies, rows, and cells
+
+(define ($process-colspecs$ tgroup)
+ ;; given tgroup or entrytbl, convert the colspecs to HTML4 COL elements
+ (if (not %html40%)
+ (empty-sosofo)
+ (let ((cols (string->number (attribute-string (normalize "cols")))))
+ (let loop ((colnum 1))
+ (if (> colnum cols)
+ (empty-sosofo)
+ (make sequence
+ (let* ((colspec (find-colspec-by-number colnum))
+ (colwidth (colspec-colwidth colspec)))
+ (if (node-list-empty? colspec)
+ (make empty-element gi: "COL")
+ (make empty-element gi: "COL"
+ attributes:
+ (append
+ (if colwidth
+ (list (list "WIDTH"
+ (if (cals-relative-colwidth? colwidth)
+ (cell-relative-colwidth colspec (cals-relative-colwidth colwidth))
+ (number->string (round (/ (colwidth-length colwidth) 1px))))))
+ '())
+ (if (attribute-string (normalize "align") colspec)
+ (list (list "ALIGN" (attribute-string (normalize "align") colspec)))
+ '())
+ (if (attribute-string (normalize "char") colspec)
+ (list (list "CHAR" (attribute-string (normalize "char") colspec)))
+ '())
+ (if (attribute-string (normalize "charoff") colspec)
+ (list (list "CHAROFF" (attribute-string (normalize "charoff") colspec)))
+ '())
+ (if (attribute-string (normalize "colname") colspec)
+ (list (list "TITLE" (attribute-string (normalize "colname") colspec)))
+ '())))))
+ (loop (+ colnum 1))))))))
+
+(define ($process-table-body$ body)
+ (let* ((tgroup (find-tgroup body))
+ (cols (string->number (attribute-string (normalize "cols")
+ tgroup))))
+ (let loop ((rows (select-elements (children body) (normalize "row")))
+ (overhang (constant-list 0 cols)))
+ (if (node-list-empty? rows)
+ (empty-sosofo)
+ (make sequence
+ ($process-row$ (node-list-first rows) overhang)
+ (loop (node-list-rest rows)
+ (update-overhang (node-list-first rows) overhang)))))))
+
+(define ($process-row$ row overhang)
+ ;; FIXME: rowsep
+ (let* ((tgroup (find-tgroup row))
+ (rowcells (node-list-filter-out-pis (children row)))
+ (rowalign (attribute-string (normalize "valign") row))
+ (maxcol (string->number (attribute-string (normalize "cols") tgroup)))
+ (lastentry (node-list-last rowcells)))
+ (make element gi: "TR"
+ attributes: (append
+ (if rowalign
+ (list (list "VALIGN" rowalign))
+ '())
+ '())
+ (let loop ((cells rowcells)
+ (prevcell (empty-node-list)))
+ (if (node-list-empty? cells)
+ (empty-sosofo)
+ (make sequence
+ ($process-cell$ (node-list-first cells)
+ prevcell overhang)
+ (loop (node-list-rest cells)
+ (node-list-first cells)))))
+
+ ;; add any necessary empty cells to the end of the row
+ (let loop ((colnum (overhang-skip overhang
+ (+ (cell-column-number
+ lastentry overhang)
+ (hspan lastentry)))))
+ (if (> colnum maxcol)
+ (empty-sosofo)
+ (make sequence
+ (make element gi: "TD"
+ (make entity-ref name: "nbsp"))
+ (loop (overhang-skip overhang (+ colnum 1)))))))))
+
+(define (empty-cell? entry)
+ ;; Return #t if and only if entry is empty (or contains only PIs)
+ (let loop ((nl (children entry)))
+ (if (node-list-empty? nl)
+ #t
+ (let* ((node (node-list-first nl))
+ (nodeclass (node-property 'class-name node))
+ (nodechar (if (equal? nodeclass 'data-char)
+ (node-property 'char node)
+ #f))
+ (whitespace? (and (equal? nodeclass 'data-char)
+ (or (equal? nodechar #\space)
+ (equal? (data node) "	")
+ (equal? (data node) "
")
+ (equal? (data node) "
")))))
+ (if (not (or (equal? (node-property 'class-name node) 'pi)
+ whitespace?))
+ #f
+ (loop (node-list-rest nl)))))))
+
+(define ($process-cell$ entry preventry overhang)
+ (let* ((colnum (cell-column-number entry overhang))
+ (lastcellcolumn (if (node-list-empty? preventry)
+ 0
+ (- (+ (cell-column-number preventry overhang)
+ (hspan preventry))
+ 1)))
+ (lastcolnum (if (> lastcellcolumn 0)
+ (overhang-skip overhang lastcellcolumn)
+ 0))
+ (htmlgi (if (have-ancestor? (normalize "tbody") entry)
+ "TD"
+ "TH")))
+ (make sequence
+ (if (node-list-empty? (preced entry))
+ (if (attribute-string (normalize "id") (parent entry))
+ (make element gi: "A"
+ attributes: (list
+ (list
+ "NAME"
+ (attribute-string (normalize "id")
+ (parent entry))))
+ (empty-sosofo))
+ (empty-sosofo))
+ (empty-sosofo))
+
+ (if (attribute-string (normalize "id") entry)
+ (make element gi: "A"
+ attributes: (list
+ (list
+ "NAME"
+ (attribute-string (normalize "id") entry)))
+ (empty-sosofo))
+ (empty-sosofo))
+
+ ;; This is a little bit complicated. We want to output empty cells
+ ;; to skip over missing data. We start count at the column number
+ ;; arrived at by adding 1 to the column number of the previous entry
+ ;; and skipping over any MOREROWS overhanging entrys. Then for each
+ ;; iteration, we add 1 and skip over any overhanging entrys.
+ (let loop ((count (overhang-skip overhang (+ lastcolnum 1))))
+ (if (>= count colnum)
+ (empty-sosofo)
+ (make sequence
+ (make element gi: htmlgi
+ (make entity-ref name: "nbsp")
+;; (literal (number->string lastcellcolumn) ", ")
+;; (literal (number->string lastcolnum) ", ")
+;; (literal (number->string (hspan preventry)) ", ")
+;; (literal (number->string colnum ", "))
+;; ($debug-pr-overhang$ overhang)
+ )
+ (loop (overhang-skip overhang (+ count 1))))))
+
+ ;; Now we've output empty cells for any missing entries, so we
+ ;; are ready to output the cell for this entry...
+ (make element gi: htmlgi
+ attributes: (append
+ (if (> (hspan entry) 1)
+ (list (list "COLSPAN" (number->string (hspan entry))))
+ '())
+ (if (> (vspan entry) 1)
+ (list (list "ROWSPAN" (number->string (vspan entry))))
+ '())
+ (if (and (not %html40%) (not (equal? (cell-colwidth entry colnum) "")))
+ (list (list "WIDTH" (cell-colwidth entry colnum)))
+ '())
+ (if (not (equal? (cell-align entry colnum) ""))
+ (list (list "ALIGN" (cell-align entry colnum)))
+ '())
+ (if (not (equal? (cell-valign entry colnum) ""))
+ (list (list "VALIGN" (cell-valign entry colnum)))
+ '()))
+ (if (empty-cell? entry)
+ (make entity-ref name: "nbsp")
+ (if (equal? (gi entry) (normalize "entrytbl"))
+ (process-node-list entry)
+ (process-node-list (children entry))))))))
+
+;; EOF dbtable.dsl
+