mirror of
https://github.com/smarty-php/smarty.git
synced 2025-10-08 10:20:16 +02:00
420 lines
15 KiB
Plaintext
Executable File
420 lines
15 KiB
Plaintext
Executable File
;; $Id$
|
|
;;
|
|
;; This file is part of the Modular DocBook Stylesheet distribution.
|
|
;; See ../README or http://www.berkshire.net/~norm/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)
|
|
(let* ((tgroup (find-tgroup cell)))
|
|
(let loop ((colspecs (select-elements (children tgroup)
|
|
(normalize "colspec")))
|
|
(reltotal 0))
|
|
(if (node-list-empty? colspecs)
|
|
(string-append (number->string (round (* (/ relative reltotal) 100))) "%")
|
|
(loop (node-list-rest colspecs)
|
|
(+ reltotal (cals-relative-colwidth
|
|
(colspec-colwidth
|
|
(node-list-first colspecs)))))))))
|
|
|
|
(define (cell-colwidth cell colnum)
|
|
(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)
|
|
(let* ((entry (ancestor-member cell (list (normalize "entry")
|
|
(normalize "entrytbl"))))
|
|
(tgroup (find-tgroup entry))
|
|
(spanname (attribute-string (normalize "spanname") entry))
|
|
(calsalign (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 (colspec-align (find-colspec-by-number colnum))
|
|
(colspec-align (find-colspec-by-number colnum))
|
|
(if (tgroup-align tgroup)
|
|
(tgroup-align tgroup)
|
|
(normalize "left")))))))
|
|
(cond
|
|
((equal? calsalign (normalize "left")) "LEFT")
|
|
((equal? calsalign (normalize "center")) "CENTER")
|
|
((equal? calsalign (normalize "right")) "RIGHT")
|
|
(else "LEFT"))))
|
|
|
|
(define (cell-valign cell colnum)
|
|
(let* ((entry (ancestor-member cell (list (normalize "entry")
|
|
(normalize "entrytbl"))))
|
|
(row (ancestor (normalize "row") entry))
|
|
(tbody (ancestor-member cell (list (normalize "tbody")
|
|
(normalize "thead") (normalize "tfoot"))))
|
|
(tgroup (find-tgroup entry))
|
|
(calsvalign (if (attribute-string (normalize "valign") entry)
|
|
(attribute-string (normalize "valign") entry)
|
|
(if (attribute-string (normalize "valign") row)
|
|
(attribute-string (normalize "valign") row)
|
|
(if (attribute-string (normalize "valign") tbody)
|
|
(attribute-string (normalize "valign") tbody)
|
|
%cals-valign-default%)))))
|
|
(cond
|
|
((equal? calsvalign (normalize "top")) "TOP")
|
|
((equal? calsvalign (normalize "middle")) "MIDDLE")
|
|
((equal? calsvalign (normalize "bottom")) "BOTTOM")
|
|
(else "MIDDLE"))))
|
|
|
|
;; ======================================================================
|
|
;; Element rules
|
|
|
|
(element tgroup
|
|
(let* ((wrapper (parent (current-node)))
|
|
(frameattr (attribute-string (normalize "frame") wrapper))
|
|
(pgwide (attribute-string (normalize "pgwide") wrapper))
|
|
(footnotes (select-elements (descendants (current-node))
|
|
(normalize "footnote")))
|
|
(border (if (equal? frameattr (normalize "none"))
|
|
'(("BORDER" "0"))
|
|
'(("BORDER" "1"))))
|
|
(width (if (equal? pgwide "1")
|
|
(list (list "WIDTH" ($table-width$)))
|
|
'()))
|
|
(head (select-elements (children (current-node)) (normalize "thead")))
|
|
(body (select-elements (children (current-node)) (normalize "tbody")))
|
|
(feet (select-elements (children (current-node)) (normalize "tfoot"))))
|
|
(make element gi: "TABLE"
|
|
attributes: (append
|
|
border
|
|
width
|
|
(if %cals-table-class%
|
|
(list (list "CLASS" %cals-table-class%))
|
|
'()))
|
|
(process-node-list head)
|
|
(process-node-list body)
|
|
(process-node-list feet)
|
|
(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")))
|
|
(border (if (and (or (equal? frameattr (normalize "none"))
|
|
(equal? tgrstyle (normalize "noborder")))
|
|
(not (equal? tgrstyle (normalize "border"))))
|
|
'(("BORDER" "0"))
|
|
'(("BORDER" "1"))))
|
|
(head (select-elements (children (current-node)) (normalize "thead")))
|
|
(body (select-elements (children (current-node)) (normalize "tbody"))))
|
|
(make element gi: "TABLE"
|
|
attributes: (append
|
|
border
|
|
(if %cals-table-class%
|
|
(list (list "CLASS" %cals-table-class%))
|
|
'()))
|
|
(process-node-list head)
|
|
(process-node-list body))))
|
|
|
|
(element colspec
|
|
(empty-sosofo))
|
|
|
|
(element spanspec
|
|
(empty-sosofo))
|
|
|
|
(element thead
|
|
(if %html40%
|
|
(make element gi: "THEAD"
|
|
($process-table-body$ (current-node)))
|
|
($process-table-body$ (current-node))))
|
|
|
|
(element tfoot
|
|
(if %html40%
|
|
(make element gi: "TFOOT"
|
|
($process-table-body$ (current-node)))
|
|
($process-table-body$ (current-node))))
|
|
|
|
(element tbody
|
|
(if %html40%
|
|
(make element gi: "TBODY"
|
|
($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-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)
|
|
(let* ((tgroup (find-tgroup row))
|
|
(rowcells (node-list-filter-out-pis (children row)))
|
|
(maxcol (string->number (attribute-string (normalize "cols") tgroup)))
|
|
(lastentry (node-list-last rowcells))
|
|
(table (ancestor-member tgroup (list (normalize "table")
|
|
(normalize "informaltable"))))
|
|
(rowsep (if (attribute-string (normalize "rowsep") row)
|
|
(attribute-string (normalize "rowsep") row)
|
|
(if (attribute-string (normalize "rowsep") tgroup)
|
|
(attribute-string (normalize "rowsep") tgroup)
|
|
(if (attribute-string (normalize "rowsep") table)
|
|
(attribute-string (normalize "rowsep") table)
|
|
%cals-rule-default%))))
|
|
(after-row-border (if rowsep
|
|
(> (string->number rowsep) 0)
|
|
#f)))
|
|
(make element gi: "TR"
|
|
(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))))))
|
|
|
|
; (if (equal? (gi entry) (normalize "entrytbl"))
|
|
; (make element gi: htmlgi
|
|
; (literal "ENTRYTBL not supported."))
|
|
(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 (equal? (cell-colwidth entry colnum) "")
|
|
'()
|
|
(list (list "WIDTH" (cell-colwidth entry colnum))))
|
|
(list (list "ALIGN" (cell-align 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
|
|
|