Browse Source

Applied patch series from Jason Riedy.

You can slice a single table full of calculations in different ways
into separate destinations.  Or you can format the table differently.
There are many fun and exciting possible uses.

A fancier implementation would scan the document *once* for the
set of destinations.  That could help implement another function
to update all destinations from all sources.

Refactor orgtbl-to-generic; explicitly separate heading from body.
Support last-line specializers.
Allow functions for some orgtbl parameters.
Add a :remove-nil-lines parameter to orgtbl-to-generic.
Carsten Dominik 17 years ago
parent
commit
1508d31b6e
7 changed files with 394 additions and 108 deletions
  1. 55 0
      ChangeLog
  2. 5 0
      contrib/ChangeLog
  3. 1 0
      contrib/README
  4. 114 0
      contrib/lisp/orgtbl-sqlinsert.el
  5. 4 1
      doc/org.texi
  6. 214 107
      lisp/org-table.el
  7. 1 0
      lisp/org.el

+ 55 - 0
ChangeLog

@@ -1,3 +1,58 @@
+2008-04-16  Jason Riedy  <jason@acm.org>
+
+	* lisp/org-table.el (orgtbl-to-generic): Add a :remove-nil-lines
+	parameter that supresses lines that evaluate to NIL.
+
+
+2008-04-15  Jason Riedy  <jason@acm.org>
+
+	* lisp/org-table.el (orgtbl-get-fmt): New inline function for
+	picking apart formats that may be lists.
+	(orgtbl-apply-fmt): New inline function for applying formats that
+	may be functions.
+	(orgtbl-eval-str): New inline function for strings that may be
+	functions.
+	(orgtbl-format-line, orgtbl-to-generic): Use and document.
+	(orgtbl-to-latex, orgtbl-to-texinfo): Document.
+
+	* doc/org.texi (A LaTeX example): Note that fmt may be a
+	one-argument function, and efmt may be a two-argument function.
+
+2008-04-15  Jason Riedy  <jason@acm.org>
+
+	* lisp/org-table.el (*orgtbl-llfmt*, *orgtbl-llstart*)
+	(*orgtbl-llend*): Dynamic variables for last-line formatting.
+	(orgtbl-format-section): Shift formatting to support detecting the
+	last line and formatting it specially.
+	(orgtbl-to-generic): Document :ll* formats.  Set to the non-ll
+	formats unless overridden.
+	(orgtbl-to-latex): Suggest using :llend to suppress the final \\.
+
+2008-04-15  Jason Riedy  <jason@acm.org>
+
+	* lisp/org-table.el (*orgtbl-table*, *orgtbl-rtn*): Dynamically
+	bound variables to hold the input collection of lines and output
+	formatted text.
+	(*orgtbl-hline*, *orgtbl-sep*, *orgtbl-fmt*, *orgtbl-efmt*,
+	(*orgtbl-lfmt*, *orgtbl-lstart*, *orgtbl-lend*): Dynamically bound
+	format parameters.
+	(orgtbl-format-line): New function encapsulating formatting for a
+	single line.
+	(orgtbl-format-section): Similar for each section.  Rebinding the
+	dynamic vars customizes the formatting for each section.
+	(orgtbl-to-generic): Use orgtbl-format-line and
+	orgtbl-format-section.
+	(org-get-param): Now unused, so delete.
+
+	* lisp/org-table.el (orgtbl-gather-send-defs): New function to
+	gather all the SEND definitions before a table.
+	(orgtbl-send-replace-tbl): New function to find the RECEIVE
+	corresponding to the current name.
+	(orgtbl-send-table): Use the previous two functions and implement
+	multiple destinations for each table.
+
+	* doc/org.texi (Radio tables): Document multiple destinations.
+
 2008-04-19  Carsten Dominik  <dominik@science.uva.nl>
 
 	* xemacs/noutline.el (outline-invisible-p): Require that

+ 5 - 0
contrib/ChangeLog

@@ -1,3 +1,8 @@
+2008-04-15  Jason Riedy  <jason@acm.org>
+
+	* lisp/orgtbl-sqlinsert.el: New file.
+	* README: Add to list.
+
 2008-04-07  Carsten Dominik  <dominik@science.uva.nl>
 
 	* lisp/org-iswitchb.el: File removed, because the functionality is

+ 1 - 0
contrib/README

@@ -24,6 +24,7 @@ org-registry.el      --- a registry for Org links
 org2rem.el           --- Convert org appointments into reminders
 org-screen.el        --- visit screen sessions through Org-mode links
 org-toc.el 	     --- Table of contents for Org-mode buffer
+orgtbl-sqlinsert.el  --- Convert Org-mode tables to SQL insertions.
 
 PACKAGES
 ========

+ 114 - 0
contrib/lisp/orgtbl-sqlinsert.el

@@ -0,0 +1,114 @@
+;;; orgtbl-sqlinsert.el --- orgtbl to SQL insert statements.
+
+;; Copyright (C) 2008  Free Software Foundation
+
+;; Author: Jason Riedy <jason@acm.org>
+;; Keywords: org, tables, sql
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Converts an orgtbl to a sequence of SQL insertion commands.
+;; Table cells are quoted and escaped very conservatively.
+
+;;; Code:
+
+(defun orgtbl-to-sqlinsert (table params)
+  "Convert the orgtbl-mode TABLE to SQL insert statements.
+TABLE is a list, each entry either the symbol `hline' for a horizontal
+separator line, or a list of fields for that line.
+PARAMS is a property list of parameters that can influence the conversion.
+
+Names and strings are modified slightly by default.  Single-ticks
+are doubled as per SQL's standard mechanism.  Backslashes and
+dollar signs are deleted.  And tildes are changed to spaces.
+These modifications were chosed for use with TeX.  See
+ORGTBL-SQL-STRIP-AND-QUOTE.
+
+Supports all parameters from ORGTBL-TO-GENERIC.  New to this function
+are:
+
+:sqlname   The name of the database table; defaults to the name of the
+           target region.
+
+:nowebname If not nil, used as a wrapping noweb fragment name.
+
+The most important parameters of ORGTBL-TO-GENERIC for SQL are:
+
+:splice    When set to t, return only insert statements, don't wrap
+           them in a transaction.  Default is nil.
+
+:tstart, :tend
+           The strings used to begin and commit the transaction.
+
+:hfmt      A function that gathers the quoted header names into a
+           dynamically scoped variable HDRLIST.  Probably should
+           not be changed by the user.
+
+The general parameters :skip and :skipcols have already been applied when
+this function is called."
+  (let* (hdrlist
+	 (alignment (mapconcat (lambda (x) (if x "r" "l"))
+			       org-table-last-alignment ""))
+	 (nowebname (plist-get params :nowebname))
+	 (breakvals (plist-get params :breakvals))
+	 (params2
+	  (list
+	   :sqlname name
+	   :tstart (lambda () (concat (if nowebname
+					  (format "<<%s>>= \n" nowebname)
+					"")
+				      "BEGIN TRANSACTION;"))
+	   :tend (lambda () (concat "COMMIT;" (if nowebname "\n@ " "")))
+	   :fmt (lambda (str) (orgtbl-sql-strip-and-quote str))
+;	   :hfmt (lambda (f) (push (concat "[" f "]") hdrlist) "")
+	   :hfmt (lambda (f) (push f hdrlist) "")
+	   :hlfmt (lambda (lst) nil)
+	   :lstart (lambda () (concat "INSERT INTO "
+				      (plist-get params :sqlname) "( "
+				      (mapconcat 'identity (reverse hdrlist)
+						 ", ")
+				      " )" (if breakvals "\n" " ")
+				      "VALUES ( "))
+	   :lend " );"
+	   :sep " , "
+	   :hline nil
+	   :remove-nil-lines t))
+	 (params (org-combine-plists params2 params)))
+    (orgtbl-to-generic table params)))
+
+(defun orgtbl-sql-quote (str)
+  "Convert single ticks to doubled single ticks and wrap in single ticks."
+  (concat "'" (mapconcat 'identity (split-string str "'") "''") "'"))
+
+(defun orgtbl-sql-strip-dollars-escapes-tildes (str)
+  "Strip dollarsigns and backslash escapes, replace tildes with spaces."
+  (mapconcat 'identity
+	     (split-string (mapconcat 'identity
+				      (split-string str "\\$\\|\\\\")
+				      "")
+			   "~")
+	     " "))
+
+(defun orgtbl-sql-strip-and-quote (str)
+  "Apply ORGBTL-SQL-QUOTE and ORGTBL-SQL-STRIP-DOLLARS-ESCAPES-TILDES
+to sanitize STR for use in SQL statements."
+  (cond ((stringp str)
+         (orgtbl-sql-quote (orgtbl-sql-strip-dollars-escapes-tildes str)))
+        ((sequencep str) (mapcar 'orgtbl-sql-strip-and-quote str))
+        (t nil)))
+
+(provide 'orgtbl-sqlinsert)
+;;; orgtbl-sqlinsert.el ends here

+ 4 - 1
doc/org.texi

@@ -8583,6 +8583,8 @@ A format to be used to wrap each field, should contain @code{%s} for the
 original field value.  For example, to wrap each field value in dollars,
 you could use @code{:fmt "$%s$"}.  This may also be a property list with
 column numbers and formats. for example @code{:fmt (2 "$%s$" 4 "%s\\%%")}.
+A function of one argument can be used in place of the strings; the
+function must return a formatted string.
 
 @item :efmt efmt
 Use this format to print numbers with exponentials.  The format should
@@ -8591,7 +8593,8 @@ have @code{%s} twice for inserting mantissa and exponent, for example
 may also be a property list with column numbers and formats, for example
 @code{:efmt (2 "$%s\\times10^@{%s@}$" 4 "$%s\\cdot10^@{%s@}$")}.  After
 @code{efmt} has been applied to a value, @code{fmt} will also be
-applied.
+applied.  Similar to @code{fmt}, functions of two arguments can be
+supplied instead of strings.
 @end table
 
 @node Translator functions, Radio lists, A LaTeX example, Tables in arbitrary syntax

+ 214 - 107
lisp/org-table.el

@@ -3492,6 +3492,41 @@ overwritten, and the table is not marked as requiring realignment."
 	  (push (> (/ (apply '+ (mapcar (lambda (x) (if (string-match org-table-number-regexp x) 1 0)) column)) maxcol) org-table-number-fraction) org-table-last-alignment))
     (funcall func table nil)))
 
+(defun orgtbl-gather-send-defs ()
+  "Gathers a plist of :name, :transform, :params for each destination before
+a radio table."
+  (save-excursion
+    (goto-char (org-table-begin))
+    (let (rtn)
+      (beginning-of-line 0)
+      (while (looking-at "#\\+ORGTBL: *SEND +\\([a-zA-Z0-9_]+\\) +\\([^ \t\r\n]+\\)\\( +.*\\)?")
+	(let ((name (org-no-properties (match-string 1)))
+	      (transform (intern (match-string 2)))
+	      (params (if (match-end 3)
+			  (read (concat "(" (match-string 3) ")")))))
+	  (push (list :name name :transform transform :params params)
+		rtn)
+	  (beginning-of-line 0)))
+      rtn)))
+
+(defun orgtbl-send-replace-tbl (name txt)
+  "Find and replace table NAME with TXT."
+  (save-excursion
+    (goto-char (point-min))
+    (unless (re-search-forward
+	     (concat "BEGIN RECEIVE ORGTBL +" name "\\([ \t]\\|$\\)") nil t)
+      (error "Don't know where to insert translated table"))
+    (goto-char (match-beginning 0))
+    (beginning-of-line 2)
+    (save-excursion
+      (let ((beg (point)))
+	(unless (re-search-forward
+		 (concat "END RECEIVE ORGTBL +" name) nil t)
+	  (error "Cannot find end of insertion region"))
+	(beginning-of-line 1)
+	(delete-region beg (point))))
+    (insert txt "\n")))
+
 (defun orgtbl-send-table (&optional maybe)
   "Send a tranformed version of this table to the receiver position.
 With argument MAYBE, fail quietly if no transformation is defined for
@@ -3501,59 +3536,45 @@ this table."
     (unless (org-at-table-p) (error "Not at a table"))
     ;; when non-interactive, we assume align has just happened.
     (when (interactive-p) (org-table-align))
-    (save-excursion
-      (goto-char (org-table-begin))
-      (beginning-of-line 0)
-      (unless (looking-at "#\\+ORGTBL: *SEND +\\([a-zA-Z0-9_]+\\) +\\([^ \t\r\n]+\\)\\( +.*\\)?")
-	(if maybe
-	    (throw 'exit nil)
-	  (error "Don't know how to transform this table."))))
-    (let* ((name (match-string 1))
-	   beg
-	   (transform (intern (match-string 2)))
-	   (params (if (match-end 3) (read (concat "(" (match-string 3) ")"))))
-	   (skip (plist-get params :skip))
-	   (skipcols (plist-get params :skipcols))
-	   (txt (buffer-substring-no-properties
-		 (org-table-begin) (org-table-end)))
-	   (lines (nthcdr (or skip 0) (org-split-string txt "[ \t]*\n[ \t]*")))
-	   (lines (org-table-clean-before-export lines))
-	   (i0 (if org-table-clean-did-remove-column 2 1))
-	   (table (mapcar
-		   (lambda (x)
-		     (if (string-match org-table-hline-regexp x)
-			 'hline
-		       (org-remove-by-index
-			(org-split-string (org-trim x) "\\s-*|\\s-*")
-			skipcols i0)))
-		   lines))
-	   (fun (if (= i0 2) 'cdr 'identity))
-	   (org-table-last-alignment
-	    (org-remove-by-index (funcall fun org-table-last-alignment)
-				 skipcols i0))
-	   (org-table-last-column-widths
-	    (org-remove-by-index (funcall fun org-table-last-column-widths)
-				 skipcols i0)))
-
-      (unless (fboundp transform)
-	(error "No such transformation function %s" transform))
-      (setq txt (funcall transform table params))
-      ;; Find the insertion place
-      (save-excursion
-	(goto-char (point-min))
-	(unless (re-search-forward
-		 (concat "BEGIN RECEIVE ORGTBL +" name "\\([ \t]\\|$\\)") nil t)
-	  (error "Don't know where to insert translated table"))
-	(goto-char (match-beginning 0))
-	(beginning-of-line 2)
-	(setq beg (point))
-	(unless (re-search-forward (concat "END RECEIVE ORGTBL +" name) nil t)
-	  (error "Cannot find end of insertion region"))
-	(beginning-of-line 1)
-	(delete-region beg (point))
-	(goto-char beg)
-	(insert txt "\n"))
-      (message "Table converted and installed at receiver location"))))
+    (let ((dests (orgtbl-gather-send-defs))
+	  (txt (buffer-substring-no-properties (org-table-begin)
+					       (org-table-end)))
+	  (ntbl 0))
+      (unless dests (if maybe (throw 'exit nil)
+		      (error "Don't know how to transform this table.")))
+      (dolist (dest dests)
+	(let* ((name (plist-get dest :name))
+	       (transform (plist-get dest :transform))
+	       (params (plist-get dest :params))
+	       (skip (plist-get params :skip))
+	       (skipcols (plist-get params :skipcols))
+	       beg
+	       (lines (org-table-clean-before-export
+		       (nthcdr (or skip 0)
+			       (org-split-string txt "[ \t]*\n[ \t]*"))))
+	       (i0 (if org-table-clean-did-remove-column 2 1))
+	       (table (mapcar
+		       (lambda (x)
+			 (if (string-match org-table-hline-regexp x)
+			     'hline
+			   (org-remove-by-index
+			    (org-split-string (org-trim x) "\\s-*|\\s-*")
+			    skipcols i0)))
+		       lines))
+	       (fun (if (= i0 2) 'cdr 'identity))
+	       (org-table-last-alignment
+		(org-remove-by-index (funcall fun org-table-last-alignment)
+				     skipcols i0))
+	       (org-table-last-column-widths
+		(org-remove-by-index (funcall fun org-table-last-column-widths)
+				     skipcols i0))
+	       (txt (if (fboundp transform)
+			(funcall transform table params)
+		      (error "No such transformation function %s" transform))))
+	  (orgtbl-send-replace-tbl name txt))
+	(setq ntbl (1+ ntbl)))
+      (message "Table converted and installed at %d receiver location%s"
+	       ntbl (if (> ntbl 1) "s" "")))))
 
 (defun org-remove-by-index (list indices &optional i0)
   "Remove the elements in LIST with indices in INDICES.
@@ -3602,15 +3623,75 @@ First element has index 0, or I0 if given."
     (insert txt)
     (goto-char pos)))
 
-(defun org-get-param (params header i sym &optional hsym)
-  "Get parameter value for symbol SYM.
-If this is a header line, actually get the value for the symbol with an
-additional \"h\" inserted after the colon.
-If the value is a protperty list, get the element for the current column.
-Assumes variables VAL, PARAMS, HEAD and I to be scoped into the function."
-  (let ((val (plist-get params sym)))
-    (and hsym header (setq val (or (plist-get params hsym) val)))
-    (if (consp val) (plist-get val i) val)))
+;; Dynamically bound input and output for table formatting.
+(defvar *orgtbl-table* nil
+  "Carries the current table through formatting routines.")
+(defvar *orgtbl-rtn* nil
+  "Formatting routines push the output lines here.")
+;; Formatting parameters for the current table section.
+(defvar *orgtbl-hline* nil "Text used for horizontal lines")
+(defvar *orgtbl-sep* nil "Text used as a column separator")
+(defvar *orgtbl-fmt* nil "Format for each entry")
+(defvar *orgtbl-efmt* nil "Format for numbers")
+(defvar *orgtbl-lfmt* nil "Format for an entire line, overrides fmt")
+(defvar *orgtbl-llfmt* nil "Specializes lfmt for the last row")
+(defvar *orgtbl-lstart* nil "Text starting a row")
+(defvar *orgtbl-llstart* nil "Specializes lstart for the last row")
+(defvar *orgtbl-lend* nil "Text ending a row")
+(defvar *orgtbl-llend* nil "Specializes lend for the last row")
+
+(defsubst orgtbl-get-fmt (fmt i)
+  "Retrieve the format from FMT corresponding to the Ith column."
+  (if (and (not (functionp fmt)) (consp fmt))
+      (plist-get fmt i)
+    fmt))
+
+(defsubst orgtbl-apply-fmt (fmt &rest args)
+  "Apply format FMT to the arguments.  NIL FMTs return the first argument."
+  (cond ((functionp fmt) (apply fmt args))
+	(fmt (apply 'format fmt args))
+	(args (car args))
+	(t args)))
+
+(defsubst orgtbl-eval-str (str)
+  "If STR is a function, evaluate it with no arguments."
+  (if (functionp str)
+      (funcall str)
+    str))
+
+(defun orgtbl-format-line (line)
+  "Format LINE as a table row."
+  (if (eq line 'hline) (if *orgtbl-hline* (push *orgtbl-hline* *orgtbl-rtn*))
+    (let* ((i 0)
+	   (line
+	    (mapcar
+	     (lambda (f)
+	       (setq i (1+ i))
+	       (let* ((efmt (orgtbl-get-fmt *orgtbl-efmt* i))
+		      (f (if (and efmt (string-match orgtbl-exp-regexp f))
+			     (orgtbl-apply-fmt efmt (match-string 1 f)
+					       (match-string 2 f))
+			   f)))
+		 (orgtbl-apply-fmt (orgtbl-get-fmt *orgtbl-fmt* i) f)))
+	     line)))
+      (push (if *orgtbl-lfmt*
+		(orgtbl-apply-fmt *orgtbl-lfmt* line)
+	      (concat (orgtbl-eval-str *orgtbl-lstart*)
+		      (mapconcat 'identity line *orgtbl-sep*)
+		      (orgtbl-eval-str *orgtbl-lend*)))
+	    *orgtbl-rtn*))))
+
+(defun orgtbl-format-section (section-stopper)
+  "Format lines until the first occurrence of SECTION-STOPPER."
+  (let (prevline)
+    (progn
+      (while (not (eq (car *orgtbl-table*) section-stopper))
+	(if prevline (orgtbl-format-line prevline))
+	(setq prevline (pop *orgtbl-table*)))
+      (if prevline (let ((*orgtbl-lstart* *orgtbl-llstart*)
+			 (*orgtbl-lend* *orgtbl-llend*)
+			 (*orgtbl-lfmt* *orgtbl-llfmt*))
+		     (orgtbl-format-line prevline))))))
 
 (defun orgtbl-to-generic (table params)
   "Convert the orgtbl-mode TABLE to some other format.
@@ -3624,31 +3705,43 @@ specify either :lfmt, or all of (:lstart :lend :sep).  If you do not use
 
 Valid parameters are
 
-:tstart     String to start the table.  Ignored when :splice is t.
-:tend       String to end the table.  Ignored when :splice is t.
-
 :splice     When set to t, return only table body lines, don't wrap
             them into :tstart and :tend.  Default is nil.
 
 :hline      String to be inserted on horizontal separation lines.
             May be nil to ignore hlines.
 
+:sep        Separator between two fields
+:remove-nil-lines Do not include lines that evaluate to nil.
+
+
+  Each in the following group may be either a string or a function
+  of no arguments returning a string:
+:tstart     String to start the table.  Ignored when :splice is t.
+:tend       String to end the table.  Ignored when :splice is t.
 :lstart     String to start a new table line.
+:llstart    String to start the last table line, defaults to :lstart.
 :lend       String to end a table line
-:sep        Separator between two fields
+:llend      String to end the last table line, defaults to :lend.
+
+  Each in the following group may be a string, a function of one
+  argument (the field or line) returning a string, or a plist
+  mapping columns to either of the above:
 :lfmt       Format for entire line, with enough %s to capture all fields.
             If this is present, :lstart, :lend, and :sep are ignored.
+:llfmt      Format for the entire last line, defaults to :lfmt.
 :fmt        A format to be used to wrap the field, should contain
             %s for the original field value.  For example, to wrap
             everything in dollars, you could use :fmt \"$%s$\".
             This may also be a property list with column numbers and
             formats. For example :fmt (2 \"$%s$\" 4 \"%s%%\")
 
-:hlstart :hlend :hlsep :hlfmt :hfmt
+:hlstart :hllstart :hlend :hllend :hlsep :hlfmt :hllfmt :hfmt
             Same as above, specific for the header lines in the table.
             All lines before the first hline are treated as header.
             If any of these is not present, the data line value is used.
 
+  This may be either a string or a function of two arguments:
 :efmt       Use this format to print numbers with exponentials.
             The format should have %s twice for inserting mantissa
             and exponent, for example \"%s\\\\times10^{%s}\".  This
@@ -3658,51 +3751,58 @@ Valid parameters are
 In addition to this, the parameters :skip and :skipcols are always handled
 directly by `orgtbl-send-table'.  See manual."
   (interactive)
-  (let* ((p params)
-	 (splicep (plist-get p :splice))
-	 (hline (plist-get p :hline))
-	 rtn line i fm efm lfmt h)
 
-    ;; Do we have a header?
-    (if (and (not splicep) (listp (car table)) (memq 'hline table))
-	(setq h t))
+  (let* ((splicep (plist-get params :splice))
+	 (hline (plist-get params :hline))
+	 (remove-nil-linesp (plist-get params :remove-nil-lines))
+	 (*orgtbl-table* table)
+	 (*orgtbl-sep* (plist-get params :sep))
+	 (*orgtbl-efmt* (plist-get params :efmt))
+	 (*orgtbl-lstart* (plist-get params :lstart))
+	 (*orgtbl-llstart* (or (plist-get params :llstart) *orgtbl-lstart*))
+	 (*orgtbl-lend* (plist-get params :lend))
+	 (*orgtbl-llend* (or (plist-get params :llend) *orgtbl-lend*))
+	 (*orgtbl-lfmt* (plist-get params :lfmt))
+	 (*orgtbl-llfmt* (or (plist-get params :llfmt) *orgtbl-lfmt*))
+	 (*orgtbl-fmt* (plist-get params :fmt))
+	 *orgtbl-rtn*)
 
     ;; Put header
     (unless splicep
-      (push (or (plist-get p :tstart) "ERROR: no :tstart") rtn))
-
-    ;; Now loop over all lines
-    (while (setq line (pop table))
-      (if (eq line 'hline)
-	  ;; A horizontal separator line
-	  (progn (if hline (push hline rtn))
-		 (setq h nil))               ; no longer in header
-	;; A normal line.  Convert the fields, push line onto the result list
-	(setq i 0)
-	(setq line
-	      (mapcar
-	       (lambda (f)
-		 (setq i (1+ i)
-		       fm (org-get-param p h i :fmt :hfmt)
-		       efm (org-get-param p h i :efmt))
-		 (if (and efm (string-match orgtbl-exp-regexp f))
-		     (setq f (format
-			      efm (match-string 1 f) (match-string 2 f))))
-		 (if fm (setq f (format fm f)))
-		 f)
-	       line))
-	(if (setq lfmt (org-get-param p h i :lfmt :hlfmt))
-	    (push (apply 'format lfmt line) rtn)
-	  (push (concat
-		 (org-get-param p h i :lstart :hlstart)
-		 (mapconcat 'identity line (org-get-param p h i :sep :hsep))
-		 (org-get-param p h i :lend :hlend))
-		rtn))))
+      (push (or (orgtbl-eval-str (plist-get params :tstart))
+		"ERROR: no :tstart") *orgtbl-rtn*))
 
-    (unless splicep
-      (push (or (plist-get p :tend) "ERROR: no :tend") rtn))
-
-    (mapconcat 'identity (nreverse rtn) "\n")))
+    ;; Do we have a heading section?  If so, format it and handle the
+    ;; trailing hline.
+    (if (and (not splicep) (listp (car *orgtbl-table*))
+	     (memq 'hline *orgtbl-table*))
+	(progn
+	  (let* ((*orgtbl-lstart* (or (plist-get params :hlstart)
+				      *orgtbl-lstart*))
+		 (*orgtbl-llstart* (or (plist-get params :hllstart)
+				       *orgtbl-llstart*))
+		 (*orgtbl-lend* (or (plist-get params :hlend) *orgtbl-lend*))
+		 (*orgtbl-llend* (or (plist-get params :hllend)
+				     (plist-get params :hlend) *orgtbl-llend*))
+		 (*orgtbl-lfmt* (or (plist-get params :hlfmt) *orgtbl-lfmt*))
+		 (*orgtbl-llfmt* (or (plist-get params :hllfmt)
+				     (plist-get params :hlfmt) *orgtbl-llfmt*))
+		 (*orgtbl-sep* (or (plist-get params :hlsep) *orgtbl-sep*))
+		 (*orgtbl-fmt* (or (plist-get params :hfmt) *orgtbl-fmt*)))
+	    (orgtbl-format-section 'hline))
+	  (if hline (push hline *orgtbl-rtn*))
+	  (pop *orgtbl-table*)))
+    
+    ;; Now format the main section.
+    (orgtbl-format-section nil)
+    
+    (unless splicep 
+      (push (or (orgtbl-eval-str (plist-get params :tend))
+		"ERROR: no :tend") *orgtbl-rtn*))
+    
+    (mapconcat 'identity (nreverse (if remove-nil-linesp
+				       (remq nil *orgtbl-rtn*)
+				     *orgtbl-rtn*)) "\n")))
 
 (defun orgtbl-to-latex (table params)
   "Convert the orgtbl-mode TABLE to LaTeX.
@@ -3719,11 +3819,16 @@ LaTeX are:
            original field value.  For example, to wrap everything in dollars,
            use :fmt \"$%s$\".  This may also be a property list with column
            numbers and formats.  For example :fmt (2 \"$%s$\" 4 \"%s%%\")
+           The format may also be a function that formats its one argument.
 
 :efmt      Format for transforming numbers with exponentials.  The format
            should have %s twice for inserting mantissa and exponent, for
            example \"%s\\\\times10^{%s}\".  LaTeX default is \"%s\\\\,(%s)\".
            This may also be a property list with column numbers and formats.
+           The format may also be a function that formats its two arguments.
+
+:llend     If you find too much space below the last line of a table,
+           pass a value of \"\" for :llend to suppress the final \\\\.
 
 The general parameters :skip and :skipcols have already been applied when
 this function is called."
@@ -3782,6 +3887,8 @@ TeXInfo are:
                    everything in @kbd{}, you could use :fmt \"@kbd{%s}\".
                    This may also be a property list with column numbers and
                    formats.  For example :fmt (2 \"@kbd{%s}\" 4 \"@code{%s}\").
+                   Each format also may be a function that formats its one
+                   argument.
 
 :cf \"f1 f2..\"    The column fractions for the table.  By default these
                    are computed automatically from the width of the columns

+ 1 - 0
lisp/org.el

@@ -185,6 +185,7 @@ to add the symbol `xyz', and the package must have a call to
 	(const :tag "C  org2rem:           Convert org appointments into reminders" org2rem)
 	(const :tag "C  screen:            Visit screen sessions through Org-mode links" org-screen)
 	(const :tag "C  toc:               Table of contents for Org-mode buffer" org-toc)
+	(const :tag "C  sqlinsert:         Convert Org-mode tables to SQL insertions" orgtbl-sqlinsert)
 	(repeat :tag "External packages" :inline t (symbol :tag "Package"))))