Browse Source

org-list: correctly handle counters in `org-list-parse-list'

* lisp/org-list.el (org-at-item-counter-p): new function.
(org-list-parse-list): handle counters and list depth.
(org-list-to-generic): a special string is used when an item has a
counter.
(org-list-to-latex): use new special string for counters. This fixes
the counter bug in LaTeX export, as the enumi counter was the only one
modified.
* lisp/org-latex.el (org-export-latex-lists): use new
`org-list-parse-list' output.
Nicolas Goaziou 14 years ago
parent
commit
212828c556
2 changed files with 106 additions and 77 deletions
  1. 0 20
      lisp/org-latex.el
  2. 106 57
      lisp/org-list.el

+ 0 - 20
lisp/org-latex.el

@@ -2482,26 +2482,6 @@ The conversion is made depending of STRING-BEFORE and STRING-AFTER."
 		    (let ((org-list-end-re "^ORG-LIST-END\n"))
 		      (org-list-parse-list t)))
 		  org-export-latex-list-parameters))
-	   ;; Replace any counter with its latex expression in string.
-           ;;
-	   ;; FIXME: enumi is for top list only. Sub-lists are using
-	   ;;        enumii, enumiii, enumiv. So, basically, using a
-	   ;;        counter within a sublist will break top-level
-	   ;;        item numbering.
-	   (while (string-match
-		   "^\\(\\\\item[ \t]+\\)\\[@\\(?:start:\\)?\\([0-9]+\\|[A-Z-a-z]\\)\\]"
-		   res)
-	     (let ((count (match-string 2 res)))
-	       (setq res (replace-match
-			  (concat
-			   ;; Filter out non-numeric counters,
-			   ;; unsupported in standard LaTeX.
-			   (if (save-match-data (string-match "[0-9]" count))
-			       (format "\\setcounter{enumi}{%d}\n"
-				       (1- (string-to-number count)))
-			     "")
-			   (match-string 1 res))
-			  t t res))))
 	   ;; Extend previous value of original-indentation to the
 	   ;; whole string
 	   (insert (org-add-props res nil 'original-indentation

+ 106 - 57
lisp/org-list.el

@@ -805,6 +805,12 @@ This checks `org-list-ending-method'."
   "Is point at a line starting a plain-list item with a checklet?"
   (org-list-at-regexp-after-bullet-p "\\(\\[[- X]\\]\\)[ \t]+"))
 
+(defun org-at-item-counter-p ()
+  "Is point at a line starting a plain-list item with a counter?"
+  (and (org-at-item-p)
+       (looking-at org-list-full-item-re)
+       (match-string 2)))
+
 ;;; Navigate
 
 (defalias 'org-list-get-item-begin 'org-in-item-p)
@@ -2491,8 +2497,9 @@ compare entries."
 
 Return a list whose car is a symbol of list type, among
 `ordered', `unordered' and `descriptive'. Then, each item is a
-list whose elements are strings and other sub-lists. Inside
-strings, checkboxes are replaced by \"[CBON]\" and \"[CBOFF]\".
+list whose car is counter, and cdr are strings and other
+sub-lists. Inside strings, checkboxes are replaced by \"[CBON]\"
+and \"[CBOFF]\".
 
 For example, the following list:
 
@@ -2500,14 +2507,17 @@ For example, the following list:
    + sub-item one
    + [X] sub-item two
    more text in first item
-2. last item
+2. [@3] last item
 
 will be parsed as:
 
-\(ordered \(\"first item\"
-	  \(unordered \(\"sub-item one\"\) \(\"[CBON] sub-item two\"\)\)
-	  \"more text in first item\"\)
-	 \(\"last item\"\)\)
+\(ordered
+  \(nil \"first item\"
+  \(unordered
+    \(nil \"sub-item one\"\)
+    \(nil \"[CBON] sub-item two\"\)\)
+  \"more text in first item\"\)
+  \(3 \"last item\"\)\)
 
 Point is left at list end."
   (let* ((struct (org-list-struct))
@@ -2516,54 +2526,70 @@ Point is left at list end."
 	 (top (org-list-get-top-point struct))
 	 (bottom (org-list-get-bottom-point struct))
 	 out
+	 (get-text
+	  (function
+	   ;; Return text between BEG and END, trimmed, with
+	   ;; checkboxes replaced.
+	   (lambda (beg end)
+	     (let ((text (org-trim (buffer-substring beg end))))
+	       (if (string-match "\\`\\[\\([xX ]\\)\\]" text)
+		   (replace-match
+		    (if (equal (match-string 1 text) " ") "CBOFF" "CBON")
+		    t nil text 1)
+		 text)))))
 	 (parse-sublist
 	  (function
-	   ;; return a list whose car is list type and cdr a list of
+	   ;; Return a list whose car is list type and cdr a list of
 	   ;; items' body.
 	   (lambda (e)
 	     (cons (org-list-get-list-type (car e) struct prevs)
 		   (mapcar parse-item e)))))
 	 (parse-item
 	  (function
-	   ;; return a list containing text and any sublist inside
-	   ;; item.
+	   ;; Return a list containing conter of item, if any, text
+	   ;; and any sublist inside it.
 	   (lambda (e)
 	     (let ((start (save-excursion
 			    (goto-char e)
-			    (looking-at (org-item-beginning-re))
+			    (or (org-at-item-counter-p) (org-at-item-p))
 			    (match-end 0)))
+		   ;; Get counter number. For alphabetic counter, get
+		   ;; its position in the alphabet.
+		   (counter (let ((c (org-list-get-counter e struct)))
+			      (cond
+			       ((not c) nil)
+			       ((string-match "[A-Za-z]" c)
+				(- (string-to-char (upcase (match-string 0 c)))
+				   64))
+			       ((string-match "[0-9]+" c)
+				(string-to-number (match-string 0 c))))))
 		   (childp (org-list-has-child-p e struct))
 		   (end (org-list-get-item-end e struct)))
+	       ;; If item has a child, store text between bullet and
+	       ;; next child, then recursively parse all sublists. At
+	       ;; the end of each sublist, check for the presence of
+	       ;; text belonging to the original item.
 	       (if childp
 		   (let* ((children (org-list-get-children e struct parents))
-			  (body (list (funcall get-text start childp t))))
+			  (body (list (funcall get-text start childp))))
 		     (while children
 		       (let* ((first (car children))
 			      (sub (org-list-get-all-items first struct prevs))
 			      (last-c (car (last sub)))
 			      (last-end (org-list-get-item-end last-c struct)))
 			 (push (funcall parse-sublist sub) body)
+			 ;; Remove children from the list just parsed.
 			 (setq children (cdr (member last-c children)))
+			 ;; There is a chunk of text belonging to the
+			 ;; item if last child doesn't end where next
+			 ;; child starts or where item ends.
 			 (unless (= (or (car children) end) last-end)
-			   (push (funcall get-text last-end (or (car children) end) nil)
+			   (push (funcall get-text
+					  last-end (or (car children) end))
 				 body))))
-		     (nreverse body))
-		 (list (funcall get-text start end t)))))))
-	 (get-text
-	  (function
-	   ;; return text between BEG and END, trimmed, with
-	   ;; checkboxes replaced if BOX is true.
-	   (lambda (beg end box)
-	     (let ((text (org-trim (buffer-substring beg end))))
-	       (if (and box
-			(string-match
-			 "^\\(?:\\[@\\(?:start:\\)?\\(?:[0-9]+\\|[A-Za-z]\\)\\][ \t]*\\)?\\[\\([xX ]\\)\\]"
-			 text))
-		   (replace-match
-		    (if (equal (match-string 1 text) " ") "CBOFF" "CBON")
-		    t nil text 1)
-		 text))))))
-    ;; store output, take care of cursor position and deletion of
+		     (cons counter (nreverse body)))
+		 (list counter (funcall get-text start end))))))))
+    ;; Store output, take care of cursor position and deletion of
     ;; list, then return output.
     (setq out (funcall parse-sublist (org-list-get-all-items top struct prevs)))
     (goto-char top)
@@ -2684,15 +2710,23 @@ Valid parameters PARAMS are
 :splice	    When set to t, return only list body lines, don't wrap
 	    them into :[u/o]start and :[u/o]end.  Default is nil.
 
-:istart	    String to start a list item
+:istart	    String to start a list item.
+:icount     String to start an item with a counter.
 :iend	    String to end a list item
 :isep	    String to separate items
 :lsep	    String to separate sublists
 
 :cboff      String to insert for an unchecked checkbox
-:cbon       String to insert for a checked checkbox"
+:cbon       String to insert for a checked checkbox
+
+Alternatively, each parameter can also be a form returning a
+string. These sexp can use keywords `counter' and `depth',
+reprensenting respectively counter associated to the current
+item, and depth of the current sub-list, starting at 0.
+Obviously, `counter' is only available for parameters applying to
+items."
   (interactive)
-  (let* ((p params) sublist
+  (let* ((p params)
 	 (splicep (plist-get p :splice))
 	 (ostart (plist-get p :ostart))
 	 (oend (plist-get p :oend))
@@ -2705,6 +2739,7 @@ Valid parameters PARAMS are
 	 (ddstart (plist-get p :ddstart))
 	 (ddend (plist-get p :ddend))
 	 (istart (plist-get p :istart))
+	 (icount (plist-get p :icount))
 	 (iend (plist-get p :iend))
 	 (isep (plist-get p :isep))
 	 (lsep (plist-get p :lsep))
@@ -2712,14 +2747,19 @@ Valid parameters PARAMS are
 	 (cboff (plist-get p :cboff))
 	 (export-item
 	  (function
-	   ;; Export an item ITEM of type TYPE. First string in item
-	   ;; is treated in a special way as it can bring extra
-	   ;; information that needs to be processed.
-	   (lambda (item type)
-	     (let ((fmt (if (eq type 'descriptive)
-			    (concat (org-trim istart) "%s" ddend iend isep)
-			  (concat istart "%s" iend isep)))
-		   (first (car item)))
+	   ;; Export an item ITEM of type TYPE, at DEPTH. First string
+	   ;; in item is treated in a special way as it can bring
+	   ;; extra information that needs to be processed.
+	   (lambda (item type depth)
+	     (let* ((counter (pop item))
+		    (fmt (cond
+			  ((eq type 'descriptive)
+			   (mapconcat 'eval `(,(org-trim istart)
+					      "%s" ,ddend ,iend ,isep) ""))
+			  ((and counter (eq type 'ordered))
+			   (mapconcat 'eval `(,icount "%s" ,iend ,isep) ""))
+			  (t (mapconcat 'eval `(,istart "%s" ,iend ,isep) ""))))
+		    (first (car item)))
 	       ;; Replace checkbox if any is found.
 	       (cond
 		((string-match "\\[CBON\\]" first)
@@ -2731,31 +2771,33 @@ Valid parameters PARAMS are
 	       ;; Insert descriptive term if TYPE is `descriptive'.
 	       (when (and (eq type 'descriptive)
 			  (string-match "^\\(.*\\)[ \t]+::" first))
-		 (setq first (concat
-			      dtstart (org-trim (match-string 1 first)) dtend
-			      ddstart (org-trim (substring first (match-end 0))))))
+		 (setq first (mapconcat
+			      'eval
+			      `(,dtstart ,(org-trim (match-string 1 first)) ,dtend
+					 ,ddstart ,(org-trim (substring first (match-end 0)))))))
 	       (setcar item first)
-	       (format fmt (mapconcat
-			    (lambda (e)
-			      (if (stringp e) e (funcall export-sublist e)))
-			    item isep))))))
+	       (format fmt
+		       (mapconcat (lambda (e)
+				    (if (stringp e) e
+				      (funcall export-sublist e (1+ depth))))
+				  item isep))))))
 	 (export-sublist
 	  (function
-	   ;; Export sublist SUB
-	   (lambda (sub)
+	   ;; Export sublist SUB at DEPTH
+	   (lambda (sub depth)
 	     (let* ((type (car sub))
 		    (items (cdr sub))
 		    (fmt (cond
 			  (splicep "%s")
 			  ((eq type 'ordered)
-			   (concat ostart "\n%s" oend))
+			   (mapconcat 'eval `(,ostart "\n%s" ,oend) ""))
 			  ((eq type 'descriptive)
-			   (concat dstart "\n%s" dend))
-			  (t (concat ustart "\n%s" uend)))))
-	       (format fmt (mapconcat
-			    (lambda (e) (funcall export-item e type))
-			    items lsep)))))))
-    (concat (funcall export-sublist list) "\n")))
+			   (mapconcat 'eval `(,dstart "\n%s" ,dend) ""))
+			  (t (mapconcat 'eval `(,ustart "\n%s" ,uend) "")))))
+	       (format fmt (mapconcat (lambda (e)
+					(funcall export-item e type depth))
+				      items lsep)))))))
+    (concat (funcall export-sublist list 0) "\n")))
 
 (defun org-list-to-latex (list &optional params)
   "Convert LIST into a LaTeX list.
@@ -2770,6 +2812,11 @@ with overruling parameters for `org-list-to-generic'."
 	       :dtstart "[" :dtend "] "
 	       :ddstart "" :ddend ""
 	       :istart "\\item " :iend ""
+	       :icount (let ((enum (nth depth '("i" "ii" "iii" "iv"))))
+			 (if enum
+			     (format "\\setcounter{enum%s}{%s}\n\\item "
+				     enum counter)
+			   "\\item "))
 	       :isep "\n" :lsep "\n"
 	       :cbon "\\texttt{[X]}" :cboff "\\texttt{[ ]}")
     params)))
@@ -2787,6 +2834,7 @@ with overruling parameters for `org-list-to-generic'."
 	       :dtstart "<dt>" :dtend "</dt>"
 	       :ddstart "<dd>" :ddend "</dd>"
 	       :istart "<li>" :iend "</li>"
+	       :icount (format "<li value=\"%s\">" counter)
 	       :isep "\n" :lsep "\n"
 	       :cbon "<code>[X]</code>" :cboff "<code>[ ]</code>")
     params)))
@@ -2804,6 +2852,7 @@ with overruling parameters for `org-list-to-generic'."
 	       :dtstart " " :dtend "\n"
 	       :ddstart "" :ddend ""
 	       :istart "@item\n" :iend ""
+	       :icount "@item\n"
 	       :isep "\n" :lsep "\n"
 	       :cbon "@code{[X]}" :cboff "@code{[ ]}")
     params)))