From 007bbddbccfd06ab9c97d51bf833a068cb1b07a2 Mon Sep 17 00:00:00 2001 From: Nicolas Goaziou Date: Sun, 10 Sep 2017 00:16:12 +0200 Subject: [PATCH] ox-html: Prevent spurious target below headlines * lisp/ox-html.el (org-html-headline): (org-html-link): Do not insert an additional target. * lisp/ox-publish.el (org-publish-resolve-external-link): Add an optional argument. * lisp/ox.el (org-export-get-reference): Improve docstring. * testing/examples/pub/a.org: * testing/examples/pub/b.org: New files. * testing/lisp/test-ox-publish.el (test-org-publish/resolve-external-link): New test. --- etc/ORG-NEWS | 4 +++ lisp/ox-html.el | 42 +++++++--------------- lisp/ox-publish.el | 35 ++++++++++++------ lisp/ox.el | 16 +++++++-- testing/examples/pub/a.org | 7 +++- testing/examples/pub/b.org | 3 ++ testing/lisp/test-ox-publish.el | 63 ++++++++++++++++++++++++++++++++- 7 files changed, 124 insertions(+), 46 deletions(-) diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS index e6ad838a6..59e6cb38b 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -58,6 +58,10 @@ size. ,#+STARTUP: shrink #+END_EXAMPLE +** Miscellaneous + +*** ~org-publish-resolve-external-link~ accepts a new optional argument. + * Version 9.1 ** Incompatible changes diff --git a/lisp/ox-html.el b/lisp/ox-html.el index 48a3b3b79..87de2be82 100644 --- a/lisp/ox-html.el +++ b/lisp/ox-html.el @@ -2599,18 +2599,8 @@ holding contextual information." (full-text (funcall (plist-get info :html-format-headline-function) todo todo-type priority text tags info)) (contents (or contents "")) - (ids (delq nil - (list (org-element-property :CUSTOM_ID headline) - (org-export-get-reference headline info) - (org-element-property :ID headline)))) - (preferred-id (car ids)) - (extra-ids - (mapconcat - (lambda (id) - (org-html--anchor - (if (org-uuidgen-p id) (concat "ID-" id) id) - nil nil info)) - (cdr ids) ""))) + (id (or (org-element-property :CUSTOM_ID headline) + (org-export-get-reference headline info)))) (if (org-export-low-level-p headline info) ;; This is a deep sub-tree: export it as a list item. (let* ((html-type (if numberedp "ol" "ul"))) @@ -2619,11 +2609,9 @@ holding contextual information." (apply #'format "<%s class=\"org-%s\">\n" (make-list 2 html-type))) (org-html-format-list-item - contents (if numberedp 'ordered 'unordered) - nil info nil - (concat (org-html--anchor preferred-id nil nil info) - extra-ids - full-text)) "\n" + contents (if numberedp 'ordered 'unordered) + nil info nil + (concat (org-html--anchor id nil nil info) full-text)) "\n" (and (org-export-last-sibling-p headline info) (format "\n" html-type)))) ;; Standard headline. Export it as a section. @@ -2636,10 +2624,9 @@ holding contextual information." (concat (format "outline-%d" level) (and extra-class " ") extra-class) - (format "\n%s%s\n" + (format "\n%s\n" level - preferred-id - extra-ids + id (concat (and numberedp (format @@ -3010,16 +2997,11 @@ INFO is a plist holding contextual information. See ;; relative to a custom-id, a headline title, a name or ;; a target. (let ((option (org-element-property :search-option link))) - (cond ((not option) raw-path) - ;; Since HTML back-end use custom-id value as-is, - ;; resolving is them is trivial. - ((eq (string-to-char option) ?#) (concat raw-path option)) - (t - (concat raw-path - "#" - (org-publish-resolve-external-link - option - (org-element-property :path link))))))) + (if (not option) raw-path + (let ((path (org-element-property :path link))) + (concat raw-path + "#" + (org-publish-resolve-external-link option path t)))))) (t raw-path))) ;; Extract attributes from parent's paragraph. HACK: Only do ;; this for the first link in parent (inner image link for diff --git a/lisp/ox-publish.el b/lisp/ox-publish.el index 8ae13d6f9..8fca54480 100644 --- a/lisp/ox-publish.el +++ b/lisp/ox-publish.el @@ -1131,7 +1131,7 @@ This function is meant to be used as a final output filter. See ;; Return output unchanged. output) -(defun org-publish-resolve-external-link (search file) +(defun org-publish-resolve-external-link (search file &optional prefer-custom) "Return reference for element matching string SEARCH in FILE. Return value is an internal reference, as a string. @@ -1139,18 +1139,31 @@ Return value is an internal reference, as a string. This function allows resolving external links with a search option, e.g., - [[file.org::*heading][description]] - [[file.org::#custom-id][description]] - [[file.org::fuzzy][description]] + [[file:file.org::*heading][description]] + [[file:file.org::#custom-id][description]] + [[file:file.org::fuzzy][description]] + +When PREFER-CUSTOM is non-nil, and SEARCH targets a headline in +FILE, return its custom ID, if any. It only makes sense to use this if export back-end builds references with `org-export-get-reference'." - (if (not org-publish-cache) - (progn - (message "Reference %S in file %S cannot be resolved without publishing" - search - file) - "MissingReference") + (cond + ((and prefer-custom + (if (string-prefix-p "#" search) + (substring search 1) + (with-current-buffer (find-file-noselect file) + (org-with-point-at 1 + (org-link-search search nil t) + (and (org-at-heading-p) + (org-string-nw-p (org-entry-get (point) "CUSTOM_ID")))))))) + ((not org-publish-cache) + (progn + (message "Reference %S in file %S cannot be resolved without publishing" + search + file) + "MissingReference")) + (t (let* ((filename (file-truename file)) (crossrefs (org-publish-cache-get-file-property filename :crossrefs nil t)) @@ -1167,7 +1180,7 @@ references with `org-export-get-reference'." (let ((new (org-export-new-reference crossrefs))) (dolist (cell cells) (push (cons cell new) crossrefs)) (org-publish-cache-set-file-property filename :crossrefs crossrefs) - (org-export-format-reference new)))))) + (org-export-format-reference new))))))) diff --git a/lisp/ox.el b/lisp/ox.el index b6528d90c..14da82ca1 100644 --- a/lisp/ox.el +++ b/lisp/ox.el @@ -4436,9 +4436,19 @@ REFERENCE is a number representing a reference, as returned by DATUM is either an element or an object. INFO is the current export state, as a plist. -This function checks `:crossrefs' property in INFO for search -cells matching DATUM before creating a new reference. Returned -reference consists of alphanumeric characters only." +References for the current document are stored in +`:internal-references' property. Its value is an alist with +associations of the following types: + + (REFERENCE . DATUM) and (SEARCH-CELL . ID) + +REFERENCE is the reference string to be used for object or +element DATUM. SEARCH-CELL is a search cell, as returned by +`org-export-search-cells'. ID is a number or a string uniquely +identifying DATUM within the document. + +This function also checks `:crossrefs' property for search cells +matching DATUM before creating a new reference." (let ((cache (plist-get info :internal-references))) (or (car (rassq datum cache)) (let* ((crossrefs (plist-get info :crossrefs)) diff --git a/testing/examples/pub/a.org b/testing/examples/pub/a.org index 41a9a0f91..daa82f1aa 100644 --- a/testing/examples/pub/a.org +++ b/testing/examples/pub/a.org @@ -1,4 +1,9 @@ #+title: A #+date: <2014-03-04 Tue> -Contents +* Headline1 +:PROPERTIES: +:CUSTOM_ID: a1 +:END: + +[[file:b.org::*Headline1]] diff --git a/testing/examples/pub/b.org b/testing/examples/pub/b.org index 707ee18ff..60387460e 100644 --- a/testing/examples/pub/b.org +++ b/testing/examples/pub/b.org @@ -1,3 +1,6 @@ #+title: b #+date: <2012-03-29 Thu> +* Headline1 + +[[file:a.org::#a1]] diff --git a/testing/lisp/test-ox-publish.el b/testing/lisp/test-ox-publish.el index cefddee34..3553807a8 100644 --- a/testing/lisp/test-ox-publish.el +++ b/testing/lisp/test-ox-publish.el @@ -94,7 +94,6 @@ Unless set otherwise in PROPERTIES, `:base-directory' is set to (cl-remove-if #'file-directory-p (directory-files dir)))))))) - ;;; Site-map @@ -327,6 +326,68 @@ Unless set otherwise in PROPERTIES, `:base-directory' is set to (insert-file-contents (expand-file-name "sitemap.org" dir)) (buffer-string))))))) + +;;; Cross references + +(ert-deftest test-org-publish/resolve-external-link () + "Test `org-publish-resolve-external-link' specifications." + ;; Function should preserve internal reference when used between + ;; published files. + (should + (apply + #'equal + (let* ((ids nil) + (backend + (org-export-create-backend + :transcoders + '((headline . (lambda (h c i) + (concat (org-export-get-reference h i) " " c))) + (paragraph . (lambda (p c i) c)) + (section . (lambda (s c i) c)) + (link . (lambda (l c i) + (let ((option (org-element-property :search-option l)) + (path (org-element-property :path l))) + (and option + (org-publish-resolve-external-link + option path)))))))) + (publish + (lambda (plist filename pub-dir) + (org-publish-org-to backend filename ".test" plist pub-dir)))) + (org-test-publish + (list :publishing-function (list publish)) + (lambda (dir) + (cl-subseq + (split-string + (mapconcat (lambda (f) (org-file-contents (expand-file-name f dir))) + (directory-files dir nil "\\.test\\'") + " ")) + 1 3)))))) + ;; When optional argument PREFER-CUSTOM is non-nil, use custom ID + ;; instead of internal reference, whenever possible. + (should + (equal + "a1" + (let* ((ids nil) + (backend + (org-export-create-backend + :transcoders + '((headline . (lambda (h c i) c)) + (paragraph . (lambda (p c i) c)) + (section . (lambda (s c i) c)) + (link . (lambda (l c i) + (let ((option (org-element-property :search-option l)) + (path (org-element-property :path l))) + (when option + (throw :exit (org-publish-resolve-external-link + option path t))))))))) + (publish + (lambda (plist filename pub-dir) + (push (catch :exit + (org-publish-org-to backend filename ".test" plist pub-dir)) + ids)))) + (org-test-publish (list :publishing-function (list publish)) #'ignore) + (car ids))))) + ;;; Tools