forked from mirrors/org-mode
Merge branch 'include'
This commit is contained in:
commit
9528bef2a8
127
lisp/ox.el
127
lisp/ox.el
|
@ -3052,18 +3052,31 @@ locally for the subtree through node properties."
|
||||||
(car key)
|
(car key)
|
||||||
(if (org-string-nw-p val) (format " %s" val) ""))))))))
|
(if (org-string-nw-p val) (format " %s" val) ""))))))))
|
||||||
|
|
||||||
(defun org-export-expand-include-keyword (&optional included dir)
|
(defun org-export-expand-include-keyword (&optional included dir footnotes)
|
||||||
"Expand every include keyword in buffer.
|
"Expand every include keyword in buffer.
|
||||||
Optional argument INCLUDED is a list of included file names along
|
Optional argument INCLUDED is a list of included file names along
|
||||||
with their line restriction, when appropriate. It is used to
|
with their line restriction, when appropriate. It is used to
|
||||||
avoid infinite recursion. Optional argument DIR is the current
|
avoid infinite recursion. Optional argument DIR is the current
|
||||||
working directory. It is used to properly resolve relative
|
working directory. It is used to properly resolve relative
|
||||||
paths."
|
paths. Optional argument FOOTNOTES is a hash-table used for
|
||||||
|
storing and resolving footnotes. It is created automatically."
|
||||||
(let ((case-fold-search t)
|
(let ((case-fold-search t)
|
||||||
(file-prefix (make-hash-table :test #'equal))
|
(file-prefix (make-hash-table :test #'equal))
|
||||||
(current-prefix 0))
|
(current-prefix 0)
|
||||||
|
(footnotes (or footnotes (make-hash-table :test #'equal)))
|
||||||
|
(include-re "^[ \t]*#\\+INCLUDE:"))
|
||||||
|
;; If :minlevel is not set the text-property
|
||||||
|
;; `:org-include-induced-level' will be used to determine the
|
||||||
|
;; relative level when expanding INCLUDE.
|
||||||
|
;; Only affects included Org documents.
|
||||||
(goto-char (point-min))
|
(goto-char (point-min))
|
||||||
(while (re-search-forward "^[ \t]*#\\+INCLUDE:" nil t)
|
(while (re-search-forward include-re nil t)
|
||||||
|
(put-text-property (line-beginning-position) (line-end-position)
|
||||||
|
:org-include-induced-level
|
||||||
|
(1+ (org-reduced-level (or (org-current-level) 0)))))
|
||||||
|
;; Expand INCLUDE keywords.
|
||||||
|
(goto-char (point-min))
|
||||||
|
(while (re-search-forward include-re nil t)
|
||||||
(let ((element (save-match-data (org-element-at-point))))
|
(let ((element (save-match-data (org-element-at-point))))
|
||||||
(when (eq (org-element-type element) 'keyword)
|
(when (eq (org-element-type element) 'keyword)
|
||||||
(beginning-of-line)
|
(beginning-of-line)
|
||||||
|
@ -3108,8 +3121,7 @@ paths."
|
||||||
(if (string-match ":minlevel +\\([0-9]+\\)" value)
|
(if (string-match ":minlevel +\\([0-9]+\\)" value)
|
||||||
(prog1 (string-to-number (match-string 1 value))
|
(prog1 (string-to-number (match-string 1 value))
|
||||||
(setq value (replace-match "" nil nil value)))
|
(setq value (replace-match "" nil nil value)))
|
||||||
(let ((cur (org-current-level)))
|
(get-text-property (point) :org-include-induced-level))))
|
||||||
(if cur (1+ (org-reduced-level cur)) 1)))))
|
|
||||||
(src-args (and (eq env 'literal)
|
(src-args (and (eq env 'literal)
|
||||||
(match-string 1 value)))
|
(match-string 1 value)))
|
||||||
(block (and (string-match "\\<\\(\\S-+\\)\\>" value)
|
(block (and (string-match "\\<\\(\\S-+\\)\\>" value)
|
||||||
|
@ -3131,8 +3143,8 @@ paths."
|
||||||
(insert
|
(insert
|
||||||
(let ((ind-str (make-string ind ? ))
|
(let ((ind-str (make-string ind ? ))
|
||||||
(arg-str (if (stringp src-args)
|
(arg-str (if (stringp src-args)
|
||||||
(format " %s" src-args)
|
(format " %s" src-args)
|
||||||
""))
|
""))
|
||||||
(contents
|
(contents
|
||||||
(org-escape-code-in-string
|
(org-escape-code-in-string
|
||||||
(org-export--prepare-file-contents file lines))))
|
(org-export--prepare-file-contents file lines))))
|
||||||
|
@ -3142,7 +3154,7 @@ paths."
|
||||||
(insert
|
(insert
|
||||||
(let ((ind-str (make-string ind ? ))
|
(let ((ind-str (make-string ind ? ))
|
||||||
(contents
|
(contents
|
||||||
(org-export--prepare-file-contents file lines)))
|
(org-export--prepare-file-contents file lines)))
|
||||||
(format "%s#+BEGIN_%s\n%s%s#+END_%s\n"
|
(format "%s#+BEGIN_%s\n%s%s#+END_%s\n"
|
||||||
ind-str block contents ind-str block))))
|
ind-str block contents ind-str block))))
|
||||||
(t
|
(t
|
||||||
|
@ -3155,15 +3167,23 @@ paths."
|
||||||
file location only-contents lines)
|
file location only-contents lines)
|
||||||
lines)))
|
lines)))
|
||||||
(org-mode)
|
(org-mode)
|
||||||
(insert
|
(insert (org-export--prepare-file-contents
|
||||||
(org-export--prepare-file-contents
|
file lines ind minlevel
|
||||||
file lines ind minlevel
|
(or (gethash file file-prefix)
|
||||||
(or (gethash file file-prefix)
|
(puthash file (incf current-prefix) file-prefix))
|
||||||
(puthash file (incf current-prefix) file-prefix)))))
|
footnotes)))
|
||||||
(org-export-expand-include-keyword
|
(org-export-expand-include-keyword
|
||||||
(cons (list file lines) included)
|
(cons (list file lines) included)
|
||||||
(file-name-directory file))
|
(file-name-directory file)
|
||||||
(buffer-string)))))))))))))
|
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 (ref def) (insert (format "\n[%s] %s\n" ref def)))
|
||||||
|
footnotes)))))))))))
|
||||||
|
|
||||||
(defun org-export--inclusion-absolute-lines (file location only-contents lines)
|
(defun org-export--inclusion-absolute-lines (file location only-contents lines)
|
||||||
"Resolve absolute lines for an included file with file-link.
|
"Resolve absolute lines for an included file with file-link.
|
||||||
|
@ -3227,8 +3247,26 @@ Return a string of lines to be included in the format expected by
|
||||||
(while (< (point) end) (incf counter) (forward-line))
|
(while (< (point) end) (incf counter) (forward-line))
|
||||||
counter))))))))
|
counter))))))))
|
||||||
|
|
||||||
(defun org-export--prepare-file-contents (file &optional lines ind minlevel id)
|
(defun org-export--update-footnote-label (ref-begin digit-label id)
|
||||||
"Prepare the contents of FILE for inclusion and return them as a string.
|
"Prefix footnote-label at point REF-BEGIN in buffer with ID.
|
||||||
|
|
||||||
|
REF-BEGIN corresponds to the property `:begin' of objects of type
|
||||||
|
footnote-definition and footnote-reference.
|
||||||
|
|
||||||
|
If DIGIT-LABEL is non-nil the label is assumed to be of the form
|
||||||
|
\[N] where N is one or more numbers.
|
||||||
|
|
||||||
|
Return the new label."
|
||||||
|
(goto-char (1+ ref-begin))
|
||||||
|
(buffer-substring (point)
|
||||||
|
(progn
|
||||||
|
(if digit-label (insert (format "fn:%d-" id))
|
||||||
|
(forward-char 3)
|
||||||
|
(insert (format "%d-" id)))
|
||||||
|
(1- (search-forward "]")))))
|
||||||
|
|
||||||
|
(defun org-export--prepare-file-contents (file &optional lines ind minlevel id footnotes)
|
||||||
|
"Prepare contents of FILE for inclusion and return it as a string.
|
||||||
|
|
||||||
When optional argument LINES is a string specifying a range of
|
When optional argument LINES is a string specifying a range of
|
||||||
lines, include only those lines.
|
lines, include only those lines.
|
||||||
|
@ -3242,11 +3280,14 @@ headline encountered.
|
||||||
Optional argument MINLEVEL, when non-nil, is an integer
|
Optional argument MINLEVEL, when non-nil, is an integer
|
||||||
specifying the level that any top-level headline in the included
|
specifying the level that any top-level headline in the included
|
||||||
file should have.
|
file should have.
|
||||||
|
|
||||||
Optional argument ID is an integer that will be inserted before
|
Optional argument ID is an integer that will be inserted before
|
||||||
each footnote definition and reference if FILE is an Org file.
|
each footnote definition and reference if FILE is an Org file.
|
||||||
This is useful to avoid conflicts when more than one Org file
|
This is useful to avoid conflicts when more than one Org file
|
||||||
with footnotes is included in a document."
|
with footnotes is included in a document.
|
||||||
|
|
||||||
|
Optional argument FOOTNOTES is a hash-table to store footnotes in
|
||||||
|
the included document.
|
||||||
|
"
|
||||||
(with-temp-buffer
|
(with-temp-buffer
|
||||||
(insert-file-contents file)
|
(insert-file-contents file)
|
||||||
(when lines
|
(when lines
|
||||||
|
@ -3307,20 +3348,40 @@ with footnotes is included in a document."
|
||||||
(insert (make-string offset ?*)))))))))))
|
(insert (make-string offset ?*)))))))))))
|
||||||
;; Append ID to all footnote references and definitions, so they
|
;; Append ID to all footnote references and definitions, so they
|
||||||
;; become file specific and cannot collide with footnotes in other
|
;; become file specific and cannot collide with footnotes in other
|
||||||
;; included files.
|
;; included files. Further, collect relevant footnotes outside of
|
||||||
|
;; LINES.
|
||||||
(when id
|
(when id
|
||||||
(goto-char (point-min))
|
(let ((marker-min (point-min-marker))
|
||||||
(while (re-search-forward org-footnote-re nil t)
|
(marker-max (point-max-marker)))
|
||||||
(let ((reference (org-element-context)))
|
(goto-char (point-min))
|
||||||
(when (memq (org-element-type reference)
|
(while (re-search-forward org-footnote-re nil t)
|
||||||
'(footnote-reference footnote-definition))
|
(let ((reference (org-element-context)))
|
||||||
(goto-char (org-element-property :begin reference))
|
(when (eq (org-element-type reference) 'footnote-reference)
|
||||||
(forward-char)
|
(let* ((label (org-element-property :label reference))
|
||||||
(let ((label (org-element-property :label reference)))
|
(digit-label (and label (org-string-match-p "\\`[0-9]+\\'" label))))
|
||||||
(cond ((not label))
|
;; Update the footnote-reference at point and collect
|
||||||
((org-string-match-p "\\`[0-9]+\\'" label)
|
;; the new label, which is only used for footnotes
|
||||||
(insert (format "fn:%d-" id)))
|
;; outsides LINES.
|
||||||
(t (forward-char 3) (insert (format "%d-" id)))))))))
|
(when label
|
||||||
|
;; If label is akin to [1] convert it to [fn:ID-1].
|
||||||
|
;; Otherwise add "ID-" after "fn:".
|
||||||
|
(let ((new-label (org-export--update-footnote-label
|
||||||
|
(org-element-property :begin reference) digit-label id)))
|
||||||
|
(unless (eq (org-element-property :type reference) 'inline)
|
||||||
|
(org-with-wide-buffer
|
||||||
|
(let* ((definition (org-footnote-get-definition label))
|
||||||
|
(beginning (nth 1 definition)))
|
||||||
|
(unless definition
|
||||||
|
(error "Definition not found for footnote %s in file %s" label file))
|
||||||
|
(if (or (< beginning marker-min) (> beginning marker-max))
|
||||||
|
;; Store since footnote-definition is outside of LINES.
|
||||||
|
(puthash new-label
|
||||||
|
(org-element-normalize-string (nth 3 definition))
|
||||||
|
footnotes)
|
||||||
|
;; Update label of definition since it is included directly.
|
||||||
|
(org-export--update-footnote-label beginning digit-label id)))))))))))
|
||||||
|
(set-marker marker-min nil)
|
||||||
|
(set-marker marker-max nil)))
|
||||||
(org-element-normalize-string (buffer-string))))
|
(org-element-normalize-string (buffer-string))))
|
||||||
|
|
||||||
(defun org-export-execute-babel-code ()
|
(defun org-export-execute-babel-code ()
|
||||||
|
|
|
@ -904,12 +904,13 @@ Footnotes[fn:1], [fn:test] and [fn:inline:anonymous footnote].
|
||||||
(org-element-property :label ref)))))))))))))
|
(org-element-property :label ref)))))))))))))
|
||||||
;; Footnotes labels are not local to each include keyword.
|
;; Footnotes labels are not local to each include keyword.
|
||||||
(should
|
(should
|
||||||
(= 3
|
(= 4
|
||||||
(length
|
(length
|
||||||
(delete-dups
|
(delete-dups
|
||||||
(let ((contents "
|
(let ((contents "
|
||||||
Footnotes[fn:1], [fn:test] and [fn:inline:anonymous footnote].
|
Footnotes[fn:1], [fn:test], [2] and [fn:inline:anonymous footnote].
|
||||||
\[fn:1] Footnote 1
|
\[fn:1] Footnote 1
|
||||||
|
\[2] Footnote 2
|
||||||
\[fn:test] Footnote \"test\""))
|
\[fn:test] Footnote \"test\""))
|
||||||
(org-test-with-temp-text-in-file contents
|
(org-test-with-temp-text-in-file contents
|
||||||
(let ((file (buffer-file-name)))
|
(let ((file (buffer-file-name)))
|
||||||
|
@ -919,6 +920,33 @@ Footnotes[fn:1], [fn:test] and [fn:inline:anonymous footnote].
|
||||||
(org-element-map (org-element-parse-buffer)
|
(org-element-map (org-element-parse-buffer)
|
||||||
'footnote-reference
|
'footnote-reference
|
||||||
(lambda (ref) (org-element-property :label ref)))))))))))
|
(lambda (ref) (org-element-property :label ref)))))))))))
|
||||||
|
;; Footnotes are supported by :lines-like elements and unnecessary
|
||||||
|
;; footnotes are dropped.
|
||||||
|
(should
|
||||||
|
(= 4
|
||||||
|
(length
|
||||||
|
(delete-dups
|
||||||
|
(let ((contents "
|
||||||
|
* foo
|
||||||
|
Footnotes[fn:1]
|
||||||
|
* bar
|
||||||
|
Footnotes[fn:2], foot[fn:test], digit only[3], and [fn:inline:anonymous footnote]
|
||||||
|
|
||||||
|
\[fn:1] Footnote 1
|
||||||
|
\[fn:2] Footnote 1
|
||||||
|
* Footnotes
|
||||||
|
\[fn:test] Footnote \"test\"
|
||||||
|
\[3] Footnote 3
|
||||||
|
"))
|
||||||
|
(org-test-with-temp-text-in-file contents
|
||||||
|
(let ((file (buffer-file-name)))
|
||||||
|
(org-test-with-temp-text
|
||||||
|
(format "#+INCLUDE: \"%s::*bar\"
|
||||||
|
" file)
|
||||||
|
(org-export-expand-include-keyword)
|
||||||
|
(org-element-map (org-element-parse-buffer)
|
||||||
|
'footnote-definition
|
||||||
|
(lambda (ref) (org-element-property :label ref)))))))))))
|
||||||
;; If only-contents is non-nil only include contents of element.
|
;; If only-contents is non-nil only include contents of element.
|
||||||
(should
|
(should
|
||||||
(equal
|
(equal
|
||||||
|
@ -975,7 +1003,42 @@ Footnotes[fn:1], [fn:test] and [fn:inline:anonymous footnote].
|
||||||
(org-test-with-temp-text
|
(org-test-with-temp-text
|
||||||
(format "#+INCLUDE: \"%s/examples/include.org::#dh\" :only-contents t" org-test-dir)
|
(format "#+INCLUDE: \"%s/examples/include.org::#dh\" :only-contents t" org-test-dir)
|
||||||
(org-export-expand-include-keyword)
|
(org-export-expand-include-keyword)
|
||||||
(buffer-string)))))
|
(buffer-string))))
|
||||||
|
;; Adjacent INCLUDE-keywords should have the same :minlevel if unspecified.
|
||||||
|
(should
|
||||||
|
(org-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))))))
|
||||||
|
;; INCLUDE does not insert induced :minlevel for src-blocks.
|
||||||
|
(should-not
|
||||||
|
(equal
|
||||||
|
(org-test-with-temp-text
|
||||||
|
(format "#+INCLUDE: \"%s/examples/include2.org\" src emacs-lisp" org-test-dir)
|
||||||
|
(org-export-expand-include-keyword)
|
||||||
|
(buffer-string))
|
||||||
|
(org-test-with-temp-text
|
||||||
|
(format "#+INCLUDE: \"%s/examples/include2.org\" src emacs-lisp :minlevel 1" org-test-dir)
|
||||||
|
(org-export-expand-include-keyword)
|
||||||
|
(buffer-string))))
|
||||||
|
;; INCLUDE assigns the relative :minlevel conditional on narrowing.
|
||||||
|
(should
|
||||||
|
(org-test-with-temp-text-in-file
|
||||||
|
(format "* h1\n<point>#+INCLUDE: \"%s/examples/include.org::#ah\"" org-test-dir)
|
||||||
|
(narrow-to-region (point) (point-max))
|
||||||
|
(org-export-expand-include-keyword)
|
||||||
|
(eq 1 (org-current-level))))
|
||||||
|
;; If :minlevel is present do not alter it.
|
||||||
|
(should
|
||||||
|
(org-test-with-temp-text
|
||||||
|
(format "* h1\n<point>#+INCLUDE: \"%s/examples/include.org::#ah\" :minlevel 3" org-test-dir)
|
||||||
|
(narrow-to-region (point) (point-max))
|
||||||
|
(org-export-expand-include-keyword)
|
||||||
|
(eq 3 (org-current-level)))))
|
||||||
|
|
||||||
(ert-deftest test-org-export/expand-macro ()
|
(ert-deftest test-org-export/expand-macro ()
|
||||||
"Test macro expansion in an Org buffer."
|
"Test macro expansion in an Org buffer."
|
||||||
|
|
Loading…
Reference in a new issue