diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS index 7da09fc38..b16b73ae1 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -313,6 +313,7 @@ you use "/!" markup when filtering TODO keywords. This variable is a ~defcustom~ and replaces the variable ~org-babel-capitalize-example-region-markers~, which is a ~defvar~ and is now obselete. +*** =INCLUDE= keywords in commented trees are now ignored. * Version 9.0 diff --git a/lisp/ox.el b/lisp/ox.el index f35963e09..0c822e0f4 100644 --- a/lisp/ox.el +++ b/lisp/ox.el @@ -3281,116 +3281,119 @@ storing and resolving footnotes. It is created automatically." ;; Expand INCLUDE keywords. (goto-char (point-min)) (while (re-search-forward include-re nil t) - (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-get-indentation)) - location - (file - (and (string-match - "^\\(\".+?\"\\|\\S-+\\)\\(?:\\s-+\\|$\\)" value) - (prog1 - (save-match-data - (let ((matched (match-string 1 value))) - (when (string-match "\\(::\\(.*?\\)\\)\"?\\'" - matched) - (setq location (match-string 2 matched)) - (setq matched - (replace-match "" nil nil matched 1))) - (expand-file-name - (org-unbracket-string "\"" "\"" matched) - 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) - ((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 + (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-get-indentation)) + location + (file + (and (string-match + "^\\(\".+?\"\\|\\S-+\\)\\(?:\\s-+\\|$\\)" value) + (prog1 + (save-match-data + (let ((matched (match-string 1 value))) + (when (string-match "\\(::\\(.*?\\)\\)\"?\\'" + matched) + (setq location (match-string 2 matched)) + (setq matched + (replace-match "" nil nil matched 1))) + (expand-file-name + (org-unbracket-string "\"" "\"" matched) + 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 - ((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)))) + ((not file) nil) + ((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 - (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))) - (org-export-expand-include-keyword - (cons (list file lines) included) - (file-name-directory file) - footnotes) - (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))))))))))) + (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))) + (org-export-expand-include-keyword + (cons (list file lines) included) + (file-name-directory file) + footnotes) + (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)))))))))))) (defun org-export--inclusion-absolute-lines (file location only-contents lines) "Resolve absolute lines for an included file with file-link. diff --git a/testing/lisp/test-ox.el b/testing/lisp/test-ox.el index 4c5dee51a..e35b762f0 100644 --- a/testing/lisp/test-ox.el +++ b/testing/lisp/test-ox.el @@ -1055,6 +1055,11 @@ Text" (should-error (org-test-with-temp-text "#+INCLUDE: dummy.org" (org-export-expand-include-keyword))) + ;; Refuse to expand keywords in commented headings. + (should + (org-test-with-temp-text "* COMMENT H1\n#+INCLUDE: dummy.org" + (org-export-expand-include-keyword) + t)) ;; Full insertion with recursive inclusion. (should (equal @@ -1262,15 +1267,15 @@ Footnotes[fn:2], foot[fn:test] and [fn:inline:inline footnote] ;; Adjacent INCLUDE-keywords should have the same :minlevel if unspecified. (should (cl-every (lambda (level) (zerop (1- level))) - (org-test-with-temp-text - (concat - (format "#+INCLUDE: \"%s/examples/include.org::#ah\"\n" - org-test-dir) - (format "#+INCLUDE: \"%s/examples/include.org::*Heading\"" - org-test-dir)) - (org-export-expand-include-keyword) - (org-element-map (org-element-parse-buffer) 'headline - (lambda (head) (org-element-property :level head)))))) + (org-test-with-temp-text + (concat + (format "#+INCLUDE: \"%s/examples/include.org::#ah\"\n" + org-test-dir) + (format "#+INCLUDE: \"%s/examples/include.org::*Heading\"" + org-test-dir)) + (org-export-expand-include-keyword) + (org-element-map (org-element-parse-buffer) 'headline + (lambda (head) (org-element-property :level head)))))) ;; INCLUDE does not insert induced :minlevel for src-blocks. (should-not (equal