forked from mirrors/org-mode
Handling of file inclusion through keywords is done before export
* contrib/lisp/org-element.el (org-element-map): Remove included file expansion part. * contrib/lisp/org-export.el (org-export-as): Expand include keywords before executing blocks. (org-export-expand-include-keyword, org-export-prepare-file-contents): New functions. (org-export-included-file, org-export-parse-included-file): Removed functions. * EXPERIMENTAL/org-e-ascii.el (org-e-ascii-keyword): Remove include keyword handling. * EXPERIMENTAL/org-e-latex.el (org-e-latex-keyword): Remove include keyword handling. Back-ends do not need anymore to take care of #+include keywords. This change is required since file inclusion can potentially break any structure. Hence, it should be done before parsing.
This commit is contained in:
parent
2402d7a192
commit
176b959c4f
|
@ -1328,9 +1328,7 @@ information."
|
|||
((string= "tables" value)
|
||||
(org-e-ascii--list-tables keyword info))
|
||||
((string= "listings" value)
|
||||
(org-e-ascii--list-listings keyword info)))))
|
||||
((string= key "include")
|
||||
(org-export-included-file keyword 'e-ascii info)))))
|
||||
(org-e-ascii--list-listings keyword info))))))))
|
||||
|
||||
|
||||
;;;; Latex Environment
|
||||
|
|
|
@ -1257,9 +1257,7 @@ CONTENTS is nil. INFO is a plist holding contextual information."
|
|||
"\\tableofcontents")))
|
||||
((string= "tables" value) "\\listoftables")
|
||||
((string= "figures" value) "\\listoffigures")
|
||||
((string= "listings" value) "\\listoflistings"))))
|
||||
((string= key "include")
|
||||
(org-export-included-file keyword 'e-latex info)))))
|
||||
((string= "listings" value) "\\listoflistings")))))))
|
||||
|
||||
|
||||
;;;; Latex Environment
|
||||
|
|
|
@ -3011,41 +3011,6 @@ Nil values returned from FUN are ignored in the result."
|
|||
(eq (plist-get info :with-archived-trees) 'headline)
|
||||
(org-element-get-property :archivedp --blob))
|
||||
(funcall accumulate-maybe --type types fun --blob --local))
|
||||
;; At an include keyword: apply mapping to its
|
||||
;; contents.
|
||||
((and --local
|
||||
(eq --type 'keyword)
|
||||
(string=
|
||||
(downcase (org-element-get-property :key --blob))
|
||||
"include"))
|
||||
(funcall accumulate-maybe --type types fun --blob --local)
|
||||
(let* ((--data
|
||||
(org-export-parse-included-file --blob --local))
|
||||
(--value (org-element-get-property :value --blob))
|
||||
(--file
|
||||
(and (string-match "^\"\\(\\S-+\\)\"" --value)
|
||||
(match-string 1 --value))))
|
||||
(funcall
|
||||
walk-tree --data
|
||||
(org-combine-plists
|
||||
--local
|
||||
;; Store full path of already included files
|
||||
;; to avoid recursive file inclusion.
|
||||
`(:included-files
|
||||
,(cons (expand-file-name --file)
|
||||
(plist-get --local :included-files))
|
||||
;; Ensure that a top-level headline in the
|
||||
;; included file becomes a direct child of
|
||||
;; the current headline in the buffer.
|
||||
:headline-offset
|
||||
,(- (let ((parent
|
||||
(org-export-get-parent-headline
|
||||
--blob --local)))
|
||||
(if (not parent) 0
|
||||
(org-export-get-relative-level
|
||||
parent --local)))
|
||||
(1- (org-export-get-min-level
|
||||
--data --local))))))))
|
||||
;; Limiting recursion to greater elements, and --BLOB
|
||||
;; isn't one.
|
||||
((and (eq --category 'greater-elements)
|
||||
|
|
|
@ -1876,9 +1876,19 @@ specified filters, if any, are called first."
|
|||
|
||||
;; Note that `org-export-as' doesn't really parse the current buffer,
|
||||
;; but a copy of it (with the same buffer-local variables and
|
||||
;; visibility), where Babel blocks are executed, if appropriate.
|
||||
;; visibility), where include keywords are expanded and Babel blocks
|
||||
;; are executed, if appropriate.
|
||||
;; `org-export-with-current-buffer-copy' macro prepares that copy.
|
||||
|
||||
;; File inclusion is taken care of by
|
||||
;; `org-export-expand-include-keyword' and
|
||||
;; `org-export-prepare-file-contents'. Structure wise, including
|
||||
;; a whole Org file in a buffer often makes little sense. For
|
||||
;; example, if the file contains an headline and the include keyword
|
||||
;; was within an item, the item should contain the headline. That's
|
||||
;; why file inclusion should be done before any structure can be
|
||||
;; associated to the file, that is before parsing.
|
||||
|
||||
(defun org-export-as (backend
|
||||
&optional subtreep visible-only body-only ext-plist)
|
||||
"Transcode current Org buffer into BACKEND code.
|
||||
|
@ -1915,9 +1925,10 @@ Return code as a string."
|
|||
;; Retrieve export options (INFO) and parsed tree (RAW-DATA),
|
||||
;; Then options can be completed with tree properties. Note:
|
||||
;; Buffer isn't parsed directly. Instead, a temporary copy is
|
||||
;; created, where all code blocks are evaluated. RAW-DATA is
|
||||
;; the parsed tree of the buffer resulting from that process.
|
||||
;; Eventually call `org-export-filter-parse-tree-functions'.
|
||||
;; created, where include keywords are expanded and code blocks
|
||||
;; are evaluated. RAW-DATA is the parsed tree of the buffer
|
||||
;; resulting from that process. Eventually call
|
||||
;; `org-export-filter-parse-tree-functions'.
|
||||
(let* ((info (org-export-collect-options backend subtreep ext-plist))
|
||||
(raw-data (progn
|
||||
(when subtreep ; Only parse subtree contents.
|
||||
|
@ -1927,6 +1938,7 @@ Return code as a string."
|
|||
(org-export-filter-apply-functions
|
||||
(plist-get info :filter-parse-tree)
|
||||
(org-export-with-current-buffer-copy
|
||||
(org-export-expand-include-keyword nil)
|
||||
(let ((org-current-export-file (current-buffer)))
|
||||
(org-export-blocks-preprocess))
|
||||
(org-element-parse-buffer nil visible-only))
|
||||
|
@ -2073,6 +2085,155 @@ Point is at buffer's beginning when BODY is applied."
|
|||
(progn ,@body))))))
|
||||
(def-edebug-spec org-export-with-current-buffer-copy (body))
|
||||
|
||||
(defun org-export-expand-include-keyword (included)
|
||||
"Expand every include keyword in buffer.
|
||||
INCLUDED is a list of included file names along with their line
|
||||
restriction, when appropriate. It is used to avoid infinite
|
||||
recursion."
|
||||
(let ((case-fold-search nil))
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "^[ \t]*#\\+include: \\(.*\\)" nil t)
|
||||
(when (eq (car (save-match-data (org-element-at-point))) 'keyword)
|
||||
(beginning-of-line)
|
||||
;; Extract arguments from keyword's value.
|
||||
(let* ((value (match-string 1))
|
||||
(ind (org-get-indentation))
|
||||
(file (and (string-match "^\"\\(\\S-+\\)\"" value)
|
||||
(prog1 (expand-file-name (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 "\\<example\\>" value) 'example)
|
||||
((string-match "\\<src\\(?: +\\(.*\\)\\)?" value)
|
||||
(match-string 1 value))))
|
||||
;; Minimal level of included file defaults to the child
|
||||
;; level of the current headline, if any, or one. It
|
||||
;; only applies is the file is meant to be included as
|
||||
;; an Org one.
|
||||
(minlevel
|
||||
(and (not env)
|
||||
(if (string-match ":minlevel +\\([0-9]+\\)" value)
|
||||
(prog1 (string-to-number (match-string 1 value))
|
||||
(setq value (replace-match "" nil nil value)))
|
||||
(let ((cur (org-current-level)))
|
||||
(if cur (1+ (org-reduced-level cur)) 1))))))
|
||||
;; Remove keyword.
|
||||
(delete-region (point) (progn (forward-line) (point)))
|
||||
(cond
|
||||
((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 'example)
|
||||
(insert
|
||||
(let ((ind-str (make-string ind ? ))
|
||||
(contents
|
||||
;; Protect sensitive contents with commas.
|
||||
(replace-regexp-in-string
|
||||
"\\(^\\)\\([*]\\|[ \t]*#\\+\\)" ","
|
||||
(org-export-prepare-file-contents file lines)
|
||||
nil nil 1)))
|
||||
(format "%s#+begin_example\n%s%s#+end_example\n"
|
||||
ind-str contents ind-str))))
|
||||
((stringp env)
|
||||
(insert
|
||||
(let ((ind-str (make-string ind ? ))
|
||||
(contents
|
||||
;; Protect sensitive contents with commas.
|
||||
(replace-regexp-in-string
|
||||
(if (string= env "org") "\\(^\\)\\(.\\)"
|
||||
"\\(^\\)\\([*]\\|[ \t]*#\\+\\)") ","
|
||||
(org-export-prepare-file-contents file lines)
|
||||
nil nil 1)))
|
||||
(format "%s#+begin_src %s\n%s%s#+end_src\n"
|
||||
ind-str env contents ind-str))))
|
||||
(t
|
||||
(insert
|
||||
(with-temp-buffer
|
||||
(org-mode)
|
||||
(insert
|
||||
(org-export-prepare-file-contents file lines ind minlevel))
|
||||
(org-export-expand-include-keyword
|
||||
(cons (list file lines) included))
|
||||
(buffer-string))))))))))))
|
||||
|
||||
(defun org-export-prepare-file-contents (file &optional lines ind minlevel)
|
||||
"Prepare the contents of FILE for inclusion and return them as a string.
|
||||
|
||||
When optional argument LINES is a string specifying a range of
|
||||
lines, include only those lines.
|
||||
|
||||
Optional argument IND, when non-nil, is an integer specifying the
|
||||
global indentation of returned contents. Since its purpose is to
|
||||
allow an included file to stay in the same environment it was
|
||||
created \(i.e. a list item), it doesn't apply past the first
|
||||
headline encountered.
|
||||
|
||||
Optional argument MINLEVEL, when non-nil, is an integer
|
||||
specifying the level that any top-level headline in the included
|
||||
file should have."
|
||||
(with-temp-buffer
|
||||
(insert-file-contents file)
|
||||
(when lines
|
||||
(let* ((lines (split-string lines "-"))
|
||||
(lbeg (string-to-number (car lines)))
|
||||
(lend (string-to-number (cadr lines)))
|
||||
(beg (if (zerop lbeg) (point-min)
|
||||
(goto-char (point-min))
|
||||
(forward-line (1- lbeg))
|
||||
(point)))
|
||||
(end (if (zerop lend) (point-max)
|
||||
(goto-char (point-min))
|
||||
(forward-line (1- lend))
|
||||
(point))))
|
||||
(narrow-to-region beg end)))
|
||||
;; Remove blank lines at beginning and end of contents. The logic
|
||||
;; behind that removal is that blank lines around include keyword
|
||||
;; override blank lines in included file.
|
||||
(goto-char (point-min))
|
||||
(org-skip-whitespace)
|
||||
(beginning-of-line)
|
||||
(delete-region (point-min) (point))
|
||||
(goto-char (point-max))
|
||||
(skip-chars-backward " \r\t\n")
|
||||
(forward-line)
|
||||
(delete-region (point) (point-max))
|
||||
;; If IND is set, preserve indentation of include keyword until
|
||||
;; the first headline encountered.
|
||||
(when ind
|
||||
(unless (eq major-mode 'org-mode) (org-mode))
|
||||
(goto-char (point-min))
|
||||
(let ((ind-str (make-string ind ? )))
|
||||
(while (not (or (eobp) (looking-at org-outline-regexp-bol)))
|
||||
;; Do not move footnote definitions out of column 0.
|
||||
(unless (and (looking-at org-footnote-definition-re)
|
||||
(eq (car (org-element-at-point)) 'footnote-definition))
|
||||
(insert ind-str))
|
||||
(forward-line))))
|
||||
;; When MINLEVEL is specified, compute minimal level for headlines
|
||||
;; in the file (CUR-MIN), and remove stars to each headline so
|
||||
;; that headlines with minimal level have a level of MINLEVEL.
|
||||
(when minlevel
|
||||
(unless (eq major-mode 'org-mode) (org-mode))
|
||||
(let ((levels (org-map-entries
|
||||
(lambda () (org-reduced-level (org-current-level))))))
|
||||
(when levels
|
||||
(let ((offset (- minlevel (apply 'min levels))))
|
||||
(unless (zerop offset)
|
||||
(when org-odd-levels-only (setq offset (* offset 2)))
|
||||
;; Only change stars, don't bother moving whole
|
||||
;; sections.
|
||||
(org-map-entries
|
||||
(lambda () (if (< offset 0) (delete-char (abs offset))
|
||||
(insert (make-string offset ?*))))))))))
|
||||
(buffer-string)))
|
||||
|
||||
|
||||
;;; Tools For Back-Ends
|
||||
|
@ -2081,9 +2242,9 @@ Point is at buffer's beginning when BODY is applied."
|
|||
;; function general enough to have its use across many back-ends
|
||||
;; should be added here.
|
||||
|
||||
;; As of now, functions operating on footnotes, headlines, include
|
||||
;; keywords, links, macros, references, src-blocks, tables and tables
|
||||
;; of contents are implemented.
|
||||
;; As of now, functions operating on footnotes, headlines, links,
|
||||
;; macros, references, src-blocks, tables and tables of contents are
|
||||
;; implemented.
|
||||
|
||||
;;;; For Footnotes
|
||||
|
||||
|
@ -2236,116 +2397,6 @@ INFO is the plist used as a communication channel."
|
|||
headline))
|
||||
|
||||
|
||||
;;;; For Include Keywords
|
||||
|
||||
;; This section provides a tool to properly handle insertion of files
|
||||
;; during export: `org-export-included-files'. It recursively
|
||||
;; transcodes a file specfied by an include keyword.
|
||||
|
||||
;; It uses two helper functions: `org-export-get-file-contents'
|
||||
;; returns contents of a file according to parameters specified in the
|
||||
;; keyword while `org-export-parse-included-file' parses the file
|
||||
;; specified by it.
|
||||
|
||||
(defun org-export-included-file (keyword backend info)
|
||||
"Transcode file specified with include KEYWORD.
|
||||
|
||||
KEYWORD is the include keyword element transcoded. BACKEND is
|
||||
the language back-end used for transcoding. INFO is the plist
|
||||
used as a communication channel.
|
||||
|
||||
This function updates `:included-files' and `:headline-offset'
|
||||
properties.
|
||||
|
||||
Return the transcoded string."
|
||||
(let ((data (org-export-parse-included-file keyword info))
|
||||
(file (let ((value (org-element-get-property :value keyword)))
|
||||
(and (string-match "^\"\\(\\S-+\\)\"" value)
|
||||
(match-string 1 value)))))
|
||||
(org-element-normalize-string
|
||||
(org-export-data
|
||||
data backend
|
||||
(org-combine-plists
|
||||
info
|
||||
;; Store full path of already included files to avoid recursive
|
||||
;; file inclusion.
|
||||
`(:included-files
|
||||
,(cons (expand-file-name file) (plist-get info :included-files))
|
||||
;; Ensure that a top-level headline in the included file
|
||||
;; becomes a direct child of the current headline in the
|
||||
;; buffer.
|
||||
:headline-offset
|
||||
,(- (let ((parent (org-export-get-parent-headline keyword info)))
|
||||
(if (not parent) 0
|
||||
(org-export-get-relative-level parent info)))
|
||||
(1- (org-export-get-min-level data info)))))))))
|
||||
|
||||
(defun org-export-get-file-contents (file &optional lines)
|
||||
"Get the contents of FILE and return them as a string.
|
||||
When optional argument LINES is a string specifying a range of
|
||||
lines, include only those lines."
|
||||
(with-temp-buffer
|
||||
(insert-file-contents file)
|
||||
(when lines
|
||||
(let* ((lines (split-string lines "-"))
|
||||
(lbeg (string-to-number (car lines)))
|
||||
(lend (string-to-number (cadr lines)))
|
||||
(beg (if (zerop lbeg) (point-min)
|
||||
(goto-char (point-min))
|
||||
(forward-line (1- lbeg))
|
||||
(point)))
|
||||
(end (if (zerop lend) (point-max)
|
||||
(goto-char (point-min))
|
||||
(forward-line (1- lend))
|
||||
(point))))
|
||||
(narrow-to-region beg end)))
|
||||
(buffer-string)))
|
||||
|
||||
(defun org-export-parse-included-file (keyword info)
|
||||
"Parse file specified by include KEYWORD.
|
||||
|
||||
KEYWORD is the include keyword element transcoded. BACKEND is
|
||||
the language back-end used for transcoding. INFO is the plist
|
||||
used as a communication channel.
|
||||
|
||||
Return the parsed tree."
|
||||
(let* ((value (org-element-get-property :value keyword))
|
||||
(file (and (string-match "^\"\\(\\S-+\\)\"" value)
|
||||
(prog1 (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 "\\<example\\>" value) "example")
|
||||
((string-match "\\<src\\(?: +\\(.*\\)\\)?" value)
|
||||
(match-string 1 value)))))
|
||||
(cond
|
||||
((or (not file)
|
||||
(not (file-exists-p file))
|
||||
(not (file-readable-p file)))
|
||||
(format "Cannot include file %s" file))
|
||||
((and (not env)
|
||||
(member (expand-file-name file) (plist-get info :included-files)))
|
||||
(error "Recursive file inclusion: %S" file))
|
||||
(t (let ((raw (org-element-normalize-string
|
||||
(org-export-get-file-contents
|
||||
(expand-file-name file) lines))))
|
||||
;; If environment isn't specified, Insert file in
|
||||
;; a temporary buffer and parse it as Org syntax.
|
||||
;; Otherwise, build the element representing the file.
|
||||
(cond
|
||||
((not env)
|
||||
(with-temp-buffer
|
||||
(insert raw) (org-mode) (org-element-parse-buffer)))
|
||||
((string= "example" env)
|
||||
`(org-data nil (example-block (:value ,raw :post-blank 0))))
|
||||
(t
|
||||
`(org-data
|
||||
nil
|
||||
(src-block (:value ,raw :language ,env :post-blank 0))))))))))
|
||||
|
||||
|
||||
;;;; For Links
|
||||
|
||||
;; `org-export-solidify-link-text' turns a string into a safer version
|
||||
|
|
Loading…
Reference in New Issue