Browse Source

Implement index generation during export

This new code will search #+INDEX lines in the buffer.  For LaTeX, it
will simple convert these into LaTeX \index{} commands.  For other
backends, it will copy thee entries to a new file, with extension
orgx.  These files can then later be post-processed to create the index.
Carsten Dominik 8 years ago
parent
commit
4892c8899e
7 changed files with 275 additions and 73 deletions
  1. 5 0
      doc/ChangeLog
  2. 65 25
      doc/org.texi
  3. 11 0
      lisp/ChangeLog
  4. 5 0
      lisp/org-ascii.el
  5. 19 4
      lisp/org-exp.el
  6. 5 0
      lisp/org-latex.el
  7. 165 44
      lisp/org-publish.el

+ 5 - 0
doc/ChangeLog

@@ -1,3 +1,8 @@
+2010-03-28  Carsten Dominik  <carsten.dominik@gmail.com>
+
+	* org.texi (Index entries): New section.
+	(Generating an index): New section.
+
 2010-03-27  Carsten Dominik  <carsten.dominik@gmail.com>
 
 	* org.texi (Column width and alignment): Document that <N> now

+ 65 - 25
doc/org.texi

@@ -304,6 +304,7 @@ Markup for rich export
 * Images and tables::           Tables and Images will be included
 * Literal examples::            Source code examples with special formatting
 * Include files::               Include additional files into a document
+* Index entries::               
 * Macro replacement::           Use macros to create complex output
 * Embedded LaTeX::              LaTeX can be freely used inside Org documents
 
@@ -385,7 +386,8 @@ Configuration
 * Publishing action::           Setting the function doing the publishing
 * Publishing options::          Tweaking HTML export
 * Publishing links::            Which links keep working after publishing?
-* Project page index::          Publishing a list of project files
+* Sitemap::                     Generating a list of all pages
+* Generating an index::         An index that reaches across pages
 
 Sample configuration
 
@@ -8199,6 +8201,7 @@ summarizes the markup rules used in an Org-mode buffer.
 * Images and tables::           Tables and Images will be included
 * Literal examples::            Source code examples with special formatting
 * Include files::               Include additional files into a document
+* Index entries::               
 * Macro replacement::           Use macros to create complex output
 * Embedded LaTeX::              LaTeX can be freely used inside Org documents
 @end menu
@@ -8550,7 +8553,7 @@ label is stored as a link @samp{(label)}, for retrieval with @kbd{C-c C-l}.
 @end table
 
 
-@node Include files, Macro replacement, Literal examples, Markup
+@node Include files, Index entries, Literal examples, Markup
 @section Include files
 @cindex include files, markup rules
 
@@ -8581,8 +8584,25 @@ the selected markup.  For example, to include a file as an item, use
 Visit the include file at point.
 @end table
 
+@node Index entries, Macro replacement, Include files, Markup
+@section Index enries
+@cindex index entries, for publishing
 
-@node Macro replacement, Embedded LaTeX, Include files, Markup
+You can specify entries that will be used for generating an index during
+publishing.  This is done by lines starting with @code{#+INDEX}.  An entry
+the contains an exclamation mark will create a sub item.  See @ref{Generating
+an index} for more information.
+
+@example
+* Curriculum Vitae 
+#+INDEX: CV
+#+INDEX: Application!CV
+@end example
+
+
+
+
+@node Macro replacement, Embedded LaTeX, Index entries, Markup
 @section Macro replacement
 @cindex macro replacement, during export
 @cindex #+MACRO
@@ -10098,7 +10118,8 @@ and many other properties of a project.
 * Publishing action::           Setting the function doing the publishing
 * Publishing options::          Tweaking HTML export
 * Publishing links::            Which links keep working after publishing?
-* Project page index::          Publishing a list of project files
+* Sitemap::                     Generating a list of all pages
+* Generating an index::         An index that reaches across pages
 @end menu
 
 @node Project alist, Sources and destinations, Configuration, Configuration
@@ -10144,13 +10165,15 @@ publish to a webserver using a file name syntax appropriate for
 the Emacs @file{tramp} package.  Or you can publish to a local directory and
 use external tools to upload your website (@pxref{Uploading files}).
 @item @code{:preparation-function}
-@tab Function called before starting the publishing process, for example, to
-run @code{make} for updating files to be published.  The project property
-list is scoped into this call as the variable @code{project-plist}.
+@tab Function or list of functions to be called before starting the
+publishing process, for example, to run @code{make} for updating files to be
+published.  The project property list is scoped into this call as the
+variable @code{project-plist}.
 @item @code{:completion-function}
-@tab Function called after finishing the publishing process, for example, to
-change permissions of the resulting files.  The project property list is
-scoped into this call as the variable @code{project-plist}.
+@tab Function or list of functions called after finishing the publishing
+process, for example, to change permissions of the resulting files.  The
+project property list is scoped into this call as the variable
+@code{project-plist}.
 @end multitable
 @noindent
 
@@ -10337,7 +10360,7 @@ its setting overrides the value of the corresponding user variable (if
 any) during publishing.  Options set within a file (@pxref{Export
 options}), however, override everything.
 
-@node Publishing links, Project page index, Publishing options, Configuration
+@node Publishing links, Sitemap, Publishing options, Configuration
 @subsection Links between published files
 @cindex links, publishing
 
@@ -10374,31 +10397,48 @@ description into the HTML file, but no link.  One option for this
 function is @code{org-publish-validate-link} which checks if the given
 file is part of any project in @code{org-publish-project-alist}.
 
-@node Project page index,  , Publishing links, Configuration
-@subsection Project page index
-@cindex index, of published pages
+@node Sitemap, Generating an index, Publishing links, Configuration
+@subsection Generating a sitemap
+@cindex sitemap, of published pages
 
-The following properties may be used to control publishing of an
-index of files or a summary page for a given project.
+The following properties may be used to control publishing of 
+a map of files for a given project.
 
 @multitable @columnfractions 0.25 0.75
-@item @code{:auto-index}
-@tab When non-nil, publish an index during @code{org-publish-current-project}
+@item @code{:auto-sitemap}
+@tab When non-nil, publish a sitemap during @code{org-publish-current-project}
 or @code{org-publish-all}.
 
-@item @code{:index-filename}
-@tab Filename for output of index. Defaults to @file{sitemap.org} (which
+@item @code{:sitemap-filename}
+@tab Filename for output of sitemap. Defaults to @file{sitemap.org} (which
 becomes @file{sitemap.html}).
 
-@item @code{:index-title}
-@tab Title of index page. Defaults to name of file.
+@item @code{:sitemap-title}
+@tab Title of sitemap page. Defaults to name of file.
 
-@item @code{:index-function}
-@tab Plug-in function to use for generation of index.
-Defaults to @code{org-publish-org-index}, which generates a plain list
+@item @code{:sitemap-function}
+@tab Plug-in function to use for generation of the sitemap.
+Defaults to @code{org-publish-org-sitemap}, which generates a plain list
 of links to all files in the project.
 @end multitable
 
+@node Generating an index,  , Sitemap, Configuration
+@subsection Generating an index
+@cindex index, in a publishing project
+
+Org-mode can generate an index across the files of a publishing project.
+
+@multitable @columnfractions 0.25 0.75
+@item @code{:makeindex}
+@tab When non-nil, generate in index in the file @file{theindex.org} and
+publish it as @file{theindex.html}.
+@end multitable
+
+The file will be create when first publishing a project with the
+@code{:makeindex} set.  The file only contains a statement @code{#+include:
+"theindex.inc"}.  You can then built around this include statement by adding
+a title, style information etc.
+
 @node Uploading files, Sample configuration, Configuration, Publishing
 @section Uploading files
 @cindex rsync

+ 11 - 0
lisp/ChangeLog

@@ -1,3 +1,14 @@
+2010-03-28  Carsten Dominik  <carsten.dominik@gmail.com>
+
+	* org-publish.el (org-publish-sanitize-plist): New function to
+	rename "index" properties to "sitemap".  Do this renaming
+	globally.
+	(org-publish-with-aux-preprocess-maybe): New macro.
+	(org-publish-org-to-pdf, org-publish-org-to-html): Use the new
+	macro.
+	(org-publish-aux-preprocess)
+	(org-publish-index-generate-theindex.inc): New function.
+
 2010-03-27  Carsten Dominik  <carsten.dominik@gmail.com>
 
 	* org-table.el (org-table-align): Interpret <N> at fixed width,

+ 5 - 0
lisp/org-ascii.el

@@ -189,6 +189,11 @@ publishing directory."
 			  (if subtree-p
 			      (org-export-add-subtree-options opt-plist rbeg)
 			    opt-plist)))
+	 ;; The following two are dynamically scoped into other
+	 ;; routines below.
+	 (org-current-export-dir
+	  (or pub-dir (org-export-directory :html opt-plist)))
+	 (org-current-export-file buffer-file-name)
 	 (custom-times org-display-custom-times)
 	 (org-ascii-current-indentation '(0 . 0))
 	 (level 0) line txt

+ 19 - 4
lisp/org-exp.el

@@ -396,6 +396,11 @@ This is run after selection of trees to be exported has happened.
 This selection includes tags-based selection, as well as removal
 of commented and archived trees.")
 
+(defvar org-export-preprocess-after-headline-targets-hook nil
+  "Hook for preprocessing export buffer.
+This is run just after the headline targets have been defined and
+the target-alist has been set up.")
+
 (defvar org-export-preprocess-before-selecting-backend-code-hook nil
   "Hook for preprocessing an export buffer.
 This is run just before backend-specific blocks get selected.")
@@ -1296,6 +1301,8 @@ translations.  There is currently no way for users to extend this.")
   "Alist of targets with invisible aliases.")
 (defvar org-export-preferred-target-alist nil
   "Alist of section id's with preferred aliases.")
+(defvar org-export-id-target-alist nil
+  "Alist of section id's with preferred aliases.")
 (defvar org-export-code-refs nil
   "Alist of code references and line numbers")
 
@@ -1320,9 +1327,10 @@ on this string to produce the exported version."
 	 (outline-regexp "\\*+ ")
 	 target-alist rtn)
 
-    (setq org-export-target-aliases nil)
-    (setq org-export-preferred-target-alist nil)
-    (setq org-export-code-refs nil)
+    (setq org-export-target-aliases nil
+	  org-export-preferred-target-alist nil
+	  org-export-id-target-alist nil
+	  org-export-code-refs nil)
 
     (with-current-buffer (get-buffer-create " org-mode-tmp")
       (erase-buffer)
@@ -1379,6 +1387,8 @@ on this string to produce the exported version."
       ;; Find all headings and compute the targets for them
       (setq target-alist (org-export-define-heading-targets target-alist))
 
+      (run-hooks 'org-export-preprocess-after-headline-targets-hook)
+
       ;; Find HTML special classes for headlines
       (org-export-remember-html-container-classes)
 
@@ -1517,7 +1527,12 @@ The new targets are added to TARGET-ALIST, which is also returned."
 	      (if (not (assoc last-section-target
 			      org-export-preferred-target-alist))
 		  (push (cons last-section-target id)
-			org-export-preferred-target-alist))))
+			org-export-preferred-target-alist)))
+	    (when (equal (match-string 1) "ID")
+	      (if (not (assoc last-section-target
+			      org-export-id-target-alist))
+		  (push (cons last-section-target (concat "ID-" id))
+			org-export-id-target-alist))))
 	(setq level (org-reduced-level
 		     (save-excursion (goto-char (point-at-bol))
 				     (org-outline-level))))

+ 5 - 0
lisp/org-latex.el

@@ -636,6 +636,11 @@ when PUB-DIR is set, use this as the publishing directory."
 			    opt-plist)))
 	 ;; Make sure the variable contains the updated values.
 	 (org-export-latex-options-plist (setq org-export-opt-plist opt-plist))
+	 ;; The following two are dynamically scoped into other
+	 ;; routines below.
+	 (org-current-export-dir
+	  (or pub-dir (org-export-directory :html opt-plist)))
+	 (org-current-export-file buffer-file-name)
 	 (title (or (and subtree-p (org-export-get-title-from-subtree))
 		    (plist-get opt-plist :title)
 		    (and (not

+ 165 - 44
lisp/org-publish.el

@@ -31,7 +31,7 @@
 ;; + Publish all one's org-files to HTML or PDF
 ;; + Upload HTML, images, attachments and other files to a web server
 ;; + Exclude selected private pages from publishing
-;; + Publish a clickable index of pages
+;; + Publish a clickable sitemap of pages
 ;; + Manage local timestamps for publishing only changed files
 ;; + Accept plugin functions to extend range of publishable content
 ;;
@@ -39,6 +39,16 @@
 
 ;;; Code:
 
+
+(defun org-publish-sanitize-plist (plist)
+  (mapcar (lambda (x)
+	    (or (cdr (assoq x '((:index-filename . :sitemap-filename)
+				(:index-title . :sitemap-title)
+				(:index-function . :sitemap-function)
+				(:index-style . :sitemap-style)
+				(:auto-index . :auto-sitemap))))
+		x))))
+
 (eval-when-compile
   (require 'cl))
 (require 'org)
@@ -112,9 +122,11 @@ project for publishing.  For example, you could call GNU Make on a
 certain makefile, to ensure published files are built up to date.
 
   :preparation-function   Function to be called before publishing
-                          this project.
+                          this project.  This may also be a list
+                          of functions.
   :completion-function    Function to be called after publishing
-                          this project.
+                          this project.  This may also be a list
+                          of functions.
 
 Some properties control details of the Org publishing process,
 and are equivalent to the corresponding user variables listed in
@@ -144,22 +156,22 @@ learn more about their use and default values.
   :author                `user-full-name'
   :email                 `user-mail-address'
 
-The following properties may be used to control publishing of an
-index of files or summary page for a given project.
+The following properties may be used to control publishing of a
+sitemap of files or summary page for a given project.
 
-  :auto-index            Whether to publish an index during
+  :auto-sitemap           Whether to publish a sitemap during
                          `org-publish-current-project' or `org-publish-all'.
-  :index-filename        Filename for output of index.  Defaults
+  :sitemap-filename      Filename for output of sitemap.  Defaults
                          to 'sitemap.org' (which becomes 'sitemap.html').
-  :index-title           Title of index page.  Defaults to name of file.
-  :index-function        Plugin function to use for generation of index.
-                         Defaults to `org-publish-org-index', which
+  :sitemap-title         Title of sitemap page.  Defaults to name of file.
+  :sitemap-function      Plugin function to use for generation of sitemap.
+                         Defaults to `org-publish-org-sitemap', which
                          generates a plain list of links to all files
                          in the project.
-  :index-style           Can be `list' (index is just an itemized list
+  :sitemap-style         Can be `list' (sitemap is just an itemized list
                          of the titles of the files involved) or
                          `tree' (the directory structure of the source
-                         files is reflected in the index).  Defaults to
+                         files is reflected in the sitemap).  Defaults to
                          `tree'."
   :group 'org-publish
   :type 'alist)
@@ -439,20 +451,32 @@ PUB-DIR is the publishing directory."
 	(unless visiting
 	  (kill-buffer init-buf))))))
 
+(defmacro org-publish-with-aux-preprocess-maybe (&rest body)
+  "Execute BODY with a modified hook to preprocess for index."
+  `(let ((org-export-preprocess-after-headline-targets-hook
+	 (if (plist-get project-plist :makeindex)
+	     (cons 'org-publish-aux-preprocess
+		   org-export-preprocess-after-headline-targets-hook)
+	   org-export-preprocess-after-headline-targets-hook)))
+     ,@body))
+
 (defun org-publish-org-to-latex (plist filename pub-dir)
   "Publish an org file to LaTeX.
 See `org-publish-org-to' to the list of arguments."
-  (org-publish-org-to "latex" plist filename pub-dir))
+  (org-publish-with-aux-preprocess-maybe
+   (org-publish-org-to "latex" plist filename pub-dir)))
 
 (defun org-publish-org-to-pdf (plist filename pub-dir)
   "Publish an org file to PDF (via LaTeX).
 See `org-publish-org-to' to the list of arguments."
-  (org-publish-org-to "pdf" plist filename pub-dir))
+  (org-publish-with-aux-preprocess-maybe
+   (org-publish-org-to "pdf" plist filename pub-dir)))
 
 (defun org-publish-org-to-html (plist filename pub-dir)
   "Publish an org file to HTML.
 See `org-publish-org-to' to the list of arguments."
-  (org-publish-org-to "html" plist filename pub-dir))
+  (org-publish-with-aux-preprocess-maybe
+    (org-publish-org-to "html" plist filename pub-dir)))
 
 (defun org-publish-org-to-org (plist filename pub-dir)
   "Publish an org file to HTML.
@@ -518,31 +542,39 @@ See `org-publish-org-to' to the list of arguments."
 
 (defun org-publish-projects (projects)
   "Publish all files belonging to the PROJECTS alist.
-If :auto-index is set, publish the index too."
+If :auto-sitemap is set, publish the sitemap too.
+If :makeindex is set, also produce a file theindex.org."
   (mapc
    (lambda (project)
      (let*
 	 ((project-plist (cdr project))
 	  (exclude-regexp (plist-get project-plist :exclude))
-	  (index-p (plist-get project-plist :auto-index))
-	  (index-filename (or (plist-get project-plist :index-filename)
-			      "sitemap.org"))
-	  (index-function (or (plist-get project-plist :index-function)
-			      'org-publish-org-index))
+	  (sitemap-p (plist-get project-plist :auto-sitemap))
+	  (sitemap-filename (or (plist-get project-plist :sitemap-filename)
+				"sitemap.org"))
+	  (sitemap-function (or (plist-get project-plist :sitemap-function)
+				'org-publish-org-sitemap))
 	  (preparation-function (plist-get project-plist :preparation-function))
 	  (completion-function (plist-get project-plist :completion-function))
 	  (files (org-publish-get-base-files project exclude-regexp)) file)
-       (when preparation-function (funcall preparation-function))
-       (if index-p (funcall index-function project index-filename))
+       (when preparation-function (run-hooks 'preparation-function))
+       (if sitemap-p (funcall sitemap-function project sitemap-filename))
        (while (setq file (pop files))
 	 (org-publish-file file project))
-       (when completion-function (funcall completion-function))))
+       (when (plist-get project-plist :makeindex)
+	 (org-publish-index-generate-theindex.inc
+	  (plist-get project-plist :base-directory))
+	 (org-publish-file (expand-file-name
+			    "theindex.org"
+			    (plist-get project-plist :base-directory))
+			   project))
+       (when completion-function (run-hooks 'completion-function))))
    (org-publish-expand-projects projects)))
 
-(defun org-publish-org-index (project &optional index-filename)
-  "Create an index of pages in set defined by PROJECT.
-Optionally set the filename of the index with INDEX-FILENAME.
-Default for INDEX-FILENAME is 'sitemap.org'."
+(defun org-publish-org-sitemap (project &optional sitemap-filename)
+  "Create an sitemap of pages in set defined by PROJECT.
+Optionally set the filename of the sitemap with SITEMAP-FILENAME.
+Default for SITEMAP-FILENAME is 'sitemap.org'."
   (let* ((project-plist (cdr project))
 	 (dir (file-name-as-directory
 	       (plist-get project-plist :base-directory)))
@@ -550,28 +582,28 @@ Default for INDEX-FILENAME is 'sitemap.org'."
 	 (indent-str (make-string 2 ?\ ))
 	 (exclude-regexp (plist-get project-plist :exclude))
 	 (files (nreverse (org-publish-get-base-files project exclude-regexp)))
-	 (index-filename (concat dir (or index-filename "sitemap.org")))
-	 (index-title (or (plist-get project-plist :index-title)
-			  (concat "Index for project " (car project))))
-	 (index-style (or (plist-get project-plist :index-style)
+	 (sitemap-filename (concat dir (or sitemap-filename "sitemap.org")))
+	 (sitemap-title (or (plist-get project-plist :sitemap-title)
+			  (concat "Sitemap for project " (car project))))
+	 (sitemap-style (or (plist-get project-plist :sitemap-style)
 			  'tree))
-	 (visiting (find-buffer-visiting index-filename))
-	 (ifn (file-name-nondirectory index-filename))
-	 file index-buffer)
-    (with-current-buffer (setq index-buffer
-			       (or visiting (find-file index-filename)))
+	 (visiting (find-buffer-visiting sitemap-filename))
+	 (ifn (file-name-nondirectory sitemap-filename))
+	 file sitemap-buffer)
+    (with-current-buffer (setq sitemap-buffer
+			       (or visiting (find-file sitemap-filename)))
       (erase-buffer)
-      (insert (concat "#+TITLE: " index-title "\n\n"))
+      (insert (concat "#+TITLE: " sitemap-title "\n\n"))
       (while (setq file (pop files))
 	(let ((fn (file-name-nondirectory file))
 	      (link (file-relative-name file dir))
 	      (oldlocal localdir))
-	  ;; index shouldn't index itself
-	  (unless (equal (file-truename index-filename)
+	  ;; sitemap shouldn't list itself
+	  (unless (equal (file-truename sitemap-filename)
 			 (file-truename file))
-	    (if (eq index-style 'list)
-		(message "Generating list-style index for %s" index-title)
-	      (message "Generating tree-style index for %s" index-title)
+	    (if (eq sitemap-style 'list)
+		(message "Generating list-style sitemap for %s" sitemap-title)
+	      (message "Generating tree-style sitemap for %s" sitemap-title)
 	      (setq localdir (concat (file-name-as-directory dir)
 				     (file-name-directory link)))
 	      (unless (string= localdir oldlocal)
@@ -600,7 +632,7 @@ Default for INDEX-FILENAME is 'sitemap.org'."
 			    (org-publish-find-title file)
 			    "]]\n")))))
       (save-buffer))
-    (or visiting (kill-buffer index-buffer))))
+    (or visiting (kill-buffer sitemap-buffer))))
 
 (defun org-publish-find-title (file)
   "Find the title of file in project."
@@ -656,6 +688,7 @@ directory and force publishing all files."
 	   (if force nil org-publish-use-timestamps-flag)))
       (org-publish-projects org-publish-project-alist))))
 
+
 ;;;###autoload
 (defun org-publish-current-file (&optional force)
   "Publish the current file.
@@ -682,6 +715,94 @@ the project."
 	  (error "File %s is not part of any known project" (buffer-file-name)))
       (org-publish project))))
 
+
+;;; Index generation
+
+(defun org-publish-aux-preprocess ()
+  "Find index entries and write them to an .orgx file."
+  (let (entry index)
+    (goto-char (point-min))
+    (while
+	(and
+	 (re-search-forward "^[ \t]*#\\+index:[ \t]*\\(.*?\\)[ \t]*$" nil t)
+	 (> (match-end 1) (match-beginning 1)))
+      (setq entry (match-string 1))
+      (when (eq backend 'latex)
+	(replace-match (format "\\index{%s}" entry) t t))
+      (save-excursion
+	(org-back-to-heading t)
+	(setq target (get-text-property (point) 'target))
+	(setq target (or (cdr (assoc target org-export-preferred-target-alist))
+			 (cdr (assoc target org-export-id-target-alist))
+			 target))
+	(push (cons entry target) index)))
+    (with-temp-file 
+	(concat (file-name-sans-extension org-current-export-file) ".orgx")
+      (dolist (entry (nreverse index))
+	(insert (format "INDEX: (%s) %s\n" (cdr entry) (car entry)))))))
+
+(defun org-publish-index-generate-theindex.inc (directory)
+  "Generate the index from all .orgx files in the current directory and below."
+  (require 'find-lisp)
+  (let* ((fulldir (file-name-as-directory
+		   (expand-file-name directory)))
+	 (full-files (find-lisp-find-files directory "\\.orgx\\'"))
+	 (re (concat "\\`" fulldir))
+	 (files (mapcar (lambda (f) (if (string-match re f)
+					(substring f (match-end 0))
+				      f))
+			full-files))
+	 (default-directory directory)
+	 index origfile buf target entry ibuffer
+	 main last-main letter last-letter)
+    ;; `files' contains the list of relative file names
+    (dolist (file files)
+      (setq origfile (substring file 0 -1))
+      (setq buf (find-file-noselect file))
+      (with-current-buffer buf
+	(goto-char (point-min))
+	(while (re-search-forward "^INDEX: (\\(.*?\\)) \\(.*\\)" nil t)
+	  (setq target (match-string 1)
+		entry (match-string 2))
+	  (push (list entry origfile target) index)))
+      (kill-buffer buf))
+    (setq index (sort index (lambda (a b) (string< (downcase (car a))
+						   (downcase (car b))))))
+    (setq ibuffer (find-file-noselect (expand-file-name "theindex.inc" directory)))
+    (with-current-buffer ibuffer
+      (erase-buffer)
+      (insert "* Index\n")
+      (setq last-letter nil)
+      (dolist (idx index)
+	(setq entry (car idx) file (nth 1 idx) target (nth 2 idx))
+	(setq letter (upcase (substring entry 0 1)))
+	(when (not (equal letter last-letter))
+	  (insert "** " letter "\n")
+	  (setq last-letter letter))
+	(if (string-match "!" entry)
+	    (setq main (substring entry 0 (match-beginning 0))
+		  sub (substring entry (match-end 0)))
+	  (setq main nil sub nil last-main nil))
+	(when (and main (not (equal main last-main)))
+	  (insert "   - " main "\n")
+	  (setq last-main main))
+	(setq link (concat "[[file:" file "::#" target "]"
+			   "[" (or sub entry) "]]"))
+	(if (and main sub)
+	    (insert "     - " link "\n")
+	  (insert "   - " link "\n")))
+      (save-buffer))
+    (kill-buffer ibuffer)
+
+    (let ((index-file (expand-file-name "theindex.org" directory)))
+      (unless (file-exists-p index-file)
+	(setq ibuffer (find-file-noselect index-file))
+	(with-current-buffer ibuffer
+	  (erase-buffer)
+	  (insert "\n\n#+include: \"theindex.inc\"\n\n")
+	  (save-buffer))
+	(kill-buffer ibuffer)))))
+
 (provide 'org-publish)