Rebol [
Title: "Web Form Encoder/Decoder for Rebol 3"
Author: "Christopher Ross-Gill"
Date: 6-Sep-2015
Home: http://www.ross-gill.com/page/Web_Forms_and_REBOL
File: %altwebform.r
Version: 0.10.1
Purpose: "Convert a Rebol block to a URL-Encoded Web Form string"
Rights: http://opensource.org/licenses/Apache-2.0
Type: 'module
Name: 'rgchris.altwebform
Exports: [url-decode url-encode load-webform to-webform]
History: [
06-Sep-2015 0.10.1 "Add Ruby-style paths to encoding"
06-Jul-2013 0.9.5 "Fix encoding/decoding of _ character"
01-Mar-2013 0.9.4 "Detach URL-DECODE and URL-ENCODE"
27-Feb-2013 0.9.2 "Correct encoding of UTF-8 values"
18-Nov-2009 0.1.0 "Original Version"
]
Example: [
"a=3&aa.a=1&b.c=1&b.c=2"
[a "3" aa [a "1"] b [c ["1" "2"]]]
]
]
url-decode: use [as-is hex space][
as-is: charset ["-.~" #"0" - #"9" #"A" - #"Z" #"a" - #"z"]
hex: charset [#"0" - #"9" #"a" - #"f" #"A" - #"F"]
func [
"Decode percent-encoded text from URLs and Web Forms"
text [any-string!] "Text to Decode"
/wiki "Assumes `_` character is used to represent spaces"
][
space: either wiki [#"_"][#"+"]
either parse text: to binary! text [
copy text any [
some as-is | remove space insert " "
| [#"_" | #"+" | #"." | #","]
| change "%0D%0A" "^/" ; de-crlf
| remove ["%" copy text 2 hex] (text: debase/base text 16) insert text
]
][to string! text][none]
]
]
url-encode: use [as-is space percent-encode][
as-is: charset ["-." #"0" - #"9" #"A" - #"Z" #"-" #"a" - #"z" #"~"]
percent-encode: func [text][
insert next text enbase/base copy/part text 1 16 change text "%"
]
func [
"Encode text using percent-encoding for URLs and Web Forms"
text [any-string!] "Text to encode"
/wiki "Use `_` character to represent spaces"
][
space: either wiki [#"_"][#"+"]
either parse text: to binary! text [
copy text any [
text: some as-is | end | change " " space
| [#"_" | #"."] (either wiki [percent-encode text][text])
| skip (percent-encode text)
]
][to string! text][""]
]
]
load-webform: use [result path string pair as-path term][
result: copy []
as-path: func [name [string!]][
to path! to block! replace/all name #"." #" "
]
path: use [aa an wd][
aa: charset [#"a" - #"z" #"A" - #"Z" #"_"]
an: charset [#"-" #"0" - #"9" #"a" - #"z" #"A" - #"Z" #"_"]
wd: [aa 0 40 an] ; one alpha, any alpha/numeric/dash/underscore
[wd 0 6 [#"." wd]]
]
string: use [ch hx][
ch: charset ["-._~" #"0" - #"9" #"A" - #"Z" #"a" - #"z"]
hx: charset [#"0" - #"9" #"a" - #"f" #"A" - #"F"]
[any [ch | #"+" | #"%" 2 hx]] ; any [unreserved | percent-encoded]
]
term: [#"&" | end]
pair: use [name value tree][
[
copy name path [
#"=" copy value string term | term (value: none)
] (
tree: :result
name: as-path name
value: url-decode value
until [
tree: any [
find/tail tree name/1
insert tail tree name/1
]
name: next name
switch type?/word tree/1 [
none! [unless tail? name [insert/only tree tree: copy []]]
string! [change/only tree tree: reduce [tree/1]]
block! [tree: tree/1]
]
if tail? name [append tree value]
]
)
]
]
func [
"Loads data from a URL-Encoded Web Form string"
webform [string! none!] "Form to decode"
][
webform: any [webform ""]
result: copy []
either parse webform [opt [#"&" | #"?"] any pair][result][
do make error! "Not a URL Encoded Web Form"
]
]
]
to-webform: use [
webform form-key emit ruby-style?
here path reference value block array object
][
path: []
form-key: does [
url-encode rejoin collect [
keep first path
foreach key next path [
keep reduce either ruby-style? [["[" key "]"]][["." key]]
]
]
]
emit: func [data][
repend webform ["&" form-key "=" url-encode data]
]
reference: [
some [
here: get-word!
(change/only here attempt [get/any here/1])
| skip
]
]
value: [
here: number! (emit form here/1)
| [logic! | 'true | 'false] (emit form here/1)
| [none! | 'none]
| date! (replace form date "/" "T")
| [any-string! | tuple! | money! | time! | pair! | issue!] (emit form here/1)
]
array: [and opt reference any value end]
object: [
and opt reference any [
here: [word! | set-word!] (
append path to string! to word! here/1
)
[value | block] (remove back tail path)
] end
]
block: [
here: and [
any-block! (change/only here copy here/1)
| object! (change/only here body-of here/1)
] into [object | array]
]
func [
"Serializes block data as URL-Encoded Web Form string"
data [block! object!] "Block or object to encode"
/prefix "Includes the `?` character used to precede URL query strings"
/ruby-style "Encodes structured keys using `a[b][c]` notation"
][
clear path
webform: copy ""
data: either object? data [body-of data][copy data]
ruby-style?: :ruby-style
if parse copy data object [
either all [
prefix not tail? next webform
][
back change webform "?"
][
remove webform
]
]
]
]