Subject: Re: bug#8706: 24.0.50; [PATCH] Function to build a URL query-string

From: Ted Zlatanov <[email-masked]>
Date: Thu, 09 Jun 2011 11:47:32 -0500

Attached is a patch which:

- adds optional SEMICOLONS and KEEP-EMPTY to `url-build-query-string' to
  separate parameters with semicolons and to keep empty values

- fixes `url-parse-query-string' to understand the '=' is not required
  in a parameter

- adds ERT tests for `url-build-query-string' and
  `url-parse-query-string'

Ian, if you could look it over, I'd appreciate it.  In particular if you
think KEEP-EMPTY should be the default or if you see any other problems...

Thanks
Ted

=== modified file 'lisp/url/url-util.el'
--- lisp/url/url-util.el	2011-01-25 04:08:28 +0000
+++ lisp/url/url-util.el	2011-06-09 16:43:39 +0000
@@ -26,7 +26,8 @@
 
 (require 'url-parse)
 (require 'url-vars)
-(eval-when-compile (require 'cl))
+(eval-when-compile (require 'cl)
+                   (require 'ert))
 (autoload 'timezone-parse-date "timezone")
 (autoload 'timezone-make-date-arpa-standard "timezone")
 (autoload 'mail-header-extract "mailheader")
@@ -263,24 +264,65 @@
 ;;;###autoload
 (defun url-parse-query-string (query &optional downcase allow-newlines)
   (let (retval pairs cur key val)
-    (setq pairs (split-string query "&"))
+    (setq pairs (split-string query "[;&]"))
     (while pairs
       (setq cur (car pairs)
 	    pairs (cdr pairs))
-      (if (not (string-match "=" cur))
-	  nil				; Grace
-	(setq key (url-unhex-string (substring cur 0 (match-beginning 0))
-				    allow-newlines))
-	(setq val (url-unhex-string (substring cur (match-end 0) nil)
-				    allow-newlines))
-	(if downcase
-	    (setq key (downcase key)))
-	(setq cur (assoc key retval))
-	(if cur
-	    (setcdr cur (cons val (cdr cur)))
-	  (setq retval (cons (list key val) retval)))))
+      (unless (string-match "=" cur)
+        (setq cur (concat cur "=")))
+
+      (when (string-match "=" cur)
+        (setq key (url-unhex-string (substring cur 0 (match-beginning 0))
+                                    allow-newlines))
+        (setq val (url-unhex-string (substring cur (match-end 0) nil)
+                                    allow-newlines))
+        (if downcase
+            (setq key (downcase key)))
+        (setq cur (assoc key retval))
+        (if cur
+            (setcdr cur (cons val (cdr cur)))
+          (setq retval (cons (list key val) retval)))))
     retval))
 
+;;;###autoload
+(defun url-build-query-string (query &optional semicolons keep-empty)
+  "Build a query-string.
+
+Given a QUERY in the form:
+'((key1 val1)
+  (key2 val2)
+  (key3 val1 val2)
+  (key4)
+  (key5 ""))
+
+\(This is the same format as produced by `url-parse-query-string')
+
+This will return a string
+\"key1=val1&key2=val2&key3=val1&key3=val2&key4&key5\". Keys may
+be strings or symbols; if they are symbols, the symbol name will
+be used.
+
+When SEMICOLONS is given, the separator will be \";\".
+
+When KEEP-EMPTY is given, empty values will show as \"key=\"
+instead of just \"key\" as in the example above."
+  (mapconcat
+   (lambda (key-vals)
+     (let ((escaped
+            (mapcar (lambda (sym)
+                      (url-hexify-string (format "%s" sym))) key-vals)))
+       (mapconcat (lambda (val)
+                    (let ((vprint (format "%s" val))
+                          (eprint (format "%s" (car escaped))))
+                      (concat eprint
+                              (if (or keep-empty
+                                      (and val (not (zerop (length vprint)))))
+                                  "="
+                                "")
+                              vprint)))
+                  (or (cdr escaped) '("")) (if semicolons ";" "&"))))
+   query (if semicolons ";" "&")))
+
 (defun url-unhex (x)
   (if (> x ?9)
       (if (>= x ?a)
@@ -529,6 +571,27 @@
          (error "Danger: `%s' is a symbolic link" file))
      (set-file-modes file #o0600))))
 
+(ert-deftest url-build-and-parse-query-string ()
+  (let ((tests
+         '(("key1=val1&key2=val2&key3=val1&key3=val2&key4&key5"
+            ((key1 val1) (key2 "val2") (key3 val1 val2) (key4) (key5 "")))
+           ("key1=val1;key2=val2;key3=val1;key3=val2;key4;key5"
+            ((key1 "val1") (key2 val2) (key3 val1 val2) ("key4") (key5 "")) t)
+           ("key1=val1;key2=val2;key3=val1;key3=val2;key4=;key5="
+            ((key1 val1) (key2 val2) ("key3" val1 val2) (key4) (key5 "")) t t)))
+        test)
+    (while tests
+      (setq test (car tests)
+            tests (cdr tests))
+      (should (equal (apply 'url-build-query-string (cdr test)) (car test)))))
+  (should (equal (url-parse-query-string
+                  "key1=val1&key2=val2&key3=val1&key3=val2&key4=&key5")
+                 '(("key5" "")
+                   ("key4" "")
+                   ("key3" "val2" "val1")
+                   ("key2" "val2")
+                   ("key1" "val1")))))
+
 (provide 'url-util)
 
 ;;; url-util.el ends here