From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Joost Kremers Newsgroups: gmane.emacs.devel Subject: Re: [PATCH] csv-mode.el: Add function for reading a CSV line Date: Wed, 22 May 2024 18:21:19 +0200 Message-ID: <86pltdizr4.fsf@fastmail.fm> References: <86wmnmixmf.fsf@fastmail.fm> <87le42v093.fsf@posteo.net> <86ttiqjppp.fsf@fastmail.fm> <87r0dthli0.fsf@posteo.net> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="36141"; mail-complaints-to="usenet@ciao.gmane.io" Cc: Emacs Devel To: Philip Kaludercic Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Wed May 22 18:22:20 2024 Return-path: Envelope-to: ged-emacs-devel@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1s9ojL-0009Cc-D9 for ged-emacs-devel@m.gmane-mx.org; Wed, 22 May 2024 18:22:20 +0200 Original-Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1s9oiZ-0000u7-NN; Wed, 22 May 2024 12:21:31 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1s9oiY-0000to-5d for emacs-devel@gnu.org; Wed, 22 May 2024 12:21:30 -0400 Original-Received: from wfhigh8-smtp.messagingengine.com ([64.147.123.159]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1s9oiV-0007SD-Et for emacs-devel@gnu.org; Wed, 22 May 2024 12:21:29 -0400 Original-Received: from compute4.internal (compute4.nyi.internal [10.202.2.44]) by mailfhigh.west.internal (Postfix) with ESMTP id AAA00180007F; Wed, 22 May 2024 12:21:25 -0400 (EDT) Original-Received: from mailfrontend2 ([10.202.2.163]) by compute4.internal (MEProxy); Wed, 22 May 2024 12:21:25 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=fastmail.fm; h= cc:cc:content-type:content-type:date:date:from:from:in-reply-to :in-reply-to:message-id:mime-version:references:reply-to:subject :subject:to:to; s=fm1; t=1716394885; x=1716481285; bh=RyD7/LnQXW ivNq2j7IsCIefrzwM4rc3m+cQcBIrwACc=; b=HUHgnWyBFdslhed3g0O7HVAgiY C9znSs2vsnlGRG3qPz1FLXtZjB9v3w0yOJgMiVSseUaBGzxrsQNQ6Fn+v9a8ybhW sIQCIKhqODLBtuFJg1+6lrFiBVmLjeVCKicV63Ho9hArvQtPHzoJhd1hjoEXhjeU kg1LlhSpu+feWOsKezgxE1GRsM861Zthjgkf74VeVIuegK9PjwJLDerSBIYH2PAE seIHHgLQvQniK6LrKAkl2zH+H6CY4ntknkPk1/unPTS7oJ7y4gXgu18og724shyp GQizpLBUO2u9kQh0bCy0V+tPwCy3mRbZ2Bh/VwneMUiby8VgPENAdlwivvGA== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-type:content-type:date:date :feedback-id:feedback-id:from:from:in-reply-to:in-reply-to :message-id:mime-version:references:reply-to:subject:subject:to :to:x-me-proxy:x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s= fm1; t=1716394885; x=1716481285; bh=RyD7/LnQXWivNq2j7IsCIefrzwM4 rc3m+cQcBIrwACc=; b=Y7sdDu+KVkEYBgywnFEkFBkz0bGQ/u1cGmw1hDEhDZk+ gVM6Xsjl5ip3r9FRcfmLgAKGdGs/E2ESlNg0CvdWN2hLgNMQi6EflfnYKvcmA3Dc PDAXn5cceUJEpxSQbJs7jq6Fg7mNAfvCxzVIsn6/gvFBs0+fIvvViVQ7+TpwKM5b RnusUv2QyP3klPPzDYUQLXFhSgztM7W4Jl7MiqWfsao4lPtvgZm55IuFJOYsAb+F ZcJbaZ8OlKIVpbEjRDLvMVzi9hmpZAtJlt0Of1NINKBopTuABqoG1SGZRPZyrKAU HE8KHGbOem2vu4NxOe+4Kbl/eG2bXRjKvb2k5XbwYA== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgedvledrvdeigedgvdelucetufdoteggodetrfdotf fvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfqfgfvpdfurfetoffkrfgpnffqhgen uceurghilhhouhhtmecufedttdenucesvcftvggtihhpihgvnhhtshculddquddttddmne cujfgurhephffvvefujghffffkgggtsehmtderredttddtnecuhfhrohhmpeflohhoshht ucfmrhgvmhgvrhhsuceojhhoohhsthhkrhgvmhgvrhhssehfrghsthhmrghilhdrfhhmqe enucggtffrrghtthgvrhhnpedvuedugffhtdevtddtledvleduleehheefveffgfdvheej vdeiieevkeejieekgfenucevlhhushhtvghrufhiiigvpedtnecurfgrrhgrmhepmhgrih hlfhhrohhmpehjohhoshhtkhhrvghmvghrshesfhgrshhtmhgrihhlrdhfmh X-ME-Proxy: Feedback-ID: ie15541ac:Fastmail Original-Received: by mail.messagingengine.com (Postfix) with ESMTPA; Wed, 22 May 2024 12:21:23 -0400 (EDT) In-Reply-To: <87r0dthli0.fsf@posteo.net> (Philip Kaludercic's message of "Wed, 22 May 2024 16:14:31 +0000") Received-SPF: pass client-ip=64.147.123.159; envelope-from=joostkremers@fastmail.fm; helo=wfhigh8-smtp.messagingengine.com X-Spam_score_int: -27 X-Spam_score: -2.8 X-Spam_bar: -- X-Spam_report: (-2.8 / 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, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_LOW=-0.7, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Original-Sender: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Xref: news.gmane.io gmane.emacs.devel:319487 Archived-At: --=-=-= Content-Type: text/plain On Wed, May 22 2024, Philip Kaludercic wrote: [patch] > Seems fine to me. I'd apply it if there are no objections. Until then, > you can prepare to modify your package to use this change. I was just about to suggest one more test. Specifically: ``` (should (equal (let ((csv-field-quotes '("\"" "'"))) (csv--unquote-value "'Hello, \"World\"'")) "Hello, \"World\"")) ``` It tests the case where the user defined more than one possible quote character, one being used to quote the field and the other being used inside the field. The attached patch adds this test but is otherwise identical to the previous one. Otherwise, thanks! :-) -- Joost Kremers Life has its moments --=-=-= Content-Type: text/x-patch Content-Disposition: inline; filename=0001-Add-function-for-reading-a-CSV-line-and-return-its-v.patch >From 2a37f695ffe5757b0f22ffe63d312d1407122e3a Mon Sep 17 00:00:00 2001 From: Joost Kremers Date: Wed, 22 May 2024 00:07:34 +0200 Subject: [PATCH] Add function for reading a CSV line and return its values as a list. * (csv-parse-current-row): New function; unlike csv--collect-fields, unquotes the field values. * (csv--unquote-value): New function. --- csv-mode-tests.el | 26 ++++++++++++++++++++++++++ csv-mode.el | 26 +++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/csv-mode-tests.el b/csv-mode-tests.el index 0caeab7..12e009e 100644 --- a/csv-mode-tests.el +++ b/csv-mode-tests.el @@ -144,5 +144,31 @@ (csv--separator-score ?\; csv-tests--data (length csv-tests--data))))) +(ert-deftest csv-tests-unquote-value () + (should (equal (csv--unquote-value "Hello, World") + "Hello, World")) + (should (equal (csv--unquote-value "\"Hello, World\"") + "Hello, World")) + (should (equal (csv--unquote-value "Hello, \"\"World") + "Hello, \"\"World")) + (should (equal (csv--unquote-value "\"Hello, \"\"World\"\"\"") + "Hello, \"World\"")) + (should (equal (csv--unquote-value "'Hello, World'") + "'Hello, World'")) + (should (equal (let ((csv-field-quotes '("\"" "'"))) + (csv--unquote-value "\"Hello, World'")) + "\"Hello, World'")) + (should (equal (let ((csv-field-quotes '("\"" "'"))) + (csv--unquote-value "'Hello, World'")) + "Hello, World")) + (should (equal (let ((csv-field-quotes '("\"" "'"))) + (csv--unquote-value "'Hello, ''World'''")) + "Hello, 'World'")) + (should (equal (let ((csv-field-quotes '("\"" "'"))) + (csv--unquote-value "'Hello, \"World\"'")) + "Hello, \"World\"")) + (should (equal (csv--unquote-value "|Hello, World|") + "|Hello, World|"))) + (provide 'csv-mode-tests) ;;; csv-mode-tests.el ends here diff --git a/csv-mode.el b/csv-mode.el index f639dcf..ebcd9da 100644 --- a/csv-mode.el +++ b/csv-mode.el @@ -4,7 +4,7 @@ ;; Author: "Francis J. Wright" ;; Maintainer: emacs-devel@gnu.org -;; Version: 1.23 +;; Version: 1.24 ;; Package-Requires: ((emacs "27.1") (cl-lib "0.5")) ;; Keywords: convenience @@ -107,6 +107,10 @@ ;;; News: +;; Since 1.24 +;; - New function `csv--unquote-value'. +;; - New function `csv-parse-current-row'. + ;; Since 1.21: ;; - New command `csv-insert-column'. ;; - New config var `csv-align-min-width' for `csv-align-mode'. @@ -1400,6 +1404,26 @@ point is assumed to be at the beginning of the line." (forward-char))) (nreverse fields))))) +(defun csv--unquote-value (value) + "Remove quotes around VALUE. +If VALUE contains escaped quote characters, un-escape them. If +VALUE is not quoted, return it unchanged." + (save-match-data + (let ((quote-regexp (apply #'concat `("[" ,@csv-field-quotes "]")))) + (if-let (((string-match (concat "^\\(" quote-regexp "\\)\\(.*\\)\\(" quote-regexp "\\)$") value)) + (quote-char (match-string 1 value)) + ((equal quote-char (match-string 3 value))) + (unquoted (match-string 2 value))) + (replace-regexp-in-string (concat quote-char quote-char) quote-char unquoted) + value)))) + +(defun csv-parse-current-row () + "Parse the current CSV line. +Return the field values as a list." + (save-mark-and-excursion + (goto-char (line-beginning-position)) + (mapcar #'csv--unquote-value (csv--collect-fields (line-end-position))))) + (defvar-local csv--header-line nil) (defvar-local csv--header-hscroll nil) (defvar-local csv--header-string nil) -- 2.45.1 --=-=-=--