Browse Source

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

Carsten Dominik 9 years ago
parent
commit
3dd4745752
2 changed files with 99 additions and 83 deletions
  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
 any other spreadsheet, you may reference fields with a letter/number
 combination like @code{B3}, meaning the 2nd field in the 3rd row.
-
-@noindent
 @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
-operator that looks like this:
+representation that looks like this:
 @example
 @@@var{row}$@var{column}
 @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
 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.
 
-@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
 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
 fields depending on the field being calculated by the formula.
 
-
 Here are a few examples:
 
 @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}
 @@-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}
+@@>$5      @r{field in the last row, in column 5}
 @end example
 
 @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:
 
 @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}
-@@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
 
 @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
 
 @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
 
 @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.
 
 @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
 @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
 treats these formulas in a special way, see @ref{Column formulas}.
 @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.
 @item @@1$2..@@4$3=
 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
 		 (mapcar
 		  (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))))
 	   (dline (org-table-current-dline))
 	   (ref (format "@%d$%d" dline col))
@@ -1999,15 +2000,23 @@ When NAMED is non-nil, look for a named equation."
 	    "\n")))
 
 (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
-     (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)
   "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)
 					" *:: *"))
 	(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)
 			   (match-string 2 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-alist (cons (cons scol eq) eq-alist))
 	    (if (member scol seen)
@@ -2673,19 +2683,19 @@ known that the table will be realigned a little later anyway."
       ;; Insert constants in all formulas
       (setq eqlist
 	    (mapcar (lambda (x)
-		      (when (string-match "\\`$>" (car x))
+		      (when (string-match "\\`$[<>]" (car x))
 			(setq lhs1 (car x))
 			(setq x (cons (substring
-				       (org-table-formula-handle-lastrc
+				       (org-table-formula-handle-first/last-rc
 					(car x)) 1)
 				      (cdr x)))
 			(if (assoc (car x) eqlist1)
 			    (error "\"%s=\" formula tries to overwrite existing formula for column %s"
 				   lhs1 (car x))))
 		      (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-handle-lastrc (cdr x)))))
+			(org-table-formula-handle-first/last-rc (cdr x)))))
 		    eqlist))
       ;; Split the equation list
       (while (setq eq (pop eqlist))
@@ -2868,19 +2878,29 @@ them to individual field equations for each field."
 				       :orig-eqn e (caar 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)
 
 (defun org-table-formula-substitute-names (f)
@@ -3003,7 +3023,7 @@ Parameters get priority."
     (setq startline (org-current-line))
     (while (setq entry (pop eql))
       (setq type (cond
-		  ((string-match "\\`$>" (car entry)) 'column)
+		  ((string-match "\\`$[<>]" (car entry)) 'column)
 		  ((equal (string-to-char (car entry)) ?@) 'field)
 		  ((string-match "^[0-9]" (car entry)) 'column)
 		  (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)
     (goto-char (point-min))
     (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)
       (setq var (if (match-end 2) (match-string 2) (match-string 1))
 	    form (match-string 3))