Browse Source

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.
Nicolas Goaziou 13 năm trước cách đây
mục cha
commit
176b959c4f
4 tập tin đã thay đổi với 170 bổ sung158 xóa
  1. 1 3
      EXPERIMENTAL/org-e-ascii.el
  2. 1 3
      EXPERIMENTAL/org-e-latex.el
  3. 0 35
      contrib/lisp/org-element.el
  4. 168 117
      contrib/lisp/org-export.el

+ 1 - 3
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

+ 1 - 3
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

+ 0 - 35
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)

+ 168 - 117
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