89 lines
2.5 KiB
Lua
89 lines
2.5 KiB
Lua
|
--[[
|
||
|
Licensed according to the included 'LICENSE' document
|
||
|
Author: Thomas Harning Jr <harningt@gmail.com>
|
||
|
]]
|
||
|
local string_char = require("string").char
|
||
|
local pairs = pairs
|
||
|
|
||
|
local jsonutil = require("json.util")
|
||
|
local util_merge = jsonutil.merge
|
||
|
|
||
|
local _ENV = nil
|
||
|
|
||
|
local normalEncodingMap = {
|
||
|
['"'] = '\\"',
|
||
|
['\\'] = '\\\\',
|
||
|
['/'] = '\\/',
|
||
|
['\b'] = '\\b',
|
||
|
['\f'] = '\\f',
|
||
|
['\n'] = '\\n',
|
||
|
['\r'] = '\\r',
|
||
|
['\t'] = '\\t',
|
||
|
['\v'] = '\\v' -- not in official spec, on report, removing
|
||
|
}
|
||
|
|
||
|
local xEncodingMap = {}
|
||
|
for char, encoded in pairs(normalEncodingMap) do
|
||
|
xEncodingMap[char] = encoded
|
||
|
end
|
||
|
|
||
|
-- Pre-encode the control characters to speed up encoding...
|
||
|
-- NOTE: UTF-8 may not work out right w/ JavaScript
|
||
|
-- JavaScript uses 2 bytes after a \u... yet UTF-8 is a
|
||
|
-- byte-stream encoding, not pairs of bytes (it does encode
|
||
|
-- some letters > 1 byte, but base case is 1)
|
||
|
for i = 0, 255 do
|
||
|
local c = string_char(i)
|
||
|
if c:match('[%z\1-\031\128-\255]') and not normalEncodingMap[c] then
|
||
|
-- WARN: UTF8 specializes values >= 0x80 as parts of sequences...
|
||
|
-- without \x encoding, do not allow encoding > 7F
|
||
|
normalEncodingMap[c] = ('\\u%.4X'):format(i)
|
||
|
xEncodingMap[c] = ('\\x%.2X'):format(i)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local defaultOptions = {
|
||
|
xEncode = false, -- Encode single-bytes as \xXX
|
||
|
processor = nil, -- Simple processor for the string prior to quoting
|
||
|
-- / is not required to be quoted but it helps with certain decoding
|
||
|
-- Required encoded characters, " \, and 00-1F (0 - 31)
|
||
|
encodeSet = '\\"/%z\1-\031',
|
||
|
encodeSetAppend = nil -- Chars to append to the default set
|
||
|
}
|
||
|
|
||
|
local modeOptions = {}
|
||
|
|
||
|
local function mergeOptions(options, mode)
|
||
|
jsonutil.doOptionMerge(options, false, 'strings', defaultOptions, mode and modeOptions[mode])
|
||
|
end
|
||
|
|
||
|
local function getEncoder(options)
|
||
|
options = options and util_merge({}, defaultOptions, options) or defaultOptions
|
||
|
local encodeSet = options.encodeSet
|
||
|
if options.encodeSetAppend then
|
||
|
encodeSet = encodeSet .. options.encodeSetAppend
|
||
|
end
|
||
|
local encodingMap = options.xEncode and xEncodingMap or normalEncodingMap
|
||
|
local encodeString
|
||
|
if options.processor then
|
||
|
local processor = options.processor
|
||
|
encodeString = function(s, state)
|
||
|
return '"' .. processor(s:gsub('[' .. encodeSet .. ']', encodingMap)) .. '"'
|
||
|
end
|
||
|
else
|
||
|
encodeString = function(s, state)
|
||
|
return '"' .. s:gsub('[' .. encodeSet .. ']', encodingMap) .. '"'
|
||
|
end
|
||
|
end
|
||
|
return {
|
||
|
string = encodeString
|
||
|
}
|
||
|
end
|
||
|
|
||
|
local strings = {
|
||
|
mergeOptions = mergeOptions,
|
||
|
getEncoder = getEncoder
|
||
|
}
|
||
|
|
||
|
return strings
|