From mboxrd@z Thu Jan 1 00:00:00 1970 From: Rasmus Subject: Re: [patch, ox] Unnumbered headlines Date: Wed, 01 Oct 2014 00:54:12 +0200 Message-ID: <87lhp0iusr.fsf@pank.eu> References: <87lhqzyubg.fsf@gmx.us> <87bnrrp0tb.fsf@nicolasgoaziou.fr> <87r40n6nrg.fsf@gmx.us> <87egwmaxte.fsf@nicolasgoaziou.fr> <87k34y701i.fsf@gmx.us> <87fvfl86ct.fsf@nicolasgoaziou.fr> <87k34xghtt.fsf@gmx.us> <87ppeon4mw.fsf@nicolasgoaziou.fr> <87tx4020lh.fsf@gmx.us> <87vboflkil.fsf@nicolasgoaziou.fr> <87iokfdvi3.fsf@gmx.us> <87d2aiesce.fsf@nicolasgoaziou.fr> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Return-path: Received: from eggs.gnu.org ([2001:4830:134:3::10]:56164) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1XZ6JL-00057e-RX for emacs-orgmode@gnu.org; Tue, 30 Sep 2014 18:54:29 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1XZ6JE-0000IS-NG for emacs-orgmode@gnu.org; Tue, 30 Sep 2014 18:54:23 -0400 Received: from mout.gmx.net ([212.227.15.18]:59509) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1XZ6JE-0000Hb-4L for emacs-orgmode@gnu.org; Tue, 30 Sep 2014 18:54:16 -0400 List-Id: "General discussions about Org-mode." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-orgmode-bounces+geo-emacs-orgmode=m.gmane.org@gnu.org Sender: emacs-orgmode-bounces+geo-emacs-orgmode=m.gmane.org@gnu.org To: emacs-orgmode@gnu.org Cc: alantyree@gmail.com --=-=-= Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Hi, Alan did some testing on a slightly older version of this patch and he managed to publish his book without errors and with working links. So let's give it another shot. I briefly tested the output of LaTeX, html, texinfo, odt, md, and plaintext and made sure links work and that the right text is shown in the output. Nicolas Goaziou writes: >> Should I write tests for the new behavior? If so, tests for each >> backend or only for vanilla-ox functions? > Tests for "ox.el" are mandatory. See "test-ox.el" I have added some. I don't know if it's comprehensive enough. It was not obvious to me what to test. >> (ids (delq nil >> (list (org-element-property :CUSTOM_ID headline) >> - (concat "sec-" section-number) >> + (and section-number (concat "sec-" section-= number)) >> (org-element-property :ID headline)))) >> - (preferred-id (car ids)) >> + (preferred-id (org-export-get-headline-id headline info)) > > I think the following is more in the spirit of the code (you don't > ignore :custom-id property): > > (ids (delq nil > (list (org-element-property :CUSTOM_ID headline) > (org-export-get-headline-id headline info) > (org-element-property :ID headline)))) > (preferred-id (car ids)) But we are not checking that :CUSTOM_ID is unique. In ox-latex you're required to turn on a variable on to get this behavior (I could be mistaken here). For now I have done as you suggest. But I don't understand why we are favoring CUSTOM_ID here over the nice, unique label we've generated? >> - (let ((href .. >> (headline-label ... >> - (org-html--anchor ... I reintroduced the CUSTOM_ID in these. >> + (let ((num 0)) >> + (org-element-map data 'headline >> + (lambda (headline) >> + (unless (org-export-numbered-headline-p headline options) >> + (cons headline (list (setq num (1+ num))))))))) > > Last line: > > (list headline (incf num)) Oh incf is quite handy. Didn't know that one. I leave it as (cons headline (list (incf num))). Why? 'Cause that's the format used by `org-export--collect-headline-numbering'. While simpler is nicer, I think it's better not to have to consider different data structures depending on whether data is from `org-export--collect-headline-numbering' or `org-export--collect-unnumbered-headline-id'. If you feel the simpler structure is better we can also use that. > Use `assq' instead of `assoc'. Right, no need for equal here. >> +(defun org-export-get-unnumberd-headline-id (headline info) >> + "Return unnumbered HEADLINE id as list of numbers. >> INFO is a plist holding contextual information." >> - (cdr (assoc headline (plist-get info :headline-numbering)))) >> + (and (not (org-export-numbered-headline-p headline info)) >> + (cdr (assoc headline (plist-get info :unnumbered-headline-id))))) > > I don't think it is worth to make this function standalone. I don't see > any use case outside `org-export-get-headline-id'. I suggest to move it > there. Yeah, seems fair. >> + (unless >> + (or (org-export-get-node-property :UNNUMBERED headline) >> + (loop for parent in (org-export-get-genealogy headline) >> + when (org-export-get-node-property :UNNUMBERED parent) >> + return t)) > > (unless (org-some > (lambda (h) (org-not-nil (org-element-property :UNNUMBERED h))) > (org-export-get-genealogy headline)) > ...) Handy. AFAIK BLOB is not a member of (org-export-get-genealogy BLOB) (or so the output suggests), so (or =C2=B7 =C2=B7) is still needed. Thanks again, Rasmus -- There are known knowns; there are things we know that we know --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=0001-ox-Support-unnumbered-headlines-via-property.patch >From e25b297d285b31fd8a842356aa5818d303b4dec9 Mon Sep 17 00:00:00 2001 From: Rasmus Date: Sun, 21 Sep 2014 16:17:55 +0200 Subject: [PATCH] ox: Support unnumbered headlines via property. * ox.el (org-export--collect-headline-numbering): Ignore unnumbered headline. (org-export-get-headline-id, org-export--collect-unnumbered-headline-id): New functions. (org-export-numbered-headline-p): Further tests for unnumbered headline. * ox-odt.el (org-odt-headline, org-odt-link, org-odt-link--infer-description) ox-md.el (org-md-headline, org-md-link), ox-latex.el (org-latex-headline, org.latex-link), ox-html.el (org-html-headline, org-html-link), ox-ascii.el (org-ascii-link): Support ununbered headlines. * test-ox.el (test-org-export/org-export-get-headline-id): New test. --- lisp/org.el | 2 +- lisp/ox-ascii.el | 8 ++++++-- lisp/ox-html.el | 39 +++++++++++++++---------------------- lisp/ox-latex.el | 23 ++++++---------------- lisp/ox-md.el | 24 +++++++++++++---------- lisp/ox-odt.el | 39 +++++++++++++++++-------------------- lisp/ox.el | 52 +++++++++++++++++++++++++++++++++++++++++-------- testing/lisp/test-ox.el | 21 ++++++++++++++++++++ 8 files changed, 126 insertions(+), 82 deletions(-) diff --git a/lisp/org.el b/lisp/org.el index 9815eb4..12fabdc 100755 --- a/lisp/org.el +++ b/lisp/org.el @@ -15339,7 +15339,7 @@ but in some other way.") "LOCATION" "LOGGING" "COLUMNS" "VISIBILITY" "TABLE_EXPORT_FORMAT" "TABLE_EXPORT_FILE" "EXPORT_OPTIONS" "EXPORT_TEXT" "EXPORT_FILE_NAME" - "EXPORT_TITLE" "EXPORT_AUTHOR" "EXPORT_DATE" + "EXPORT_TITLE" "EXPORT_AUTHOR" "EXPORT_DATE" "UNNUMBERED" "ORDERED" "NOBLOCKING" "COOKIE_DATA" "LOG_INTO_DRAWER" "REPEAT_TO_STATE" "CLOCK_MODELINE_TOTAL" "STYLE" "HTML_CONTAINER_CLASS") "Some properties that are used by Org-mode for various purposes. diff --git a/lisp/ox-ascii.el b/lisp/ox-ascii.el index 6316e59..91bb7a5 100644 --- a/lisp/ox-ascii.el +++ b/lisp/ox-ascii.el @@ -1530,9 +1530,13 @@ INFO is a plist holding contextual information." (let ((number (org-export-get-ordinal destination info nil 'org-ascii--has-caption-p))) - (when number + (if number (if (atom number) (number-to-string number) - (mapconcat 'number-to-string number ".")))))))) + (mapconcat #'number-to-string number ".")) + ;; unnumbered headline + (when (eq 'headline (org-element-type destination)) + (format "[%s]" (org-export-data + (org-element-property :title destination) info))))))))) (t (if (not (org-string-nw-p desc)) (format "[%s]" raw-link) (concat (format "[%s]" desc) diff --git a/lisp/ox-html.el b/lisp/ox-html.el index 1d424cc..e32640d 100644 --- a/lisp/ox-html.el +++ b/lisp/ox-html.el @@ -2096,8 +2096,7 @@ INFO is a plist used as a communication channel." ;; Label. (org-export-solidify-link-text (or (org-element-property :CUSTOM_ID headline) - (concat "sec-" - (mapconcat #'number-to-string headline-number "-")))) + (org-export-get-headline-id headline info))) ;; Body. (concat (and (not (org-export-low-level-p headline info)) @@ -2321,7 +2320,7 @@ holding contextual information." (unless (org-element-property :footnote-section-p headline) (let* ((numberedp (org-export-numbered-headline-p headline info)) (numbers (org-export-get-headline-number headline info)) - (section-number (mapconcat #'number-to-string numbers "-")) + (section-number (and numbers (mapconcat #'number-to-string numbers "-"))) (level (+ (org-export-get-relative-level headline info) (1- (plist-get info :html-toplevel-hlevel)))) (todo (and (plist-get info :with-todo-keywords) @@ -2338,7 +2337,7 @@ holding contextual information." (contents (or contents "")) (ids (delq nil (list (org-element-property :CUSTOM_ID headline) - (concat "sec-" section-number) + (org-export-get-headline-id headline info) (org-element-property :ID headline)))) (preferred-id (car ids)) (extra-ids (mapconcat @@ -2369,7 +2368,7 @@ holding contextual information." (org-html--container headline info) (format "outline-container-%s" (or (org-element-property :CUSTOM_ID headline) - (concat "sec-" section-number))) + (org-export-get-headline-id headline info))) (concat (format "outline-%d" level) (and extra-class " ") extra-class) @@ -2808,20 +2807,11 @@ INFO is a plist holding contextual information. See ;; Link points to a headline. (headline (let ((href - ;; What href to use? - (cond - ;; Case 1: Headline is linked via it's CUSTOM_ID - ;; property. Use CUSTOM_ID. - ((string= type "custom-id") - (org-element-property :CUSTOM_ID destination)) - ;; Case 2: Headline is linked via it's ID property - ;; or through other means. Use the default href. - ((member type '("id" "fuzzy")) - (format "sec-%s" - (mapconcat 'number-to-string - (org-export-get-headline-number - destination info) "-"))) - (t (error "Shouldn't reach here")))) + ;; headline linked via CUSTOM_ID + (or (and (string= type "custom-id") + (org-element-property :CUSTOM_ID destination)) + (org-export-get-headline-id destination info) + (t (error "Shouldn't reach here")))) ;; What description to use? (desc ;; Case 1: Headline is numbered and LINK has no @@ -3073,13 +3063,16 @@ holding contextual information." (let* ((class-num (+ (org-export-get-relative-level parent info) (1- (plist-get info :html-toplevel-hlevel)))) (section-number - (mapconcat - 'number-to-string - (org-export-get-headline-number parent info) "-"))) + (and (org-export-numbered-headline-p parent info) + (mapconcat + 'number-to-string + (org-export-get-headline-number parent info) "-")))) ;; Build return value. (format "
\n%s
" class-num - (or (org-element-property :CUSTOM_ID parent) section-number) + (or (org-element-property :CUSTOM_ID parent) + section-number + (org-export-get-headline-id parent info)) contents))))) ;;;; Radio Target diff --git a/lisp/ox-latex.el b/lisp/ox-latex.el index 5dd79d3..ee48d19 100644 --- a/lisp/ox-latex.el +++ b/lisp/ox-latex.el @@ -1477,15 +1477,10 @@ holding contextual information." todo todo-type priority text tags info)) ;; Associate \label to the headline for internal links. (headline-label - (let ((custom-label - (and (plist-get info :latex-custom-id-labels) - (org-element-property :CUSTOM_ID headline)))) - (if custom-label (format "\\label{%s}\n" custom-label) - (format "\\label{sec-%s}\n" - (mapconcat - #'number-to-string - (org-export-get-headline-number headline info) - "-"))))) + (format "\\label{%s}\n" + (or (and (plist-get info :latex-custom-id-labels) + (org-element-property :CUSTOM_ID headline)) + (org-export-get-headline-id headline info)))) (pre-blanks (make-string (org-element-property :pre-blank headline) 10))) (if (or (not section-fmt) (org-export-low-level-p headline info)) @@ -1975,14 +1970,8 @@ INFO is a plist holding contextual information. See (let* ((custom-label (and (plist-get info :latex-custom-id-labels) (org-element-property :CUSTOM_ID destination))) - (label - (or - custom-label - (format "sec-%s" - (mapconcat - #'number-to-string - (org-export-get-headline-number destination info) - "-"))))) + (label (or custom-label + (org-export-get-headline-id destination info)))) (if (and (not desc) (org-export-numbered-headline-p destination info)) (format "\\ref{%s}" label) diff --git a/lisp/ox-md.el b/lisp/ox-md.el index 695fb61..b1f2bfb 100644 --- a/lisp/ox-md.el +++ b/lisp/ox-md.el @@ -204,10 +204,7 @@ a communication channel." (when (plist-get info :with-toc) (org-html--anchor (or (org-element-property :CUSTOM_ID headline) - (concat "sec-" - (mapconcat 'number-to-string - (org-export-get-headline-number - headline info) "-"))) + (org-export-get-headline-id headline info)) nil nil info))) ;; Headline text without tags. (heading (concat todo priority title)) @@ -330,10 +327,13 @@ a communication channel." (and contents (concat contents " ")) (format "(%s)" (format (org-export-translate "See section %s" :html info) - (mapconcat 'number-to-string - (org-export-get-headline-number - destination info) - "."))))))) + (if (org-export-numbered-headline-p destination info) + (mapconcat #'number-to-string + (org-export-get-headline-number + destination info) + ".") + (org-export-data + (org-element-property :title destination) info)))))))) ((org-export-inline-image-p link org-html-inline-image-rules) (let ((path (let ((raw-path (org-element-property :path link))) (if (not (file-name-absolute-p raw-path)) raw-path @@ -354,9 +354,13 @@ a communication channel." (if (org-string-nw-p contents) contents (when destination (let ((number (org-export-get-ordinal destination info))) - (when number + (if number (if (atom number) (number-to-string number) - (mapconcat 'number-to-string number ".")))))))) + (mapconcat #'number-to-string number ".")) + ;; unnumbered headline + (and (eq 'headline (org-element-type destination)) + ;; BUG: shouldn't headlines have a form like [ref](name) in md? + (org-export-data (org-element-property :title headline) info)))))))) ;; Link type is handled by a special function. ((let ((protocol (nth 2 (assoc type org-link-protocols)))) (and (functionp protocol) diff --git a/lisp/ox-odt.el b/lisp/ox-odt.el index 96a3b83..816bb96 100644 --- a/lisp/ox-odt.el +++ b/lisp/ox-odt.el @@ -1122,7 +1122,7 @@ See `org-odt--build-date-styles' for implementation details." (setq text (concat ;; Section number. - (when section-number (concat section-number ". ")) + (and section-number (concat section-number ". ")) ;; Todo. (when todo (let ((style (if (member todo org-done-keywords) @@ -1789,8 +1789,7 @@ INFO is a plist holding contextual information." (org-element-property :title headline) backend info)) (tags (and (plist-get info :with-tags) (org-export-get-tags headline info))) - (headline-label (concat "sec-" (mapconcat 'number-to-string - headline-number "-"))) + (headline-label (org-export-get-headline-id headline info)) (format-function (if (functionp format-function) format-function (function* @@ -1816,9 +1815,7 @@ holding contextual information." ;; Get level relative to current parsed data. (level (org-export-get-relative-level headline info)) ;; Get canonical label for the headline. - (id (concat "sec-" (mapconcat 'number-to-string - (org-export-get-headline-number - headline info) "-"))) + (id (org-export-get-headline-id headline info)) ;; Get user-specified labels for the headline. (extra-ids (list (org-element-property :CUSTOM_ID headline) (org-element-property :ID headline))) @@ -1870,9 +1867,10 @@ holding contextual information." (t (concat (format - "\n%s" + "\n%s" (format "Heading_20_%s" level) level + (if (org-export-numbered-headline-p headline info) "false" "true") (concat extra-targets anchored-title)) contents)))))) @@ -2643,10 +2641,7 @@ Return nil, otherwise." (let* ((genealogy (org-export-get-genealogy destination)) (data (reverse genealogy)) (label (case (org-element-type destination) - (headline - (format "sec-%s" (mapconcat 'number-to-string - (org-export-get-headline-number - destination info) "-"))) + (headline (org-export-get-headline-id destination info)) (target (org-element-property :value destination)) (t (error "FIXME: Resolve %S" destination))))) @@ -2692,11 +2687,15 @@ Return nil, otherwise." item-numbers ""))))) ;; Case 2: Locate a regular and numbered headline in the ;; hierarchy. Display its section number. - (let ((headline (loop for el in (cons destination genealogy) - when (and (eq (org-element-type el) 'headline) - (not (org-export-low-level-p el info)) - (org-export-numbered-headline-p el info)) - return el))) + (let ((headline + (and + ;; Test if destination is a numbered headline + (org-export-numbered-headline-p destination info) + (loop for el in (cons destination genealogy) + when (and (eq (org-element-type el) 'headline) + (not (org-export-low-level-p el info)) + (org-export-numbered-headline-p el info)) + return el)))) ;; We found one. (when headline (format "%s" @@ -2776,11 +2775,9 @@ INFO is a plist holding contextual information. See ;; If there's a description, create a hyperlink. ;; Otherwise, try to provide a meaningful description. (if (not desc) (org-odt-link--infer-description destination info) - (let* ((headline-no - (org-export-get-headline-number destination info)) - (label - (format "sec-%s" - (mapconcat 'number-to-string headline-no "-")))) + (let ((label (or (and (string= type "custom-id") + (org-element-property :CUSTOM_ID destination)) + (org-export-get-headline-id destination info)))) (format "%s" label desc)))) diff --git a/lisp/ox.el b/lisp/ox.el index 90c623e..b05e52f 100644 --- a/lisp/ox.el +++ b/lisp/ox.el @@ -1974,6 +1974,7 @@ Return updated plist." ;; properties. (nconc `(:headline-numbering ,(org-export--collect-headline-numbering data info) + :unnumbered-headline-id ,(org-export--collect-unnumbered-headline-id data info) :exported-data ,(make-hash-table :test 'eq :size 4001)) info)) @@ -1996,7 +1997,7 @@ OPTIONS is a plist holding export options." (if (= min-level 10000) 1 min-level)))) (defun org-export--collect-headline-numbering (data options) - "Return numbering of all exportable headlines in a parse tree. + "Return numbering of all exportable, numbered headlines in a parse tree. DATA is the parse tree. OPTIONS is the plist holding export options. @@ -2007,7 +2008,8 @@ for a footnotes section." (let ((numbering (make-vector org-export-max-depth 0))) (org-element-map data 'headline (lambda (headline) - (unless (org-element-property :footnote-section-p headline) + (when (and (org-export-numbered-headline-p headline options) + (not (org-element-property :footnote-section-p headline))) (let ((relative-level (1- (org-export-get-relative-level headline options)))) (cons @@ -2019,6 +2021,19 @@ for a footnotes section." when (> idx relative-level) do (aset numbering idx 0)))))) options))) +(defun org-export--collect-unnumbered-headline-id (data options) + "Return numbering of all exportable, unnumbered headlines in a parse tree. + +DATA is the parse tree. OPTIONS is the plist holding export +options. + +Unnumbered headlines are numbered as a function of occurrence." + (let ((num 0)) + (org-element-map data 'headline + (lambda (headline) + (unless (org-export-numbered-headline-p headline options) + (cons headline (list (incf num)))))))) + (defun org-export--populate-ignore-list (data options) "Return list of elements and objects to ignore during export. DATA is the parse tree to traverse. OPTIONS is the plist holding @@ -3776,7 +3791,12 @@ INFO is the plist used as a communication channel." ;; ;; `org-export-get-headline-number' returns the section number of an ;; headline, while `org-export-number-to-roman' allows to convert it -;; to roman numbers. +;; to roman numbers. With an optional argument, +;; `org-export-get-headline-number' returns a number to unnumbered +;; headlines (used for internal id). +;; +;; `org-export-get-headline-id' returns the unique internal id of a +;; headline. ;; ;; `org-export-low-level-p', `org-export-first-sibling-p' and ;; `org-export-last-sibling-p' are three useful predicates when it @@ -3811,17 +3831,33 @@ and the last level being considered as high enough, or nil." (let ((level (org-export-get-relative-level headline info))) (and (> level limit) (- level limit)))))) +(defun org-export-get-headline-id (headline info) + "Return a unique ID for HEADLINE. +INFO is a plist holding contextual information." + (let ((numbered (org-export-numbered-headline-p headline info))) + (concat + (if numbered "sec-" "unnumbered-") + (mapconcat #'number-to-string + (if numbered + (org-export-get-headline-number headline info) + (cdr (assq headline (plist-get info :unnumbered-headline-id)))) "-")))) + (defun org-export-get-headline-number (headline info) - "Return HEADLINE numbering as a list of numbers. + "Return numbered HEADLINE numbering as a list of numbers. INFO is a plist holding contextual information." - (cdr (assoc headline (plist-get info :headline-numbering)))) + (and (org-export-numbered-headline-p headline info) + (cdr (assq headline (plist-get info :headline-numbering))))) (defun org-export-numbered-headline-p (headline info) "Return a non-nil value if HEADLINE element should be numbered. INFO is a plist used as a communication channel." - (let ((sec-num (plist-get info :section-numbers)) - (level (org-export-get-relative-level headline info))) - (if (wholenump sec-num) (<= level sec-num) sec-num))) + (unless + (or (org-element-property :UNNUMBERED headline) + (org-some (lambda (head) (org-not-nil (org-element-property :UNNUMBERED head))) + (org-export-get-genealogy headline))) + (let ((sec-num (plist-get info :section-numbers)) + (level (org-export-get-relative-level headline info))) + (if (wholenump sec-num) (<= level sec-num) sec-num)))) (defun org-export-number-to-roman (n) "Convert integer N into a roman numeral." diff --git a/testing/lisp/test-ox.el b/testing/lisp/test-ox.el index 4af3510..8e5e5d3 100644 --- a/testing/lisp/test-ox.el +++ b/testing/lisp/test-ox.el @@ -1576,6 +1576,27 @@ Paragraph[fn:1]" (lambda (h) (org-export-numbered-headline-p h info)) (plist-put info :section-numbers t))))) +(ert-deftest test-org-export/org-export-get-headline-id () + "Test `org-export-get-headline-id' specifications." + (should + (equal "sec-1" + (org-test-with-parsed-data "* Headline" + (org-export-get-headline-id + (org-element-map tree 'headline 'identity info t) + info)))) + (should + (equal "unnumbered-1" + (org-test-with-parsed-data "* Headline\n:PROPERTIES:\n:UNNUMBERED: t\n:END:" + (org-export-get-headline-id + (org-element-map tree 'headline 'identity info t) + info)))) + (should + (equal "unnumbered-1" + (org-test-with-parsed-data "* Headline\n#+OPTIONS: num:nil" + (org-export-get-headline-id + (org-element-map tree 'headline 'identity info t) + info))))) + (ert-deftest test-org-export/number-to-roman () "Test `org-export-number-to-roman' specifications." ;; If number is negative, return it as a string. -- 2.1.1 --=-=-=--