From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from mp2 ([2001:41d0:2:4a6f::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by ms11 with LMTPS id YLUdDmlo9157YAAA0tVLHw (envelope-from ) for ; Sat, 27 Jun 2020 15:40:25 +0000 Received: from aspmx1.migadu.com ([2001:41d0:2:4a6f::]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) by mp2 with LMTPS id YE3zCWlo914hTwAAB5/wlQ (envelope-from ) for ; Sat, 27 Jun 2020 15:40:25 +0000 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by aspmx1.migadu.com (Postfix) with ESMTPS id 476C3940214 for ; Sat, 27 Jun 2020 15:40:23 +0000 (UTC) Received: from localhost ([::1]:53308 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1jpCwG-0000uu-Mz for larch@yhetil.org; Sat, 27 Jun 2020 11:40:20 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:47648) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1jpCvp-0000uY-EJ for emacs-orgmode@gnu.org; Sat, 27 Jun 2020 11:39:53 -0400 Received: from confino.investici.org ([2a00:c38:11e:ffff::a020]:44059) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1jpCvl-0001NU-JJ for emacs-orgmode@gnu.org; Sat, 27 Jun 2020 11:39:53 -0400 Received: from mx1.investici.org (unknown [127.0.0.1]) by confino.investici.org (Postfix) with ESMTP id 3C10A21090; Sat, 27 Jun 2020 15:39:44 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=anche.no; s=stigmate; t=1593272384; bh=Xu6jrmTKa5UeHmjp9GgrAoS+KLNcJjxi2pb50cF67eQ=; h=Subject:To:References:Cc:From:Date:In-Reply-To:From; b=X7Y4zoKRzeAtX30Ccspur7vNMsiVCn9d940e+n+CJ1xj2NoL/7ggDu7z7+Wg2GAIM 8FtfyhCwvpfExGHuECedwdtYAdgriT5GeJMZjKsHgo7eIavMk9+3F2XjrVvqHz5iW3 e9bNyrKW7SCNhrWV4abE0Lrb81WUosa2cmTF1VT4= Received: from [212.103.72.250] (mx1.investici.org [212.103.72.250]) (Authenticated sender: mariotomo@inventati.org) by localhost (Postfix) with ESMTPSA id BF24B21083; Sat, 27 Jun 2020 15:39:42 +0000 (UTC) Subject: Re: [PATCH] allow for multiline headers To: emacs-orgmode@gnu.org References: <87wo4bhpkx.fsf@nicolasgoaziou.fr> <87tuzeehk2.fsf@nicolasgoaziou.fr> <87a715e9ju.fsf@nicolasgoaziou.fr> <3e6ee551-4ef7-7d96-93dc-19a4973e1af8@anche.no> <871rm5vslh.fsf@nicolasgoaziou.fr> From: Mario Frasca Message-ID: <381d1495-7d26-6105-6a29-d6f001f7004d@anche.no> Date: Sat, 27 Jun 2020 10:39:04 -0500 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Thunderbird/68.9.0 MIME-Version: 1.0 In-Reply-To: <871rm5vslh.fsf@nicolasgoaziou.fr> Content-Type: multipart/mixed; boundary="------------4AC30C9057F34A288C38649C" Content-Language: en-US Received-SPF: pass client-ip=2a00:c38:11e:ffff::a020; envelope-from=mario@anche.no; helo=confino.investici.org X-detected-operating-system: by eggs.gnu.org: No matching host in p0f cache. That's all we know. X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001, URIBL_BLOCKED=0.001 autolearn=_AUTOLEARN X-Spam_action: no action X-BeenThere: emacs-orgmode@gnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: "General discussions about Org-mode." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Nicolas Goaziou Errors-To: emacs-orgmode-bounces+larch=yhetil.org@gnu.org Sender: "Emacs-orgmode" X-Scanner: scn0 Authentication-Results: aspmx1.migadu.com; dkim=pass header.d=anche.no header.s=stigmate header.b=X7Y4zoKR; dmarc=none; spf=pass (aspmx1.migadu.com: domain of emacs-orgmode-bounces@gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=emacs-orgmode-bounces@gnu.org X-Spam-Score: -1.21 X-TUID: GSliLR6BWrho This is a multi-part message in MIME format. --------------4AC30C9057F34A288C38649C Content-Type: text/plain; charset=utf-8; format=flowed Content-Transfer-Encoding: 8bit Hi Nicolas and the whole world, On 24/06/2020 02:19, Nicolas Goaziou wrote: > >>> This could be extracted as an independent function, which would return >>> the header, or nil. We can also imagine a function returning a cons cell >>> (HEADER . BODY), both HEADER and BODY being list of rows (possibly >>> empty). >> I was thinking of this myself too.  but, after all, the goal of this >> function is not only to find the header, but to collapse it into >> a single line. > I suggested this because you were saying earlier in this thread IIRC > that Org has no tooling to handle table headers. I would like to discuss this in a chat, who's available to join #org-mode on freenode? >> if it was splitting the header from the body, then yes, it would >> definitely make sense, the cons cell you suggest. > It _is_ splitting the header from the body. Barring initial `hline' > symbols, header-lines and trailer variables are exactly HEADER and BODY > above. same as above, I wish to hear opinions, collect them, and that we take a decision with shorter communication lines. > >> + (table (org-table-collapse-header (org-table-to-lisp))) >> + (num-cols (length (car table)))) > Note that there is no guarantee that all rows have the same length. > E.g., > > | a | > | b | c | many other points in the code assume rows have the same length. I haven't checked if the assumption is correct, I just used it as I saw the code already does. I think I have processed most other remarks in the new patch. and I have signed and received confirmation of reception of my FSF paperwork :-) ciao, Mario --------------4AC30C9057F34A288C38649C Content-Type: text/x-patch; charset=UTF-8; name="0001-lisp-org-table.el-Allow-collapsing-header-into-singl.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0001-lisp-org-table.el-Allow-collapsing-header-into-singl.pa"; filename*1="tch" >From ca92fb1e4ee66ed39e5b567880faccc513d263d4 Mon Sep 17 00:00:00 2001 From: mfrasca Date: Fri, 12 Jun 2020 11:42:34 -0500 Subject: [PATCH] lisp/org-table.el: Allow collapsing header into single line * lisp/org-table.el (org-table-collapse-header): New function. * lisp/org-plot.el (org-plot/gnuplot): Use org-table-collapse-header and trust there will be no more leading `hline' symbols in lisp table. * testing/lisp/test-org-table.el (test-org-table/to-lisp): Adding tests to already existing to-lisp function. (test-org-table/collapse-header): Adding tests to new collapse-header function. * testing/lisp/test-ox.el (test-org-export/has-header-p): Testing exporting table with multi-line header. --- lisp/org-plot.el | 8 ++--- lisp/org-table.el | 27 +++++++++++++++- testing/lisp/test-org-table.el | 58 ++++++++++++++++++++++++++++++++++ testing/lisp/test-ox.el | 10 ++++++ 4 files changed, 97 insertions(+), 6 deletions(-) diff --git a/lisp/org-plot.el b/lisp/org-plot.el index a23195d2a..35077cfc3 100644 --- a/lisp/org-plot.el +++ b/lisp/org-plot.el @@ -289,14 +289,12 @@ line directly before or after the table." (setf params (plist-put params (car pair) (cdr pair))))) ;; collect table and table information (let* ((data-file (make-temp-file "org-plot")) - (table (org-table-to-lisp)) - (num-cols (length (if (eq (nth 0 table) 'hline) (nth 1 table) - (nth 0 table))))) + (table (org-table-collapse-header (org-table-to-lisp))) + (num-cols (length (car table)))) (run-with-idle-timer 0.1 nil #'delete-file data-file) - (while (eq 'hline (car table)) (setf table (cdr table))) (when (eq (cadr table) 'hline) (setf params - (plist-put params :labels (nth 0 table))) ; headers to labels + (plist-put params :labels (car table))) ; headers to labels (setf table (delq 'hline (cdr table)))) ; clean non-data from table ;; Collect options. (save-excursion (while (and (equal 0 (forward-line -1)) diff --git a/lisp/org-table.el b/lisp/org-table.el index 6462b99c4..248b1ed50 100644 --- a/lisp/org-table.el +++ b/lisp/org-table.el @@ -5458,6 +5458,31 @@ The table is taken from the parameter TXT, or from the buffer at point." (forward-line)) (nreverse table))))) +(defun org-table-collapse-header (table &optional separator max-header-lines) + "Collapse the lines before 'hline into a single header. + +The given TABLE is a list of lists as returned by `org-table-to-lisp'. +The leading lines before the first `hline' symbol are considered +forming the table header. This function collapses all leading header +lines into a single header line, followed by the `hline' symbol, and +the rest of the TABLE. Header cells are glued together with a space, +or the given SEPARATOR." + (while (eq (car table) 'hline) (pop table)) + (let* ((separator (or separator " ")) + (max-header-lines (or max-header-lines 4)) + (trailer table) + (header-lines (cl-loop for line in table + until (eq 'hline line) + collect (pop trailer)))) + (if (and trailer (<= (length header-lines) max-header-lines)) + (cons (apply #'mapcar + (lambda (&rest x) + (org-trim + (mapconcat #'identity x separator))) + header-lines) + trailer) + table))) + (defun orgtbl-send-table (&optional maybe) "Send a transformed version of table at point to the receiver position. With argument MAYBE, fail quietly if no transformation is defined @@ -6139,7 +6164,7 @@ which will prompt for the width." ((numberp ask) ask) (t 12)))) ;; Skip any hline a the top of table. - (while (eq (car table) 'hline) (setq table (cdr table))) + (while (eq (car table) 'hline) (pop table)) ;; Skip table header if any. (dolist (x (or (cdr (memq 'hline table)) table)) (when (consp x) diff --git a/testing/lisp/test-org-table.el b/testing/lisp/test-org-table.el index 64a1b4b16..5d54f4999 100644 --- a/testing/lisp/test-org-table.el +++ b/testing/lisp/test-org-table.el @@ -1304,6 +1304,64 @@ See also `test-org-table/copy-field'." (should (string= got expect))))) +;;; the initial to lisp converter + +(ert-deftest test-org-table/to-lisp () + "Test `orgtbl-to-lisp' specifications." + ;; 2x2 no header + (should + (equal '(("a" "b") ("c" "d")) + (org-table-to-lisp "|a|b|\n|c|d|"))) + ;; 2x2 with 1-line header + (should + (equal '(("a" "b") hline ("c" "d")) + (org-table-to-lisp "|a|b|\n|-\n|c|d|"))) + ;; 2x4 with 2-line header + (should + (equal '(("a" "b") ("A" "B") hline ("c" "d") ("aa" "bb")) + (org-table-to-lisp "|a|b|\n|A|B|\n|-\n|c|d|\n|aa|bb|"))) + ;; leading hlines do not get stripped + (should + (equal '(hline ("a" "b") hline ("c" "d")) + (org-table-to-lisp "|-\n|a|b|\n|-\n|c|d|"))) + (should + (equal '(hline ("a" "b") ("c" "d")) + (org-table-to-lisp "|-\n|a|b|\n|c|d|"))) + (should + (equal '(hline hline hline hline ("a" "b") ("c" "d")) + (org-table-to-lisp "|-\n|-\n|-\n|-\n|a|b|\n|c|d|")))) + +(ert-deftest test-org-table/collapse-header () + "Test `orgtbl-to-lisp' specifications." + ;; 2x2 no header - no collapsing + (should + (equal '(("a" "b") ("c" "d")) + (org-table-collapse-header (org-table-to-lisp "|a|b|\n|c|d|")))) + ;; 2x2 with 1-line header - no collapsing + (should + (equal '(("a" "b") hline ("c" "d")) + (org-table-collapse-header (org-table-to-lisp "|a|b|\n|-\n|c|d|")))) + ;; 2x4 with 2-line header - collapsed + (should + (equal '(("a A" "b B") hline ("c" "d") ("aa" "bb")) + (org-table-collapse-header (org-table-to-lisp "|a|b|\n|A|B|\n|-\n|c|d|\n|aa|bb|")))) + ;; 2x4 with 2-line header, custom glue - collapsed + (should + (equal '(("a.A" "b.B") hline ("c" "d") ("aa" "bb")) + (org-table-collapse-header (org-table-to-lisp "|a|b|\n|A|B|\n|-\n|c|d|\n|aa|bb|") "."))) + ;; 2x4 with 2-line header, threshold 1 - not collapsed + (should + (equal '(("a" "b") ("A" "B") hline ("c" "d") ("aa" "bb")) + (org-table-collapse-header (org-table-to-lisp "|a|b|\n|A|B|\n|-\n|c|d|\n|aa|bb|") nil 1))) + ;; 2x4 with 2-line header, threshold 2 - collapsed + (should + (equal '(("a A" "b B") hline ("c" "d") ("aa" "bb")) + (org-table-collapse-header (org-table-to-lisp "|a|b|\n|A|B|\n|-\n|c|d|\n|aa|bb|") nil 2))) + ;; 2x8 with 6-line header, default threshold 5 - not collapsed + (should + (equal '(("a" "b") ("A" "B") ("a" "b") ("A" "B") ("a" "b") ("A" "B") hline ("c" "d") ("aa" "bb")) + (org-table-collapse-header (org-table-to-lisp "|a|b|\n|A|B|\n|a|b|\n|A|B|\n|a|b|\n|A|B|\n|-\n|c|d|\n|aa|bb|"))))) + ;;; Radio Tables (ert-deftest test-org-table/to-generic () diff --git a/testing/lisp/test-ox.el b/testing/lisp/test-ox.el index 92ccec08e..a5b3bd770 100644 --- a/testing/lisp/test-ox.el +++ b/testing/lisp/test-ox.el @@ -4129,6 +4129,16 @@ Another text. (ref:text) (org-export-table-has-header-p (org-element-map tree 'table 'identity info 'first-match) info))) + ;; With a multi-line header. + (should + (org-test-with-parsed-data " +| a | b | +| 0 | 1 | +|---+---| +| a | w |" + (org-export-table-has-header-p + (org-element-map tree 'table 'identity info 'first-match) + info))) ;; Without an header. (should-not (org-test-with-parsed-data " -- 2.20.1 --------------4AC30C9057F34A288C38649C--