emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
From: Rasmus <rasmus@gmx.us>
To: emacs-orgmode@gnu.org
Subject: Re: [bug, patch, ox] INCLUDE and footnotes
Date: Sat, 13 Dec 2014 22:45:24 +0100	[thread overview]
Message-ID: <87388j9qbv.fsf@gmx.us> (raw)
In-Reply-To: <878uifbjc7.fsf@nicolasgoaziou.fr> (Nicolas Goaziou's message of "Wed, 10 Dec 2014 16:44:24 +0100")

[-- Attachment #1: Type: text/plain, Size: 2176 bytes --]

Hi,

Attached is a patch that enables footnotes in INCLUDEd documents when
using :lines and friends.  It stores the footnotes in a hash-table
initialized in `org-export-expand-include-keyword' and updated via
`org-export--prepare-file-contents'.  The footnotes are then inserted when
all include keywords are expanded.

At the moment only footnotes from INCLUDEs with :lines-like arguments will
be picket up here.  But I think it might be nice to also use this
functionality with footnotes when whole documents are included, and not
include the footnote section directly from these documents.  Though I
expect the to be accused of worm-nurturing, do consider this curious example:

     $> cat t{1,0*}.org
     #+TITLE: This is t1.org 
     #+INCLUDE: "/tmp/t00.org"
     #+INCLUDE: "/tmp/t01.org"

     # This is t00.org
     Footnotes[fn:1], [fn:test] and [fn:inline:anonymous footnote].
     * Footnotes

     [fn:1] Footnote 1
     [fn:test] Footnote "test"
     
     # t01.org
     Footnotes[fn:1], [fn:test] and [fn:inline:anonymous footnote].
     * Footnotes

     [fn:1] Footnote 1
     [fn:test] Footnote "test"

ox will in fact interpret t1.org as:

    #+TITLE: This is t1.org
    Footnotes[fn:1-1], [fn:1-test] and [fn:1-inline:anonymous footnote].

    [fn:1-1] Footnote 1

    [fn:1-test] Footnote "test"
    Footnotes[fn:2-1], [fn:2-test] and [fn:2-inline:anonymous footnote].

So I see three approaches:
   1. let the user shoot himself in the foot
   2. fix the "bug" (IMO) that is that
          #+INCLUDE: "/tmp/t00.org"
          #+INCLUDE: "/tmp/t01.org"
      Is "read" as
          #+INCLUDE: "/tmp/t00.org" :minlevel N
          #+INCLUDE: "/tmp/t01.org" :minlevel N+1
      The easiest way I can think of would be to do a pre-scan of the
      buffer to see if there exists any instances where include is only
      separated by whitespace in which case they should have the same
      level.
   3. Fix the particular nastiness above by removing footnote sections and
      reinserting them using the mechanism of this patch.

—Rasmus

-- 
A page of history is worth a volume of logic

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-ox.el-Fix-footnote-bug-in-INCLUDE-keyword.patch --]
[-- Type: text/x-diff, Size: 8624 bytes --]

From 04726b6e5915fc47f3ecc261f9c7d9dfb2b44f56 Mon Sep 17 00:00:00 2001
From: rasmus <rasmus@gmx.us>
Date: Tue, 9 Dec 2014 12:40:52 +0100
Subject: [PATCH] ox.el: Fix footnote-bug in #+INCLUDE-keyword

* ox.el (org-export--prepare-file-contents): Preserve footnotes
when using the LINES argument.  New optional argument FOOTNOTES.
 (org-export-expand-include-keyword): New optional argument
 FOOTNOTES.
* test-ox.el: Add test for INCLUDE with :lines and footnotes.
* ORG-NEWS: Update.
---
 etc/ORG-NEWS            |  3 +-
 lisp/ox.el              | 94 +++++++++++++++++++++++++++++++++++--------------
 testing/lisp/test-ox.el | 25 +++++++++++++
 3 files changed, 95 insertions(+), 27 deletions(-)

diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index f719886..f831c60 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -103,7 +103,8 @@ The content of the included file can now be optionally marked up, for
 instance as HTML.  See the documentation for details.
 *** File links with =#+INCLUDE= keyword
 Objects can be extracted via =#+INCLUDE= using file links.  It is
-possible to include only the contents of the object.  See manual for
+possible to include only the contents of the object.  Further,
+footnotes are now supported when using =#+INCLUDE=.  See manual for
 more information.
 *** Additional =:hline= processing to ob-shell
 If the argument =:hlines yes= is present in a babel call, an optional
diff --git a/lisp/ox.el b/lisp/ox.el
index 9d9e794..975f178 100644
--- a/lisp/ox.el
+++ b/lisp/ox.el
@@ -3052,16 +3052,18 @@ locally for the subtree through node properties."
 		   (car key)
 		   (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.
 Optional argument INCLUDED is a list of included file names along
 with their line restriction, when appropriate.  It is used to
 avoid infinite recursion.  Optional argument DIR is the current
 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)
 	(file-prefix (make-hash-table :test #'equal))
-	(current-prefix 0))
+	(current-prefix 0)
+	(footnotes (or footnotes (make-hash-table :test 'equal))))
     (goto-char (point-min))
     (while (re-search-forward "^[ \t]*#\\+INCLUDE:" nil t)
       (let ((element (save-match-data (org-element-at-point))))
@@ -3155,14 +3157,27 @@ paths."
 			       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)))))
+                     (insert (org-export--prepare-file-contents
+			      file lines ind minlevel
+			      (or (gethash file file-prefix)
+				  (puthash file (incf current-prefix) file-prefix))
+			      footnotes)))
 		   (org-export-expand-include-keyword
 		    (cons (list file lines) included)
-		    (file-name-directory file))
+		    (file-name-directory file)
+		    footnotes)
+		   (goto-char (point-min))
+		   (while (and (search-forward-regexp org-footnote-re nil t))
+		     (let* ((reference (org-element-context))
+			    (type (org-element-type reference))
+			    (label (org-element-property :label reference)))
+		       (when (and label (eq type 'footnote-reference))
+			 (unless (org-footnote-get-definition label)
+			   (save-excursion
+			     (org-footnote-create-definition label)
+			     ;; We do not need an error here since ox
+			     ;; will complain if a footnote is missing.
+			     (insert (or (gethash label footnotes) "")))))))
 		   (buffer-string)))))))))))))
 
 (defun org-export--inclusion-absolute-lines (file location only-contents lines)
@@ -3227,8 +3242,8 @@ Return a string of lines to be included in the format expected by
 		       (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.
+(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
 lines, include only those lines.
@@ -3246,7 +3261,11 @@ file should have.
 Optional argument ID is an integer that will be inserted before
 each footnote definition and reference if FILE is an 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
     (insert-file-contents file)
     (when lines
@@ -3308,20 +3327,43 @@ with footnotes is included in a document."
     ;; Append ID to all footnote references and definitions, so they
     ;; become file specific and cannot collide with footnotes in other
     ;; included files.
-    (when id
-      (goto-char (point-min))
-      (while (re-search-forward org-footnote-re nil t)
-	(let ((reference (org-element-context)))
-	  (when (memq (org-element-type reference)
-		      '(footnote-reference footnote-definition))
-	    (goto-char (org-element-property :begin reference))
-	    (forward-char)
-	    (let ((label (org-element-property :label reference)))
-	      (cond ((not label))
-		    ((org-string-match-p "\\`[0-9]+\\'" label)
-		     (insert (format "fn:%d-" id)))
-		    (t (forward-char 3) (insert (format "%d-" id)))))))))
-    (org-element-normalize-string (buffer-string))))
+    (let* ((include-footnotes (and id lines)))
+      (when id
+	(unless (eq major-mode 'org-mode)
+	  (let ((org-inhibit-startup t)) (org-mode)))
+	(goto-char (point-min))
+	(while (re-search-forward org-footnote-re nil t)
+	  (let* ((reference (org-element-context))
+		 (type (org-element-type reference))
+		 (footnote-type (org-element-property :type reference))
+                 (label (org-element-property :label reference)))
+	    (when (and (eq type 'footnote-reference))
+	      (goto-char (org-element-property :begin reference))
+	      (when label
+		(goto-char (org-element-property :begin reference))
+		(forward-char 4)
+		(insert (format "%d-" id))
+		(and (not (eq footnote-type 'inline))
+		     (let ((new-label (org-element-property
+				       :label (org-element-context))))
+		       (save-restriction
+			 (save-excursion
+			   (widen)
+			   (org-footnote-goto-definition label)
+			   (let ((definition (org-element-context)))
+			     (and include-footnotes
+				  (puthash new-label
+					   (org-element-normalize-string
+					    (buffer-substring
+					     (org-element-property
+					      :contents-begin definition)
+					     (org-element-property
+					      :contents-end definition)))
+					   footnotes))
+			     (goto-char (org-element-property :begin definition))
+			     (forward-char 4)
+			     (insert (format "%d-" id))))))))))))
+      (org-element-normalize-string (buffer-string)))))
 
 (defun org-export-execute-babel-code ()
   "Execute every Babel code in the visible part of current buffer."
diff --git a/testing/lisp/test-ox.el b/testing/lisp/test-ox.el
index 9a0e787..da4850d 100644
--- a/testing/lisp/test-ox.el
+++ b/testing/lisp/test-ox.el
@@ -919,6 +919,31 @@ Footnotes[fn:1], [fn:test] and [fn:inline:anonymous footnote].
 		(org-element-map (org-element-parse-buffer)
 		    'footnote-reference
 		  (lambda (ref) (org-element-property :label ref)))))))))))
+  ;; Footnotes are supported by :lines-like elements and unnecessary
+  ;; footnotes are dropped.
+  (should
+   (= 3
+      (length
+       (delete-dups
+	(let ((contents "
+* foo
+Footnotes[fn:1]
+* bar
+Footnotes[fn:2], [fn:test], and [fn:inline:anonymous footnote]
+
+[fn:1] Footnote 1
+[fn:2] Footnote 1
+* Footnotes
+[fn:test] Footnote \"test\""))
+	  (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.
   (should
    (equal
-- 
2.1.3


  reply	other threads:[~2014-12-13 21:45 UTC|newest]

Thread overview: 33+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2014-12-09 11:44 [bug, patch, ox] INCLUDE and footnotes Rasmus
2014-12-09 19:10 ` Rasmus
2014-12-09 19:14 ` Nicolas Goaziou
2014-12-09 21:21   ` Rasmus
2014-12-09 21:37     ` Nicolas Goaziou
2014-12-10  0:57       ` Rasmus
2014-12-10 11:21         ` Nicolas Goaziou
2014-12-10 11:58           ` Rasmus
2014-12-10 15:44             ` Nicolas Goaziou
2014-12-13 21:45               ` Rasmus [this message]
2014-12-17 23:30                 ` Nicolas Goaziou
2014-12-18 17:37                   ` Rasmus
2014-12-19 16:44                     ` Rasmus
2014-12-21 21:04                       ` Nicolas Goaziou
2014-12-21 22:39                         ` Rasmus
2014-12-21 23:38                           ` Nicolas Goaziou
2014-12-22  1:42                             ` Rasmus
2014-12-22  9:05                               ` Nicolas Goaziou
2014-12-24 18:03                                 ` Rasmus
2014-12-24 21:14                                   ` Nicolas Goaziou
2014-12-25  1:38                                     ` Rasmus
2014-12-25  2:04                                     ` Rasmus
2014-12-21 20:52                     ` Nicolas Goaziou
2014-12-22  1:49                       ` Rasmus
2014-12-22 11:10                         ` Nicolas Goaziou
2014-12-22 12:36                           ` Rasmus
2014-12-22 20:54                             ` Nicolas Goaziou
2014-12-22 22:11                               ` Rasmus
2014-12-22 22:51                                 ` Nicolas Goaziou
2014-12-23  2:09                                   ` Rasmus
2014-12-24 17:54                                   ` Rasmus
2014-12-24 18:10                                     ` [git-101] How to push a branch and avoid merge-message? (was: [bug, patch, ox] INCLUDE and footnotes) Rasmus
2014-12-24 21:09                                       ` [git-101] How to push a branch and avoid merge-message? Nicolas Goaziou

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.orgmode.org/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=87388j9qbv.fsf@gmx.us \
    --to=rasmus@gmx.us \
    --cc=emacs-orgmode@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).