ox: Allow file-links with #+INCLUDE-keyword
* org.el (org-edit-special): Handle file-links for INCLUDE. * ox.el (org-export--prepare-file-contents): Handle links and add option no-heading. * ox.el (org-export-expand-include-keyword): Resolve headline links and add option :only-contents. * orgguide.texi (Include files) org.texi (Include files): Updated. * testing/examples/include.org: New examples. * test-ox.el (test-org-export/expand-include): New tests.
This commit is contained in:
parent
58d95c3a4f
commit
986037a538
17
doc/org.texi
17
doc/org.texi
|
@ -10008,6 +10008,23 @@ to use the obvious defaults.
|
|||
#+INCLUDE: "~/.emacs" :lines "10-" @r{Include lines from 10 to EOF}
|
||||
@end example
|
||||
|
||||
Finally, you may use a file-link to extract an object as matched by
|
||||
@code{org-link-search}@footnote{Note that
|
||||
@code{org-link-search-must-match-exact-headline} is locally bound to non-nil.
|
||||
Therefore, @code{org-link-search} only matches headlines and named elements.}
|
||||
(@pxref{Search options}). If the @code{:only-contents} property is non-nil,
|
||||
only the contents of the requested element will be included, omitting
|
||||
properties drawer and planning-line if present. The @code{:lines} keyword
|
||||
operates locally with respect to the requested element. Some examples:
|
||||
|
||||
@example
|
||||
#+INCLUDE: "./paper.org::#theory" :only-contents t
|
||||
@r{Include the body of the heading with the custom id @code{theory}}
|
||||
#+INCLUDE: "./paper.org::mytable" @r{Include named element.}
|
||||
#+INCLUDE: "./paper.org::*conclusion" :lines 1-20
|
||||
@r{Include the first 20 lines of the headline named conclusion.}
|
||||
@end example
|
||||
|
||||
@table @kbd
|
||||
@kindex C-c '
|
||||
@item C-c '
|
||||
|
|
|
@ -2264,8 +2264,13 @@ include your @file{.emacs} file, you could use:
|
|||
The optional second and third parameter are the markup (i.e., @samp{example}
|
||||
or @samp{src}), and, if the markup is @samp{src}, the language for formatting
|
||||
the contents. The markup is optional, if it is not given, the text will be
|
||||
assumed to be in Org mode format and will be processed normally. @kbd{C-c '}
|
||||
will visit the included file.
|
||||
assumed to be in Org mode format and will be processed normally. File-links
|
||||
will be interpreted as well:
|
||||
@smallexample
|
||||
#+INCLUDE: "./otherfile.org::#my_custom_id" :only-contents t
|
||||
@end smallexample
|
||||
@noindent
|
||||
@kbd{C-c '} will visit the included file.
|
||||
|
||||
@node Embedded @LaTeX{}, , Include files, Markup
|
||||
@section Embedded @LaTeX{}
|
||||
|
|
|
@ -20525,9 +20525,12 @@ Otherwise, return a user error."
|
|||
session params))))))
|
||||
(keyword
|
||||
(if (member (org-element-property :key element) '("INCLUDE" "SETUPFILE"))
|
||||
(find-file-other-window
|
||||
(org-remove-double-quotes
|
||||
(car (org-split-string (org-element-property :value element)))))
|
||||
(org-open-link-from-string
|
||||
(format "[[%s]]"
|
||||
(expand-file-name
|
||||
(org-remove-double-quotes
|
||||
(car (org-split-string
|
||||
(org-element-property :value element)))))))
|
||||
(user-error "No special environment to edit here")))
|
||||
(table
|
||||
(if (eq (org-element-property :type element) 'table.el)
|
||||
|
|
117
lisp/ox.el
117
lisp/ox.el
|
@ -3325,13 +3325,25 @@ paths."
|
|||
;; 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 (expand-file-name
|
||||
(org-remove-double-quotes
|
||||
(match-string 1 value))
|
||||
dir)
|
||||
(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-remove-double-quotes
|
||||
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]+\\)?\\)\""
|
||||
|
@ -3391,17 +3403,88 @@ paths."
|
|||
(t
|
||||
(insert
|
||||
(with-temp-buffer
|
||||
(let ((org-inhibit-startup t)) (org-mode))
|
||||
(insert
|
||||
(org-export--prepare-file-contents
|
||||
file lines ind minlevel
|
||||
(or (gethash file file-prefix)
|
||||
(puthash file (incf current-prefix) file-prefix))))
|
||||
(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 (incf current-prefix) file-prefix)))))
|
||||
(org-export-expand-include-keyword
|
||||
(cons (list file lines) included)
|
||||
(file-name-directory file))
|
||||
(buffer-string)))))))))))))
|
||||
|
||||
(defun org-export--inclusion-absolute-lines (file location only-contents lines)
|
||||
"Resolve absolute lines for an included file with file-link.
|
||||
|
||||
FILE is string file-name of the file to include. LOCATION is a
|
||||
string name within FILE to be included (located via
|
||||
`org-link-search'). If ONLY-CONTENTS is non-nil only the
|
||||
contents of the named element will be included, as determined
|
||||
Org-Element. If LINES is non-nil only those lines are included.
|
||||
|
||||
Return a string of lines to be included in the format expected by
|
||||
`org-export--prepare-file-contents'."
|
||||
(with-temp-buffer
|
||||
(insert-file-contents file)
|
||||
(unless (eq major-mode 'org-mode)
|
||||
(let ((org-inhibit-startup t)) (org-mode)))
|
||||
(condition-case err
|
||||
;; Enforce consistent search.
|
||||
(let ((org-link-search-must-match-exact-headline t))
|
||||
(org-link-search location))
|
||||
(error
|
||||
(error (format "%s for %s::%s" (error-message-string err) file location))))
|
||||
(let* ((element (org-element-at-point))
|
||||
(contents-begin
|
||||
(and only-contents (org-element-property :contents-begin element))))
|
||||
(narrow-to-region
|
||||
(or contents-begin (org-element-property :begin element))
|
||||
(org-element-property (if contents-begin :contents-end :end) element))
|
||||
(when (and only-contents
|
||||
(memq (org-element-type element) '(headline inlinetask)))
|
||||
;; Skip planning line and property-drawer. If a normal drawer
|
||||
;; precedes a property-drawer both will be included.
|
||||
;; Remaining property-drawers are removed as needed in
|
||||
;; `org-export--prepare-file-contents'.
|
||||
(goto-char (point-min))
|
||||
(when (org-looking-at-p org-planning-line-re) (forward-line))
|
||||
(when (looking-at org-property-drawer-re) (goto-char (match-end 0)))
|
||||
(unless (bolp) (forward-line))
|
||||
(narrow-to-region (point) (point-max))))
|
||||
(when lines
|
||||
(org-skip-whitespace)
|
||||
(beginning-of-line)
|
||||
(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 beg)
|
||||
(forward-line (1- lend))
|
||||
(point))))
|
||||
(narrow-to-region beg end)))
|
||||
(let ((end (point-max)))
|
||||
(goto-char (point-min))
|
||||
(widen)
|
||||
(let ((start-line (line-number-at-pos)))
|
||||
(format "%d-%d"
|
||||
start-line
|
||||
(save-excursion
|
||||
(+ start-line
|
||||
(let ((counter 0))
|
||||
(while (< (point) end) (incf counter) (forward-line))
|
||||
counter))))))))
|
||||
|
||||
(defun org-export--prepare-file-contents (file &optional lines ind minlevel id)
|
||||
"Prepare the contents of FILE for inclusion and return them as a string.
|
||||
|
||||
|
@ -3448,6 +3531,20 @@ with footnotes is included in a document."
|
|||
(skip-chars-backward " \r\t\n")
|
||||
(forward-line)
|
||||
(delete-region (point) (point-max))
|
||||
;; Remove property-drawers after drawers.
|
||||
(when (or ind minlevel)
|
||||
(unless (eq major-mode 'org-mode)
|
||||
(let ((org-inhibit-startup t)) (org-mode)))
|
||||
(goto-char (point-min))
|
||||
(when (looking-at org-drawer-regexp)
|
||||
(goto-char (match-end 0))
|
||||
(search-forward-regexp org-drawer-regexp)
|
||||
(forward-line 1)
|
||||
(beginning-of-line))
|
||||
(when (looking-at org-property-drawer-re)
|
||||
(delete-region (match-beginning 0) (match-end 0))
|
||||
(beginning-of-line))
|
||||
(delete-region (point) (save-excursion (and (org-skip-whitespace) (point)))))
|
||||
;; If IND is set, preserve indentation of include keyword until
|
||||
;; the first headline encountered.
|
||||
(when ind
|
||||
|
|
|
@ -8,3 +8,28 @@ Small Org file with an include keyword.
|
|||
|
||||
* Heading
|
||||
body
|
||||
|
||||
* Another heading
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: ah
|
||||
:END:
|
||||
1
|
||||
2
|
||||
3
|
||||
|
||||
* A headline with a table
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: ht
|
||||
:END:
|
||||
#+CAPTION: a table
|
||||
#+NAME: tbl
|
||||
| 1 |
|
||||
|
||||
* drawer-headline
|
||||
:LOGBOOK:
|
||||
drawer
|
||||
:END:
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: dh
|
||||
:END:
|
||||
content
|
||||
|
|
|
@ -918,7 +918,64 @@ Footnotes[fn:1], [fn:test] and [fn:inline:anonymous footnote].
|
|||
(org-export-expand-include-keyword)
|
||||
(org-element-map (org-element-parse-buffer)
|
||||
'footnote-reference
|
||||
(lambda (ref) (org-element-property :label ref))))))))))))
|
||||
(lambda (ref) (org-element-property :label ref)))))))))))
|
||||
;; If only-contents is non-nil only include contents of element.
|
||||
(should
|
||||
(equal
|
||||
"body\n"
|
||||
(org-test-with-temp-text
|
||||
(concat
|
||||
(format "#+INCLUDE: \"%s/examples/include.org::*Heading\" " org-test-dir)
|
||||
":only-contents t")
|
||||
(org-export-expand-include-keyword)
|
||||
(buffer-string))))
|
||||
;; Headings can be included via CUSTOM_ID.
|
||||
(should
|
||||
(org-test-with-temp-text
|
||||
(format "#+INCLUDE: \"%s/examples/include.org::#ah\"" org-test-dir)
|
||||
(org-export-expand-include-keyword)
|
||||
(goto-char (point-min))
|
||||
(looking-at "* Another heading")))
|
||||
;; Named objects can be included.
|
||||
(should
|
||||
(equal
|
||||
"| 1 |\n"
|
||||
(org-test-with-temp-text
|
||||
(format "#+INCLUDE: \"%s/examples/include.org::tbl\" :only-contents t" org-test-dir)
|
||||
(org-export-expand-include-keyword)
|
||||
(buffer-string))))
|
||||
;; Including non-existing elements should result in an error.
|
||||
(should-error
|
||||
(org-test-with-temp-text
|
||||
(format "#+INCLUDE: \"%s/examples/include.org::*non-existing heading\"" org-test-dir)
|
||||
(org-export-expand-include-keyword)))
|
||||
;; Lines work relatively to an included element.
|
||||
(should
|
||||
(equal
|
||||
"2\n3\n"
|
||||
(org-test-with-temp-text
|
||||
(format "#+INCLUDE: \"%s/examples/include.org::#ah\" :only-contents t :lines \"2-3\"" org-test-dir)
|
||||
(org-export-expand-include-keyword)
|
||||
(buffer-string))))
|
||||
;; Properties should be dropped from headlines.
|
||||
(should
|
||||
(equal
|
||||
(org-test-with-temp-text
|
||||
(format "#+INCLUDE: \"%s/examples/include.org::#ht\" :only-contents t" org-test-dir)
|
||||
(org-export-expand-include-keyword)
|
||||
(buffer-string))
|
||||
(org-test-with-temp-text
|
||||
(format "#+INCLUDE: \"%s/examples/include.org::tbl\"" org-test-dir)
|
||||
(org-export-expand-include-keyword)
|
||||
(buffer-string))))
|
||||
;; Properties should be dropped, drawers should not be.
|
||||
(should
|
||||
(equal
|
||||
":LOGBOOK:\ndrawer\n:END:\ncontent\n"
|
||||
(org-test-with-temp-text
|
||||
(format "#+INCLUDE: \"%s/examples/include.org::#dh\" :only-contents t" org-test-dir)
|
||||
(org-export-expand-include-keyword)
|
||||
(buffer-string)))))
|
||||
|
||||
(ert-deftest test-org-export/expand-macro ()
|
||||
"Test macro expansion in an Org buffer."
|
||||
|
|
Loading…
Reference in New Issue