mirror of
				https://github.com/smarty-php/smarty.git
				synced 2025-10-31 12:21:36 +01: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
 | |
| 
 |