From 5cde90e3c8df5d95dd291025acf3bf3acdc248ac Mon Sep 17 00:00:00 2001 From: TEC Date: Wed, 8 Jun 2022 23:56:56 +0800 Subject: [PATCH] ox: Refactor org-export-expand-include-keyword * lisp/ox.el (org-export-expand-include-keyword): Split the parsing and inclusion logic of the ~150 line `org-export-expand-include-keyword' into two new functions: `org-export-parse-include-value' and `org-export--blindly-expand-include'. --- lisp/ox.el | 285 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 162 insertions(+), 123 deletions(-) diff --git a/lisp/ox.el b/lisp/ox.el index b9c57321f..b6240cb1f 100644 --- a/lisp/ox.el +++ b/lisp/ox.el @@ -3303,7 +3303,6 @@ not have `buffer-file-name' assigned." (buffer-file-name (buffer-base-buffer)))) (case-fold-search t) (file-prefix (make-hash-table :test #'equal)) - (current-prefix 0) (footnotes (or footnotes (make-hash-table :test #'equal))) (include-re "^[ \t]*#\\+INCLUDE:")) ;; If :minlevel is not set the text-property @@ -3319,128 +3318,168 @@ not have `buffer-file-name' assigned." (goto-char (point-min)) (while (re-search-forward include-re nil t) (unless (org-in-commented-heading-p) - (let ((element (save-match-data (org-element-at-point)))) - (when (eq (org-element-type element) 'keyword) - (beginning-of-line) - ;; Extract arguments from keyword's value. - (let* ((value (org-element-property :value element)) - (ind (org-current-text-indentation)) - location - (coding-system-for-read - (or (and (string-match ":coding +\\(\\S-+\\)>" value) - (prog1 (intern (match-string 1 value)) - (setq value (replace-match "" nil nil value)))) - coding-system-for-read)) - (file - (and (string-match "^\\(\".+?\"\\|\\S-+\\)\\(?:\\s-+\\|$\\)" - value) - (prog1 - (save-match-data - (let ((matched (match-string 1 value)) - stripped) - (when (string-match "\\(::\\(.*?\\)\\)\"?\\'" - matched) - (setq location (match-string 2 matched)) - (setq matched - (replace-match "" nil nil matched 1))) - (setq stripped (org-strip-quotes matched)) - (if (org-url-p stripped) - stripped - (expand-file-name stripped dir)))) - (setq value (replace-match "" nil nil value))))) - (only-contents - (and (string-match ":only-contents *\\([^: \r\t\n]\\S-*\\)?" - value) - (prog1 (org-not-nil (match-string 1 value)) - (setq value (replace-match "" nil nil value))))) - (lines - (and (string-match - ":lines +\"\\([0-9]*-[0-9]*\\)\"" - value) - (prog1 (match-string 1 value) - (setq value (replace-match "" nil nil value))))) - (env (cond - ((string-match "\\" value) 'literal) - ((string-match "\\" value) - (match-string 1 value)))) - ;; Remove keyword. - (delete-region (point) (line-beginning-position 2)) - (cond - ((not file) nil) - ((and (not (org-url-p file)) (not (file-readable-p file))) - (error "Cannot include file %s" file)) - ;; Check if files has already been parsed. Look after - ;; inclusion lines too, as different parts of the same - ;; file can be included too. - ((member (list file lines) included) - (error "Recursive file inclusion: %s" file)) - (t - (cond - ((eq env 'literal) - (insert - (let ((ind-str (make-string ind ?\s)) - (arg-str (if (stringp args) (format " %s" args) "")) - (contents - (org-escape-code-in-string - (org-export--prepare-file-contents file lines)))) - (format "%s#+BEGIN_%s%s\n%s%s#+END_%s\n" - ind-str block arg-str contents ind-str block)))) - ((stringp block) - (insert - (let ((ind-str (make-string ind ?\s)) - (contents - (org-export--prepare-file-contents file lines))) - (format "%s#+BEGIN_%s\n%s%s#+END_%s\n" - ind-str block contents ind-str block)))) - (t - (insert - (with-temp-buffer - (let ((org-inhibit-startup t) - (lines - (if location - (org-export--inclusion-absolute-lines - file location only-contents lines) - lines))) - (org-mode) - (insert - (org-export--prepare-file-contents - file lines ind minlevel - (or (gethash file file-prefix) - (puthash file - (cl-incf current-prefix) - file-prefix)) - footnotes - includer-file))) - (org-export-expand-include-keyword - (cons (list file lines) included) - (unless (org-url-p file) - (file-name-directory file)) - footnotes includer-file) - (buffer-string))))) - ;; Expand footnotes after all files have been - ;; included. Footnotes are stored at end of buffer. - (unless included - (org-with-wide-buffer - (goto-char (point-max)) - (maphash (lambda (k v) - (insert (format "\n[fn:%s] %s\n" k v))) - footnotes)))))))))))) + (let ((element (save-match-data (org-element-at-point)))) + (when (eq (org-element-type element) 'keyword) + (beginning-of-line) + ;; Extract arguments from keyword's value. + (let* ((value (org-element-property :value element)) + (parameters (org-export-parse-include-value value dir))) + ;; Remove keyword. + (delete-region (point) (line-beginning-position 2)) + (pcase (plist-get parameters :file) + ((pred not) nil) + ((and (pred (not org-url-p)) (pred (not file-readable-p)) f) + (error "Cannot include file %s" f)) + ;; Check if files has already been parsed. Look after + ;; inclusion lines too, as different parts of the same + ;; file can be included too. + ((and f (guard (member (list f (plist-get parameters :lines)) + included))) + (error "Recursive file inclusion: %s" f)) + (_ + (org-export--blindly-expand-include + parameters + :includer-file includer-file + :file-prefix file-prefix + :footnotes footnotes + :already-included included) + ;; Expand footnotes after all files have been + ;; included. Footnotes are stored at end of buffer. + (unless included + (org-with-wide-buffer + (goto-char (point-max)) + (maphash (lambda (k v) + (insert (format "\n[fn:%s] %s\n" k v))) + footnotes)))))))))))) + +(defun org-export-parse-include-value (value &optional dir) + "Extract the various parameters from #+include: VALUE." + (let* ((ind (org-current-text-indentation)) + location + (coding-system + (and (string-match ":coding +\\(\\S-+\\)>" value) + (prog1 (intern (match-string 1 value)) + (setq value (replace-match "" nil nil value))))) + (file + (and (string-match "^\\(\".+?\"\\|\\S-+\\)\\(?:\\s-+\\|$\\)" value) + (prog1 + (save-match-data + (let ((matched (match-string 1 value)) + stripped) + (when (string-match "\\(::\\(.*?\\)\\)\"?\\'" + matched) + (setq location (match-string 2 matched)) + (setq matched + (replace-match "" nil nil matched 1))) + (setq stripped (org-strip-quotes matched)) + (if (org-url-p stripped) + stripped + (expand-file-name stripped dir)))) + (setq value (replace-match "" nil nil value))))) + (only-contents + (and (string-match ":only-contents *\\([^: \r\t\n]\\S-*\\)?" + value) + (prog1 (org-not-nil (match-string 1 value)) + (setq value (replace-match "" nil nil value))))) + (lines + (and (string-match + ":lines +\"\\([0-9]*-[0-9]*\\)\"" + value) + (prog1 (match-string 1 value) + (setq value (replace-match "" nil nil value))))) + (env (cond + ((string-match "\\" value) 'literal) + ((string-match "\\" value) + (match-string 1 value)))) + (list :file file + :coding-system coding-system + :location location + :only-contents only-contents + :lines lines + :env env + :minlevel minlevel + :args args + :block block))) + +(cl-defun org-export--blindly-expand-include (parameters &key includer-file file-prefix footnotes already-included) + "Unconditionally include reference defined by PARAMETERS in the buffer. +PARAMETERS is a plist of the form returned by `org-export-parse-include-value'. + +INCLUDER-FILE is a path to the file where the include keyword is +being expanded. FILE-PREFIX is a hash-table of file and +prefixes, which can be provided to ensure consistent prefixing. +FOOTNOTES is a hash-table for storing and resolving footnotes, +which when provided allows footnotes to be handled appropriately. +ALREADY-INCLUDED is a list of included names along with their +line restriction which prevents recursion." + (let* ((coding-system-for-read + (or (plist-get parameters :coding-system) + coding-system-for-read)) + (file (plist-get parameters :file)) + (lines (plist-get parameters :lines)) + (args (plist-get parameters :args)) + (block (plist-get parameters :block)) + (ind (org-current-text-indentation))) + (cond + ((eq (plist-get parameters :env) 'literal) + (insert + (let ((ind-str (make-string ind ?\s)) + (arg-str (if (stringp args) (format " %s" args) "")) + (contents + (org-escape-code-in-string + (org-export--prepare-file-contents file lines)))) + (format "%s#+BEGIN_%s%s\n%s%s#+END_%s\n" + ind-str block arg-str contents ind-str block)))) + ((stringp block) + (insert + (let ((ind-str (make-string ind ?\s)) + (contents + (org-export--prepare-file-contents file lines))) + (format "%s#+BEGIN_%s\n%s%s#+END_%s\n" + ind-str block contents ind-str block)))) + (t + (insert + (with-temp-buffer + (let ((org-inhibit-startup t) + (lines + (if-let ((location (plist-get parameters :location))) + (org-export--inclusion-absolute-lines + file location + (plist-get parameters :only-contents) + lines) + lines))) + (org-mode) + (insert + (org-export--prepare-file-contents + file lines ind (plist-get parameters :minlevel) + (and file-prefix + (or (gethash file file-prefix) + (puthash file + (hash-table-count file-prefix) + file-prefix))) + footnotes includer-file))) + (org-export-expand-include-keyword + (cons (list file lines) already-included) + (unless (org-url-p file) + (file-name-directory file)) + footnotes includer-file) + (buffer-string))))))) (defun org-export--inclusion-absolute-lines (file location only-contents lines) "Resolve absolute lines for an included file with file-link.