diff options
author | Nicolas Goaziou <n.goaziou@gmail.com> | 2012-02-04 21:49:58 +0100 |
---|---|---|
committer | Nicolas Goaziou <n.goaziou@gmail.com> | 2012-02-05 12:57:50 +0100 |
commit | 176b959c4f55fc7b3f59a0022284d6826d14725d (patch) | |
tree | b2b6db7f0965763d38f89f8d01d52eca6cbd3afe | |
parent | 2402d7a192e266026454030033f5fcf887ca8d4b (diff) | |
download | org-mode-176b959c4f55fc7b3f59a0022284d6826d14725d.tar.gz |
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.
-rw-r--r-- | EXPERIMENTAL/org-e-ascii.el | 4 | ||||
-rw-r--r-- | EXPERIMENTAL/org-e-latex.el | 4 | ||||
-rw-r--r-- | contrib/lisp/org-element.el | 35 | ||||
-rw-r--r-- | contrib/lisp/org-export.el | 285 |
4 files changed, 170 insertions, 158 deletions
diff --git a/EXPERIMENTAL/org-e-ascii.el b/EXPERIMENTAL/org-e-ascii.el index a2862f0..e1b8a67 100644 --- a/EXPERIMENTAL/org-e-ascii.el +++ b/EXPERIMENTAL/org-e-ascii.el @@ -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 diff --git a/EXPERIMENTAL/org-e-latex.el b/EXPERIMENTAL/org-e-latex.el index 88849a6..3f94ca3 100644 --- a/EXPERIMENTAL/org-e-latex.el +++ b/EXPERIMENTAL/org-e-latex.el @@ -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 diff --git a/contrib/lisp/org-element.el b/contrib/lisp/org-element.el index 9f9f9da..a8b014d 100644 --- a/contrib/lisp/org-element.el +++ b/contrib/lisp/org-element.el @@ -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) diff --git a/contrib/lisp/org-export.el b/contrib/lisp/org-export.el index 85fb268..d22d972 100644 --- a/contrib/lisp/org-export.el +++ b/contrib/lisp/org-export.el @@ -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 |