>From 0508461b2d57629e1c391c57a7326093f61b07e6 Mon Sep 17 00:00:00 2001 From: Nicolas Goaziou Date: Sat, 10 Oct 2015 16:03:05 +0200 Subject: [PATCH] ox: Add an option to ignore broken links * lisp/ox.el (org-export-with-broken-links): New variable. (org-export-options-alist): Add new OPTIONS item. (broken-link): New error type. (org-export-resolve-coderef): (org-export-resolve-fuzzy-link): (org-export-resolve-id-link): Raise appropriate error symbol when a link cannot be resolved. (org-export-data): Handle new error type. --- lisp/ox.el | 221 ++++++++++++++++++++++++++++++++++++------------------------- 1 file changed, 132 insertions(+), 89 deletions(-) diff --git a/lisp/ox.el b/lisp/ox.el index d140f17..d74f48b 100644 --- a/lisp/ox.el +++ b/lisp/ox.el @@ -112,6 +112,7 @@ (:time-stamp-file nil "timestamp" org-export-time-stamp-file) (:with-archived-trees nil "arch" org-export-with-archived-trees) (:with-author nil "author" org-export-with-author) + (:with-broken-links nil "broken-links" org-export-with-broken-links) (:with-clocks nil "c" org-export-with-clocks) (:with-creator nil "creator" org-export-with-creator) (:with-date nil "date" org-export-with-date) @@ -797,6 +798,27 @@ is nil. You can also allow them through local buffer variables." :package-version '(Org . "8.0") :type 'boolean) +(defcustom org-export-with-broken-links nil + "Non-nil means do not raise an error on broken links. + +When this variable is non-nil, broken links are ignored, without +stopping the export process. If it is set to `mark', broken +links are marked as such in the output, with a string like + + [BROKEN LINK: path] + +where PATH is the un-resolvable reference. + +This option can also be set with the OPTIONS keyword, e.g., +\"broken-links:mark\"." + :group 'org-export-general + :version "25.1" + :package-version '(Org . "8.4") + :type '(choice + (const :tag "Ignore broken links" t) + (const :tag "Mark broken links in output" mark) + (const :tag "Raise an error" nil))) + (defcustom org-export-snippet-translation-alist nil "Alist between export snippets back-ends and exporter back-ends. @@ -1851,91 +1873,106 @@ string. INFO is a plist holding export options. Return a string." (or (gethash data (plist-get info :exported-data)) - (let* ((type (org-element-type data)) - (results - (cond - ;; Ignored element/object. - ((memq data (plist-get info :ignore-list)) nil) - ;; Plain text. - ((eq type 'plain-text) - (org-export-filter-apply-functions - (plist-get info :filter-plain-text) - (let ((transcoder (org-export-transcoder data info))) - (if transcoder (funcall transcoder data info) data)) - info)) - ;; Secondary string. - ((not type) - (mapconcat (lambda (obj) (org-export-data obj info)) data "")) - ;; Element/Object without contents or, as a special - ;; case, headline with archive tag and archived trees - ;; restricted to title only. - ((or (not (org-element-contents data)) - (and (eq type 'headline) - (eq (plist-get info :with-archived-trees) 'headline) - (org-element-property :archivedp data))) - (let ((transcoder (org-export-transcoder data info))) - (or (and (functionp transcoder) - (funcall transcoder data nil info)) - ;; Export snippets never return a nil value so - ;; that white spaces following them are never - ;; ignored. - (and (eq type 'export-snippet) "")))) - ;; Element/Object with contents. - (t - (let ((transcoder (org-export-transcoder data info))) - (when transcoder - (let* ((greaterp (memq type org-element-greater-elements)) - (objectp - (and (not greaterp) - (memq type org-element-recursive-objects))) - (contents - (mapconcat - (lambda (element) (org-export-data element info)) - (org-element-contents - (if (or greaterp objectp) data - ;; Elements directly containing - ;; objects must have their indentation - ;; normalized first. - (org-element-normalize-contents - data - ;; When normalizing contents of the - ;; first paragraph in an item or - ;; a footnote definition, ignore - ;; first line's indentation: there is - ;; none and it might be misleading. - (when (eq type 'paragraph) - (let ((parent (org-export-get-parent data))) - (and - (eq (car (org-element-contents parent)) - data) - (memq (org-element-type parent) - '(footnote-definition item)))))))) - ""))) - (funcall transcoder data - (if (not greaterp) contents - (org-element-normalize-string contents)) - info)))))))) - ;; Final result will be memoized before being returned. - (puthash - data - (cond - ((not results) "") - ((memq type '(org-data plain-text nil)) results) - ;; Append the same white space between elements or objects - ;; as in the original buffer, and call appropriate filters. - (t - (let ((results + ;; Handle broken links according to + ;; `org-export-with-broken-links'. + (cl-macrolet + ((broken-link-handler + (&rest body) + `(condition-case err + (progn ,@body) + (broken-link + (pcase (plist-get info :with-broken-links) + (`nil (user-error "Unable to resolve link: %S" (nth 1 err))) + (`mark (org-export-data + (format "[BROKEN LINK: %s]" (nth 1 err)) info)) + (_ nil)))))) + (let* ((type (org-element-type data)) + (results + (cond + ;; Ignored element/object. + ((memq data (plist-get info :ignore-list)) nil) + ;; Plain text. + ((eq type 'plain-text) (org-export-filter-apply-functions - (plist-get info (intern (format ":filter-%s" type))) - (let ((post-blank (or (org-element-property :post-blank data) - 0))) - (if (memq type org-element-all-elements) - (concat (org-element-normalize-string results) - (make-string post-blank ?\n)) - (concat results (make-string post-blank ?\s)))) - info))) - results))) - (plist-get info :exported-data))))) + (plist-get info :filter-plain-text) + (let ((transcoder (org-export-transcoder data info))) + (if transcoder (funcall transcoder data info) data)) + info)) + ;; Secondary string. + ((not type) + (mapconcat (lambda (obj) (org-export-data obj info)) data "")) + ;; Element/Object without contents or, as a special + ;; case, headline with archive tag and archived trees + ;; restricted to title only. + ((or (not (org-element-contents data)) + (and (eq type 'headline) + (eq (plist-get info :with-archived-trees) 'headline) + (org-element-property :archivedp data))) + (let ((transcoder (org-export-transcoder data info))) + (or (and (functionp transcoder) + (broken-link-handler + (funcall transcoder data nil info))) + ;; Export snippets never return a nil value so + ;; that white spaces following them are never + ;; ignored. + (and (eq type 'export-snippet) "")))) + ;; Element/Object with contents. + (t + (let ((transcoder (org-export-transcoder data info))) + (when transcoder + (let* ((greaterp (memq type org-element-greater-elements)) + (objectp + (and (not greaterp) + (memq type org-element-recursive-objects))) + (contents + (mapconcat + (lambda (element) (org-export-data element info)) + (org-element-contents + (if (or greaterp objectp) data + ;; Elements directly containing + ;; objects must have their indentation + ;; normalized first. + (org-element-normalize-contents + data + ;; When normalizing contents of the + ;; first paragraph in an item or + ;; a footnote definition, ignore + ;; first line's indentation: there is + ;; none and it might be misleading. + (when (eq type 'paragraph) + (let ((parent (org-export-get-parent data))) + (and + (eq (car (org-element-contents parent)) + data) + (memq (org-element-type parent) + '(footnote-definition item)))))))) + ""))) + (broken-link-handler + (funcall transcoder data + (if (not greaterp) contents + (org-element-normalize-string contents)) + info))))))))) + ;; Final result will be memoized before being returned. + (puthash + data + (cond + ((not results) "") + ((memq type '(org-data plain-text nil)) results) + ;; Append the same white space between elements or objects + ;; as in the original buffer, and call appropriate filters. + (t + (let ((results + (org-export-filter-apply-functions + (plist-get info (intern (format ":filter-%s" type))) + (let ((post-blank (or (org-element-property :post-blank data) + 0))) + (if (memq type org-element-all-elements) + (concat (org-element-normalize-string results) + (make-string post-blank ?\n)) + (concat results (make-string post-blank ?\s)))) + info))) + results))) + (plist-get info :exported-data)))))) (defun org-export-data-with-backend (data backend info) "Convert DATA into BACKEND format. @@ -3990,11 +4027,11 @@ meant to be translated with `org-export-data' or alike." ;; ;; `org-export-resolve-fuzzy-link' searches destination of fuzzy links ;; (i.e. links with "fuzzy" as type) within the parsed tree, and -;; returns an appropriate unique identifier when found, or nil. +;; returns an appropriate unique identifier. ;; ;; `org-export-resolve-id-link' returns the first headline with ;; specified id or custom-id in parse tree, the path to the external -;; file with the id or nil when neither was found. +;; file with the id. ;; ;; `org-export-resolve-coderef' associates a reference to a line ;; number in the element it belongs, or returns the reference itself @@ -4002,6 +4039,12 @@ meant to be translated with `org-export-data' or alike." ;; ;; `org-export-file-uri' expands a filename as stored in :path value ;; of a "file" link into a file URI. +;; +;; Broken links raise a `broken-link' error, which is caught by +;; `org-export-data' for further processing, depending on +;; `org-export-with-broken-links' value. + +(define-error 'broken-link "Unable to resolve link; aborting") (defun org-export-custom-protocol-maybe (link desc backend) "Try exporting LINK with a dedicated function. @@ -4083,7 +4126,7 @@ error if no block contains REF." (+ (org-export-get-loc el info) (line-number-at-pos))) (t (line-number-at-pos))))))) info 'first-match) - (user-error "Unable to resolve code reference: %s" ref))) + (signal 'broken-link (list ref)))) (defun org-export-resolve-fuzzy-link (link info) "Return LINK destination. @@ -4151,7 +4194,7 @@ significant." path) h)) info 'first-match)) - (t (user-error "Unable to resolve link \"%s\"" raw-path))) + (t (signal 'broken-link (list raw-path)))) link-cache))))) (defun org-export-resolve-id-link (link info) @@ -4172,7 +4215,7 @@ tree or a file name. Assume LINK type is either \"id\" or info 'first-match) ;; Otherwise, look for external files. (cdr (assoc id (plist-get info :id-alist))) - (user-error "Unable to resolve ID \"%s\"" id)))) + (signal 'broken-link (list id))))) (defun org-export-resolve-radio-link (link info) "Return radio-target object referenced as LINK destination. -- 2.6.1