emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
* [ANN] orgtbl-join
@ 2015-01-22 21:03 Thierry Banel
  2015-01-23  2:21 ` Eric Abrahamsen
  2015-08-05  0:00 ` Bastien Guerry
  0 siblings, 2 replies; 6+ messages in thread
From: Thierry Banel @ 2015-01-22 21:03 UTC (permalink / raw)
  To: emacs-orgmode

Hi The List

I am glad to announce the orgtbl-join package.

It merges Org tables. A master table is enriched with material from a
reference table.

Example. This master table is a cooking recipe, with ingredients
and quantities (gramms).

| food     | quty |
|----------+------|
| onion    |   70 |
| tomatoe  |  120 |
| eggplant |  300 |
| tofu     |  100 |
| corn     |  250 |
| tomatoe  |   90 |

We also have a reference table of nutrition facts (freely borrowed
from Nut-Nutrition, http://nut.sourceforge.net/, by Jim Jozwiak).

#+tblname: nut
| food     | Fiber | Sugar | Protein | Carb |
|----------+-------+-------+---------+------|
| eggplant |   2.5 |   3.2 |     0.8 |  8.6 |
| tomatoe  |   0.6 |   2.1 |     0.8 |  3.4 |
| onion    |   1.3 |   4.4 |     1.3 |  9.0 |
| egg      |     0 |  18.3 |    31.9 | 18.3 |
| rice     |   0.2 |     0 |     1.5 | 16.0 |
| bread    |   0.7 |   0.7 |     3.3 | 16.0 |
| orange   |   3.1 |  11.9 |     1.3 | 17.6 |
| banana   |   2.1 |   9.9 |     0.9 | 18.5 |
| tofu     |   0.7 |   0.5 |     6.6 |  1.4 |
| nut      |   2.6 |   1.3 |     4.9 |  7.2 |
| corn     |   4.7 |   1.8 |     2.8 | 21.3 |

Then, typing M-x orgtbl-join, the recipe get enriched with nutrition
facts:

| food     | quty | Fiber | Sugar | Protein | Carb |
|----------+------+-------+-------+---------+------|
| onion    |   70 |   1.3 |   4.4 |     1.3 |  9.0 |
| tomatoe  |  120 |   0.6 |   2.1 |     0.8 |  3.4 |
| eggplant |  300 |   2.5 |   3.2 |     0.8 |  8.6 |
| tofu     |  100 |   0.7 |   0.5 |     6.6 |  1.4 |
| corn     |  250 |   4.7 |   1.8 |     2.8 | 21.3 |
| tomatoe  |   90 |   0.6 |   2.1 |     0.8 |  3.4 |

Documentation here:
https://github.com/tbanel/orgtbljoin

---------
Tha package is available on MELPA.
To enable MELPA, add those lines to your .emacs:
  (require 'package)
  (add-to-list
     'package-archives
     '("melpa" . "http://melpa.milkbox.net/packages/") t)
  (package-initialize)

Then browse the 2000 available packages (and growing)
by typing:
  M-x package-list-packages

Comments, use-cases, help, criticisms, etc. welcome.
Have fun
Thierry Banel

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [ANN] orgtbl-join
  2015-01-22 21:03 [ANN] orgtbl-join Thierry Banel
@ 2015-01-23  2:21 ` Eric Abrahamsen
  2015-08-05  0:00 ` Bastien Guerry
  1 sibling, 0 replies; 6+ messages in thread
From: Eric Abrahamsen @ 2015-01-23  2:21 UTC (permalink / raw)
  To: emacs-orgmode

Thierry Banel <tbanelwebmin@free.fr> writes:

> Hi The List
>
> I am glad to announce the orgtbl-join package.
>
> It merges Org tables. A master table is enriched with material from a
> reference table.

This is *really* cool -- thanks for making these packages!

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [ANN] orgtbl-join
  2015-01-22 21:03 [ANN] orgtbl-join Thierry Banel
  2015-01-23  2:21 ` Eric Abrahamsen
@ 2015-08-05  0:00 ` Bastien Guerry
  2015-08-16 21:57   ` Thierry Banel
  1 sibling, 1 reply; 6+ messages in thread
From: Bastien Guerry @ 2015-08-05  0:00 UTC (permalink / raw)
  To: Thierry Banel; +Cc: emacs-orgmode

Hi Thierry,

Thierry Banel <tbanelwebmin@free.fr> writes:

> I am glad to announce the orgtbl-join package.

this is really nice.

Would you be okay to add this to Org's core feature for Org > 8.3?

-- 
 Bastien

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [ANN] orgtbl-join
  2015-08-05  0:00 ` Bastien Guerry
@ 2015-08-16 21:57   ` Thierry Banel
  2015-08-18 15:22     ` Bastien
  0 siblings, 1 reply; 6+ messages in thread
From: Thierry Banel @ 2015-08-16 21:57 UTC (permalink / raw)
  To: emacs-orgmode

Le 05/08/2015 02:00, Bastien Guerry a écrit :
> Hi Thierry,
>
> Thierry Banel <tbanelwebmin@free.fr> writes:
>
>> I am glad to announce the orgtbl-join package.
> this is really nice.
>
> Would you be okay to add this to Org's core feature for Org > 8.3?
>

Of course I'm okay.
As 8.3 is already out, it should be added to an upcoming release.
(Sorry Bastien, I missed your message, I was off-line for a few days).

I will submit a patch to the mailing-list, including a translation of
the documentation (https://github.com/tbanel/orgtbljoin) to the texi format.

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [ANN] orgtbl-join
  2015-08-16 21:57   ` Thierry Banel
@ 2015-08-18 15:22     ` Bastien
  2015-08-20 21:04       ` Thierry Banel
  0 siblings, 1 reply; 6+ messages in thread
From: Bastien @ 2015-08-18 15:22 UTC (permalink / raw)
  To: Thierry Banel; +Cc: emacs-orgmode

Hi Thierry,

Thierry Banel <tbanelwebmin@free.fr> writes:

> As 8.3 is already out, it should be added to an upcoming release.
> (Sorry Bastien, I missed your message, I was off-line for a few
> days).
>
> I will submit a patch to the mailing-list, including a translation of
> the documentation (https://github.com/tbanel/orgtbljoin) to the texi
> format.

Great, thanks in advance!

-- 
 Bastien

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [ANN] orgtbl-join
  2015-08-18 15:22     ` Bastien
@ 2015-08-20 21:04       ` Thierry Banel
  0 siblings, 0 replies; 6+ messages in thread
From: Thierry Banel @ 2015-08-20 21:04 UTC (permalink / raw)
  To: emacs-orgmode

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

Le 18/08/2015 17:22, Bastien a écrit :
>> I will submit a patch to the mailing-list, including a translation of
>> the documentation (https://github.com/tbanel/orgtbljoin) to the texi
>> format.
> Great, thanks in advance!
>

Hi the List

Here is the patch to add table-joining feature in Org mode.
It comes with unit tests and texi documentation.

Of course, everything can be reviewed, commented, changed.
Of particular interest are:

- key and menu bindings (which tend to be over-crowded)
  C-c C-x j  (orgtbl-join)
  Menu > Tbl > Column > Join with a reference table

- documentation
     cd org-mode
     make doc
     C-u C-h i  doc/org
     goto node "Joining tables"

Thierry



[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Add-table-joining-feature.patch --]
[-- Type: text/x-diff; name="0001-Add-table-joining-feature.patch", Size: 46649 bytes --]

From 44507a06d5213ca986bd901c50fb96915a208ef4 Mon Sep 17 00:00:00 2001
From: Thierry Banel <tbanelwebmin@free.fr>
Date: Mon, 17 Aug 2015 14:45:50 +0200
Subject: [PATCH] Add table joining feature

* org-tbljoin.el (orgtbl-join), (orgtbl-to-joined-table),
  (org-insert-dblock:join), (org-dblock-write:join):
  the new joining engine
* org.el, org-table.el: key and menu bindings
* test-org-tbljoin.el, org-tbljoin.org: unit tests
* org.texi: document feature under the "Joining ables"
  entry in the "Tables" section.
---
 doc/org.texi                     | 290 +++++++++++++++++++++++++
 lisp/org-table.el                |   3 +-
 lisp/org-tbljoin.el              | 450 +++++++++++++++++++++++++++++++++++++++
 lisp/org.el                      |   3 +-
 testing/examples/org-tbljoin.org | 251 ++++++++++++++++++++++
 testing/lisp/test-org-tbljoin.el | 233 ++++++++++++++++++++
 6 files changed, 1228 insertions(+), 2 deletions(-)
 create mode 100644 lisp/org-tbljoin.el
 create mode 100644 testing/examples/org-tbljoin.org
 create mode 100644 testing/lisp/test-org-tbljoin.el

diff --git a/doc/org.texi b/doc/org.texi
index 0f5747d..4259ccb 100644
--- a/doc/org.texi
+++ b/doc/org.texi
@@ -374,6 +374,7 @@ Tables
 * Orgtbl mode::                 The table editor as minor mode
 * The spreadsheet::             The table editor has spreadsheet capabilities
 * Org-Plot::                    Plotting from org tables
+* Joining tables::              Adding material from a table to another
 
 The spreadsheet
 
@@ -2095,6 +2096,7 @@ calculations are supported using the Emacs @file{calc} package
 * Orgtbl mode::                 The table editor as minor mode
 * The spreadsheet::             The table editor has spreadsheet capabilities
 * Org-Plot::                    Plotting from org tables
+* Joining tables::              Adding material from a table to another
 @end menu
 
 @node Built-in table editor
@@ -3402,6 +3404,294 @@ The formula is an elisp call:
 
 @end table
 
+@node Joining tables
+@section Joining tables
+@cindex joining two tables
+
+One table (the master table) is grown by selectively appending rows of
+another table (the reference table).  As an example, here is a list of
+products for a cooking recipe.
+
+@verbatim
+| type     | quty |
+|----------+------|
+| onion    |   70 |
+| tomatoe  |  120 |
+| eggplant |  300 |
+| tofu     |  100 |
+@end verbatim
+
+We want to complete it with nutritional facts: quantities of fiber,
+sugar, proteins, and carbohydrates.  For this purpose, we have a long
+reference table of standard products.  (This table has been freely
+borrowed from Nut-Nutrition, @uref{http://nut.sourceforge.net/}, by Jim
+Jozwiak).
+
+@verbatim
+#+tblname: nut
+| type     | Fiber | Sugar | Protein | Carb |
+|----------+-------+-------+---------+------|
+| eggplant |   2.5 |   3.2 |     0.8 |  8.6 |
+| tomatoe  |   0.6 |   2.1 |     0.8 |  3.4 |
+| onion    |   1.3 |   4.4 |     1.3 |  9.0 |
+| egg      |     0 |  18.3 |    31.9 | 18.3 |
+| rice     |   0.2 |     0 |     1.5 | 16.0 |
+| bread    |   0.7 |   0.7 |     3.3 | 16.0 |
+| orange   |   3.1 |  11.9 |     1.3 | 17.6 |
+| banana   |   2.1 |   9.9 |     0.9 | 18.5 |
+| tofu     |   0.7 |   0.5 |     6.6 |  1.4 |
+| nut      |   2.6 |   1.3 |     4.9 |  7.2 |
+| corn     |   4.7 |   1.8 |     2.8 | 21.3 |
+@end verbatim
+
+Let us put the cursor on the "type" column of the recipe table, and type
+@kbd{C-c C-x j} or @code{M-x orgtbl-join}.  A few questions are asked.  Then
+the recipe gets new columns appended with the needed nutrition facts:
+
+@verbatim
+| type     | quty | Fiber | Sugar | Protein | Carb |
+|----------+------+-------+-------+---------+------|
+| onion    |   70 |   1.3 |   4.4 |     1.3 |  9.0 |
+| tomatoe  |  120 |   0.6 |   2.1 |     0.8 |  3.4 |
+| eggplant |  300 |   2.5 |   3.2 |     0.8 |  8.6 |
+| tofu     |  100 |   0.7 |   0.5 |     6.6 |  1.4 |
+@end verbatim
+
+If you are familiar with SQL, you would get a similar result with the
+a @emph{join} (actually a @emph{left outer join}).
+
+@example
+select *
+from recipe, nut
+left outer join nut on recipe.type = nut.type;
+@end example
+
+@menu
+* In-place push pull::          Enriching a table or deriving an enriched table
+* Block parameters::            Specifying tables and columns to use
+* Duplicates or missing values:: Accept non-perfect fit
+* Keeping headers::             Keeping table headers
+@end menu
+
+@node In-place push pull
+@subsection In-place push pull
+@cindex in-place, push, pull
+
+Three modes are available: @emph{in-place}, @emph{push}, @emph{pull}.
+
+@subheading In-place mode
+
+The master table is changed (in-place) by appending columns from the
+reference table.
+
+@table @kbd
+
+@orgcmd{C-c C-x j,orgtbl-join}
+The cursor must be positioned on the column used to perform the join.  User
+is prompted for the reference table and the column to use. Then material is
+added from the reference table into the table under the cursor.  Also
+available from the menu @kbd{Tbl > Column > Join with a reference table}.
+
+@end table
+
+@subheading Push mode
+
+The master table drives the creation of derived tables.  Specify the wanted
+result in @code{#+ORGTBL: SEND} directives (as many as desired):
+
+@verbatim
+#+ORGTBL: SEND enriched orgtbl-to-joined-table :ref-table nut :mas-column type :ref-column type
+| type     | quty |
+|----------+------|
+| onion    |   70 |
+| tomatoe  |  120 |
+| eggplant |  300 |
+| tofu     |  100 |
+@end verbatim
+
+The receiving blocks must be created somewhere else in the same file:
+
+@verbatim
+#+BEGIN RECEIVE ORGTBL enriched
+#+END RECEIVE ORGTBL enriched
+@end verbatim
+
+Typing @kbd{C-c C-c} with the cursor on the first pipe of the master table
+refreshes all derived tables.
+
+@subheading Pull mode
+
+So-called "dynamic blocks" may also be used.  The resulting table knows how
+to build itself.  Here is an example of a master table which is unaware that
+it will be enriched in a joined table:
+
+@verbatim
+#+TBLNAME: recipe
+| type     | quty |
+|----------+------|
+| onion    |   70 |
+| tomatoe  |  120 |
+| eggplant |  300 |
+| tofu     |  100 |
+@end verbatim
+
+Create somewhere else a @emph{dynamic block} which carries the specification of
+the join:
+
+@verbatim
+#+BEGIN: join :mas-table recipe :mas-column type :ref-table nut :ref-column type
+| type     | quty | type     | Fiber | Sugar | Protein | Carb |
+|----------+------+----------+-------+-------+---------+------|
+| onion    |   70 | onion    |   1.3 |   4.4 |     1.3 |  9.0 |
+| tomatoe  |  120 | tomatoe  |   0.6 |   2.1 |     0.8 |  3.4 |
+| eggplant |  300 | eggplant |   2.5 |   3.2 |     0.8 |  8.6 |
+| tofu     |  100 | tofu     |   0.7 |   0.5 |     6.6 |  1.4 |
+#+END:
+@end verbatim
+
+Typing @kbd{C-c C-c} with the cursor on the @code{#+BEGIN:} line refreshes the
+table.
+
+A wizard is available for the full process.  Type @kbd{M-x
+org-insert-dblock:join} and answer the questions.
+
+@subheading As a rule of thumb
+
+For quick and once-only processing, use @emph{in-place} mode.
+
+Use @emph{pull} or @emph{push} modes for reproductible work.  The @emph{pull}
+mode might be easier to use than the @emph{push}, because the
+@kbd{org-insert-dblock:join} wizard is available.  Other than that, the two
+modes use the same underlying engine, so using one or the other is just a
+matter or convenience.
+
+@node Block parameters
+@subsection Block parameters
+@cindex block
+
+Table creation is driven by bloc lines: @code{#+ORGTBL: SEND} in pull mode
+and @code{#+BEGIN} in pull mode.  The form is as follow:
+
+@verbatim
+#+ORGTBL: SEND ENRICHED orgtbl-to-joined-table :ref-table REFTABLE :mas-column COLUMN :ref-column COLUMN
+#+BEGIN: join :mas-table MASTABLE :mas-column COLUMN :ref-table REFTABLE :ref-column COLUMN
+@end verbatim
+
+Parameters meaning is as follow:
+
+@table @code
+
+@item SEND ENRICHED
+
+  The name of the derived table, which must match a receiving block:
+@verbatim
+#+BEGIN RECEIVE ORGTBL ENRICHED
+#+END RECEIVE ORGTBL ENRICHED
+@end verbatim
+
+@item orgtbl-to-joined-table
+
+  This is the name of the lisp function in charge of creating the derived
+  table.  Do not change this name.
+
+@item :mas-table MASTABLE
+
+  Gives the name of the master table.  This is the table which will be
+  enriched with material from the reference table.
+
+@item :ref-table REFTABLE
+
+  Gives the name of the reference table.  This is the table from which
+  material will be borrowed.
+
+@item :mas-column COLUMN
+
+  Specifies the column in the master table to match a corresponding column in
+  the reference table.  @code{COLUMN} may be a dollar form as @code{$1}
+  (first column), @code{$2} (second column), and so on, or the name of the
+  column if the table has a header.
+
+@item :ref-column COLUMN
+
+  Specifies the column in the reference table to match a corresponding column
+  in the master table.  @code{COLUMN} may be a dollar form as @code{$1}
+  (first column), @code{$2} (second column), and so on, or the name of the
+  column if the table has a header.
+
+@end table
+
+
+@node Duplicates or missing values
+@subsection Duplicates or missing values
+@cindex duplicate values
+@cindex missing values
+
+It may happen that no row in the reference table matches a value in
+the master table.  In this case, the master row is kept, with empty
+cells added to it.  Information from the master table is never lost.
+If, for example, a line in the recipe refers to an unkown "amarante"
+product (a cereal known by the ancient Incas), then the resulting
+table will still contain the "amarante" row, with empty nutritional
+facts.
+
+@verbatim
+| type     | quty | type     | Fiber | Sugar | Protein | Carb |
+|----------+------+----------+-------+-------+---------+------|
+| onion    |   70 | onion    |   1.3 |   4.4 |     1.3 |  9.0 |
+| tomatoe  |  120 | tomatoe  |   0.6 |   2.1 |     0.8 |  3.4 |
+| eggplant |  300 | eggplant |   2.5 |   3.2 |     0.8 |  8.6 |
+| tofu     |  100 | tofu     |   0.7 |   0.5 |     6.6 |  1.4 |
+| amarante |  120 |          |       |       |         |      |
+@end verbatim
+
+The reference table may contain several matching rows for the same
+value in the master table.  In this case, as many rows are created in
+the joined table.  Therefore, the resulting table may be longer than
+the master table.  Example, if the reference table contains three rows
+for "eggplants":
+
+@verbatim
+#+tblname: nut
+| type     | Cooking | Fiber | Sugar | Protein | Carb |
+|----------+---------+-------+-------+---------+------|
+| ...      | ...     |   ... |   ... |     ... |  ... |
+| eggplant | boiled  |   2.5 |   3.2 |     0.8 |  8.6 |
+| eggplant | pickled |   3.4 |   6.5 |     1.2 | 13.3 |
+| eggplant | raw     |   2.8 |   1.9 |     0.8 |  4.7 |
+| ...      | ...     |   ... |   ... |     ... |  ... |
+@end verbatim
+
+Then the resulting table will have those three rows appended:
+
+@verbatim
+| type     | quty | type     | Cooking | Fiber | Sugar | Protein | Carb |
+|----------+------+----------+---------+-------+-------+---------+------|
+| eggplant |  300 | eggplant | boiled  |   2.5 |   3.2 |     0.8 |  8.6 |
+| eggplant |  300 | eggplant | pickled |   3.4 |   6.5 |     1.2 | 13.3 |
+| eggplant |  300 | eggplant | raw     |   2.8 |   1.9 |     0.8 |  4.7 |
+@end verbatim
+
+If you are familiar with SQL, this behavior is reminicent of the
+@emph{left outer join}.
+
+@node Keeping headers
+@subsection Keeping headers
+@cindex header
+
+The master and the reference tables may or may not have a header.  When
+there is a header, it may extend over several lines.  A header ends
+with an horizontal line.
+
+The join system tries to preserve as much of the master table as possible.
+Therefore, if the master table has a header, the joined table will have it
+verbatim, over as many lines as needed.
+
+The reference table header (if any), will fill-in the header (if any)
+of the resulting table.  But if there is no room in the resulting
+table header, the reference table header lines will be ignored, partly
+of fully.
+
+
 @node Hyperlinks
 @chapter Hyperlinks
 @cindex hyperlinks
diff --git a/lisp/org-table.el b/lisp/org-table.el
index b6d59f1..1ed0804 100644
--- a/lisp/org-table.el
+++ b/lisp/org-table.el
@@ -4396,7 +4396,8 @@ to execute outside of tables."
 	 ["Move Column Left" org-metaleft :active (org-at-table-p) :keys "M-<left>"]
 	 ["Move Column Right" org-metaright :active (org-at-table-p) :keys "M-<right>"]
 	 ["Delete Column" org-shiftmetaleft :active (org-at-table-p) :keys "M-S-<left>"]
-	 ["Insert Column" org-shiftmetaright :active (org-at-table-p) :keys "M-S-<right>"])
+	 ["Insert Column" org-shiftmetaright :active (org-at-table-p) :keys "M-S-<right>"]
+	 ["Join with a reference table" orgtbl-join :active (org-at-table-p) :keys "C-c C-x j"])
 	("Row"
 	 ["Move Row Up" org-metaup :active (org-at-table-p) :keys "M-<up>"]
 	 ["Move Row Down" org-metadown :active (org-at-table-p) :keys "M-<down>"]
diff --git a/lisp/org-tbljoin.el b/lisp/org-tbljoin.el
new file mode 100644
index 0000000..271edd7
--- /dev/null
+++ b/lisp/org-tbljoin.el
@@ -0,0 +1,450 @@
+;;; orgtbl-join.el --- join columns from another table
+
+;; Copyright (C) 2014-2015 Free Software Foundation, Inc.
+
+;; Author: Thierry Banel tbanelwebmin at free dot fr
+;; Keywords: org, table, join, filtering
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;; 
+;; A master table is enriched with columns coming from a reference
+;; table.  For enriching a row of the master table, matching rows from
+;; the reference table are selected.  The matching succeeds when the
+;; key cells of the master row and the reference row are equal.
+
+;;; Requires:
+(require 'org-table)
+
+;;; Code:
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Utility functions
+
+(defun orgtbl--join-colname-to-int (col table)
+  "Convert the column name into an integer (first column is numbered 0)
+COL may be:
+- a dollar form, like $5 which is converted to 4
+- a number, like 5 which is converted to 4
+- an alphanumeric name which appears in the column header (if any)
+When COL does not match any actual column, an error is generated.
+TABLE is an Org mode table passed as a list of lists of cells.
+It is used to check COL against TABLE header."
+  ;; skip first hlines if any
+  (while (not (listp (car table)))
+    (setq table (cdr table)))
+  (if (symbolp col)
+      (setq col (symbol-name col)))
+  (cond ((numberp col)
+	 t)
+	((string-match "^\\$?\\([0-9]+\\)$" col)
+	 (setq col (string-to-number (match-string 1 col))))
+	(t
+	 ;; TABLE has no header, COL does not make sense
+	 (unless (memq 'hline table)
+	   (user-error "No header on the table, and no such column '%s'" col))
+	 ;; iterate over first line of header to find COL
+	 (let ((i 0)
+	       (n))
+	   (mapc (lambda (c)
+		   (setq i (1+ i))
+		   (if (equal col c)
+		       (setq n i)))
+		 (car table))
+	   (unless n (user-error "No such column '%s'" col))
+	   (setq col n))))
+  (setq col (1- col))
+  (if (or (< col 0) (>= col (length (car table))))
+      (user-error "Column %s outside table" col))
+  col)
+
+(defun orgtbl--join-query-column (prompt table)
+  "Interactively query a column.
+PROMPT is displayed to the user to explain what answer is expected.
+TABLE is the org mode table from which a column will be choosen
+by the user.  Its header is used for column names completion.  If
+TABLE has no header, completion is done on generic column names:
+$1, $2..."
+  (while (eq 'hline (car table))
+    (setq table (cdr table)))
+  (org-icompleting-read
+    prompt
+    (if (memq 'hline table) ;; table has a header
+	(car table)
+      (let ((i 0))
+	(mapcar (lambda (x) (format "$%s" (setq i (1+ i))))
+		(car table))))))
+
+(defun orgtbl--join-convert-to-hashtable (table col)
+  "Convert an Org-mode TABLE into a hash table.
+The purpose is to provide fast lookup to TABLE's rows.  The COL
+column contains the keys for the hashtable entries.  Return a
+cons, the car contains the header, the cdr contains the
+hashtable."
+  ;; skip heading horinzontal lines if any
+  (while (eq (car table) 'hline)
+    (setq table (cdr table)))
+  ;; split header and body
+  (let ((head)
+	(body (memq 'hline table))
+	(hash (make-hash-table :test 'equal :size (+ 20 (length table)))))
+    (if (not body)
+	(setq body table)
+      (setq head table)
+      ;; terminate header with nil
+      (let ((h head))
+	(while (not (eq (cadr h) 'hline))
+	  (setq h (cdr h)))
+	(setcdr h nil)))
+    ;; fill-in the hashtable
+    (mapc (lambda (row)
+	    (when (listp row)
+	      (let ((key (nth col row)))
+		(puthash key (nconc (gethash key hash) (list row)) hash))))
+	  body)
+    (cons head hash)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; The following functions are borrowed from the orgtbl-aggregate package.
+;; They are general enough to be moved to org-table.el
+
+(defun orgtbl-list-local-tables ()
+  "Search for available tables in the current file."
+  (interactive)
+  (let ((tables))
+    (save-excursion
+      (save-restriction
+	(widen)
+	(goto-char (point-min))
+	(while (re-search-forward "^[ \t]*#\\+\\(tbl\\)?name:[ \t]*\\(.*\\)" nil t)
+	  (let ((text (match-string 2)))
+	    (set-text-properties 0 (length text) () text)
+	    (setq tables (cons text tables))))))
+    tables))
+
+(defun orgtbl-get-distant-table (name-or-id)
+  "Find a table in the current buffer named NAME-OR-ID.
+Returns it as a list of lists of cells.  An horizontal line is
+translated as the special symbol `hline'."
+  (unless (stringp name-or-id)
+    (setq name-or-id (format "%s" name-or-id)))
+  (let (buffer loc id-loc tbeg form)
+    (save-excursion
+      (save-restriction
+	(widen)
+	(save-excursion
+	  (goto-char (point-min))
+	  (if (re-search-forward
+	       (concat "^[ \t]*#\\+\\(tbl\\)?name:[ \t]*"
+		       (regexp-quote name-or-id)
+		       "[ \t]*$")
+	       nil t)
+	      (setq buffer (current-buffer) loc (match-beginning 0))
+	    (setq id-loc (org-id-find name-or-id 'marker))
+	    (unless (and id-loc (markerp id-loc))
+	      (error "Can't find remote table \"%s\"" name-or-id))
+	    (setq buffer (marker-buffer id-loc)
+		  loc (marker-position id-loc))
+	    (move-marker id-loc nil)))
+	(with-current-buffer buffer
+	  (save-excursion
+	    (save-restriction
+	      (widen)
+	      (goto-char loc)
+	      (forward-char 1)
+	      (unless (and (re-search-forward "^\\(\\*+ \\)\\|[ \t]*|" nil t)
+			   (not (match-beginning 1)))
+		(user-error "Cannot find a table at NAME or ID %s" name-or-id))
+	      (setq tbeg (point-at-bol))
+	      (org-table-to-lisp))))))))
+
+(defun orgtbl-insert-elisp-table (table)
+  "Insert TABLE in current buffer at point.
+TABLE is a list of lists of cells.  The list may contain the
+special symbol 'hline to mean an horizontal line."
+    (while table
+      (let ((row (car table)))
+	(setq table (cdr table))
+	(cond ((consp row)
+	       (insert "|")
+	       (insert (mapconcat #'identity row "|")))
+	      ((eq row 'hline)
+	       (insert "|-"))
+	      (t (error "Bad row in elisp table")))
+	(insert "\n")))
+    (delete-char -1)
+    (org-table-align))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; In-place mode
+
+;;;###autoload
+(defun orgtbl-join ()
+  "Add material from a reference table to the current table.
+Rows from the reference table are appended to rows of the current
+table.  For each row of the current table, matching rows from the
+reference table are searched and appended.  The matching is
+performed by testing for equality of cells in the current column,
+and a joining column in the reference table.  If a row in the
+current table matches several rows in the reference table, then
+the current row is duplicated and each copy is appended with a
+different reference row.  If no matching row is found in the
+reference table, then the current row is kept, with empty cells
+appended to it."
+  (interactive)
+  (org-table-check-inside-data-field)
+  (let* ((col (1- (org-table-current-column)))
+	 (tbl (org-table-to-lisp))
+	 (ref (orgtbl-get-distant-table
+	       (org-icompleting-read
+		"Reference table: "
+		(orgtbl-list-local-tables))))
+	 (dcol (orgtbl--join-colname-to-int
+		(orgtbl--join-query-column "Reference column: " ref)
+		ref))
+	 (refhead)
+	 (refhash))
+    (setq ref (orgtbl--join-convert-to-hashtable ref dcol)
+	  refhead (car ref)
+	  refhash (cdr ref))
+    (goto-char (org-table-begin))
+    ;; Skip any hline a the top of tbl.
+    (while (eq (car tbl) 'hline)
+      (setq tbl (cdr tbl))
+      (forward-line 1))
+    ;; is there a header on tbl ? append the ref header (if any)
+    (when (memq 'hline tbl)
+      ;; for each line of header in tbl, add a header from ref
+      ;; if ref-header empties too fast, continue with nils
+      ;; if tbl-header empties too fast, ignore remaining ref-headers
+      (while (listp (pop tbl))
+	(end-of-line)
+	(when refhead
+	  (orgtbl--join-insert-ref-row (car refhead) dcol)
+	  (setq refhead (cdr refhead)))
+	(forward-line 1))
+      (forward-line 1))
+    ;; now the body of the tbl
+    (mapc (lambda (masline)
+	    (if (listp masline)
+		(let ((done))
+		  ;; if several ref-lines match, all of them are considered
+		  (mapc (lambda (refline)
+			  (end-of-line)
+			  (when done ;; make a copy of the current row
+			    (open-line 1)
+			    (forward-line 1)
+			    (insert "|")
+			    (mapc (lambda (y) (insert y) (insert "|"))
+				  masline))
+			  (orgtbl--join-insert-ref-row refline dcol)
+			  (setq done t))
+			(gethash (nth col masline) refhash))))
+	    (forward-line 1))
+	  tbl))
+  (forward-line -1)
+  (org-table-align))
+
+(defun orgtbl--join-insert-ref-row (row dcol)
+  "Insert a distant ROW in the buffer.
+The DCOL columns (joining column) is skipped."
+  (let ((i 0))
+    (while row
+      (unless (equal i dcol)
+	(insert (car row))
+	(insert "|"))
+      (setq i (1+ i))
+      (setq row (cdr row)))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; PULL & PUSH engine
+
+(defun orgtbl--join-append-mas-ref-row (masrow refrow refcol)
+  "Concatenate master and reference rows, skiping the reference column.
+MASROW is a list of cells from the master table.  REFROW is a
+list of cells from the reference table.  REFCOL is the position,
+numbered from zero, of the column in REFROW that should not be
+appended in the result, because it is already present in MASROW."
+  (let ((result (reverse masrow))
+	(i 0))
+    (while refrow
+      (unless (equal i refcol)
+	(setq result (cons (car refrow) result)))
+      (setq refrow (cdr refrow))
+      (setq i (1+ i)))
+    (reverse result)))
+
+(defun orgtbl--create-table-joined (mastable mascol reftable refcol)
+  "Join a master table with a reference table.
+MASTABLE is the master table, as a list of lists of cells.
+MASCOL is the name of the joining column in the master table.
+REFTABLE is the reference table.
+REFCOL is the name of the joining column in the reference table.
+Returns MASTABLE enriched with material from REFTABLE."
+  (let ((result)  ;; result built in reverse order
+	(refhead)
+	(refhash))
+    ;; skip any hline a the top of both tables
+    (while (eq (car mastable) 'hline)
+      (setq result (cons 'hline result))
+      (setq mastable (cdr mastable)))
+    (while (eq (car reftable) 'hline)
+      (setq reftable (cdr reftable)))
+    ;; convert column-names to numbers
+    (setq mascol (orgtbl--join-colname-to-int mascol mastable))
+    (setq refcol (orgtbl--join-colname-to-int refcol reftable))
+    ;; convert reference table into fast-lookup hashtable
+    (setq reftable (orgtbl--join-convert-to-hashtable reftable refcol)
+	  refhead (car reftable)
+	  refhash (cdr reftable))
+    ;; iterate over master table header if any
+    ;; and join it with reference table header if any
+    (if (memq 'hline mastable)
+	(while (listp (car mastable))
+	  (setq result
+		(cons (orgtbl--join-append-mas-ref-row
+		       (car mastable)
+		       (and refhead (car refhead))
+		       refcol)
+		      result))
+	  (setq mastable (cdr mastable))
+	  (if refhead
+	      (setq refhead (cdr refhead)))))
+    ;; create the joined table
+    (mapc (lambda (masline)
+	    (if (not (listp masline))
+		(setq result (cons masline result))
+	      (let ((result0 result))
+		;; if several ref-lines match, all of them are considered
+		(mapc (lambda (refline)
+			(setq result
+			      (cons
+			       (orgtbl--join-append-mas-ref-row
+				masline
+				refline
+				refcol)
+			       result)))
+		      (gethash (nth mascol masline) refhash))
+		;; if no ref-line matches, add the non-matching master-line anyway
+		(if (eq result result0)
+		    (setq result (cons masline result))))))
+	  mastable)
+    (nreverse result)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; PUSH mode
+
+;;;###autoload
+(defun orgtbl-to-joined-table (table params)
+  "Enrich the master TABLE with lines from a reference table.
+
+PARAMS contains pairs of key-value with the following keys:
+
+:ref-table   the reference table.
+             Lines from the reference table will be added to the
+             master table.
+
+:mas-column  the master joining column.
+             This column names one of the master table columns.
+
+:ref-column  the reference joining column.
+             This column names one of the reference table columns.
+
+Columns names are either found in the header of the table, if the
+table have a header, or a dollar form: $1, $2, and so on.
+
+The destination must be specified somewhere in the
+same file with a bloc like this:
+#+BEGIN RECEIVE ORGTBL destination_table_name
+#+END RECEIVE ORGTBL destination_table_name"
+  (interactive)
+  (orgtbl-to-generic
+   (orgtbl--create-table-joined
+    table
+    (plist-get params :mas-column)
+    (orgtbl-get-distant-table (plist-get params :ref-table))
+    (plist-get params :ref-column))
+   (org-combine-plists
+    (list :sep "|" :hline "|-" :lstart "|" :lend "|")
+    params)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; PULL mode
+
+;;;###autoload
+(defun org-insert-dblock:join ()
+  "Wizard to interactively insert a joined table as a dynamic block."
+  (interactive)
+  (let* ((localtables (orgtbl-list-local-tables))
+	 (mastable
+	  (org-icompleting-read
+	   "Master table: "
+	   localtables))
+	 (mascol
+	  (orgtbl--join-query-column
+	   "Master joining column: "
+	   (orgtbl-get-distant-table mastable)))
+	 (reftable
+	  (org-icompleting-read
+	   "Reference table: "
+	   localtables))
+	 (refcol
+	  (orgtbl--join-query-column
+	   "Reference joining column: "
+	   (orgtbl-get-distant-table reftable))))
+    (org-create-dblock
+     (list :name "join"
+	   :mas-table mastable :mas-column mascol
+	   :ref-table reftable :ref-column refcol))
+    (org-update-dblock)))
+
+;;;###autoload
+(defun org-dblock-write:join (params)
+  "Create a joined table out of a master and a reference table.
+
+PARAMS contains pairs of key-value with the following keys:
+
+:mas-table   the master table.
+             This table will be copied and enriched with material
+             from the reference table.
+
+:ref-table   the reference table.
+             Lines from the reference table will be added to the
+             master table.
+
+:mas-column  the master joining column.
+             This column names one of the master table columns.
+
+:ref-column  the reference joining column.
+             This column names one of the reference table columns.
+
+Columns names are either found in the header of the table, if the
+table have a header, or a dollar form: $1, $2, and so on.
+
+The
+#+BEGIN RECEIVE ORGTBL destination_table_name
+#+END RECEIVE ORGTBL destination_table_name"
+  (interactive)
+  (orgtbl-insert-elisp-table
+   (orgtbl--create-table-joined
+    (orgtbl-get-distant-table (plist-get params :mas-table))
+    (plist-get params :mas-column)
+    (orgtbl-get-distant-table (plist-get params :ref-table))
+    (plist-get params :ref-column))))
+
+(provide 'org-tbljoin)
+;;; org-tbljoin.el ends here
diff --git a/lisp/org.el b/lisp/org.el
index 9336183..0dea3fa 100755
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -21643,7 +21643,8 @@ on context.  See the individual commands for more information."
      ["Move Column Left" org-metaleft (org-at-table-p)]
      ["Move Column Right" org-metaright (org-at-table-p)]
      ["Delete Column" org-shiftmetaleft (org-at-table-p)]
-     ["Insert Column" org-shiftmetaright (org-at-table-p)])
+     ["Insert Column" org-shiftmetaright (org-at-table-p)]
+     ["Join with a reference table" orgtbl-join :active (org-at-table-p) :keys "C-c C-x j"])
     ("Row"
      ["Move Row Up" org-metaup (org-at-table-p)]
      ["Move Row Down" org-metadown (org-at-table-p)]
diff --git a/testing/examples/org-tbljoin.org b/testing/examples/org-tbljoin.org
new file mode 100644
index 0000000..5ca2333
--- /dev/null
+++ b/testing/examples/org-tbljoin.org
@@ -0,0 +1,251 @@
+#+Title: a collection of examples org-tbljoin tests
+
+* In-place mode
+
+** Nutritional reference table with header
+
+There are multiple "eggplant" entries on purpose.
+They will all get added to the master table when joining.
+
+The header extends on 3 lines. All 3 lines will be concatenated to the
+master table header, provided the master table header has at least 3
+lines. The excess lines will be ignored.
+
+#+tblname: nut_with_header
+|------+----------+-------+-------+---------|
+| Carb | type     | Fiber | Sugar | Protein |
+| ohyd |          |       |       |         |
+| rate |          |       |       |         |
+|------+----------+-------+-------+---------|
+|  8.6 | eggplant |   2.5 |   3.2 |     0.8 |
+|  8.7 | eggplant |   2.6 |   3.3 |     0.9 |
+|  3.4 | tomatoe  |   0.6 |   2.1 |     0.8 |
+|  9.0 | onion    |   1.3 |   4.4 |     1.3 |
+| 18.3 | egg      |     0 |  18.3 |    31.9 |
+| 16.0 | rice     |   0.2 |     0 |     1.5 |
+| 16.0 | bread    |   0.7 |   0.7 |     3.3 |
+| 17.6 | orange   |   3.1 |  11.9 |     1.3 |
+| 18.5 | banana   |   2.1 |   9.9 |     0.9 |
+|  1.4 | tofu     |   0.7 |   0.5 |     6.6 |
+|  7.2 | nut      |   2.6 |   1.3 |     4.9 |
+| 21.3 | corn     |   4.7 |   1.8 |     2.8 |
+|  8.5 | eggplant |     ? |     ? |       ? |
+|      |          |       |       |         |
+
+** Nutritional reference table without header
+
+#+tblname: nut_no_header
+|  8.6 | eggplant | 2.5 |  3.2 |  0.8 |
+|  8.7 | eggplant | 2.6 |  3.3 |  0.9 |
+|  3.4 | tomatoe  | 0.6 |  2.1 |  0.8 |
+|  9.0 | onion    | 1.3 |  4.4 |  1.3 |
+| 18.3 | egg      |   0 | 18.3 | 31.9 |
+| 16.0 | rice     | 0.2 |    0 |  1.5 |
+| 16.0 | bread    | 0.7 |  0.7 |  3.3 |
+| 17.6 | orange   | 3.1 | 11.9 |  1.3 |
+| 18.5 | banana   | 2.1 |  9.9 |  0.9 |
+|  1.4 | tofu     | 0.7 |  0.5 |  6.6 |
+|  7.2 | nut      | 2.6 |  1.3 |  4.9 |
+| 21.3 | corn     | 4.7 |  1.8 |  2.8 |
+|  8.5 | eggplant |   ? |    ? |    ? |
+|      |          |     |      |      |
+
+** With a header and a formula
+   :PROPERTIES:
+   :ID:       cc039f82-24d2-422c-a5ae-4dea09cce684
+   :END:
+
+- Put the cursor on the "type" column
+- type
+  : M-x orgtbl-join
+- answer
+  : nut_with_header
+  : type
+
+| quty | type     | units |  mul |
+|------+----------+-------+------|
+|   70 | onion    |     5 |  350 |
+|  120 | tomatoe  |     8 |  960 |
+|  300 | eggplant |     2 |  600 |
+|------+----------+-------+------|
+|  100 | tofu     |     1 |  100 |
+|  250 | corn     |    15 | 3750 |
+|   90 | tomatoe  |     5 |  450 |
+|------+----------+-------+------|
+|   80 | amarante |     1 |   80 |
+#+TBLFM: $4=$1*$3
+
+The master tables have a formula on the last column, which will be
+preserved after joining.
+
+** Without a header, with a formula
+- Put the cursor on the second column
+- type
+  : M-x orgtbl-join
+- answer
+  : nut_with_header
+  : type
+
+|  70 | onion    |  5 |  350 |
+| 120 | tomatoe  |  8 |  960 |
+| 300 | eggplant |  2 |  600 |
+| 100 | tofu     |  1 |  100 |
+| 250 | corn     | 15 | 3750 |
+|  90 | tomatoe  |  5 |  450 |
+|  80 | amarante |  1 |   80 |
+#+TBLFM: $4=$1*$3
+
+The master tables have a formula on the last column, which will be
+preserved after joining.
+
+* PULL mode
+
+** Master table with oversized header
+
+#+tblname: meal_with_header
+| product   |   quty |
+| common    |     in |
+| name      | gramms |
+| (english) |        |
+|-----------+--------|
+| onion     |     70 |
+| unknown   |    999 |
+| tomatoe   |    120 |
+| eggplant  |    300 |
+| corn      |    250 |
+
+** Master table without header
+
+#+tblname: meal_no_header
+| onion     |  70 |
+| not known | 999 |
+| tomatoe   | 120 |
+| eggplant  | 300 |
+| corn      | 250 |
+
+** Join header+header
+Marker: a14723d3-13c8-4fd1-a69f-caf2fdb2d2b1
+#+BEGIN: join :mas-table meal_with_header :mas-column $1 :ref-table nut_with_header :ref-column 2
+#+END:
+
+** join header+bare
+Marker: 79a90117-fc0e-4556-b790-c925b6acd450
+#+BEGIN: join :mas-table "meal_with_header" :mas-column "product" :ref-table "nut_no_header" :ref-column "2"
+#+END:
+
+** join bare+header
+Marker: 24c5a7b4-2815-40d4-89ec-32b58f492b32
+#+BEGIN: join :mas-table meal_no_header :mas-column $1 :ref-table nut_with_header :ref-column type
+#+END:
+
+** join bare+bare
+Marker: e872df5c-dd5c-4ad6-b395-2e5a000488b5
+#+BEGIN: join :mas-table meal_no_header :mas-column 1 :ref-table nut_no_header :ref-column $2
+#+END:
+
+* PUSH mode
+
+** Push a master table with header
+
+1st reference table has a larger header
+2nd reference table has no header
+
+Marker: 6426c948-bbd4-4b25-8b3d-2584b70af4d0
+#+ORGTBL: SEND joined1 orgtbl-to-joined-table :ref-table nut_with_header :mas-column product :ref-column type
+#+ORGTBL: SEND joined2 orgtbl-to-joined-table :ref-table "nut_no_header" :mas-column "$1"    :ref-column $2
+| product       | quty |
+| (yes)         |  (g) |
+|---------------+------|
+| onion         |   70 |
+| not specified |  999 |
+| tomatoe       |  120 |
+| eggplant      |  300 |
+| corn          |  250 |
+
+#+BEGIN RECEIVE ORGTBL joined1
+#+END RECEIVE ORGTBL joined1
+
+#+BEGIN RECEIVE ORGTBL joined2
+#+END RECEIVE ORGTBL joined2
+
+** Push a master table with not header
+
+1st reference table has a larger header
+2nd reference table has no header
+
+Marker: 1683a68c-f4df-4b04-9f2d-bd4c8a909bf3
+#+ORGTBL: SEND joined3 orgtbl-to-joined-table :ref-table nut_with_header :mas-column "1" :ref-column type
+#+ORGTBL: SEND joined4 orgtbl-to-joined-table :ref-table "nut_no_header" :mas-column $1  :ref-column $2
+| onion         |  70 |
+| not specified | 999 |
+| tomatoe       | 120 |
+| eggplant      | 300 |
+| corn          | 250 |
+
+#+BEGIN RECEIVE ORGTBL joined3
+#+END RECEIVE ORGTBL joined3
+
+#+BEGIN RECEIVE ORGTBL joined4
+#+END RECEIVE ORGTBL joined4
+
+* Cartesian product
+
+What happens when the master and the reference table are the same
+table?  A so-called cartesian product (named after the mathematician
+René Descartes) is created.  Every possible combination of rows is
+created.
+
+** Simple auto-join in pull-mode
+
+The table is joined with itself, creating a cartesian product.  The
+resulting table size is the square of the original table size (7*7 =
+49).
+
+#+tblname: auto
+| t | n |
+|---+---|
+| a | 1 |
+| a | 2 |
+| a | 3 |
+| a | 4 |
+| a | 5 |
+| a | 6 |
+| a | 7 |
+
+Marker: b6e51dab-cded-427e-8967-d14a34070d08
+#+BEGIN: join :mas-table auto :mas-column t :ref-table auto :ref-column "t"
+#+END:
+
+** Two sub-cartesian-products in push mode
+
+Because the table has two keys (a & b), two completely unrelated
+cartesian products are created, each the square size of the source
+(3^2 + 2^2 = 13).
+
+#+tblname: buto
+#+ORGTBL: SEND buto2 orgtbl-to-joined-table :ref-table buto :mas-column "t" :ref-column t
+| t | n |
+|---+---|
+| a | 1 |
+| a | 2 |
+| a | 3 |
+| b | 4 |
+| b | 5 |
+
+#+BEGIN RECEIVE ORGTBL buto2
+| t | n | n |
+|---+---+---|
+| a | 1 | 1 |
+| a | 1 | 2 |
+| a | 1 | 3 |
+| a | 2 | 1 |
+| a | 2 | 2 |
+| a | 2 | 3 |
+| a | 3 | 1 |
+| a | 3 | 2 |
+| a | 3 | 3 |
+| b | 4 | 4 |
+| b | 4 | 5 |
+| b | 5 | 4 |
+| b | 5 | 5 |
+#+END RECEIVE ORGTBL buto2
diff --git a/testing/lisp/test-org-tbljoin.el b/testing/lisp/test-org-tbljoin.el
new file mode 100644
index 0000000..54cd6df
--- /dev/null
+++ b/testing/lisp/test-org-tbljoin.el
@@ -0,0 +1,233 @@
+;;; test-org-tbljoin.el --- tests for org-tbljoin.el
+
+;; Copyright (C) 2015  Thierry Banel
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Code:
+
+;(unless (featurep 'org-tbljoin)
+;  (signal 'missing-test-dependency "Table Join"))
+
+(defun org-tbljoin-test-pull (marker expected)
+  (org-test-at-marker
+   "../examples/org-tbljoin.org"
+   marker
+   (forward-line 1)
+   (org-update-dblock)
+   (let ((result (buffer-substring-no-properties (point) (progn (search-forward "#+END:") (point)))))
+     (set-buffer-modified-p nil)
+     (kill-buffer)
+     (should (equal result expected)))))
+
+(ert-deftest org-tbljoin/pull/hh ()
+  "Joining two tables."
+  (org-tbljoin-test-pull
+   "a14723d3-13c8-4fd1-a69f-caf2fdb2d2b1"
+   "#+BEGIN: join :mas-table meal_with_header :mas-column $1 :ref-table nut_with_header :ref-column 2
+| product   |   quty | Carb | Fiber | Sugar | Protein |
+| common    |     in | ohyd |       |       |         |
+| name      | gramms | rate |       |       |         |
+| (english) |        |      |       |       |         |
+|-----------+--------+------+-------+-------+---------|
+| onion     |     70 |  9.0 |   1.3 |   4.4 |     1.3 |
+| unknown   |    999 |      |       |       |         |
+| tomatoe   |    120 |  3.4 |   0.6 |   2.1 |     0.8 |
+| eggplant  |    300 |  8.6 |   2.5 |   3.2 |     0.8 |
+| eggplant  |    300 |  8.7 |   2.6 |   3.3 |     0.9 |
+| eggplant  |    300 |  8.5 |     ? |     ? |       ? |
+| corn      |    250 | 21.3 |   4.7 |   1.8 |     2.8 |
+#+END:"))
+
+(ert-deftest org-tbljoin/pull/hb ()
+  "Joining two tables."
+  (org-tbljoin-test-pull
+   "79a90117-fc0e-4556-b790-c925b6acd450"
+   "#+BEGIN: join :mas-table \"meal_with_header\" :mas-column \"product\" :ref-table \"nut_no_header\" :ref-column \"2\"
+| product   |   quty |      |     |     |     |
+| common    |     in |      |     |     |     |
+| name      | gramms |      |     |     |     |
+| (english) |        |      |     |     |     |
+|-----------+--------+------+-----+-----+-----|
+| onion     |     70 |  9.0 | 1.3 | 4.4 | 1.3 |
+| unknown   |    999 |      |     |     |     |
+| tomatoe   |    120 |  3.4 | 0.6 | 2.1 | 0.8 |
+| eggplant  |    300 |  8.6 | 2.5 | 3.2 | 0.8 |
+| eggplant  |    300 |  8.7 | 2.6 | 3.3 | 0.9 |
+| eggplant  |    300 |  8.5 |   ? |   ? |   ? |
+| corn      |    250 | 21.3 | 4.7 | 1.8 | 2.8 |
+#+END:"))
+
+(ert-deftest org-tbljoin/pull/bh ()
+  "Joining two tables."
+  (org-tbljoin-test-pull
+   "24c5a7b4-2815-40d4-89ec-32b58f492b32"
+   "#+BEGIN: join :mas-table meal_no_header :mas-column $1 :ref-table nut_with_header :ref-column type
+| onion     |  70 |  9.0 | 1.3 | 4.4 | 1.3 |
+| not known | 999 |      |     |     |     |
+| tomatoe   | 120 |  3.4 | 0.6 | 2.1 | 0.8 |
+| eggplant  | 300 |  8.6 | 2.5 | 3.2 | 0.8 |
+| eggplant  | 300 |  8.7 | 2.6 | 3.3 | 0.9 |
+| eggplant  | 300 |  8.5 |   ? |   ? |   ? |
+| corn      | 250 | 21.3 | 4.7 | 1.8 | 2.8 |
+#+END:"))
+
+(ert-deftest org-tbljoin/pull/bb ()
+  "Joining two tables."
+  (org-tbljoin-test-pull
+   "e872df5c-dd5c-4ad6-b395-2e5a000488b5"
+   "#+BEGIN: join :mas-table meal_no_header :mas-column 1 :ref-table nut_no_header :ref-column $2
+| onion     |  70 |  9.0 | 1.3 | 4.4 | 1.3 |
+| not known | 999 |      |     |     |     |
+| tomatoe   | 120 |  3.4 | 0.6 | 2.1 | 0.8 |
+| eggplant  | 300 |  8.6 | 2.5 | 3.2 | 0.8 |
+| eggplant  | 300 |  8.7 | 2.6 | 3.3 | 0.9 |
+| eggplant  | 300 |  8.5 |   ? |   ? |   ? |
+| corn      | 250 | 21.3 | 4.7 | 1.8 | 2.8 |
+#+END:"))
+
+(defun org-tbljoin-test-push (markersrc marker1 marker2 expected1 expected2)
+  (org-test-at-marker
+    "../examples/org-tbljoin.org"
+    markersrc
+    (beginning-of-line)
+    (forward-line 3)
+    (orgtbl-send-table 'maybe)
+    (let ((result1
+	   (buffer-substring-no-properties
+	    (progn (search-forward marker1) (forward-char 1) (org-table-align) (point))
+	    (progn (search-forward "#+END") (point))))
+	  (result2
+	   (buffer-substring-no-properties
+	    (progn (search-forward marker2) (forward-char 1) (org-table-align) (point))
+	    (progn (search-forward "#+END") (point)))))
+      (set-buffer-modified-p nil)
+      (kill-buffer)
+      (message "result1 = %s" result1)
+      (should
+       (and
+	(equal result1 expected1)
+	(equal result2 expected2))))))
+
+(ert-deftest org-tbljoin/push/h ()
+  (org-tbljoin-test-push
+   "6426c948-bbd4-4b25-8b3d-2584b70af4d0"
+   "#+BEGIN RECEIVE ORGTBL joined1"
+   "#+BEGIN RECEIVE ORGTBL joined2"
+   "| product       | quty | Carb | Fiber | Sugar | Protein |
+| (yes)         |  (g) | ohyd |       |       |         |
+|---------------+------+------+-------+-------+---------|
+| onion         |   70 |  9.0 |   1.3 |   4.4 |     1.3 |
+| not specified |  999 |      |       |       |         |
+| tomatoe       |  120 |  3.4 |   0.6 |   2.1 |     0.8 |
+| eggplant      |  300 |  8.6 |   2.5 |   3.2 |     0.8 |
+| eggplant      |  300 |  8.7 |   2.6 |   3.3 |     0.9 |
+| eggplant      |  300 |  8.5 |     ? |     ? |       ? |
+| corn          |  250 | 21.3 |   4.7 |   1.8 |     2.8 |
+#+END"
+   "| product       | quty |      |     |     |     |
+| (yes)         |  (g) |      |     |     |     |
+|---------------+------+------+-----+-----+-----|
+| onion         |   70 |  9.0 | 1.3 | 4.4 | 1.3 |
+| not specified |  999 |      |     |     |     |
+| tomatoe       |  120 |  3.4 | 0.6 | 2.1 | 0.8 |
+| eggplant      |  300 |  8.6 | 2.5 | 3.2 | 0.8 |
+| eggplant      |  300 |  8.7 | 2.6 | 3.3 | 0.9 |
+| eggplant      |  300 |  8.5 |   ? |   ? |   ? |
+| corn          |  250 | 21.3 | 4.7 | 1.8 | 2.8 |
+#+END"))
+
+(ert-deftest org-tbljoin/push/b ()
+  (org-tbljoin-test-push
+   "1683a68c-f4df-4b04-9f2d-bd4c8a909bf3"
+   "#+BEGIN RECEIVE ORGTBL joined3"
+   "#+BEGIN RECEIVE ORGTBL joined4"
+   "| onion         |  70 |  9.0 | 1.3 | 4.4 | 1.3 |
+| not specified | 999 |      |     |     |     |
+| tomatoe       | 120 |  3.4 | 0.6 | 2.1 | 0.8 |
+| eggplant      | 300 |  8.6 | 2.5 | 3.2 | 0.8 |
+| eggplant      | 300 |  8.7 | 2.6 | 3.3 | 0.9 |
+| eggplant      | 300 |  8.5 |   ? |   ? |   ? |
+| corn          | 250 | 21.3 | 4.7 | 1.8 | 2.8 |
+#+END"
+   "| onion         |  70 |  9.0 | 1.3 | 4.4 | 1.3 |
+| not specified | 999 |      |     |     |     |
+| tomatoe       | 120 |  3.4 | 0.6 | 2.1 | 0.8 |
+| eggplant      | 300 |  8.6 | 2.5 | 3.2 | 0.8 |
+| eggplant      | 300 |  8.7 | 2.6 | 3.3 | 0.9 |
+| eggplant      | 300 |  8.5 |   ? |   ? |   ? |
+| corn          | 250 | 21.3 | 4.7 | 1.8 | 2.8 |
+#+END"))
+
+
+(ert-deftest org-tbljoin/pull/auto ()
+  "Cartesian product of a table with itself."
+  (org-tbljoin-test-pull
+   "b6e51dab-cded-427e-8967-d14a34070d08"
+   "#+BEGIN: join :mas-table auto :mas-column t :ref-table auto :ref-column \"t\"
+| t | n | n |
+|---+---+---|
+| a | 1 | 1 |
+| a | 1 | 2 |
+| a | 1 | 3 |
+| a | 1 | 4 |
+| a | 1 | 5 |
+| a | 1 | 6 |
+| a | 1 | 7 |
+| a | 2 | 1 |
+| a | 2 | 2 |
+| a | 2 | 3 |
+| a | 2 | 4 |
+| a | 2 | 5 |
+| a | 2 | 6 |
+| a | 2 | 7 |
+| a | 3 | 1 |
+| a | 3 | 2 |
+| a | 3 | 3 |
+| a | 3 | 4 |
+| a | 3 | 5 |
+| a | 3 | 6 |
+| a | 3 | 7 |
+| a | 4 | 1 |
+| a | 4 | 2 |
+| a | 4 | 3 |
+| a | 4 | 4 |
+| a | 4 | 5 |
+| a | 4 | 6 |
+| a | 4 | 7 |
+| a | 5 | 1 |
+| a | 5 | 2 |
+| a | 5 | 3 |
+| a | 5 | 4 |
+| a | 5 | 5 |
+| a | 5 | 6 |
+| a | 5 | 7 |
+| a | 6 | 1 |
+| a | 6 | 2 |
+| a | 6 | 3 |
+| a | 6 | 4 |
+| a | 6 | 5 |
+| a | 6 | 6 |
+| a | 6 | 7 |
+| a | 7 | 1 |
+| a | 7 | 2 |
+| a | 7 | 3 |
+| a | 7 | 4 |
+| a | 7 | 5 |
+| a | 7 | 6 |
+| a | 7 | 7 |
+#+END:"))
+
+(provide 'test-org-tbljoin)
+;;; test-org-tbljoin.el ends here
-- 
2.1.4


^ permalink raw reply related	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2015-08-20 21:05 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-01-22 21:03 [ANN] orgtbl-join Thierry Banel
2015-01-23  2:21 ` Eric Abrahamsen
2015-08-05  0:00 ` Bastien Guerry
2015-08-16 21:57   ` Thierry Banel
2015-08-18 15:22     ` Bastien
2015-08-20 21:04       ` Thierry Banel

Code repositories for project(s) associated with this public inbox

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

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