From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!not-for-mail From: Simen =?UTF-8?Q?Heggest=C3=B8yl?= Newsgroups: gmane.emacs.bugs Subject: bug#21798: 25.0.50; [PATCH] Add support for retrieving paths to JSON elements Date: Sun, 08 Nov 2015 13:34:14 +0100 Message-ID: <1446986054.1975.1@smtp.gmail.com> References: <1446281162.2607.0@smtp.gmail.com> <5634CEE7.3070200@yandex.ru> <1446407553.4906.0@smtp.gmail.com> <1446420466.13180.0@smtp.gmail.com> <56381558.7050607@yandex.ru> <1446827488.11140.2@smtp.gmail.com> <563CE043.9060404@yandex.ru> <1446922215.4732.0@smtp.gmail.com> <563E4E73.50702@yandex.ru> <1446985933.1975.0@smtp.gmail.com> NNTP-Posting-Host: plane.gmane.org Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-FskjqSS2DlfAsNoIPXuh" X-Trace: ger.gmane.org 1446986123 523 80.91.229.3 (8 Nov 2015 12:35:23 GMT) X-Complaints-To: usenet@ger.gmane.org NNTP-Posting-Date: Sun, 8 Nov 2015 12:35:23 +0000 (UTC) Cc: 21798@debbugs.gnu.org To: Dmitry Gutov Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane.org@gnu.org Sun Nov 08 13:35:12 2015 Return-path: Envelope-to: geb-bug-gnu-emacs@m.gmane.org Original-Received: from lists.gnu.org ([208.118.235.17]) by plane.gmane.org with esmtp (Exim 4.69) (envelope-from ) id 1ZvPBg-0006Tq-0f for geb-bug-gnu-emacs@m.gmane.org; Sun, 08 Nov 2015 13:35:12 +0100 Original-Received: from localhost ([::1]:47134 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1ZvPBf-0002Sx-HM for geb-bug-gnu-emacs@m.gmane.org; Sun, 08 Nov 2015 07:35:11 -0500 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:60068) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1ZvPBb-0002RZ-Oe for bug-gnu-emacs@gnu.org; Sun, 08 Nov 2015 07:35:09 -0500 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1ZvPBW-00022p-LU for bug-gnu-emacs@gnu.org; Sun, 08 Nov 2015 07:35:07 -0500 Original-Received: from debbugs.gnu.org ([208.118.235.43]:38393) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1ZvPBW-00022k-HE for bug-gnu-emacs@gnu.org; Sun, 08 Nov 2015 07:35:02 -0500 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.80) (envelope-from ) id 1ZvPBW-0000O7-3H for bug-gnu-emacs@gnu.org; Sun, 08 Nov 2015 07:35:02 -0500 X-Loop: help-debbugs@gnu.org Resent-From: Simen =?UTF-8?Q?Heggest=C3=B8yl?= Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Sun, 08 Nov 2015 12:35:01 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 21798 X-GNU-PR-Package: emacs X-GNU-PR-Keywords: patch Original-Received: via spool by 21798-submit@debbugs.gnu.org id=B21798.14469860601406 (code B ref 21798); Sun, 08 Nov 2015 12:35:01 +0000 Original-Received: (at 21798) by debbugs.gnu.org; 8 Nov 2015 12:34:20 +0000 Original-Received: from localhost ([127.0.0.1]:57334 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.80) (envelope-from ) id 1ZvPAp-0000Ma-Cs for submit@debbugs.gnu.org; Sun, 08 Nov 2015 07:34:20 -0500 Original-Received: from mail-lf0-f50.google.com ([209.85.215.50]:33448) by debbugs.gnu.org with esmtp (Exim 4.80) (envelope-from ) id 1ZvPAm-0000MS-Uc for 21798@debbugs.gnu.org; Sun, 08 Nov 2015 07:34:17 -0500 Original-Received: by lffz63 with SMTP id z63so20926336lff.0 for <21798@debbugs.gnu.org>; Sun, 08 Nov 2015 04:34:16 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=date:from:subject:to:cc:message-id:in-reply-to:references :mime-version:content-type; bh=eUIymT/NCWjmVodBhW1FkC2DtZcyHPcY2ykuoi19occ=; b=kMgoOfSS/MEVdjhrGUutjQuC2aiotsPEAS88rPEG+v21JoBNBV4R9TiYuWCtul0aU8 DdNFvUObMhM0OPpjC+LK0WIcvfW8zIFYE5k/f3u4qYl8dzv+qAWSGSeqpmuehiLOgRiq FaSQh6oWOoGa0OrZmoQBP48Y7/Xj+KWVNOyMb1OFvJ9W3QpIRGKwYDJXkZjQ8+WYC/zN p7J/lGkUyNTm/3k1f4mrV0oiFoPOkObseD47i7y/0By+d4CGd5Wp8jDEPo5SH3DyY2zX nAGrIryh7PqpQRwMHOfquDmPMQD5S9u1Qnvje9/6Z0IZVycRQnP7raWUDvUZd3YzlM26 FsXQ== X-Received: by 10.25.207.149 with SMTP id f143mr7488310lfg.73.1446986056107; Sun, 08 Nov 2015 04:34:16 -0800 (PST) Original-Received: from [192.168.100.7] (cm-84.210.143.4.getinternet.no. [84.210.143.4]) by smtp.gmail.com with ESMTPSA id c7sm1571055lfe.22.2015.11.08.04.34.14 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Sun, 08 Nov 2015 04:34:15 -0800 (PST) In-Reply-To: <1446985933.1975.0@smtp.gmail.com> X-Mailer: geary/0.10.0 X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.15 Precedence: list X-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x X-Received-From: 208.118.235.43 X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane.org@gnu.org Original-Sender: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane.org@gnu.org Xref: news.gmane.org gmane.emacs.bugs:108568 Archived-At: --=-FskjqSS2DlfAsNoIPXuh Content-Type: multipart/alternative; boundary="=-6Y8hTKAbrOKGUHNjnFHa" --=-6Y8hTKAbrOKGUHNjnFHa Content-Type: text/plain; charset=utf-8; format=flowed Content-Transfer-Encoding: quoted-printable On Sun, Nov 8, 2015 at 1:32 PM, Simen Heggest=C3=B8yl =20 wrote: > A revised patch implementing your suggestion is attached. Sorry, I forgot to attach the patch. Here it is! -- Simen = --=-6Y8hTKAbrOKGUHNjnFHa Content-Type: text/html; charset=utf-8 Content-Transfer-Encoding: quoted-printable On Sun, Nov 8, 2015 at 1:32 PM, Simen Heggest=C3=B8yl <simenheg@gmail.co= m> wrote:
A revised patch impleme= nting your suggestion is attached.

So= rry, I forgot to attach the patch.

Here it is!

-- Simen
= --=-6Y8hTKAbrOKGUHNjnFHa-- --=-FskjqSS2DlfAsNoIPXuh Content-Type: text/x-patch Content-Disposition: attachment; filename=0001-Add-support-for-retrieving-paths-to-JSON-elements.patch Content-Transfer-Encoding: quoted-printable >From 2e220a1bfce42081ed84c343ecd809263215a54e Mon Sep 17 00:00:00 2001 From: =3D?UTF-8?q?Simen=3D20Heggest=3DC3=3DB8yl?=3D Date: Sun, 25 Oct 2015 14:44:59 +0100 Subject: [PATCH] Add support for retrieving paths to JSON elements Add support for retrieving the path to a JSON element. This can for instance be useful to retrieve paths in deeply nested JSON structures. * lisp/json.el (json-pre-read-function, json-post-read-function): New variables to hold pre- and post read callback functions for `json-read'. (json--path): New variable used internally by `json-path-to-position'. (json--record-path, json--check-position): New functions used internally by `json-path-to-position'. (json-path-to-position): New function for retrieving the path to a JSON element at a given position. (json-read-object, json-read-array): Pass the current JSON key to subsequent calls to `json-read'. (json-read): Call `json-pre-read-function' and `json-post-read-function' before and after reading a JSON element, respectively. * test/automated/json-tests.el (test-json-path-to-position-with-objects) (test-json-path-to-position-with-arrays) (test-json-path-to-position-no-match): New tests for `json-path-to-position'. --- lisp/json.el | 88 ++++++++++++++++++++++++++++++++++++++++= +--- test/automated/json-tests.el | 19 ++++++++++ 2 files changed, 101 insertions(+), 6 deletions(-) diff --git a/lisp/json.el b/lisp/json.el index b23d12a..2982f08 100644 --- a/lisp/json.el +++ b/lisp/json.el @@ -111,6 +111,16 @@ json-encoding-lisp-style-closings "If non-nil, ] and } closings will be formatted lisp-style, without indentation.") =20 +(defvar json-pre-read-function nil + "Function called (if non-nil) by `json-read' right before +reading a JSON element. The function is called with one +argument, which is the current JSON key when reading an array or +object, else it is nil.") + +(defvar json-post-read-function nil + "Function called (if non-nil) by `json-read' right after +reading a JSON element.") + =0C =20 ;;; Utilities @@ -196,6 +206,63 @@ 'json-end-of-file =20 =0C =20 +;;; Paths + +(defvar json--path '() + "Used internally by `json-path-to-position' to keep track of +the path during recursive calls to `json-read'. Each entry is a +cons of a buffer position, and a JSON key (if any, else nil).") + +(defun json--record-path (key) + "Record the KEY to the current JSON path. +Used internally by `json-path-to-position'." + (push (cons (point) key) json--path)) + +(defun json--check-position (position) + "Check if the last parsed JSON structure passed POSITION. +Used internally by `json-path-to-position'." + (let ((start (caar json--path))) + (when (< start position (+ (point) 1)) + (let ((path (nreverse (delq nil (mapcar #'cdr json--path))))) + (throw :json-path (list :path path + :match-start start + :match-end (point)))))) + (pop json--path)) + +(defun json-path-to-position (position &optional string) + "Return the path to the JSON element at POSITION. + +When STRING is provided, return the path to the position in the +string, else to the position in the current buffer. + +The return value is a property list with the following +properties: + +:path -- A list of strings and numbers forming the path to + the JSON element at the given position. Strings + denote object names, while numbers denote array + indexes. + +:match-start -- Position where the matched JSON element begins. + +:match-end -- Position where the matched JSON element ends. + +This can for instance be useful to determine the path to a JSON +element in a deeply nested structure." + (save-excursion + (unless string + (goto-char (point-min))) + (let* ((json--path '()) + (json-pre-read-function #'json--record-path) + (json-post-read-function + (apply-partially #'json--check-position position)) + (path (catch :json-path + (if string + (json-read-from-string string) + (json-read))))) + (when (plist-get path :path) + path)))) + ;;; Keywords =20 (defvar json-keywords '("true" "false" "null") @@ -403,7 +470,8 @@ json-read-object (if (char-equal (json-peek) ?:) (json-advance) (signal 'json-object-format (list ":" (json-peek)))) - (setq value (json-read)) + (json-skip-whitespace) + (setq value (json-read key)) (setq elements (json-add-to-object elements key value)) (json-skip-whitespace) (unless (char-equal (json-peek) ?}) @@ -507,14 +575,17 @@ json-read-array (json-advance) (json-skip-whitespace) ;; read values until "]" - (let (elements) + (let ((index 0) + elements) (while (not (char-equal (json-peek) ?\])) - (push (json-read) elements) + (json-skip-whitespace) + (push (json-read index) elements) (json-skip-whitespace) (unless (char-equal (json-peek) ?\]) (if (char-equal (json-peek) ?,) (json-advance) - (signal 'json-error (list 'bleah))))) + (signal 'json-error (list 'bleah)))) + (setq index (+ index 1))) ;; Skip over the "]" (json-advance) (apply json-array-type (nreverse elements)))) @@ -558,7 +629,7 @@ json-readtable table) "Readtable for JSON reader.") =20 -(defun json-read () +(defun json-read (&optional current-key) "Parse and return the JSON object following point. Advances point just past JSON object." (json-skip-whitespace) @@ -566,7 +637,12 @@ json-read (if (not (eq char :json-eof)) (let ((record (cdr (assq char json-readtable)))) (if (functionp (car record)) - (apply (car record) (cdr record)) + (prog2 + (when json-pre-read-function + (funcall json-pre-read-function current-key)) + (apply (car record) (cdr record)) + (when json-post-read-function + (funcall json-post-read-function))) (signal 'json-readtable-error record))) (signal 'json-end-of-file nil)))) =20 diff --git a/test/automated/json-tests.el b/test/automated/json-tests.el index d1b7a2f..fa1f548 100644 --- a/test/automated/json-tests.el +++ b/test/automated/json-tests.el @@ -49,5 +49,24 @@ (should (equal (json-read-from-string "\"\\nasd\\u0444\\u044b\\u0432fgh\= \t\"") "\nasd=D1=84=D1=8B=D0=B2fgh\t"))) =20 +(ert-deftest test-json-path-to-position-with-objects () + (let* ((json-string "{\"foo\": {\"bar\": {\"baz\": \"value\"}}}") + (matched-path (json-path-to-position 32 json-string))) + (should (equal (plist-get matched-path :path) '("foo" "bar" "baz"))) + (should (equal (plist-get matched-path :match-start) 25)) + (should (equal (plist-get matched-path :match-end) 32)))) + +(ert-deftest test-json-path-to-position-with-arrays () + (let* ((json-string "{\"foo\": [\"bar\", [\"baz\"]]}") + (matched-path (json-path-to-position 20 json-string))) + (should (equal (plist-get matched-path :path) '("foo" 1 0))) + (should (equal (plist-get matched-path :match-start) 18)) + (should (equal (plist-get matched-path :match-end) 23)))) + +(ert-deftest test-json-path-to-position-no-match () + (let* ((json-string "{\"foo\": {\"bar\": \"baz\"}}") + (matched-path (json-path-to-position 5 json-string))) + (should (null matched-path)))) + (provide 'json-tests) ;;; json-tests.el ends here --=20 2.6.2 = --=-FskjqSS2DlfAsNoIPXuh--