ox-publish: Better handling of cross-references

* lisp/ox-publish.el (org-publish--collect-references): Renamed...
(org-publish--store-crossrefs): ... to this.
(org-publish-org-to): Use previous function.  Small refactoring.

(org-publish-resolve-external-link): Use tight integration with
`org-export-get-reference' so as to provide reliable cross references.

* lisp/ox.el (org-export-get-reference): Conversely, take into
  consideration references suggested by
  `org-publish-resolve-external-link'.
This commit is contained in:
Nicolas Goaziou 2016-03-10 21:50:44 +01:00
parent 32c3f33d00
commit bef3fc6f82
2 changed files with 63 additions and 96 deletions

View File

@ -568,27 +568,25 @@ Return output file name."
(unless (or (not pub-dir) (file-exists-p pub-dir)) (make-directory pub-dir t))
;; Check if a buffer visiting FILENAME is already open.
(let* ((org-inhibit-startup t)
(visitingp (find-buffer-visiting filename))
(work-buffer (or visitingp (find-file-noselect filename))))
(prog1 (with-current-buffer work-buffer
(let ((output-file
(org-export-output-file-name extension nil pub-dir))
(body-p (plist-get plist :body-only)))
(org-export-to-file backend output-file
nil nil nil body-p
;; Add `org-publish--collect-references' and
;; `org-publish-collect-index' to final output
;; filters. The latter isn't dependent on
;; `:makeindex', since we want to keep it up-to-date
;; in cache anyway.
(org-combine-plists
plist
`(:filter-final-output
,(cons 'org-publish--collect-references
(cons 'org-publish-collect-index
(plist-get plist :filter-final-output))))))))
(visiting (find-buffer-visiting filename))
(work-buffer (or visiting (find-file-noselect filename))))
(unwind-protect
(with-current-buffer work-buffer
(let ((output (org-export-output-file-name extension nil pub-dir)))
(org-export-to-file backend output
nil nil nil (plist-get plist :body-only)
;; Add `org-publish--store-crossrefs' and
;; `org-publish-collect-index' to final output filters.
;; The latter isn't dependent on `:makeindex', since we
;; want to keep it up-to-date in cache anyway.
(org-combine-plists
plist
`(:filter-final-output
(org-publish--store-crossrefs
org-publish-collect-index
,@(plist-get plist :filter-final-output)))))))
;; Remove opened buffer in the process.
(unless visitingp (kill-buffer work-buffer)))))
(unless visiting (kill-buffer work-buffer)))))
(defun org-publish-attachment (_plist filename pub-dir)
"Publish a file with no transformation of any kind.
@ -1061,68 +1059,23 @@ publishing directory."
;; This part implements tools to resolve [[file.org::*Some headline]]
;; links, where "file.org" belongs to the current project.
(defun org-publish--collect-references (output _backend info)
"Store references for current published file.
(defun org-publish--store-crossrefs (output _backend info)
"Store cross-references for current published file.
OUPUT is the produced output, as a string. BACKEND is the export
back-end used, as a symbol. INFO is the final export state, as
a plist.
References are stored as an alist ((TYPE SEARCH) . VALUE) where
TYPE is a symbol among `headline', `custom-id', `target' and
`other'.
SEARCH is the string a link is expected to match. It is
- headline's title, as a string, with all whitespace
characters and statistics cookies removed, if TYPE is
`headline'.
- CUSTOM_ID value if TYPE is `custom-id'.
- target's or radio-target's name if TYPE is `target'.
- NAME affiliated keyword is TYPE is `other'.
VALUE is an internal reference used in the document, as
a string.
This function is meant to be used as a final output filter. See
`org-publish-org-to'."
(org-publish-cache-set-file-property
(plist-get info :input-file) :references
(let (refs)
(when (hash-table-p (plist-get info :internal-references))
(maphash
(lambda (k v)
(pcase (org-element-type k)
(`nil nil)
((or `headline `inlinetask)
(push (cons
(cons 'headline
(org-split-string
(replace-regexp-in-string
"\\[[0-9]+%\\]\\|\\[[0-9]+/[0-9]+\\]" ""
(org-element-property :raw-value k))))
v)
refs)
(let ((custom-id (org-element-property :CUSTOM_ID k)))
(when custom-id
(push (cons (cons 'custom-id custom-id) v) refs))))
((or `radio-target `target)
(push
(cons (cons 'target
(org-split-string (org-element-property :value k)))
v)
refs))
((and (let name (org-element-property :name k))
(guard name))
(push (cons (cons 'other (org-split-string name)) v)
refs)))
refs)
(plist-get info :internal-references)))
refs))
(plist-get info :input-file) :crossrefs
;; Update `:crossrefs' so as to remove unused references and search
;; cells. Actually used references are extracted from
;; `:internal-references', with references as strings removed. See
;; `org-export-get-reference' for details.
(cl-remove-if (lambda (pair) (stringp (car pair)))
(plist-get info :internal-references)))
;; Return output unchanged.
output)
@ -1131,32 +1084,38 @@ This function is meant to be used as a final output filter. See
Return value is an internal reference, as a string.
This function allows the resolution of external links like:
This function allows resolving external links with a search
option, e.g.,
[[file.org::*fuzzy][description]]
[[file.org::*heading][description]]
[[file.org::#custom-id][description]]
[[file.org::fuzzy][description]]"
[[file.org::fuzzy][description]]
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"
(message "Reference %S in file %S cannot be resolved without publishing"
search
file)
"MissingReference")
(let ((references (org-publish-cache-get-file-property
(expand-file-name file) :references nil t)))
(cond
((cdr (pcase (aref search 0)
(?* (assoc (cons 'headline (org-split-string (substring search 1)))
references))
(?# (assoc (cons 'custom-id (substring search 1)) references))
(_
(let ((s (org-split-string search)))
(or (assoc (cons 'target s) references)
(assoc (cons 'other s) references)
(assoc (cons 'headline s) references)))))))
(t (message "Unknown cross-reference \"%s\" in file \"%s\"" search file)
"MissingReference")))))
(let* ((filename (expand-file-name file))
(crossrefs
(org-publish-cache-get-file-property filename :crossrefs nil t))
(cells (org-export-string-to-search-cell search)))
(or
;; Look for reference associated to search cells triggered by
;; LINK. It can match when targeted file has been published
;; already.
(let ((known (cdr (cl-some (lambda (c) (assoc c crossrefs)) cells))))
(and known (org-export-format-reference known)))
;; Search cell is unknown so far. Generate a new internal
;; reference that will be used when the targeted file will be
;; published.
(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))))))

View File

@ -4354,13 +4354,21 @@ cells matching DATUM before creating a new reference. Returned
reference consists of alphanumeric characters only."
(let ((cache (plist-get info :internal-references)))
(or (car (rassq datum cache))
(let* ((new (org-export-new-reference cache))
(search-cells (org-export-search-cells datum))
(let* ((crossrefs (plist-get info :crossrefs))
(cells (org-export-search-cells datum))
;; If any other published document relies on an
;; association between a search cell and a reference,
;; make sure to preserve it. See
;; `org-publish-resolve-external-link' for details.
(new (or (cdr (cl-some (lambda (c) (assoc c crossrefs)) cells))
(org-export-new-reference cache)))
(reference-string (org-export-format-reference new)))
;; Cache contains both data already associated to
;; a reference and in-use internal references, so as to make
;; unique references.
(push (cons search-cells new) cache)
(dolist (cell cells) (push (cons cell new) cache))
;; Keep an associated related to DATUM as not every object
;; and element can be associated to a search cell.
(push (cons reference-string datum) cache)
(plist-put info :internal-references cache)
reference-string))))