removed org-refer-by-number.el
authorMarc-Oliver Ihm <ihm@think.(none)>
Fri, 7 Dec 2012 21:54:03 +0000 (22:54 +0100)
committerMarc-Oliver Ihm <ihm@think.(none)>
Fri, 7 Dec 2012 21:54:03 +0000 (22:54 +0100)
code/elisp/org-reftable.el [new file with mode: 0755]
org-contrib/ob-table-operations.org [new file with mode: 0644]

diff --git a/code/elisp/org-reftable.el b/code/elisp/org-reftable.el
new file mode 100755 (executable)
index 0000000..bb1e625
--- /dev/null
@@ -0,0 +1,1299 @@
+;;; org-reftable.el --- Ordered lookup table for reference numbers\r
+\r
+;; Copyright (C) 2011,2012 Free Software Foundation, Inc.\r
+\r
+;; Author: Marc-Oliver Ihm <org-reftable@ferntreffer.de>\r
+;; Keywords: hypermedia, matching\r
+;; Requires: org\r
+;; Download: http://orgmode.org/worg/code/elisp/org-reftable.el\r
+;; Version: 2.0.0\r
+\r
+;;; License:\r
+\r
+;; This program is free software; you can redistribute it and/or modify\r
+;; it under the terms of the GNU General Public License as published by\r
+;; the Free Software Foundation; either version 3, or (at your option)\r
+;; any later version.\r
+;;\r
+;; This program is distributed in the hope that it will be useful,\r
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+;; GNU General Public License for more details.\r
+;;\r
+;; You should have received a copy of the GNU General Public License\r
+;; along with GNU Emacs; see the file COPYING.  If not, write to the\r
+;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,\r
+;; Boston, MA 02110-1301, USA.\r
+\r
+;;; Commentary:\r
+\r
+;; Purpose:\r
+;;\r
+;;  Create, search and look up numbers from a dedicated reference table.\r
+;;  These numbers (e.g. "R237" or "-455-") may be used to refer to:\r
+;;\r
+;;   - Nodes in Org-mode (insert them into the heading)\r
+;;\r
+;;   - Things outside of org (e.g. mailfolders, directories, reports or\r
+;;     pieces of paper)\r
+;;\r
+;;  The table is kept sorted for most frequently or most recently used\r
+;;  reference numbers. Additionally, lines can be selected by keywords, so\r
+;;  that specific references can be found very easily. Earlier versions of \r
+;;  this extension had been named org-refer-by-number.el.\r
+;;\r
+;;\r
+;; Setup:\r
+;;\r
+;;  - Add these lines to your .emacs:\r
+;;\r
+;;    (require 'org-reftable)\r
+;;    ;; Later you should probably change this id, as will be explained below\r
+;;    (setq org-reftable-id "00e26bef-1929-4110-b8b4-7eb9c9ab1fd4")\r
+;;    ;; Optionally assign a key; pick your own favorite\r
+;;    (global-set-key (kbd "C-+") 'org-reftable)\r
+;;\r
+;;  - Just invoke `org-reftable', which will explain how to complete your\r
+;;    setup by creating the necessary reference table.\r
+;;\r
+;;\r
+;; Further reading:\r
+;;\r
+;;  - For the necessary setup read the documentation of `org-reftable-id'\r
+;;    (which is, what `org-reftable' shows, as long as your setup is still\r
+;;    incomplete).\r
+;;\r
+;;  - For regular usage, see the function `org-reftable'.\r
+;;\r
+\r
+;;; Change Log:\r
+\r
+;;   [2012-12-07 Fr] Version 2.0.0:\r
+;;    - renamed the package from \"org-refer-by-number\" to \"org-reftable\"\r
+;;    - The format of the reference table has changed ! You need to bring\r
+;;      your existing table into the new format by hand (which however is\r
+;;      easy and explained below)\r
+;;    - Reference table can be sorted after usage count or date of last access\r
+;;    - Ask user explicitly, which command to invoke\r
+\r
+;;   [2012-09-22 Sa] Version 1.5.0:\r
+;;    - New command "sort" to sort a buffer or region by reference number\r
+;;    - New commands "highlight" and "unhighlight" to mark references\r
+\r
+;;   [2012-07-13 Fr] Version 1.4.0:\r
+;;    - New command "head" to find a headline with a reference number\r
+\r
+;;   [2012-04-28 Sa] Version 1.3.0:\r
+;;    - New commands occur and multi-occur\r
+;;    - All commands can now be invoked explicitly\r
+;;    - New documentation\r
+;;    - Many bugfixes\r
+\r
+;;   [2011-12-10 Sa] Version 1.2.0:\r
+;;    - Fixed a bug, which lead to a loss of newly created reference numbers\r
+;;    - Introduced single and double prefix arguments\r
+;;    - Started this Change Log\r
+\r
+;;; Code:\r
+\r
+(require 'org-table)\r
+(require 'cl)\r
+\r
+(defvar org-reftable-preferred-command nil\r
+  "Preferred command when choosing")\r
+\r
+(defvar org-reftable-commands '(occur head new enter leave goto help reorder sort update highlight unhighlight)\r
+  "List of commands known to org-reftable:\r
\r
+\r
+  occur: If you supply a keyword (text): Apply emacs standard\r
+    occur operation on the table of references; ask for a\r
+    string (keyword) to select lines. Occur will only show you\r
+    references which contain the given keyword, so you can easily\r
+    find the right one\r
+\r
+   If you supply a reference (number): Apply emacs standard\r
+   multi-occur operation all org-mode buffers to search for a\r
+   specific reference\r
+\r
+  head: Scan all headings until the first one with the given\r
+    reference number is found\r
+\r
+  new: Create a new reference. Copy any previously selected text\r
+\r
+  leave: Leave the table of references. If the last command has\r
+    been \"new\", the new reference is copied and ready to yank\r
+\r
+  enter: Just enter the node with the table of references\r
+\r
+  goto: Search for a specific references within the table of\r
+    references\r
+\r
+  help: Show this list of commands\r
+\r
+  all: Show all commands including the less frequently used ones\r
+    given below.\r
+\r
+  reorder: Temporarily reorder the table of references, e.g. by\r
+    cound or last access\r
+\r
+  sort: Sort a set of lines (either the active region or the\r
+    whole buffer) by the references found within each line\r
+\r
+  update: For the given reference update the line in the\r
+    reference table\r
+\r
+  highlight: Highlight references in region or buffer\r
+\r
+  unhighlight: Remove highlights\r
+\r
+\r
+When prompting for a command, org-reftable puts the most likely\r
+chosen one (e.g. \"occur\" or \"new\") at the front of the list,\r
+so that you may just type RET. If this command needs additional\r
+input (like e.g. \"occur\" does, which needs a string to search\r
+for), you may supply this input right away, although you are\r
+still beeing prompted for the command (in that case your input\r
+will not match any of the given choices).\r
+\r
+")\r
+\r
+(defvar org-reftable-commands-some '(occur head new leave enter goto all help)\r
+  "Subset of org-reftable-commands shown initially" )\r
+\r
+(defvar org-reftable-id nil \r
+  "Id of the Org-mode node, which contains the reference table.\r
+\r
+Read below, on how to set up things. See the documentation of\r
+`org-reftable' for normal usage after setup.\r
+\r
+Setup requires two steps:\r
+\r
+ - Adjust your .emacs initialization file\r
+\r
+ - Create a suitable org-mode node\r
+\r
+\r
+Here are the lines, you should add to your .emacs:\r
+\r
+  (require 'org-reftable)\r
+  ;; Later you should probably change this id, as will be explained below\r
+  (setq org-reftable-id \"00e26bef-1929-4110-b8b4-7eb9c9ab1fd4\")\r
+  ;; Optionally assign a key; pick your own favorite\r
+  (global-set-key (kbd \"C-+\") 'org-reftable)\r
+\r
+Do not forget to restart emacs to make these lines effective.\r
+\r
+The id given above is an example, yours can be different.\r
+\r
+\r
+As a second step you need to create the org-mode node, where your\r
+reference numbers will be stored. It may look like this:\r
+\r
+\r
+  * org-reftable\r
+    :PROPERTIES:\r
+    :ID:       00e26bef-1929-4110-b8b4-7eb9c9ab1fd4\r
+    :END:\r
+\r
+\r
+    |     | comment            |         |         |               |\r
+    | ref | ;c                 | count;s | created | last-accessed |\r
+    |-----+--------------------+---------+---------+---------------|\r
+    | R1  | My first reference |         |         |               |\r
+\r
+\r
+\r
+You may just want to copy this node into one of your org-files.\r
+Many things however can or should be adjusted:\r
+\r
+ - The node needs not be a top level node.\r
+\r
+ - Its name is completely at you choice. The node is found\r
+   through its ID.\r
+\r
+ - There are two lines of headings above the first hline. The\r
+   first one is ignored by org-reftable, and you can use them to\r
+   give meaningful names to columns; the second line however\r
+   contains configuration information for org-reftable; please\r
+   read further below for its format.\r
+\r
+ - The sequence of columns does not matter. You may reorder them\r
+    any way you like; e.g. make the comment-column the last\r
+    columns within the table.\r
+\r
+ - You can add further columns or even remove the\r
+   \"Comment\"-column. The columns \"ref\" and \"created\"\r
+   however are required. Columns \"cound\" and \"last-accessed\"\r
+   are optional, but highly suggested anyway.\r
+\r
+ - Your references need not start at \"R1\"; However, having an\r
+   initial row is required (it servers a template for subsequent\r
+   references).\r
+\r
+ - Your reference need not have the form \"R1\"; you may just as\r
+   well choose any text, that contains a single number,\r
+   e.g. \"reference-{1}\" or \"#1\" or \"++1++\" or \"-1-\". The\r
+   function `org-reftable' will inspect your first reference and\r
+   create all subsequent references in the same way.\r
+    \r
+ - You may want to change the ID-Property of the node above and\r
+   create a new one, which is unique (and not just a copy of\r
+   mine). You need to change it in the lines copied to your .emacs\r
+   too. However, this is not strictly required to make things\r
+   work, so you may do this later, after trying out this package.\r
+\r
+\r
+Optionally you may tweak the second header line to adjust\r
+`org-reftable' a bit. In the ecample above it looks like this:\r
+\r
+\r
+    | ref | ;c                 | count;s | created | last-accessed |\r
+    |-----+--------------------+---------+---------+---------------|\r
+\r
+The different fields have different meanings:\r
+\r
+ - ref : this denotes the column which contains you references\r
+\r
+ - ;c : the flag \"c\" (\"c\" for \"copy\") denotes this column\r
+   as the one beeing copied on command \"leave\". In the example above,\r
+   it is the comment-column.\r
+\r
+ - count;s : this is the column which counts last access, whence\r
+   \"count\"; the flag \"s\" stands for \"sort\", so this is the\r
+   column after which the table is sorted. You may also sort\r
+   after columns \"ref\" or \"last-accessed\".\r
+\r
+ - created : date when this reference was created.\r
+\r
+ - last-accessed : date and time, when this reference was last accessed.\r
+\r
+\r
+After this two-step setup process you may invoke `org-reftable'\r
+to create a new reference number; read there for instructions on\r
+normal usage.\r
+\r
+If you have an existing reference table from a version of\r
+org-reftable before 2.0 (in fact earlier versions were rather\r
+named org-refer-by-number), you need to add a second headline\r
+like this, just about the hline to reflect the usage of columns\r
+in earlier versions:\r
+\r
+  | ref | created |\r
+\r
+This will mark the first column as the actual references and the\r
+second column as the date of creation. However, to take advantage\r
+of the new features you should also add two other columns \"count;s\" \r
+(marked as the sort-column) and \"last-accessed\".\r
+\r
+")\r
+\r
+(defvar org-reftable-windowconfig-before nil \r
+  "Saved window-configuration for `org-reftable'.\r
+This variable is only used internally.")\r
+\r
+(defvar org-reftable-marker-outside-before nil \r
+  "Saved position in reftable-buffer bit outside of reftable (if at all).\r
+This variable is only used internally.")\r
+\r
+(defvar org-reftable-last-action nil \r
+  "Last action performed by `org-reftable'.\r
+This variable is only used internally.")\r
+\r
+(defvar org-reftable-occur-buffer nil\r
+  "Buffer (if any) with result from occur or multi-occur.\r
+This variable is only used internally.")\r
+\r
+(defvar org-reftable-ref-regex nil\r
+  "Regular expression to search for\r
+This variable is only used internally.")\r
+\r
+\r
+\r
+(defun org-reftable  (&optional what search) \r
+  "Create, search and look up numbers from a dedicated reference table.\r
+These numbers (e.g. \"R237\" or \"-455-\") may be used to refer to:\r
+\r
+ - Nodes in Org-mode (insert them into the heading)\r
+\r
+ - Things outside of org (e.g. mailfolders, directories, reports or\r
+   pieces of paper)\r
+\r
+The table is kept sorted for most frequently or most recently used\r
+reference numbers. Additionally, lines can be selected by keywords, so\r
+that specific references can be found easily.\r
+\r
+\r
+Read below for a detailed description of this function. See the\r
+documentation of `org-reftable-id' for the necessary\r
+setup.\r
+\r
+\r
+The function `org-reftable' operates on a dedicated table (called\r
+the reference table) within a special Org-mode node. The node has\r
+to be created as part of your initial setup. Each line of the\r
+reference table contains:\r
+\r
+ - A reference \r
+\r
+ - Its respective creation date\r
+\r
+ - A number; counting, how often each reference has been\r
+   used. This number is updated automatically and the table can\r
+   be sorted according to it, so that most frequently used\r
+   references appear at the top of the table and can be spotted\r
+   easily.\r
+\r
+ - Date and time of last access. This column can alternatively be\r
+   used to sort the table.\r
+\r
+The reference table is found through the id of the containing\r
+node; this id must be stored within `org-reftable-id' (see there\r
+for details).\r
+\r
+\r
+The function `org-reftable' is the only interactive function of\r
+this package and its sole entry point; it offers several commands\r
+to create, find and look up these reference numbers. All of them\r
+are described in the docstring of `org-reftable-commands' (see\r
+there for details).\r
+\r
+\r
+Finally, org-reftable can also be invoked from elisp; the two\r
+optional arguments to be accepted are:\r
+\r
+  search : string to search for\r
+    what : symbol of the command to invoke\r
+\r
+An example would be:\r
+\r
+ (org-reftable \"237\" 'head)   ;; find heading with ref 237\r
+\r
+"\r
+\r
+  (interactive "P")\r
+\r
+  (let (within-node        ; True, if we are within node with reference table\r
+        result-is-visible  ; True, if node or occur is visible in any window\r
+        ref-node-buffer-and-point  ; cons with buffer and point of reference node\r
+        below-cursor       ; word below cursor\r
+        active-region      ; active region (if any)\r
+        guarded-search     ; with guard against additional digits\r
+        commands           ; currently active set of selectable commands\r
+        what-adjusted      ; True, if we had to adjust what\r
+        what-input         ; Input on what question (need not necessary be "what")\r
+        reorder-once       ; Column to use for single time sorting\r
+        parts              ; Parts of a typical reference number (which\r
+                           ; need not be a plain number); these are:\r
+        head               ; Any header before number (e.g. "R")\r
+        maxref             ; Maximum number from reference table (e.g. "153")\r
+        tail               ; Tail after number (e.g. "}" or "")\r
+        ref-regex          ; Regular expression to match a reference\r
+        numcols            ; Number of columns in reference table\r
+        columns            ; Associate column names with numbers\r
+        kill-new-text      ; Text that will be appended to kill ring\r
+        message-text       ; Text that will be issued as an explanation,\r
+                           ; what we have done\r
+        )\r
+\r
+    ;;\r
+    ;; Examine current buffer and location, before turning to reference table\r
+    ;;\r
+\r
+    ;; Get the content of the active region or the word under cursor\r
+    (if (and transient-mark-mode\r
+             mark-active)\r
+        (setq active-region (buffer-substring (region-beginning) (region-end))))\r
+    (setq below-cursor (thing-at-point 'symbol))\r
+\r
+\r
+    ;; Find out, if we are within reference table or not\r
+    (setq within-node (string= (org-id-get) org-reftable-id))\r
+\r
+    ;; Find out, if point in any window is within node with reference table\r
+    (mapc (lambda (x) (with-current-buffer (window-buffer x)\r
+                        (when (or \r
+                               (string= (org-id-get) org-reftable-id)\r
+                               (eq (window-buffer x) \r
+                                   org-reftable-occur-buffer))\r
+                          (setq result-is-visible t))))\r
+          (window-list))\r
+        \r
+\r
+\r
+    ;;\r
+    ;; Get decoration of references and highest number from reference table\r
+    ;;\r
+\r
+    ;; Find node\r
+    (setq ref-node-buffer-and-point (org-reftable-id-find))\r
+    (unless ref-node-buffer-and-point\r
+      (org-reftable-report-setup-error \r
+       (format "Cannot find node with id \"%s\"" org-reftable-id)))\r
+\r
+    ;; Get configuration of reftable\r
+    (with-current-buffer (car ref-node-buffer-and-point)\r
+      (unless (string= (org-id-get) org-reftable-id)\r
+        ;; Get marker for point within reftable-buffer, but only if outside\r
+        ;; of reftable (if point is within reftable, we will try to stay at\r
+        ;; the same ref)\r
+        (setq org-reftable-marker-outside-before (point-marker))\r
+        (goto-char (cdr ref-node-buffer-and-point)))\r
+\r
+      ;; parse table\r
+      (setq parts (org-reftable-parse-and-adjust-table reorder-once)))\r
+    \r
+    ;; Give names to parts of configuration\r
+    (setq head (nth 0 parts))\r
+    (setq maxref (nth 1 parts))\r
+    (setq tail (nth 2 parts))\r
+    (setq numcols (nth 3 parts))\r
+    (setq columns (nth 4 parts))\r
+    (setq ref-regex (nth 5 parts))\r
+    \r
+    ;;\r
+    ;; Find out, what we are supposed to do\r
+    ;;\r
+\r
+    (if (equal what '(4)) (setq what 'leave))\r
+\r
+    ;; Set preferred action, that will be the default choice\r
+    (setq org-reftable-preferred-command\r
+          (if within-node\r
+              (if (eq org-reftable-last-action 'new)\r
+                  'leave\r
+                'occur)\r
+          (if active-region\r
+              'new\r
+           (if (and below-cursor (string-match ref-regex below-cursor))\r
+            'occur\r
+            nil))))\r
+    \r
+    ;; Ask user\r
+    (unless what\r
+      (setq commands (copy-list org-reftable-commands-some))\r
+      (while (progn\r
+               (setq what-input\r
+                     (org-icompleting-read \r
+                      "Please choose: " \r
+                      (mapcar 'symbol-name \r
+                              ;; Construct unique list of commands with\r
+                              ;; preferred one at front\r
+                              (delq nil (delete-dups \r
+                                         (append \r
+                                          (list org-reftable-preferred-command)\r
+                                          commands))))\r
+                      nil nil))\r
+               (setq what (intern what-input))\r
+               \r
+               ;; user is not required to input one of the commands; if\r
+               ;; not, take the first one and use the original input for\r
+               ;; next question\r
+               (if (memq what commands)\r
+                   ;; input matched one element of list, dont need original\r
+                   ;; input any more\r
+                   (setq what-input nil)\r
+                 ;; what-input will be used for next question, use first\r
+                 ;; command for what\r
+                 (setq what (or org-reftable-preferred-command\r
+                                (first commands)))\r
+                 ;; remove any trailing dot, that user might have added to\r
+                 ;; disambiguate his input\r
+                 (if (equal (substring what-input -1) ".")\r
+                     ;; but do this only, if dot was really necessary to\r
+                     ;; disambiguate\r
+                     (let ((shortened-what-input (substring what-input 0 -1)))\r
+                       (unless (test-completion shortened-what-input \r
+                                            (mapcar 'symbol-name \r
+                                                    org-reftable-commands))\r
+                           (setq what-input shortened-what-input)))))\r
+                     \r
+\r
+               ;; ask for reorder in loop, because we have to ask for\r
+               ;; what right again\r
+               (if (eq what 'reorder)\r
+                   (setq reorder-once\r
+                         (intern\r
+                          (org-icompleting-read \r
+                           "Please choose column to reorder reftable once: " \r
+                           (mapcar 'symbol-name '(ref count last-accessed))\r
+                           nil t))))\r
+               \r
+               ;; offer extended selection of commands, if asked for\r
+               (if (eq what 'all)\r
+                   (setq commands (copy-list org-reftable-commands)))\r
+\r
+               ;; maybe ask initial question again\r
+               (memq what '(reorder all)))))\r
+\r
+\r
+    ;;\r
+    ;; Get search, if required\r
+    ;;\r
+\r
+    ;; These actions need a search string:\r
+    (when (memq what '(goto occur head update))\r
+\r
+      ;; Maybe we've got a search string from the arguments\r
+      (unless search\r
+        (let (search-from-table\r
+              search-from-cursor)\r
+          \r
+          ;; Search string can come from several sources:\r
+          ;; From ref column of table\r
+          (when within-node\r
+            (save-excursion (setq search-from-table (org-table-get-field (cdr (assoc 'ref columns)))))\r
+            (if (string= search-from-table "") (setq search-from-table nil)))      \r
+          ;; From string below cursor\r
+          (when (and (not within-node)\r
+                     below-cursor\r
+                     (string-match (concat "\\(" ref-regex "\\)") \r
+                                   below-cursor))\r
+            (setq search-from-cursor (match-string 1 below-cursor)))\r
+          \r
+          ;; Depending on requested action, get search from one of the sources above\r
+          (cond ((eq what 'goto)\r
+                 (setq search (or what-input search-from-cursor)))\r
+                ((memq what '(head occur))\r
+                 (setq search (or what-input search-from-table search-from-cursor))))))\r
+\r
+\r
+      ;; If we still do not have a search string, ask user explicitly\r
+      (unless search\r
+        \r
+        (if what-input \r
+            (setq search what-input)\r
+          (setq search (read-from-minibuffer\r
+                        (cond ((memq what '(goto occur head))\r
+                               "Text or reference number to search for: ")\r
+                              ((eq what 'update)\r
+                               "Reference number to update: ")))))\r
+\r
+        (if (string-match "^\\s *[0-9]*\\s *$" search)\r
+            (unless (string= search "")\r
+              (setq search (format "%s%s%s" head (org-trim search) tail)))))\r
+      \r
+      ;; Clean up search string\r
+      (if (string= search "") (setq search nil))\r
+      (if search (setq search (org-trim search)))\r
+\r
+      (setq guarded-search \r
+            (concat (regexp-quote search)\r
+                    ;; if there is no tail in reference number, we\r
+                    ;; have to guard agains trailing digits\r
+                    (if (string= tail "") "\\($\\|[^0-9]\\)" "")))\r
+\r
+\r
+      ;;\r
+      ;; Do some sanity checking before really starting\r
+      ;;\r
+\r
+      ;; Correct requested action, if nothing to search\r
+      (when (and (not search)\r
+                 (memq what '(search occur head)))\r
+        (setq what 'enter)\r
+        (setq what-adjusted t))\r
+\r
+      ;; Check for invalid combinations of arguments; try to be helpful\r
+      (if (string-match ref-regex search)\r
+          (progn\r
+            ;; Count searches and update last access date\r
+            (if search (org-reftable-update-reference-line search columns))\r
+            (if (eq what 'occur) (setq what 'multi-occur)))\r
+        (when (memq what '(goto head))\r
+            (error "Can do '%s' only for a number (not '%s'), try 'occur' to search for text" what search))))\r
+\r
+    \r
+    ;;\r
+    ;; Prepare\r
+    ;;\r
+\r
+    ;; Move into table, if outside\r
+    (when (memq what '(enter new goto occur multi-occur))\r
+      ;; Save current window configuration\r
+      (when (or (not result-is-visible)\r
+                (not org-reftable-windowconfig-before))\r
+        (setq org-reftable-windowconfig-before (current-window-configuration)))\r
+\r
+      ;; Switch to reference table\r
+      (org-pop-to-buffer-same-window (car ref-node-buffer-and-point))\r
+      (goto-char (cdr ref-node-buffer-and-point))\r
+      (show-subtree)\r
+      (org-show-context))\r
+\r
+\r
+    ;;\r
+    ;; Actually do, what is requested\r
+    ;;\r
+\r
+    (cond\r
+\r
+\r
+     ((eq what 'help)\r
+      \r
+      (let ((help-what\r
+             ;; which sort of help ?\r
+             (intern\r
+              (concat \r
+               "help-"\r
+               (org-icompleting-read \r
+                "Help on: "\r
+                (mapcar 'symbol-name '(commands usage setup)) \r
+                nil t)))))\r
+\r
+        ;; help is taken from docstring of functions or variables\r
+        (cond ((eq help-what 'help-commands)\r
+               (org-reftable-show-help 'org-reftable-commands))\r
+              ((eq help-what 'help-usage)\r
+               (org-reftable-show-help 'org-reftable))\r
+              ((eq help-what 'help-setup)\r
+               (org-reftable-show-help 'org-reftable-id)))))\r
+\r
+\r
+     ((eq what 'multi-occur) \r
+      \r
+      ;; Conveniently position cursor on number to search for\r
+      (org-reftable-goto-top)\r
+      (let (found (initial (point)))\r
+        (while (and (not found)\r
+                    (forward-line)\r
+                    (org-at-table-p))\r
+          (save-excursion \r
+            (setq found (string= search \r
+                                 (org-trim (org-table-get-field (cdr (assoc 'ref columns))))))))\r
+        (if found \r
+            (org-table-goto-column (cdr (assoc 'ref columns)))\r
+          (goto-char initial)))\r
+\r
+      ;; Construct list of all org-buffers\r
+      (let (buff org-buffers)\r
+        (dolist (buff (buffer-list))\r
+          (set-buffer buff)\r
+          (if (string= major-mode "org-mode")\r
+              (setq org-buffers (cons buff org-buffers))))\r
+\r
+        ;; Do multi-occur\r
+        (multi-occur org-buffers guarded-search)\r
+        (if (get-buffer "*Occur*")\r
+            (progn \r
+              (setq message-text (format "multi-occur for '%s'" search))\r
+              (setq org-reftable-occur-buffer (get-buffer "*Occur*"))\r
+              (other-window 1)\r
+              (toggle-truncate-lines 1))\r
+          (setq message-text (format "Did not find '%s'" search)))))\r
+\r
+\r
+     ((eq what 'head)\r
+\r
+      (message (format "Scanning headlines for '%s' ..." search))\r
+      (let (buffer point)\r
+        (if (catch 'found\r
+              (progn\r
+                ;; loop over all headlines, stop on first match\r
+                (org-map-entries \r
+                 (lambda () \r
+                   (when (looking-at (concat ".*\\b" guarded-search))\r
+                     (setq buffer (current-buffer))\r
+                     (setq point (point))\r
+                     (throw 'found t)))          \r
+                 nil 'agenda)\r
+                nil))\r
+            (progn\r
+              (setq message-text (format "Found '%s'" search))\r
+              (org-pop-to-buffer-same-window buffer)\r
+              (goto-char point)\r
+              (org-reveal))\r
+          (setq message-text (format "Did not find '%s'" search)))))\r
+\r
+\r
+     ((eq what 'leave)\r
+\r
+      (when result-is-visible\r
+\r
+        ;; If we are within the occur-buffer, switch over to get current line\r
+        (if (and (string= (buffer-name) "*Occur*")\r
+                 (eq org-reftable-last-action 'occur))\r
+            (occur-mode-goto-occurrence))\r
+\r
+        (let (copy-column)              \r
+          ;; Try to copy requested column\r
+          (setq copy-column (cdr (assoc \r
+                                  (if (eq org-reftable-last-action 'new)\r
+                                      'goto\r
+                                    'copy)\r
+                                  columns)))\r
+            \r
+          ;; Add to kill ring\r
+          (if (memq org-reftable-last-action '(new enter goto occur))\r
+              (setq kill-new-text \r
+                    (org-trim (org-table-get-field copy-column))))))\r
+\r
+      ;; Restore position within buffer with reference table\r
+      (with-current-buffer (car ref-node-buffer-and-point)\r
+        (when org-reftable-marker-outside-before\r
+          (goto-char (marker-position org-reftable-marker-outside-before))\r
+          (move-marker org-reftable-marker-outside-before nil)))\r
+\r
+      ;; Restore windowconfig\r
+      (if org-reftable-windowconfig-before \r
+          (progn  \r
+            ;; Restore initial window configuration\r
+            (set-window-configuration org-reftable-windowconfig-before)\r
+            (setq org-reftable-windowconfig-before nil)\r
+            ;; Goto initial position\r
+            (recenter)\r
+            (setq message-text "Back"))\r
+        ;; We did not have a window-configuration to restore, so we cannot\r
+        ;; pretend we have returned back\r
+        (setq message-text "Cannot leave; nowhere to go to")\r
+        (setq kill-new-text nil)))\r
+\r
+\r
+     ((eq what 'goto)\r
+\r
+      ;; Go downward in table to requested reference\r
+      (let (found (initial (point)))\r
+        (org-reftable-goto-top)\r
+        (while (and (not found)\r
+                    (forward-line)\r
+                    (org-at-table-p))\r
+          (save-excursion \r
+            (setq found \r
+                  (string= search \r
+                           (org-trim (org-table-get-field (cdr (assoc 'ref columns))))))))\r
+        (if found\r
+            (progn\r
+              (setq message-text (format "Found '%s'" search))\r
+              (org-table-goto-column (cdr (assoc 'ref columns)))\r
+              (if (looking-back " ") (backward-char)))\r
+          (setq message-text (format "Did not find '%s'" search))\r
+          (goto-char initial)\r
+          (forward-line)\r
+          (setq what 'missed))))\r
+\r
+\r
+     ((eq what 'occur)\r
+\r
+      ;; search for string: occur\r
+      (save-restriction\r
+        (org-narrow-to-subtree)\r
+        (occur search)\r
+        (widen)\r
+        (if (get-buffer "*Occur*")\r
+            (with-current-buffer "*Occur*"\r
+\r
+              ;; install helpful keyboard-shortcuts within occur-buffer\r
+              (let ((keymap (make-sparse-keymap)))\r
+                (set-keymap-parent keymap occur-mode-map)\r
+\r
+                (define-key keymap (kbd "RET") \r
+                  (lambda () (interactive) \r
+                    (org-reftable-occur-helper 'head)))\r
+\r
+                (define-key keymap (kbd "<C-return>") \r
+                  (lambda () (interactive) \r
+                    (org-reftable-occur-helper 'multi-occur)))\r
+\r
+                (use-local-map keymap))\r
+              (setq org-reftable-ref-regex ref-regex)\r
+\r
+              ;; insert some help text\r
+              (other-window 1)\r
+              (toggle-truncate-lines 1)\r
+              (let ((inhibit-read-only t)) \r
+                (insert (substitute-command-keys \r
+                         "Type RET to find heading, C-RET for multi-occur, \\[next-error-follow-minor-mode] for follow-mode.\n\n")))\r
+              (forward-line 1)\r
+              (setq message-text\r
+                    (format  "Occur for '%s'" search)))\r
+          (setq message-text\r
+                (format "Did not find any matches for '%s'" search)))))\r
+\r
+\r
+     ((eq what 'new)\r
+\r
+      ;; add a new row\r
+      (org-reftable-goto-top)\r
+      (let ((new (format "%s%d%s" head (1+ maxref) tail)))\r
+\r
+        (org-table-insert-row)\r
+\r
+        ;; fill special columns with standard values\r
+        (org-table-goto-column (cdr (assoc 'ref columns)))\r
+        (insert new)\r
+        (org-table-goto-column (cdr (assoc 'created columns)))\r
+        (org-insert-time-stamp nil nil t)\r
+\r
+        ;; goto first nonempty field\r
+        (catch 'empty\r
+          (dotimes (col numcols)\r
+            (org-table-goto-column (+ col 1))\r
+            (if (string= (org-trim (org-table-get-field)) "")\r
+                (throw 'empty t)))\r
+          ;; none found, goto first\r
+          (org-table-goto-column 1))\r
+\r
+        (org-table-align)\r
+        (if active-region (setq kill-new-text active-region))\r
+        (setq message-text (format "Adding a new row '%s'" new))))\r
+\r
+\r
+     ((eq what 'enter)\r
+\r
+      ;; simply go into table\r
+      (org-reftable-goto-top)\r
+      (show-subtree)\r
+      (recenter)\r
+      (if what-adjusted\r
+          (setq message-text "Nothing to search for; at reference table")\r
+        (setq message-text "At reference table")))\r
+\r
+\r
+     ((eq what 'sort)\r
+\r
+      ;; sort lines according to contained reference\r
+      (let (begin end where)\r
+        (catch 'aborted\r
+          ;; either active region or whole buffer\r
+          (if (and transient-mark-mode\r
+                   mark-active)\r
+              ;; sort only region\r
+              (progn\r
+                (setq begin (region-beginning))\r
+                (setq end (region-end))\r
+                (setq where "region"))\r
+            ;; sort whole buffer\r
+            (setq begin (point-min))\r
+            (setq end (point-max))\r
+            (setq where "whole buffer")\r
+            ;; make sure\r
+            (unless (y-or-n-p "Sort whole buffer ")\r
+              (setq message-text "Sort aborted")\r
+              (throw 'aborted nil)))\r
+          \r
+          (save-excursion\r
+            (save-restriction\r
+              (goto-char (point-min))\r
+              (narrow-to-region begin end)\r
+              (sort-subr nil 'forward-line 'end-of-line \r
+                         (lambda ()\r
+                           (if (looking-at (concat "^.*\\b" ref-regex "\\b"))\r
+                               (string-to-number (match-string 1))\r
+                             0))))\r
+            (highlight-regexp ref-regex)\r
+            (setq message-text (format "Sorted %s from character %d to %d, %d lines" \r
+                                       where begin end\r
+                                       (count-lines begin end)))))))\r
+    \r
+\r
+     ((eq what 'update)\r
+\r
+      ;; simply update line in reftable\r
+      (save-excursion\r
+        (beginning-of-line)\r
+        (if (org-reftable-update-reference-line search columns)\r
+            (setq message-text (format "Updated reference '%s'" search))\r
+          (setq message-text (format "Did not find reference '%s'" search)))))\r
+\r
+\r
+     ((memq what '(highlight unhighlight))\r
+\r
+      (let ((where "buffer"))\r
+        (save-excursion\r
+          (save-restriction\r
+            (when (and transient-mark-mode\r
+                       mark-active)\r
+              (narrow-to-region (region-beginning) (region-end))\r
+              (setq where "region"))\r
+\r
+            (if (eq what 'highlight)\r
+                (progn\r
+                  (highlight-regexp ref-regex)\r
+                  (setq message-text (format "Highlighted references in %s" where)))\r
+              (unhighlight-regexp ref-regex)\r
+              (setq message-text (format "Removed highlights for references in %s" where)))))))\r
+\r
+\r
+     (t (error "This is a bug: Unmatched condition '%s'" what)))\r
+\r
+\r
+    ;; remember what we have done for next time\r
+    (setq org-reftable-last-action what)\r
+    \r
+    ;; tell, what we have done and what can be yanked\r
+    (if kill-new-text (setq kill-new-text \r
+                            (substring-no-properties kill-new-text)))\r
+    (if (string= kill-new-text "") (setq kill-new-text nil))\r
+    (let ((m (concat \r
+              message-text\r
+              (if (and message-text kill-new-text) \r
+                  " and r" \r
+                (if kill-new-text "R" ""))\r
+              (if kill-new-text (format "eady to yank '%s'" kill-new-text) ""))))\r
+      (unless (string= m "") (message m)))\r
+    (if kill-new-text (kill-new kill-new-text))))\r
+\r
+\r
+\r
+(defun org-reftable-parse-and-adjust-table (&optional sort-column)\r
+  "Trim reference table, only used internally"\r
+\r
+  (let ((maxref 0)\r
+        top\r
+        bottom\r
+        field\r
+        parts\r
+        numcols\r
+        columns\r
+        head\r
+        tail\r
+        ref-regex\r
+        initial-ref\r
+        initial-point)\r
+\r
+    (setq initial-point (point))\r
+    (org-reftable-goto-top)\r
+    (setq top (point))\r
+    \r
+    (goto-char top)\r
+    \r
+    ;; count columns\r
+    (org-table-goto-column 100)\r
+    (setq numcols (- (org-table-current-column) 1))\r
+    (org-table-goto-column 1)\r
+    \r
+    ;; get contents of columns\r
+    (forward-line -2)\r
+    (unless (org-at-table-p)\r
+      (org-reftable-report-setup-error \r
+       "Reference table starts with a hline" t))\r
+    \r
+    (setq columns (org-reftable-parse-headings numcols))\r
+    \r
+    ;; Go beyond end of table\r
+    (while (org-at-table-p) (forward-line 1))\r
+    \r
+    ;; Kill all empty rows at bottom\r
+    (while (progn\r
+             (forward-line -1)\r
+             (org-table-goto-column 1)\r
+             (string= "" (org-trim (org-table-get-field (cdr (assoc 'ref columns))))))\r
+      (org-table-kill-row))\r
+    (forward-line)\r
+    (setq bottom (point))\r
+    (forward-line -1)\r
+    \r
+    ;; Retrieve any decorations around the number within ref-field of\r
+    ;; the first row\r
+    (goto-char top)\r
+    (setq field (org-trim (org-table-get-field  (cdr (assoc 'ref columns)))))\r
+    (or (numberp (string-match "^\\([^0-9]*\\)\\([0-9]+\\)\\([^0-9]*\\)$" field))\r
+        (org-reftable-report-setup-error \r
+         (format "reference column in first line of reference table '%s' does not contain a number" field) t))\r
+    \r
+    ;; These are the decorations used within the first row of the\r
+    ;; reference table\r
+    (setq head (match-string 1 field))\r
+    (setq tail (match-string 3 field))\r
+    (setq ref-regex (concat (regexp-quote head)\r
+                            "\\([0-9]+\\)" \r
+                            (regexp-quote tail)))\r
+\r
+    ;; Save initial ref\r
+    (save-excursion\r
+      (let (field)\r
+        (goto-char initial-point)\r
+        (setq field (org-table-get-field (cdr (assoc 'ref columns))))\r
+        (if (string-match ref-regex field)\r
+            (setq initial-ref (concat head (match-string 1 field) tail)))))\r
+\r
+    ;; Go through table to find maximum number\r
+    (let ((ref 0)\r
+          field)\r
+      (while (org-at-table-p) \r
+        (setq field (org-trim (org-table-get-field (cdr (assoc 'ref columns)))))\r
+        (if (string-match ref-regex field)\r
+            (setq ref (string-to-number (match-string 1 field)))\r
+          \r
+          (unless (string= "" field)\r
+            (org-reftable-report-setup-error \r
+             (format "Reference field in line of reference table '%s' does not contain a number" field) t)))\r
+        (if (> ref maxref) (setq maxref ref))\r
+        (forward-line 1)))\r
+    \r
+    (setq parts (list head maxref tail numcols columns ref-regex))\r
+    \r
+    ;; sort table after sort-column\r
+    (unless sort-column (setq sort-column (cdr (assoc 'sort columns))))\r
+    (goto-char top)\r
+    (forward-line 0)\r
+    (save-restriction\r
+      (narrow-to-region (point) bottom)\r
+      (sort-subr t\r
+                 'forward-line \r
+                 'end-of-line \r
+                 (lambda ()\r
+                   (let (ref\r
+                         (ref-field (org-table-get-field \r
+                                     (cdr (assoc 'ref columns)))))\r
+                     (string-match ref-regex ref-field)\r
+                     ;; get reference with leading zeroes, so it can be\r
+                     ;; sorted as text\r
+                     (setq ref (format \r
+                                "%06d" \r
+                                (string-to-number \r
+                                 (match-string 1 ref-field))))\r
+                     \r
+                     ;; Construct different sort-keys according to\r
+                     ;; requested sort column; append ref as a secondary\r
+                     ;; sort key\r
+\r
+                     ;; \r
+                     (cond ((eq sort-column 'count)\r
+                            (concat (format \r
+                                     "%08d" \r
+                                     (string-to-number \r
+                                      (org-table-get-field \r
+                                       (cdr (assoc 'count columns))))) \r
+                                    " " ref))\r
+                           \r
+                           ((eq sort-column 'last-accessed)\r
+                            (concat (org-table-get-field \r
+                                     (cdr (assoc 'last-accessed columns))) \r
+                                    " " ref))\r
+                           \r
+                           ((eq sort-column 'ref)\r
+                            ref)\r
+                           \r
+                           (t \r
+                            (error "Bug !")))))\r
+                 nil\r
+                 'string<)\r
+      )\r
+\r
+    ;; align table\r
+    (org-table-align)\r
+    \r
+    ;; go back to top of table\r
+    (goto-char top)\r
+\r
+    ;; Goto back to initial ref, because reformatting of table above might\r
+    ;; have moved point\r
+    (when initial-ref\r
+      (while (and (org-at-table-p)\r
+                  (not (string= initial-ref (org-trim (org-table-get-field (cdr (assoc 'ref columns)))))))\r
+        (forward-line))\r
+      ;; did not find ref, go back to top\r
+      (if (not (org-at-table-p)) (goto-char top)))\r
+\r
+    parts))\r
+\r
+\r
+\r
+(defun org-reftable-goto-top ()\r
+  "Goto topmost reference line in reftable"\r
+\r
+  ;; go to heading of node\r
+  (while (not (org-at-heading-p)) (forward-line -1))\r
+  (forward-line 1)\r
+  ;; go to table within node, but make sure we do not get into another node\r
+  (while (and (not (org-at-heading-p))\r
+              (not (org-at-table-p))\r
+              (not (eq (point) (point-max)))) \r
+    (forward-line 1))\r
+     \r
+  ;; check, if there really is a table\r
+  (unless (org-at-table-p)\r
+    (org-reftable-report-setup-error \r
+     "Cannot find reference table within reference node" t))\r
+\r
+  ;; go to first hline\r
+  (while (and (not (org-at-table-hline-p))\r
+              (org-at-table-p))\r
+    (forward-line 1))\r
+     \r
+  ;; and check\r
+  (unless (org-at-table-hline-p)\r
+    (org-reftable-report-setup-error \r
+     "Cannot find hline within reference table" t))      \r
+\r
+  (forward-line 1)\r
+  (org-table-goto-column 1))\r
+\r
+\r
+\r
+(defun org-reftable-id-find ()\r
+  "Find org-reftable-id"\r
+  (let ((marker (org-id-find org-reftable-id 'marker))\r
+        marker-and-buffer)\r
+\r
+    (if marker \r
+        (progn \r
+          (setq marker-and-buffer (cons (marker-buffer marker) (marker-position marker)))\r
+          (move-marker marker nil)\r
+          marker-and-buffer)\r
+      nil)))\r
+\r
+\r
+\r
+(defun org-reftable-parse-headings (numcols)\r
+  "Parse headings to find special columns"\r
+\r
+  (let (columns)\r
+\r
+    ;; Associate names of special columns with column-numbers\r
+    (setq columns (copy-tree '((ref . 0) (created . 0) (last-accessed . 0) \r
+                               (count . 0) (sort . nil) (copy . nil))))\r
+\r
+    ;; For each column\r
+    (dotimes (col numcols)\r
+      (let* (field-flags ;; raw heading, consisting of file name and maybe\r
+                         ;; flags (seperated by ";")\r
+             field       ;; field name only\r
+             field-symbol ;; and as a symbol\r
+             flags       ;; flags from field-flags\r
+             found)\r
+\r
+        ;; parse field-flags into field and flags\r
+        (setq field-flags (org-trim (org-table-get-field (+ col 1))))\r
+        (if (string-match "^\\([^;]*\\);\\([a-z]+\\)$" field-flags)\r
+            (progn \r
+              (setq field (downcase (or (match-string 1 field-flags) "")))\r
+              ;; get flags as list of characters\r
+              (setq flags (mapcar 'string-to-char \r
+                                  (split-string \r
+                                   (downcase (match-string 2 field-flags)) \r
+                                   "" t))))\r
+          ;; no flags\r
+          (setq field field-flags))\r
+\r
+        (unless (string= field "") (setq field-symbol (intern (downcase field))))\r
+\r
+        ;; Check, that no flags appear twice\r
+        (mapc (lambda (x)\r
+                (when (memq (car x) flags)\r
+                  (if (cdr (assoc (cdr x) columns))\r
+                      (org-reftable-report-setup-error \r
+                       (format "More than one heading is marked with flag '%c'" (car x)) t))))\r
+              '((?s . sort)\r
+                (?c . copy)))\r
+        \r
+        ;; Process flags\r
+        (if (memq ?s flags)\r
+            (setcdr (assoc 'sort columns) field-symbol))\r
+        (if (memq ?c flags)\r
+            (setcdr (assoc 'copy columns) (+ col 1)))\r
+        \r
+        ;; Store columns in alist\r
+        (setq found (assoc field-symbol columns))\r
+        (when found\r
+          (if (> (cdr found) 0) \r
+              (org-reftable-report-setup-error \r
+               (format "'%s' appears two times as column heading" (downcase field)) t))\r
+          (setcdr found (+ col 1)))))\r
+\r
+    ;; check if all necessary informations have been specified\r
+    (unless (> (cdr (assoc 'ref columns)) 0)\r
+      (org-reftable-report-setup-error \r
+       "column 'ref' has not been set" t))\r
+\r
+    ;; use ref as a default sort-column\r
+    (unless (cdr (assoc 'sort columns))\r
+      (setcdr (assoc 'sort columns) 'ref))\r
+    columns))\r
+\r
+\r
+\r
+(defun org-reftable-report-setup-error (text &optional switch-to-node)\r
+  "Report error, which might be related with incomplete setup; offer help"\r
+\r
+  (when switch-to-node \r
+    (org-id-goto org-reftable-id)\r
+    (delete-other-windows))\r
+  \r
+  (when (y-or-n-p (concat\r
+                   text \r
+                   ";\n"\r
+                   "the correct setup is explained in the documentation of 'org-reftable-id'.\n" \r
+                   "Do you want to read it ? "))\r
+    (org-reftable-show-help 'org-reftable-id))\r
+\r
+  (error "")\r
+  (setq org-reftable-windowconfig-before nil)\r
+  (move-marker org-reftable-marker-outside-before nil)\r
+  (setq org-reftable-last-action 'leave))\r
+\r
+\r
+\r
+(defun org-reftable-show-help (function-or-variable)\r
+  "Show help on command or function and trim help buffer displayed"\r
+\r
+  (let ((isfun (functionp function-or-variable)))\r
+    ;; bring up help-buffer for function or variable\r
+    (if isfun\r
+        (describe-function function-or-variable)\r
+      (describe-variable function-or-variable))\r
+\r
+    \r
+    ;; clean up help-buffer\r
+    (pop-to-buffer "*Help*")\r
+    (let ((inhibit-read-only t)) \r
+      (goto-char (point-min))\r
+      (while (progn\r
+               (kill-line 1)\r
+               (not (looking-at \r
+                     (if isfun\r
+                         "(" \r
+                       "Documentation:")))))\r
+      (kill-line (if isfun 2 1))\r
+      (goto-char (point-max))\r
+      (kill-line -2)\r
+      (goto-char (point-min)))))\r
+                 \r
+\r
+\r
+(defun org-reftable-update-reference-line (reference columns)\r
+  "Update access count and time of reference number"\r
+\r
+  (let ((initial (point))\r
+        found\r
+        (ref-node-buffer-and-point (org-reftable-id-find)))\r
+    (with-current-buffer (car ref-node-buffer-and-point)\r
+      (goto-char (cdr ref-node-buffer-and-point))\r
+      (org-reftable-goto-top)\r
+      (while (and (org-at-table-p)\r
+                  (if (string= reference (org-trim (org-table-get-field (cdr (assoc 'ref columns)))))\r
+                      (progn (org-table-get-field (cdr (assoc 'count columns))\r
+                                                  (number-to-string \r
+                                                   (+ 1 (string-to-number \r
+                                                         (org-table-get-field (cdr (assoc 'count columns)))))))\r
+                             (org-table-goto-column (cdr (assoc 'last-accessed columns)))\r
+                             (org-table-blank-field)\r
+                             (org-insert-time-stamp nil t t)\r
+                             (org-table-align)\r
+                             (setq found t)\r
+                             nil)\r
+                    t))\r
+        (forward-line))\r
+      (goto-char initial))\r
+    found))\r
+\r
+\r
+\r
+(defun org-reftable-occur-helper (action)\r
+  "Internal helper function for occur in org-reftable"\r
+  (save-excursion\r
+    (beginning-of-line)\r
+    (if (looking-at (concat ".*\\b\\(" org-reftable-ref-regex "\\)\\b"))\r
+        (org-reftable action (match-string 1)))))\r
+\r
+\r
+(provide 'org-reftable)\r
+\r
+;; Local Variables:\r
+;; fill-column: 75\r
+;; comment-column: 50\r
+;; End:\r
+\r
+;;; org-reftable.el ends here\r
+\r
diff --git a/org-contrib/ob-table-operations.org b/org-contrib/ob-table-operations.org
new file mode 100644 (file)
index 0000000..a9b1b72
--- /dev/null
@@ -0,0 +1,543 @@
+
+* Table operations --- filter or combine tables
+
+  This section within the library of babel provides table operations.
+  See the documentation just below for details and working examples.
+
+  Author  : Marc-Oliver Ihm <ihm@ferntreffer.de>
+  Version : 1.0
+
+** Documentation
+
+*** Introduction
+
+    The table operations (currently four) are grouped in two categories:
+    
+    - Filtering the rows of a single table: keeping or removing
+    - Combining two tables into one: merging or intersecting
+     
+    All four operations are demonstrated below.
+
+*** Example tables
+
+    To demonstrate we need three tables: upper, lower and keys:
+
+#+name: upper
+|  1 | A |
+|  3 | C |
+|  4 | D |
+| 10 | J |
+|  2 | B |
+
+#+name: lower
+| Position | Letter |
+|----------+--------|
+|        2 | b      |
+|        4 | d      |
+|        5 | e      |
+|        6 | h      |
+
+#+name: keys
+| Position |
+|----------|
+|        1 |
+|        2 |
+|        4 |
+
+    The tables upper and lower both have two columns and associate a position in
+    the alphabet with the matching letter.  E.g. the row "| 1 | A |" from table
+    upper, just states that the letter "A" comes at position 1 in the alphabet.
+
+    Nearly the same is true for table lower, only that it contains lower case
+    letters.  Some of its letters (e.g. "b") have counterparts in table upper
+    ("B"), some (e.g. "e") dont.
+
+    The table keys finally, contains keys (i.e. positions within the alphabet),
+    that can be used to select rows from either table upper or lower.
+
+    Note, that tables may have column headings or not.
+
+*** Filtering a table
+
+**** Keeping rows
+
+     Let's say, we want to select the upper-case letters (i.e. rows from the
+     table upper), that are given in table keys (i.e. the first, second and
+     fourth letter).
+
+     This can be described as filtering table upper and keeping only those rows,
+     that appear in table keys.
+
+     As a babel-call, this reads:
+
+#+call: table-operations-filter-keep(upper,keys)
+
+#+results: table-operations-filter-keep(upper,keys)
+| 1 | A |
+| 4 | D |
+| 2 | B |
+
+     ,which gives exactly those rows from table upper, that are specified in
+     keys.
+
+**** Removing rows
+
+     Now, if on the contrary you want to filter table upper to remove any rows,
+     which are given in table keys:
+
+#+call: table-operations-filter-remove(upper,keys) :colnames yes
+
+#+results: table-operations-filter-remove(upper,keys)
+| Position | t2c2 |
+|----------+------|
+|        3 | C    |
+|       10 | J    |
+
+     ,which is the expected result.
+
+     Please note, that the call contains the header argument ":colnames yes",
+     which causes the result table to contain the headings "Position" and
+     "t2c2". These headings are taken from the input-tables upper and
+     keys. However, as upper does not contain any headings, the heading "t2c2"
+     is generated artificially; it stands for "table 2 column 2".
+
+     If you do not want to have column names in the result table, just leave out
+     the header argument ":colnames yes" like in the first example. Note
+     however, that ":colnames no" does not give the expected effect.
+
+*** Combining tables
+
+    Now, lets have a look at the tables upper and lower alone and see how to
+    combine them.
+
+    Note, that we only look at combining two tables for simplicity, however, all
+    operations can be easily scaled up to seven tables.
+
+**** Merging rows
+
+     We have two tables, one with upper case letters and one with lower
+     case. What now, if you want to have only one table, which contains both,
+     upper and lower case letters ?
+     
+     You may want to merge them:
+
+#+call: table-operations-combine-merge(upper,lower) :colnames yes
+
+#+results: table-operations-combine-merge(upper,lower)
+| Position | t1c2 | Letter |
+|----------+------+--------|
+|        1 | A    |        |
+|        2 | B    | b      |
+|        3 | C    |        |
+|        4 | D    | d      |
+|        5 |      | e      |
+|        6 |      | h      |
+|       10 | J    |        |
+
+
+     This result combines both upper and lower case letters and lists them by
+     their position within the alphabet.
+
+**** Intersecting rows
+
+     If you only want the rows, that are complete (i.e. have both upper and
+     lower case letters) you may compute the intersection:
+
+#+call: table-operations-combine-intersect(upper,lower)
+
+#+results: table-operations-combine-intersect(upper,lower)
+| 2 | B | b |
+| 4 | D | d |
+
+
+     ,which has only those keys and letters, that appear in both tables.
+
+     Note, that we have ommitted the headeragument ":colnames yes" so that the
+     result table has no headings.
+
+** Internals
+
+   This section is not required if you just want to use table operations as
+   described above. Only if you are curious about its implementation or
+   development, you might want to have a look.
+
+*** Implementation
+   
+   Here is the actual lisp code, that implements the functionality of table
+   operations.
+
+**** table-operations-filter
+***** Directly callable blocks
+
+#+name: table-operations-filter-keep
+#+begin_src emacs-lisp :noweb yes :results silent :var table=() :var filter=() 
+  <<lob-table-operations-helper-get-headings-defun>>
+  <<lob-table-operations-filter-defun>>
+  (let ((filter-and-table (list filter table)))
+    (lob-table-operations-filter 'keep filter-and-table))
+#+end_src
+
+#+name: table-operations-filter-remove
+#+begin_src emacs-lisp :noweb yes :results silent :var table=() :var filter=() :colnames nil
+  <<lob-table-operations-helper-get-headings-defun>>
+  <<lob-table-operations-filter-defun>>
+  (let ((filter-and-table (list filter table)))
+    (lob-table-operations-filter 'remove filter-and-table))
+#+end_src
+
+***** Included defuns
+
+#+name: lob-table-operations-filter-defun
+#+begin_src emacs-lisp
+  (defun lob-table-operations-filter (what filter-and-table)
+    "Internal function for table operations in orgmode library of babel"
+  
+    (let (keys
+          result-table
+          headings-all-tables
+          filter
+          table)
+  
+      ;; seperate headings from rest of tables
+      (setq headings-all-tables 
+            (lob-table-operations-helper-get-headings filter-and-table))
+  
+      ;; extract arguments
+      (setq filter (car filter-and-table))
+      (setq table (cadr filter-and-table))
+  
+      ;; remove hlines
+      (setq table (org-babel-del-hlines table))
+      (setq filter (org-babel-del-hlines filter))
+      (setq keys (mapcar 'car filter))
+  
+      ;; start result with headings (reversed)
+      (setq result-table (cons 'hline (cons headings-all-tables nil)))
+  
+      (dolist (line table) ; loop over table lines 
+        (if (equal (not (not (member (car line) keys))) 
+                   (equal what 'keep)) ; 'keep or 'remove ?
+            (setq result-table (cons line result-table))))
+      (nreverse result-table)))
+#+end_src
+
+**** table-operations-combine
+***** Directly callable blocks
+
+#+name: table-operations-combine-merge 
+#+begin_src emacs-lisp :noweb yes :results silent :var t1=() :var t2=() :var t3=() :var t4=() :var t5=() :var t6=() :var t7=()
+  <<lob-table-operations-helper-get-headings-defun>>
+  <<lob-table-operations-combine-defun>>
+  (let ((tables (list t1 t2 t3 t4 t5 t6 t7)))
+    (lob-table-operations-combine 'merge tables))
+#+end_src
+
+#+name: table-operations-combine-intersect 
+#+begin_src emacs-lisp :noweb yes :results silent :var t1=() :var t2=() :var t3=() :var t4=() :var t5=() :var t6=() :var t7=()
+  <<lob-table-operations-helper-get-headings-defun>>
+  <<lob-table-operations-combine-defun>>
+  (let ((tables (list t1 t2 t3 t4 t5 t6 t7)))
+    (lob-table-operations-combine 'intersect tables))
+#+end_src
+
+***** Included defuns
+
+#+name: lob-table-operations-combine-defun
+#+begin_src emacs-lisp
+  (defun lob-table-operations-combine (what tables)
+    "Internal function for table-operations in orgmode library of babel"
+    (let (is-all-numbers                 
+          format-specifier
+          rest-of-tables
+          rests-of-tables
+          rest-of-rests-of-tables
+          rest-of-table
+          headings-all-tables
+          widths-of-tables
+          current-key
+          current-key-in-intersection
+          result-table
+          result-line
+          i)
+  
+      ;; remove possible empty trailing tables
+      (setq rest-of-tables tables)
+      (while (cadr rest-of-tables) (setq rest-of-tables (cdr rest-of-tables)))
+      (setcdr rest-of-tables nil)
+  
+      ;; seperate headings from rest of tables
+      (setq headings-all-tables (lob-table-operations-helper-get-headings 
+                                 tables))
+      (setq result-table (cons 'hline (cons headings-all-tables nil)))
+      
+      ;; remove all remaining hlines
+      (setq tables (mapcar 'org-babel-del-hlines tables))
+  
+      ;; Find out, if all keys in all tables are numbers or if 
+      ;; there are strings among them
+      (setq is-all-numbers
+            (catch 'not-a-number
+              (dolist (table tables) 
+                (dolist (line table) 
+                  (unless (numberp (car line)) 
+                    (throw 'not-a-number 'nil))))
+              't))
+      
+      (setq format-specifier (if is-all-numbers "%g" "%s"))
+      ;; Prepare functions to treat table contents in a unified way
+      (flet ((convert (x) 
+                      (if is-all-numbers
+                          x
+                        (if (numberp x) 
+                            (number-to-string x) 
+                          x)))
+             (less-than (x y) 
+                        (if is-all-numbers (< x y) 
+                          (string< (convert x) 
+                                   (convert y))))
+             (compare (x y) 
+                      (if is-all-numbers (= x y) 
+                        (string= (convert x) 
+                                 (convert y)))))
+        
+        ;; sort tables
+        (setq tables (mapcar (lambda (table) 
+                               (sort table (lambda (x y) 
+                                             (less-than (car x) 
+                                                        (car y))))) 
+                             tables))
+        
+        ;; compute and remember table widths
+        (setq widths-of-tables (mapcar (lambda (x) (length (car x))) tables))
+        
+        ;; copy initially and shorten below
+        (setq rests-of-tables (copy-list tables))
+  
+        ;; loop as long as the rest of table still contains lines
+        (while (progn 
+                 ;; find lowest key among all tables, which is the key for the
+                 ;; next line of the result
+                 (setq current-key nil)
+                 (setq current-key-in-intersection 't) ; remember for later
+                 (dolist (rest-of-table rests-of-tables) ; loop over all tables
+                   (when (and rest-of-table ; and compare against all keys
+                              (or (null current-key) 
+                                  (less-than (caar rest-of-table) 
+                                             current-key)))
+                     (setq current-key (caar rest-of-table))))
+                 current-key)
+          
+          (progn
+            
+            (setq result-line (list current-key))
+            
+            ;; go through all tables and collect one line for the result table
+            (setq i 0)                      ; table-count
+            ;; cannot use dolist like above, because we need to modify the
+            ;; cons-cells
+            (setq rest-of-rests-of-tables rests-of-tables)
+            (while (progn
+                     (setq rest-of-table (car rest-of-rests-of-tables))
+                     (incf i)
+                     ;; if table contains current key 
+                     (if (and rest-of-table
+                              (compare current-key (caar rest-of-table)))
+                         ;; then copy rest of line
+                         (progn (nconc result-line (cdar rest-of-table))
+                                ;; and shorten rest
+                                (setcar rest-of-rests-of-tables 
+                                        (cdar rest-of-rests-of-tables))
+                                ;; and check, if current-key appears again
+                                (when (and (caadr rest-of-table)
+                                           (compare current-key 
+                                                    (caadr rest-of-table)))
+                                  (error (concat "Key '" 
+                                                 format-specifier 
+                                                 "' appears twice within "
+                                                 "input table %i") 
+                                         (convert current-key) i)
+                                  )
+                                )
+                       ;; otherwise fill with nil and do not shorte
+                       ;; rest of table
+                       (progn 
+                         (setq current-key-in-intersection nil)
+                         (nconc result-line (make-list (1- 
+                                                        (elt widths-of-tables 
+                                                             (1- i))) 
+                                                       ""))))
+                     
+                     (setq rest-of-rests-of-tables 
+                           (cdr rest-of-rests-of-tables))
+                     rest-of-rests-of-tables)) ; condition for loop
+            (if (or (eq what 'merge) current-key-in-intersection)
+                ;; store away line
+                (setq result-table (cons  
+                                    result-line 
+                                    result-table)))))
+  
+        (nreverse result-table))))
+#+end_src
+
+**** Common helper functions
+
+#+name: lob-table-operations-helper-get-headings-defun
+#+begin_src emacs-lisp
+  (defun lob-table-operations-helper-get-headings (tables)
+    "Internal function for table-operations in orgmode library of babel"
+    (let ((rest-of-tables tables)
+          (i 1)
+          headings-all-tables
+          headings-one-table
+          heading-of-key)
+      (while rest-of-tables 
+        (progn
+          (setq table (car rest-of-tables))
+          (if (eq (cadr table) 'hline)
+              ;; second line is a hline, so first is a heading
+              (progn 
+                ; take headings from first table row
+                (setq headings-one-table (cdar table)) 
+                (unless heading-of-key (setq heading-of-key (caar table)))
+                (unless (string= heading-of-key (caar table))
+                  (error "Name of first column is not the same in all tables"))
+                (setcar rest-of-tables 
+                        (cdar rest-of-tables))) ; and shorten rest
+            ;; table does not contain headings, so make them up
+            (setq headings-one-table 
+                  (mapcar 
+                   (lambda (x) (format "t%dc%d" i x))
+                   (number-sequence 2 (length (car table))))))
+          (setq headings-all-tables (append headings-all-tables 
+                                            headings-one-table))
+          (setq rest-of-tables (cdr rest-of-tables))
+          (incf i)
+          rest-of-tables)) ; condition for while loop
+      (unless heading-of-key (setq heading-of-key "key"))
+      (setq headings-all-tables (cons heading-of-key headings-all-tables))
+      headings-all-tables))
+  
+#+end_src
+
+**** Debugging and testing
+***** Clean up
+#+begin_src emacs-lisp
+  (save-excursion
+    (beginning-of-buffer)
+    (while (re-search-forward "^#\\+results:.*\n\\(^\|.+\n\\)*\n" nil t)
+      (replace-match ""))
+    )
+#+end_src
+
+#+results:
+
+***** Byte Compilation
+
+   (byte-compile 'lob-table-operations-combine)
+   (byte-compile 'lob-table-operations-filter)
+
+*** Development
+**** Versions and history
+
+     [2012-03-18 So] Version 1.0: 
+     - Added handling of hlines and table headings
+
+     [2012-01-07 Sa] Version 0.01:
+     - Restructured as a single org-file; no special .el-file needed any more
+     - Combined and restructured documentation and implementation
+
+**** Bugs and Todos
+
+     - [X] Brush up documentation
+     - [X] Stay below 80 columns
+     - [X] Tests with more than two columns per table
+     - [X] Tests with more than two tables for merging
+     - [X] Handle optional table captions
+     - [X] Handle hlines
+     - [X] flet within lob-table-operations-combine
+     - [-] flet within directly callable blocks; try to avoid global functions
+       Not feasible, because that hinders debugging to much
+     - [X] Use :results silent
+       
+**** Testcases
+
+#+name: upper-wide
+| Position | c1 | c2 | c3 | c4 |
+|----------+----+----+----+----|
+|        1 | A1 | A2 | A3 | A4 |
+|        3 | C1 | C2 | C3 | C4 |
+|        4 | D1 | D2 | D3 | D4 |
+|       10 | J1 | J2 | J3 | J4 |
+|        2 | B1 | B2 | B3 | B4 |
+
+#+name: lower-wide
+| 2 | b1 | b2 | b3 | b4 |
+| 4 | d1 | d2 | d3 | d4 |
+| 5 | e1 | e2 | e3 | e4 |
+| 6 | h1 | h2 | h3 | h4 |
+
+#+name: upper-lower-wide 
+|  2 | Bb1 | Bb2 | Bb3 | Bb4 |
+|  6 | Hh1 | Hh2 | Hh3 | Hh4 |
+|  4 | Dd1 | Dd2 | Dd3 | Dd4 |
+| 10 | Jj1 | Jj2 | Jj3 | Jj4 |
+
+#+call: table-operations-filter-keep(upper-wide,keys)
+
+#+results: table-operations-filter-keep(upper-wide,keys)
+| 1 | A1 | A2 | A3 | A4 |
+| 4 | D1 | D2 | D3 | D4 |
+| 2 | B1 | B2 | B3 | B4 |
+
+#+call: table-operations-filter-remove(lower-wide,keys) :colnames yes
+
+#+results: table-operations-filter-remove(lower-wide,keys)
+| Position | t2c2 | t2c3 | t2c4 | t2c5 |
+|----------+------+------+------+------|
+|        5 | e1   | e2   | e3   | e4   |
+|        6 | h1   | h2   | h3   | h4   |
+
+#+call: table-operations-combine-merge(upper-wide,lower-wide) :colnames yes
+
+#+results: table-operations-combine-merge(upper-wide,lower-wide)
+| Position | c1 | c2 | c3 | c4 | t2c2 | t2c3 | t2c4 | t2c5 |
+|----------+----+----+----+----+------+------+------+------|
+|        1 | A1 | A2 | A3 | A4 |      |      |      |      |
+|        2 | B1 | B2 | B3 | B4 | b1   | b2   | b3   | b4   |
+|        3 | C1 | C2 | C3 | C4 |      |      |      |      |
+|        4 | D1 | D2 | D3 | D4 | d1   | d2   | d3   | d4   |
+|        5 |    |    |    |    | e1   | e2   | e3   | e4   |
+|        6 |    |    |    |    | h1   | h2   | h3   | h4   |
+|       10 | J1 | J2 | J3 | J4 |      |      |      |      |
+
+#+call: table-operations-combine-intersect(upper-wide,lower-wide)
+
+#+results: table-operations-combine-intersect(upper-wide,lower-wide)
+| 2 | B1 | B2 | B3 | B4 | b1 | b2 | b3 | b4 |
+| 4 | D1 | D2 | D3 | D4 | d1 | d2 | d3 | d4 |
+
+#+call: table-operations-combine-merge(upper-wide,lower-wide,upper-lower-wide) :colnames yes
+
+#+results: table-operations-combine-merge(upper-wide,lower-wide,upper-lower-wide)
+| Position | c1 | c2 | c3 | c4 | t2c2 | t2c3 | t2c4 | t2c5 | t3c2 | t3c3 | t3c4 | t3c5 |
+|----------+----+----+----+----+------+------+------+------+------+------+------+------|
+|        1 | A1 | A2 | A3 | A4 |      |      |      |      |      |      |      |      |
+|        2 | B1 | B2 | B3 | B4 | b1   | b2   | b3   | b4   | Bb1  | Bb2  | Bb3  | Bb4  |
+|        3 | C1 | C2 | C3 | C4 |      |      |      |      |      |      |      |      |
+|        4 | D1 | D2 | D3 | D4 | d1   | d2   | d3   | d4   | Dd1  | Dd2  | Dd3  | Dd4  |
+|        5 |    |    |    |    | e1   | e2   | e3   | e4   |      |      |      |      |
+|        6 |    |    |    |    | h1   | h2   | h3   | h4   | Hh1  | Hh2  | Hh3  | Hh4  |
+|       10 | J1 | J2 | J3 | J4 |      |      |      |      | Jj1  | Jj2  | Jj3  | Jj4  |
+
+#+call: table-operations-combine-intersect(upper-wide,lower-wide,upper-lower-wide)
+
+#+results: table-operations-combine-intersect(upper-wide,lower-wide,upper-lower-wide)
+| 2 | B1 | B2 | B3 | B4 | b1 | b2 | b3 | b4 | Bb1 | Bb2 | Bb3 | Bb4 |
+| 4 | D1 | D2 | D3 | D4 | d1 | d2 | d3 | d4 | Dd1 | Dd2 | Dd3 | Dd4 |
+
+**** Keeping the margins
+
+     (setq-default fill-column 80)
+     (column-marker-3 80)
+
+