REBOL [
    Title: "REBOL Web Form Encoder/Decoder"
    Author: "Christopher Ross-Gill"
    Date: 30-Jan-2013
    Version: 0.1.7
    Purpose: "Convert a Rebol block to a URL-Encoded Web Form string"
    Comment: "Conforms to application/x-www-form-urlencoded"
    Home: http://www.ross-gill.com/page/Web_Forms_and_REBOL
    File: %altwebform.r
    Type: 'module
    Exports: [url-decode url-encode load-webform to-webform]
    Example: [
        "a=3&aa.a=1&b.c=1&b.c=2"
        [a "3" aa [a "1"] b [c ["1" "2"]]]
    ]
]

url-decode: use [deplus sp decrlf][
    deplus: func [text][
        parse/all text [
            some [to sp text: (text: change text #" ") :text] to end
        ]
        head text
    ]

    decrlf: func [text][
        parse/all text [
            some [to crlf text: (text: change/part text #"^/" 2) :text] to end
        ]
        head text
    ]

    func [text [any-string! none!] /wiki][
        sp: either wiki [#"_"][#"+"]
        decrlf dehex deplus form any [text ""]
    ]
]

load-webform: use [result path string pair term as-path][
    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: union aa charset ["-" #"0" - #"9"]
        wd: [aa 0 40 an] ; one alpha, any alpha/digit/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 [
        [catch] "Loads Data from a URL-Encoded Web Form string"
        webform [string! none!]
    ][
        webform: any [webform ""]
        result: copy []

        either parse/all webform [opt [#"&" | #"?"] any pair][
            new-line/all/skip result true 2
        ][
            make error! "Not a URL Encoded Web Form"
        ]
    ]
]

url-encode: use [ch sp encode][
    ch: charset ["!'*,-.~" #"0" - #"9" #"A" - #"Z" #"a" - #"z"]
    encode: func [text][insert next text enbase/base form text/1 16 change text "%"]

    func [text [any-string!] /wiki][
        sp: either wiki [#"_"][#"+"]

        parse/all copy text [
            copy text any [
                  text: some ch | #" " (change text sp)
                | #"_" (all [wiki encode text]) | skip (encode text)
            ]
        ]
        text
    ]
]

to-webform: use [
    webform form-key emit
    here path reference value block array object
][
    path: []
    form-key: does [
        remove head foreach key path [insert "" reduce ["." key]]
    ]

    emit: func [data][
        repend webform ["&" form-key "=" url-encode data]
    ]

    reference: [
        here: get-word! (change/only here attempt [get/any here/1]) :here
    ]

    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!] (emit form here/1)
    ]

    array: [any value end]

    object: [
        any [
            here: [word! | set-word!] (insert path to-word here/1)
            any reference [value | block] (remove path)
        ] end
    ]

    block: [
        here: [
              any-block! (change/only here copy here/1)
            | object! (change/only here third here/1)
        ] :here into [object | mk: array]
    ]

    func [
        "Serializes block data as URL-Encoded Web Form string"
        data [block! object!] /prefix
    ][
        clear path
        webform: copy ""
        data: either object? data [third data][copy data]
        if parse copy data object [
            either all [prefix not tail? next webform][back change webform "?"][remove webform]
        ]
    ]
]