浏览代码

Tables: Make @< and $< point to row/column 1 in a stable way

Carsten Dominik 14 年之前
父节点
当前提交
3dd4745752
共有 2 个文件被更改,包括 99 次插入83 次删除
  1. 49 53
      doc/org.texi
  2. 50 30
      lisp/org-table.el

+ 49 - 53
doc/org.texi

@@ -2263,48 +2263,42 @@ field, or press @kbd{C-c @}} to toggle the display of a grid.
 Formulas can reference the value of another field in two ways.  Like in
 Formulas can reference the value of another field in two ways.  Like in
 any other spreadsheet, you may reference fields with a letter/number
 any other spreadsheet, you may reference fields with a letter/number
 combination like @code{B3}, meaning the 2nd field in the 3rd row.
 combination like @code{B3}, meaning the 2nd field in the 3rd row.
-
-@noindent
 @vindex org-table-use-standard-references
 @vindex org-table-use-standard-references
-Org prefers@footnote{Org will understand references typed by the user as
-@samp{B4}, but it will not use this syntax when offering a formula for
-editing.  You can customize this behavior using the variable
+However, Org prefers@footnote{Org will understand references typed by the
+user as @samp{B4}, but it will not use this syntax when offering a formula
+for editing.  You can customize this behavior using the variable
 @code{org-table-use-standard-references}.}  to use another, more general
 @code{org-table-use-standard-references}.}  to use another, more general
-operator that looks like this:
+representation that looks like this:
 @example
 @example
 @@@var{row}$@var{column}
 @@@var{row}$@var{column}
 @end example
 @end example
 
 
-@noindent
-and allows relative references, i.e. references relative to the
-row/column of the field whose value is being computed.  These relative
-references make it possible to store a formula only once and use it in many
-fields without copying and modifying it.
-
-Column references can be absolute like @samp{1}, @samp{2},...@samp{@var{N}},
-or relative to the current column like @samp{+1} or @samp{-2}.  @code{$>}
-references the last column in the table, and you can use offsets like
-@code{$>-2}, meaning the third column from the right.
+Column specifications can be absolute like @code{$1},
+@code{$2},...@code{$@var{N}}, or relative to the current column (i.e.@: the
+column of the field which is being computed) like @code{$+1} or @code{$-2}.
+@code{$<} and @code{$>} are immutable references to the first and last
+column, respectively, and you can use @code{$>>>} to indicate the third
+column from the right.
 
 
 The row specification only counts data lines and ignores horizontal separator
 The row specification only counts data lines and ignores horizontal separator
 lines (hlines).  Like with columns, you can use absolute row numbers
 lines (hlines).  Like with columns, you can use absolute row numbers
-@samp{1}...@samp{@var{N}}, and row numbers relative to the current row like
-@samp{+3} or @samp{-1}, and @code{@@>} references the last row in the
-table@footnote{For backward compatibility you can also use special names like
-@samp{$LR5} and @samp{$LR12} to refer in a stable way to the 5th and 12th
-field in the last row of the table.  However, this syntax is deprecated, it
-should not be used for new documents.}.  You may also specify the row
-relative to one of the hlines: @samp{I} refers to the first
-hline@footnote{Note that only hlines are counted that @emph{separate} table
-lines.  If the table starts with a hline above the header, it does not
-count.}, @samp{II} to the second, etc@.  @samp{-I} refers to the first such
-line above the current line, @samp{+I} to the first such line below the
-current line.  You can also write @samp{III+2} which is the second data line
+@code{@@1}, @code{@@2},...@code{@@@var{N}}, and row numbers relative to the
+current row like @code{@@+3} or @code{@@-1}.  @code{@@<} and @code{@@>} are
+immutable references the first and last@footnote{For backward compatibility
+you can also use special names like @code{$LR5} and @code{$LR12} to refer in
+a stable way to the 5th and 12th field in the last row of the table.
+However, this syntax is deprecated, it should not be used for new documents.
+Use @code{@@>$} instead.} row in the table, respectively.  You may also
+specify the row relative to one of the hlines: @code{@@I} refers to the first
+hline, @code{@@II} to the second, etc@.  @code{@@-I} refers to the first such
+line above the current line, @code{@@+I} to the first such line below the
+current line.  You can also write @code{@@III+2} which is the second data line
 after the third hline in the table.
 after the third hline in the table.
 
 
-@samp{0} refers to the current row and column.  Also, if you omit
-either the column or the row part of the reference, the current
-row/column is implied.
+@code{@@0} and @code{$0} refer to the current row and column, respectively,
+i.e. to the row/column for the field being computed.  Also, if you omit
+either the column or the row part of the reference, the current row/column is
+implied.
 
 
 Org's references with @emph{unsigned} numbers are fixed references
 Org's references with @emph{unsigned} numbers are fixed references
 in the sense that if you use the same reference in the formula for two
 in the sense that if you use the same reference in the formula for two
@@ -2313,17 +2307,15 @@ Org's references with @emph{signed} numbers are floating
 references because the same reference operator can reference different
 references because the same reference operator can reference different
 fields depending on the field being calculated by the formula.
 fields depending on the field being calculated by the formula.
 
 
-
 Here are a few examples:
 Here are a few examples:
 
 
 @example
 @example
-@@2$3      @r{2nd row, 3rd column}
-C2        @r{same as previous}
-$5        @r{column 5 in the current row}
-E&        @r{same as previous}
+@@2$3      @r{2nd row, 3rd column (same as @code{C2})}
+$5        @r{column 5 in the current row (same as @code{E&})}
 @@2        @r{current column, row 2}
 @@2        @r{current column, row 2}
 @@-1$-3    @r{the field one row up, three columns to the left}
 @@-1$-3    @r{the field one row up, three columns to the left}
 @@-I$2     @r{field just under hline above current row, column 2}
 @@-I$2     @r{field just under hline above current row, column 2}
+@@>$5      @r{field in the last row, in column 5}
 @end example
 @end example
 
 
 @subsubheading Range references
 @subsubheading Range references
@@ -2338,12 +2330,12 @@ format at least for the first field (i.e the reference must start with
 @samp{@@} in order to be interpreted correctly).  Examples:
 @samp{@@} in order to be interpreted correctly).  Examples:
 
 
 @example
 @example
-$1..$3        @r{First three fields in the current row}
-$P..$Q        @r{Range, using column names (see under Advanced)}
-@@2$1..@@4$3    @r{6 fields between these two fields}
-A2..C4        @r{Same as above}
+$1..$3        @r{first three fields in the current row}
+$P..$Q        @r{range, using column names (see under Advanced)}
+$<<<..$>>     @r{start in third column, continue to the one but last}
+@@2$1..@@4$3    @r{6 fields between these two fields (same as @code{A2..C4})}
 @@-1$-2..@@-1   @r{3 numbers from the column to the left, 2 up to current row}
 @@-1$-2..@@-1   @r{3 numbers from the column to the left, 2 up to current row}
-@@I..II        @r{Between first and second hline, short for @code{@@I..@@II}}
+@@I..II        @r{between first and second hline, short for @code{@@I..@@II}}
 @end example
 @end example
 
 
 @noindent Range references return a vector of values that can be fed
 @noindent Range references return a vector of values that can be fed
@@ -2505,7 +2497,7 @@ taylor($3,x=7,2)     @r{Taylor series of $3, at x=7, second degree}
 Calc also contains a complete set of logical operations.  For example
 Calc also contains a complete set of logical operations.  For example
 
 
 @example
 @example
-if($1<20,teen,string(""))  @r{``teen'' if age $1 less than 20, else empty}
+if($1<20,teen,string(""))  @r{"teen" if age $1 less than 20, else empty}
 @end example
 @end example
 
 
 @node Formula syntax for Lisp, Field and range formulas, Formula syntax for Calc, The spreadsheet
 @node Formula syntax for Lisp, Field and range formulas, Formula syntax for Calc, The spreadsheet
@@ -2553,16 +2545,20 @@ the formula will be stored as the formula for this field, evaluated, and the
 current field will be replaced with the result.
 current field will be replaced with the result.
 
 
 @cindex #+TBLFM
 @cindex #+TBLFM
-Formulas are stored in a special line starting with @samp{#+TBLFM:}
-directly below the table.  If you type the equation in the 4th field of
-the 3rd data line in the table, the formula will look like
-@samp{@@3$4=$1+$2}.  When inserting/deleting/swapping column and rows
-with the appropriate commands, @i{absolute references} (but not relative
-ones) in stored formulas are modified in order to still reference the
-same field.  Of course this is not true if you edit the table structure
-with normal editing commands---then you must fix the equations yourself.
-Instead of typing an equation into the field, you may also use the
-following command
+Formulas are stored in a special line starting with @samp{#+TBLFM:} directly
+below the table.  If you type the equation in the 4th field of the 3rd data
+line in the table, the formula will look like @samp{@@3$4=$1+$2}.  When
+inserting/deleting/swapping column and rows with the appropriate commands,
+@i{absolute references} (but not relative ones) in stored formulas are
+modified in order to still reference the same field.  To avoid this from
+happening, in particular in range references, anchor ranges at the table
+borders (using @code{@@<}, @code{@@>}, @code{$<}, @code{$>}), or at hlines
+using the @code{@@I} notation.  Automatic adaptation of field references does
+of cause not happen if you edit the table structure with normal editing
+commands---then you must fix the equations yourself.
+
+Instead of typing an equation into the field, you may also use the following
+command
 
 
 @table @kbd
 @table @kbd
 @orgcmd{C-u C-c =,org-table-eval-formula}
 @orgcmd{C-u C-c =,org-table-eval-formula}
@@ -2582,7 +2578,7 @@ directly.
 Column formula, valid for the entire column.  This is so common that Org
 Column formula, valid for the entire column.  This is so common that Org
 treats these formulas in a special way, see @ref{Column formulas}.
 treats these formulas in a special way, see @ref{Column formulas}.
 @item @@3=
 @item @@3=
-Row formula, applies to all fields in the specified row.  @code{@@L=} means
+Row formula, applies to all fields in the specified row.  @code{@@>=} means
 the last row.
 the last row.
 @item @@1$2..@@4$3=
 @item @@1$2..@@4$3=
 Range formula, applies to all fields in the given rectangular range.  This
 Range formula, applies to all fields in the given rectangular range.  This

+ 50 - 30
lisp/org-table.el

@@ -1144,7 +1144,8 @@ is always the old value."
 	   (eql (org-table-expand-lhs-ranges
 	   (eql (org-table-expand-lhs-ranges
 		 (mapcar
 		 (mapcar
 		  (lambda (e)
 		  (lambda (e)
-		    (cons (org-table-formula-handle-lastrc (car e)) (cdr e)))
+		    (cons (org-table-formula-handle-first/last-rc
+			   (car e)) (cdr e)))
 		  (org-table-get-stored-formulas))))
 		  (org-table-get-stored-formulas))))
 	   (dline (org-table-current-dline))
 	   (dline (org-table-current-dline))
 	   (ref (format "@%d$%d" dline col))
 	   (ref (format "@%d$%d" dline col))
@@ -1999,15 +2000,23 @@ When NAMED is non-nil, look for a named equation."
 	    "\n")))
 	    "\n")))
 
 
 (defsubst org-table-formula-make-cmp-string (a)
 (defsubst org-table-formula-make-cmp-string (a)
-  (when (string-match "\\`$>" a)
-    ;; Fake a high number to make sure this is sorted at the end.
-    (setq a (org-table-formula-handle-lastrc a))
-    (setq a (format "$%d" (+ 10000 (string-to-number (substring a 1))))))
-  (when (string-match "^\\(@\\([0-9]+\\)\\)?\\(\\$?\\([0-9]+\\)\\)?\\(\\$?[a-zA-Z0-9]+\\)?" a)
+  (when (string-match "\\`$[<>]" a)
+    (let ((arrow (string-to-char (substring a 1))))
+      ;; Fake a high number to make sure this is sorted at the end.
+      (setq a (org-table-formula-handle-first/last-rc a))
+      (setq a (format "$%d" (+ 10000
+			       (if (= arrow ?<) -1000 0)
+			       (string-to-number (substring a 1)))))))
+  (when (string-match
+	 "^\\(@\\([0-9]+\\)\\)?\\(\\$?\\([0-9]+\\)\\)?\\(\\$?[a-zA-Z0-9]+\\)?"
+	 a)
     (concat
     (concat
-     (if (match-end 2) (format "@%05d" (string-to-number (match-string 2 a))) "")
-     (if (match-end 4) (format "$%05d" (string-to-number (match-string 4 a))) "")
-     (if (match-end 5) (concat "@@" (match-string 5 a))))))
+     (if (match-end 2)
+	 (format "@%05d" (string-to-number (match-string 2 a))) "")
+     (if (match-end 4)
+	 (format "$%05d" (string-to-number (match-string 4 a))) "")
+     (if (match-end 5)
+	 (concat "@@" (match-string 5 a))))))
 
 
 (defun org-table-formula-less-p (a b)
 (defun org-table-formula-less-p (a b)
   "Compare two formulas for sorting."
   "Compare two formulas for sorting."
@@ -2025,11 +2034,12 @@ When NAMED is non-nil, look for a named equation."
 	(setq strings (org-split-string (org-match-string-no-properties 2)
 	(setq strings (org-split-string (org-match-string-no-properties 2)
 					" *:: *"))
 					" *:: *"))
 	(while (setq string (pop strings))
 	(while (setq string (pop strings))
-	  (when (string-match "\\`\\(@[-+I>0-9.$@]+\\|@?[0-9]+\\|\\$\\([a-zA-Z0-9]+\\|>\\(?:[-+][0-9]+\\)?\\)\\) *= *\\(.*[^ \t]\\)" string)
+	  (when (string-match "\\`\\(@[-+I<>0-9.$@]+\\|@?[0-9]+\\|\\$\\([a-zA-Z0-9]+\\|[<>]+\\)\\) *= *\\(.*[^ \t]\\)" string)
 	    (setq scol (if (match-end 2)
 	    (setq scol (if (match-end 2)
 			   (match-string 2 string)
 			   (match-string 2 string)
 			 (match-string 1 string))
 			 (match-string 1 string))
-		  scol (if (= (string-to-char scol) ?>) (concat "$" scol) scol)
+		  scol (if (member (string-to-char scol) '(?< ?>))
+			   (concat "$" scol) scol)
 		  eq (match-string 3 string)
 		  eq (match-string 3 string)
 		  eq-alist (cons (cons scol eq) eq-alist))
 		  eq-alist (cons (cons scol eq) eq-alist))
 	    (if (member scol seen)
 	    (if (member scol seen)
@@ -2673,19 +2683,19 @@ known that the table will be realigned a little later anyway."
       ;; Insert constants in all formulas
       ;; Insert constants in all formulas
       (setq eqlist
       (setq eqlist
 	    (mapcar (lambda (x)
 	    (mapcar (lambda (x)
-		      (when (string-match "\\`$>" (car x))
+		      (when (string-match "\\`$[<>]" (car x))
 			(setq lhs1 (car x))
 			(setq lhs1 (car x))
 			(setq x (cons (substring
 			(setq x (cons (substring
-				       (org-table-formula-handle-lastrc
+				       (org-table-formula-handle-first/last-rc
 					(car x)) 1)
 					(car x)) 1)
 				      (cdr x)))
 				      (cdr x)))
 			(if (assoc (car x) eqlist1)
 			(if (assoc (car x) eqlist1)
 			    (error "\"%s=\" formula tries to overwrite existing formula for column %s"
 			    (error "\"%s=\" formula tries to overwrite existing formula for column %s"
 				   lhs1 (car x))))
 				   lhs1 (car x))))
 		      (cons
 		      (cons
-		       (org-table-formula-handle-lastrc (car x))
+		       (org-table-formula-handle-first/last-rc (car x))
 		       (org-table-formula-substitute-names
 		       (org-table-formula-substitute-names
-			(org-table-formula-handle-lastrc (cdr x)))))
+			(org-table-formula-handle-first/last-rc (cdr x)))))
 		    eqlist))
 		    eqlist))
       ;; Split the equation list
       ;; Split the equation list
       (while (setq eq (pop eqlist))
       (while (setq eq (pop eqlist))
@@ -2868,19 +2878,29 @@ them to individual field equations for each field."
 				       :orig-eqn e (caar res)))))))
 				       :orig-eqn e (caar res)))))))
     (nreverse res)))
     (nreverse res)))
 
 
-(defun org-table-formula-handle-lastrc (s)
-  "Replace @> / $> with the last row/column of the table."
-  (while (string-match "\\([@$]\\)>\\(-[0-9]+\\)?" s)
-    (setq s (replace-match
-	     (format "%s%d"
-		     (match-string 1 s)
-		     (+ (if (equal (match-string 1 s) "@")
-			    (1- (length org-table-dlines))
-			  org-table-current-ncol)
-			(if (match-end 2)
-			    (string-to-number (match-string 2 s))
-			  0)))
-	     t t s)))
+(defun org-table-formula-handle-first/last-rc (s)
+  "Replace @<, @>, $<, $> with first/last row/column of the table.
+So @< and $< will always be replaced with @1 and $1, respectively.
+The advantage of these special markers are that structure editing of
+the table will not change them, while @1 and $1 will be modified
+when a line/row is swaped out of that privileged position.  So for
+formulas that use a range of rows or columns, it may often be better
+to anchor the formula with \"I\" row markers, or to offset from the
+borders of the table using the @< @> $< $> makers."
+  (let (n nmax len)
+    (while (string-match "\\([@$]\\)\\(<+\\|>+\\)" s)
+      (setq nmax (if (equal (match-string 1 s) "@")
+		     (1- (length org-table-dlines))
+		   org-table-current-ncol)
+	    len (- (match-end 2) (match-beginning 2))
+	    char (string-to-char (match-string 2 s))
+	    n (if (= char ?<)
+		  len
+		(- nmax len -1)))
+      (if (or (< n 1) (> n nmax))
+	  (error "Reference \"%s\" in expression \"%s\" points outside table"
+		 (match-string 0 s) s))
+      (setq s (replace-match (format "%s%d" (match-string 1 s) n) t t s))))
   s)
   s)
 
 
 (defun org-table-formula-substitute-names (f)
 (defun org-table-formula-substitute-names (f)
@@ -3003,7 +3023,7 @@ Parameters get priority."
     (setq startline (org-current-line))
     (setq startline (org-current-line))
     (while (setq entry (pop eql))
     (while (setq entry (pop eql))
       (setq type (cond
       (setq type (cond
-		  ((string-match "\\`$>" (car entry)) 'column)
+		  ((string-match "\\`$[<>]" (car entry)) 'column)
 		  ((equal (string-to-char (car entry)) ?@) 'field)
 		  ((equal (string-to-char (car entry)) ?@) 'field)
 		  ((string-match "^[0-9]" (car entry)) 'column)
 		  ((string-match "^[0-9]" (car entry)) 'column)
 		  (t 'named)))
 		  (t 'named)))
@@ -3227,7 +3247,7 @@ With prefix ARG, apply the new formulas to the table."
   (let ((pos org-pos) (sel-win org-selected-window) eql var form)
   (let ((pos org-pos) (sel-win org-selected-window) eql var form)
     (goto-char (point-min))
     (goto-char (point-min))
     (while (re-search-forward
     (while (re-search-forward
-	    "^\\(@[-+I>0-9.$@]+\\|@?[0-9]+\\|\\$\\([a-zA-Z0-9]+\\|>[-+0-9]*\\)\\) *= *\\(.*\\(\n[ \t]+.*$\\)*\\)"
+	    "^\\(@[-+I<>0-9.$@]+\\|@?[0-9]+\\|\\$\\([a-zA-Z0-9]+\\|[<>]+\\)\\) *= *\\(.*\\(\n[ \t]+.*$\\)*\\)"
 	    nil t)
 	    nil t)
       (setq var (if (match-end 2) (match-string 2) (match-string 1))
       (setq var (if (match-end 2) (match-string 2) (match-string 1))
 	    form (match-string 3))
 	    form (match-string 3))