661 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
		
		
			
		
	
	
			661 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
|  | /* | ||
|  | 文件名:Json_Class.nut | ||
|  | 路径:Base/_Tool/Json_Class.nut | ||
|  | 创建日期:2024-09-27	23:54 | ||
|  | 文件用途:Json类 | ||
|  | */ | ||
|  | 
 | ||
|  | class JSONParser { | ||
|  | 
 | ||
|  | 
 | ||
|  |     static version = "1.0.1"; | ||
|  | 
 | ||
|  | 
 | ||
|  |     state = ""; | ||
|  |     stack = null; | ||
|  |     container = null; | ||
|  |     key = ""; | ||
|  |     value = ""; | ||
|  |     converter = null; | ||
|  | 
 | ||
|  |     constructor() { | ||
|  |         stack = []; | ||
|  |         container = {}; | ||
|  |     } | ||
|  | 
 | ||
|  |     function parse(str, ...) { | ||
|  |         if (vargc > 0) converter = vargc[0]; | ||
|  | 
 | ||
|  | 
 | ||
|  |         // actions for string tokens | ||
|  |         local string = { | ||
|  |             go = function() { | ||
|  |                 state = "ok"; | ||
|  |             }.bindenv(this), | ||
|  |             firstokey = function() { | ||
|  |                 key = value; | ||
|  |                 state = "colon"; | ||
|  |             }.bindenv(this), | ||
|  |             okey = function() { | ||
|  |                 key = value; | ||
|  |                 state = "colon"; | ||
|  |             }.bindenv(this), | ||
|  |             ovalue = function() { | ||
|  |                 value = this._convert(value, "string", converter); | ||
|  |                 state = "ocomma"; | ||
|  |             }.bindenv(this), | ||
|  |             firstavalue = function() { | ||
|  |                 value = this._convert(value, "string", converter); | ||
|  |                 state = "acomma"; | ||
|  |             }.bindenv(this), | ||
|  |             avalue = function() { | ||
|  |                 value = value; | ||
|  |                 this._convert(value, "string", converter); | ||
|  |                 state = "acomma"; | ||
|  |             }.bindenv(this) | ||
|  |         }; | ||
|  | 
 | ||
|  |         // the actions for number tokens | ||
|  |         local number = { | ||
|  |             go = function() { | ||
|  |                 state = "ok"; | ||
|  |             }.bindenv(this), | ||
|  |             ovalue = function() { | ||
|  |                 value = this._convert(value, "number", converter); | ||
|  |                 state = "ocomma"; | ||
|  |             }.bindenv(this), | ||
|  |             firstavalue = function() { | ||
|  |                 value = this._convert(value, "number", converter); | ||
|  |                 state = "acomma"; | ||
|  |             }.bindenv(this), | ||
|  |             avalue = function() { | ||
|  |                 value = this._convert(value, "number", converter); | ||
|  |                 state = "acomma"; | ||
|  |             }.bindenv(this) | ||
|  |         }; | ||
|  | 
 | ||
|  | 
 | ||
|  |         // action table | ||
|  |         // describes where the state machine will go from each given state | ||
|  |         local action = {}; | ||
|  | 
 | ||
|  | 
 | ||
|  |         action["{"] <- { | ||
|  |                 go = function() { | ||
|  |                     stack.push({ | ||
|  |                         state = "ok" | ||
|  |                     }); | ||
|  |                     container = {}; | ||
|  |                     state = "firstokey"; | ||
|  |                 }.bindenv(this), | ||
|  |                 ovalue = function() { | ||
|  |                     stack.push({ | ||
|  |                         container = container, | ||
|  |                         state = "ocomma", | ||
|  |                         key = key | ||
|  |                     }); | ||
|  |                     container = {}; | ||
|  |                     state = "firstokey"; | ||
|  |                 }.bindenv(this), | ||
|  |                 firstavalue = function() { | ||
|  |                     stack.push({ | ||
|  |                         container = container, | ||
|  |                         state = "acomma" | ||
|  |                     }); | ||
|  |                     container = {}; | ||
|  |                     state = "firstokey"; | ||
|  |                 }.bindenv(this), | ||
|  |                 avalue = function() { | ||
|  |                     stack.push({ | ||
|  |                         container = container, | ||
|  |                         state = "acomma" | ||
|  |                     }); | ||
|  |                     container = {}; | ||
|  |                     state = "firstokey"; | ||
|  |                 }.bindenv(this) | ||
|  |             }, | ||
|  | 
 | ||
|  |             action["}"] <- { | ||
|  |                 firstokey = function() { | ||
|  |                     local pop = stack.pop(); | ||
|  |                     value = container; | ||
|  |                     container = ("container" in pop) ? pop.container : null; | ||
|  |                     key = ("key" in pop) ? pop.key : null; | ||
|  |                     state = pop.state; | ||
|  |                 }.bindenv(this), | ||
|  |                 ocomma = function() { | ||
|  |                     local pop = stack.pop(); | ||
|  |                     container[key] <- value; | ||
|  |                     value = container; | ||
|  |                     container = ("container" in pop) ? pop.container : null; | ||
|  |                     key = ("key" in pop) ? pop.key : null; | ||
|  |                     state = pop.state; | ||
|  |                 }.bindenv(this) | ||
|  |             }, | ||
|  | 
 | ||
|  |             action["["] <- { | ||
|  |                 go = function() { | ||
|  |                     stack.push({ | ||
|  |                         state = "ok" | ||
|  |                     }); | ||
|  |                     container = []; | ||
|  |                     state = "firstavalue"; | ||
|  |                 }.bindenv(this), | ||
|  |                 ovalue = function() { | ||
|  |                     stack.push({ | ||
|  |                         container = container, | ||
|  |                         state = "ocomma", | ||
|  |                         key = key | ||
|  |                     }); | ||
|  |                     container = []; | ||
|  |                     state = "firstavalue"; | ||
|  |                 }.bindenv(this), | ||
|  |                 firstavalue = function() { | ||
|  |                     stack.push({ | ||
|  |                         container = container, | ||
|  |                         state = "acomma" | ||
|  |                     }); | ||
|  |                     container = []; | ||
|  |                     state = "firstavalue"; | ||
|  |                 }.bindenv(this), | ||
|  |                 avalue = function() { | ||
|  |                     stack.push({ | ||
|  |                         container = container, | ||
|  |                         state = "acomma" | ||
|  |                     }); | ||
|  |                     container = []; | ||
|  |                     state = "firstavalue"; | ||
|  |                 }.bindenv(this) | ||
|  |             }, | ||
|  | 
 | ||
|  |             action["]"] <- { | ||
|  |                 firstavalue = function() { | ||
|  |                     local pop = stack.pop(); | ||
|  |                     value = container; | ||
|  |                     container = ("container" in pop) ? pop.container : null; | ||
|  |                     key = ("key" in pop) ? pop.key : null; | ||
|  |                     state = pop.state; | ||
|  |                 }.bindenv(this), | ||
|  |                 acomma = function() { | ||
|  |                     local pop = stack.pop(); | ||
|  |                     container.push(value); | ||
|  |                     value = container; | ||
|  |                     container = ("container" in pop) ? pop.container : null; | ||
|  |                     key = ("key" in pop) ? pop.key : null; | ||
|  |                     state = pop.state; | ||
|  |                 }.bindenv(this) | ||
|  |             }, | ||
|  | 
 | ||
|  |             action[":"] <- { | ||
|  |                 colon = function() { | ||
|  |                     // Check if the key already exists | ||
|  |                     // NOTE previous code used 'if (key in container)...' | ||
|  |                     //      but this finds table ('container') member methods too | ||
|  |                     local err = false; | ||
|  |                     foreach(akey, avalue in container) { | ||
|  |                         if (akey == key) err = true; | ||
|  |                         break | ||
|  |                     } | ||
|  |                     if (err) throw "Duplicate key \"" + key + "\""; | ||
|  |                     state = "ovalue"; | ||
|  |                 }.bindenv(this) | ||
|  |             }, | ||
|  | 
 | ||
|  |             action[","] <- { | ||
|  |                 ocomma = function() { | ||
|  |                     container[key] <- value; | ||
|  |                     state = "okey"; | ||
|  |                 }.bindenv(this), | ||
|  |                 acomma = function() { | ||
|  |                     container.push(value); | ||
|  |                     state = "avalue"; | ||
|  |                 }.bindenv(this) | ||
|  |             }, | ||
|  | 
 | ||
|  |             action["true"] <- { | ||
|  |                 go = function() { | ||
|  |                     value = true; | ||
|  |                     state = "ok"; | ||
|  |                 }.bindenv(this), | ||
|  |                 ovalue = function() { | ||
|  |                     value = true; | ||
|  |                     state = "ocomma"; | ||
|  |                 }.bindenv(this), | ||
|  |                 firstavalue = function() { | ||
|  |                     value = true; | ||
|  |                     state = "acomma"; | ||
|  |                 }.bindenv(this), | ||
|  |                 avalue = function() { | ||
|  |                     value = true; | ||
|  |                     state = "acomma"; | ||
|  |                 }.bindenv(this) | ||
|  |             }, | ||
|  | 
 | ||
|  |             action["false"] <- { | ||
|  |                 go = function() { | ||
|  |                     value = false; | ||
|  |                     state = "ok"; | ||
|  |                 }.bindenv(this), | ||
|  |                 ovalue = function() { | ||
|  |                     value = false; | ||
|  |                     state = "ocomma"; | ||
|  |                 }.bindenv(this), | ||
|  |                 firstavalue = function() { | ||
|  |                     value = false; | ||
|  |                     state = "acomma"; | ||
|  |                 }.bindenv(this), | ||
|  |                 avalue = function() { | ||
|  |                     value = false; | ||
|  |                     state = "acomma"; | ||
|  |                 }.bindenv(this) | ||
|  |             }, | ||
|  | 
 | ||
|  |             action["null"] <- { | ||
|  |                 go = function() { | ||
|  |                     value = null; | ||
|  |                     state = "ok"; | ||
|  |                 }.bindenv(this), | ||
|  |                 ovalue = function() { | ||
|  |                     value = null; | ||
|  |                     state = "ocomma"; | ||
|  |                 }.bindenv(this), | ||
|  |                 firstavalue = function() { | ||
|  |                     value = null; | ||
|  |                     state = "acomma"; | ||
|  |                 }.bindenv(this), | ||
|  |                 avalue = function() { | ||
|  |                     value = null; | ||
|  |                     state = "acomma"; | ||
|  |                 }.bindenv(this) | ||
|  |             } | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  |         // | ||
|  |         state = "go"; | ||
|  |         stack = []; | ||
|  | 
 | ||
|  |         // current tokenizeing position | ||
|  |         local start = 0; | ||
|  | 
 | ||
|  |         try { | ||
|  | 
 | ||
|  |             local result, token, tokenizer = _JSONTokenizer(); | ||
|  | 
 | ||
|  |             while (token = tokenizer.nextToken(str, start)) { | ||
|  | 
 | ||
|  |                 if ("ptfn" == token.type) { | ||
|  |                     // punctuation/true/false/null | ||
|  |                     action[token.value][state](); | ||
|  |                 } else if ("number" == token.type) { | ||
|  |                     // number | ||
|  |                     value = token.value; | ||
|  |                     number[state](); | ||
|  |                 } else if ("string" == token.type) { | ||
|  |                     // string | ||
|  |                     value = tokenizer.unescape(token.value); | ||
|  |                     string[state](); | ||
|  |                 } | ||
|  | 
 | ||
|  |                 start += token.length; | ||
|  |             } | ||
|  | 
 | ||
|  |         } catch (e) { | ||
|  |             state = e; | ||
|  |         } | ||
|  | 
 | ||
|  | 
 | ||
|  |         if (state != "ok" || regexp("[^\\s]").capture(str, start)) { | ||
|  |             local near = str.slice(start, GetMin(str.len(), start + 10)); | ||
|  |             throw "JSON Syntax Error near `" + near + "`"; | ||
|  |         } | ||
|  | 
 | ||
|  | 
 | ||
|  |         return value; | ||
|  |     } | ||
|  | 
 | ||
|  |     function GetMin(a, b) { | ||
|  |         return a< b ? a : b; | ||
|  |     } | ||
|  | 
 | ||
|  | 
 | ||
|  |     function _convert(value, type, converter) { | ||
|  |         if ("function" == typeof converter) { | ||
|  | 
 | ||
|  |             // # of params for converter function | ||
|  | 
 | ||
|  |             local parametercCount = 2; | ||
|  | 
 | ||
|  |             // .getinfos() is missing on ei platform | ||
|  |             if ("getinfos" in converter) { | ||
|  |                 parametercCount = converter.getinfos().parameters.len() - | ||
|  |                     1; | ||
|  |             } | ||
|  | 
 | ||
|  |             if (parametercCount == 1) { | ||
|  |                 return converter(value); | ||
|  |             } else if (parametercCount == 2) { | ||
|  |                 return converter(value, type); | ||
|  |             } else { | ||
|  |                 throw "Error: converter function must take 1 or 2 parameters" | ||
|  |             } | ||
|  | 
 | ||
|  |         } else if ("number" == type) { | ||
|  |             local Ret = (value.find(".") == null && value.find("e") == null && value.find("E") == null) ? value.tointeger() : value.tofloat(); | ||
|  |             return Ret; | ||
|  |         } else { | ||
|  |             return value; | ||
|  |         } | ||
|  | 
 | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | class _JSONTokenizer { | ||
|  | 
 | ||
|  |     _ptfnRegex = null; | ||
|  |     _numberRegex = null; | ||
|  |     _stringRegex = null; | ||
|  |     _ltrimRegex = null; | ||
|  |     _unescapeRegex = null; | ||
|  | 
 | ||
|  |     constructor() { | ||
|  |         // punctuation/true/false/null | ||
|  |         this._ptfnRegex = regexp("^(?:\\,|\\:|\\[|\\]|\\{|\\}|true|false|null)"); | ||
|  | 
 | ||
|  |         // numbers | ||
|  |         this._numberRegex = regexp("^(?:\\-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?)"); | ||
|  | 
 | ||
|  |         // strings | ||
|  |         this._stringRegex = regexp("^(?:\\\"((?:[^\\r\\n\\t\\\\\\\"]|\\\\(?:[\"\\\\\\/trnfb]|u[0-9a-fA-F]{4}))*)\\\")"); | ||
|  | 
 | ||
|  |         // ltrim pattern | ||
|  |         this._ltrimRegex = regexp("^[\\s\\t\\n\\r]*"); | ||
|  | 
 | ||
|  |         // string unescaper tokenizer pattern | ||
|  |         this._unescapeRegex = regexp("\\\\(?:(?:u\\d{4})|[\\\"\\\\/bfnrt])"); | ||
|  |     } | ||
|  | 
 | ||
|  | 
 | ||
|  |     function nextToken(str, ...) { | ||
|  |         local start = 0; | ||
|  |         if (vargc > 0) start = vargv[0]; | ||
|  |         local | ||
|  |         m, | ||
|  |         type, | ||
|  |         token, | ||
|  |         value, | ||
|  |         length, | ||
|  |         whitespaces; | ||
|  | 
 | ||
|  |         // count # of left-side whitespace chars | ||
|  |         whitespaces = this._leadingWhitespaces(str, start); | ||
|  |         start += whitespaces; | ||
|  | 
 | ||
|  |         if (m = this._ptfnRegex.capture(str, start)) { | ||
|  |             // punctuation/true/false/null | ||
|  |             value = str.slice(m[0].begin, m[0].end); | ||
|  |             type = "ptfn"; | ||
|  |         } else if (m = this._numberRegex.capture(str, start)) { | ||
|  |             // number | ||
|  |             value = str.slice(m[0].begin, m[0].end); | ||
|  |             type = "number"; | ||
|  |         } else if (m = this._stringRegex.capture(str, start)) { | ||
|  |             // string | ||
|  |             value = str.slice(m[1].begin, m[1].end); | ||
|  |             type = "string"; | ||
|  |         } else { | ||
|  |             return null; | ||
|  |         } | ||
|  | 
 | ||
|  |         token = { | ||
|  |             type = type, | ||
|  |             value = value, | ||
|  |             length = m[0].end - m[0].begin + whitespaces | ||
|  |         }; | ||
|  | 
 | ||
|  |         return token; | ||
|  |     } | ||
|  | 
 | ||
|  | 
 | ||
|  |     function _leadingWhitespaces(str, start) { | ||
|  |         local r = this._ltrimRegex.capture(str, start); | ||
|  | 
 | ||
|  |         if (r) { | ||
|  |             return r[0].end - r[0].begin; | ||
|  |         } else { | ||
|  |             return 0; | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     // unesacape() replacements table | ||
|  |     _unescapeReplacements = { | ||
|  |         b = "\b", | ||
|  |         f = "\f", | ||
|  |         n = "\n", | ||
|  |         r = "\r", | ||
|  |         t = "\t" | ||
|  |     }; | ||
|  | 
 | ||
|  |     function unescape(str) { | ||
|  | 
 | ||
|  |         local start = 0; | ||
|  |         local res = ""; | ||
|  | 
 | ||
|  |         while (start< str.len()) { | ||
|  |             local m = this._unescapeRegex.capture(str, start); | ||
|  | 
 | ||
|  |             if (m) { | ||
|  |                 local token = str.slice(m[0].begin, m[0].end); | ||
|  | 
 | ||
|  |                 // append chars before match | ||
|  |                 local pre = str.slice(start, m[0].begin); | ||
|  |                 res += pre; | ||
|  | 
 | ||
|  |                 if (token.len() == 6) { | ||
|  |                     res += token; | ||
|  |                 } else { | ||
|  |                     local char = token.slice(1); | ||
|  | 
 | ||
|  |                     if (char in this._unescapeReplacements) { | ||
|  |                         res += this._unescapeReplacements[char]; | ||
|  |                     } else { | ||
|  |                         res += char; | ||
|  |                     } | ||
|  |                 } | ||
|  | 
 | ||
|  |             } else { | ||
|  |                 res += str.slice(start); | ||
|  |                 break; | ||
|  |             } | ||
|  | 
 | ||
|  |             start = m[0].end; | ||
|  |         } | ||
|  | 
 | ||
|  |         return res; | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | class JSONEncoder { | ||
|  | 
 | ||
|  |     static VERSION = "2.0.0"; | ||
|  | 
 | ||
|  |     // max structure depth | ||
|  |     // anything above probably has a cyclic ref | ||
|  |     static _maxDepth = 32; | ||
|  | 
 | ||
|  | 
 | ||
|  |     function encode(value) { | ||
|  |         return this._encode(value); | ||
|  |     } | ||
|  | 
 | ||
|  | 
 | ||
|  |     function _encode(val, ...) { | ||
|  |         local depth = 0; | ||
|  |         if (vargc > 0) depth = vargv[0]; | ||
|  |         // detect cyclic reference | ||
|  |         if (depth > this._maxDepth) { | ||
|  |             throw "Possible cyclic reference"; | ||
|  |         } | ||
|  | 
 | ||
|  |         local | ||
|  |         r = "", | ||
|  |             s = "", | ||
|  |             i = 0; | ||
|  | 
 | ||
|  |         switch (typeof val) { | ||
|  | 
 | ||
|  |             case "table": | ||
|  |             case "class": | ||
|  |                 s = ""; | ||
|  | 
 | ||
|  |                 // serialize properties, but not functions | ||
|  |                 foreach(k, v in val) { | ||
|  |                     if (typeof v != "function") { | ||
|  |                         s += ",\"" + k + "\":" + this._encode(v, depth + 1); | ||
|  |                     } | ||
|  |                 } | ||
|  | 
 | ||
|  |                 s = s.len() > 0 ? s.slice(1) : s; | ||
|  |                 r += "{" + s + "}"; | ||
|  |                 break; | ||
|  | 
 | ||
|  |             case "array": | ||
|  |                 s = ""; | ||
|  | 
 | ||
|  |                 for (i = 0; i< val.len(); i++) { | ||
|  |                     s += "," + this._encode(val[i], depth + 1); | ||
|  |                 } | ||
|  | 
 | ||
|  |                 s = (i > 0) ? s.slice(1) : s; | ||
|  |                 r += "[" + s + "]"; | ||
|  |                 break; | ||
|  | 
 | ||
|  |             case "integer": | ||
|  |             case "float": | ||
|  |             case "bool": | ||
|  |                 r += val; | ||
|  |                 break; | ||
|  | 
 | ||
|  |             case "null": | ||
|  |                 r += "null"; | ||
|  |                 break; | ||
|  | 
 | ||
|  |             case "instance": | ||
|  | 
 | ||
|  |                 if ("_serializeRaw" in val && typeof val._serializeRaw == "function") { | ||
|  | 
 | ||
|  |                     // include value produced by _serializeRaw() | ||
|  |                     r += val._serializeRaw().tostring(); | ||
|  | 
 | ||
|  |                 } else if ("_serialize" in val && typeof val._serialize == "function") { | ||
|  | 
 | ||
|  |                     // serialize instances by calling _serialize method | ||
|  |                     r += this._encode(val._serialize(), depth + 1); | ||
|  | 
 | ||
|  |                 } else { | ||
|  | 
 | ||
|  |                     s = ""; | ||
|  | 
 | ||
|  |                     try { | ||
|  | 
 | ||
|  |                         // iterate through instances which implement _nexti meta-method | ||
|  |                         foreach(k, v in val) { | ||
|  |                             s += ",\"" + k + "\":" + this._encode(v, depth + 1); | ||
|  |                         } | ||
|  | 
 | ||
|  |                     } catch (e) { | ||
|  | 
 | ||
|  |                         // iterate through instances w/o _nexti | ||
|  |                         // serialize properties, but not functions | ||
|  |                         foreach(k, v in val.getclass()) { | ||
|  |                             if (typeof v != "function") { | ||
|  |                                 s += ",\"" + k + "\":" + this._encode(val[k], depth + 1); | ||
|  |                             } | ||
|  |                         } | ||
|  | 
 | ||
|  |                     } | ||
|  | 
 | ||
|  |                     s = s.len() > 0 ? s.slice(1) : s; | ||
|  |                     r += "{" + s + "}"; | ||
|  |                 } | ||
|  | 
 | ||
|  |                 break; | ||
|  | 
 | ||
|  |             case "blob": | ||
|  |                 // This is a workaround for a known bug: | ||
|  |                 // on device side Blob.tostring() returns null | ||
|  |                 // (instaead of an empty string) | ||
|  |                 r += "\"" + (val.len() ? this._escape(val.tostring()) : "") + "\""; | ||
|  |                 break; | ||
|  | 
 | ||
|  |                 // strings and all other | ||
|  |             default: | ||
|  |                 r += "\"" + this._escape(val.tostring()) + "\""; | ||
|  |                 break; | ||
|  |         } | ||
|  | 
 | ||
|  |         return r; | ||
|  |     } | ||
|  | 
 | ||
|  |     function _escape(str) { | ||
|  |         local res = ""; | ||
|  | 
 | ||
|  |         for (local i = 0; i< str.len(); i++) { | ||
|  | 
 | ||
|  |             local ch1 = (str[i] & 0xFF); | ||
|  | 
 | ||
|  |             if ((ch1 & 0x80) == 0x00) { | ||
|  |                 // 7-bit Ascii | ||
|  | 
 | ||
|  |                 ch1 = format("%c", ch1); | ||
|  | 
 | ||
|  |                 if (ch1 == "\"") { | ||
|  |                     res += "\\\""; | ||
|  |                 } else if (ch1 == "\\") { | ||
|  |                     res += "\\\\"; | ||
|  |                 } else if (ch1 == "/") { | ||
|  |                     res += "\\/"; | ||
|  |                 } else if (ch1 == "\b") { | ||
|  |                     res += "\\b"; | ||
|  |                 } else if (ch1 == "\f") { | ||
|  |                     res += "\\f"; | ||
|  |                 } else if (ch1 == "\n") { | ||
|  |                     res += "\\n"; | ||
|  |                 } else if (ch1 == "\r") { | ||
|  |                     res += "\\r"; | ||
|  |                 } else if (ch1 == "\t") { | ||
|  |                     res += "\\t"; | ||
|  |                 } else if (ch1 == "\0") { | ||
|  |                     res += "\\u0000"; | ||
|  |                 } else { | ||
|  |                     res += ch1; | ||
|  |                 } | ||
|  | 
 | ||
|  |             } else { | ||
|  | 
 | ||
|  |                 if ((ch1 & 0xE0) == 0xC0) { | ||
|  |                     // 110xxxxx = 2-byte unicode | ||
|  |                     local ch2 = (str[++i] & 0xFF); | ||
|  |                     res += format("%c%c", ch1, ch2); | ||
|  |                 } else if ((ch1 & 0xF0) == 0xE0) { | ||
|  |                     // 1110xxxx = 3-byte unicode | ||
|  |                     local ch2 = (str[++i] & 0xFF); | ||
|  |                     local ch3 = (str[++i] & 0xFF); | ||
|  |                     res += format("%c%c%c", ch1, ch2, ch3); | ||
|  |                 } else if ((ch1 & 0xF8) == 0xF0) { | ||
|  |                     // 11110xxx = 4 byte unicode | ||
|  |                     local ch2 = (str[++i] & 0xFF); | ||
|  |                     local ch3 = (str[++i] & 0xFF); | ||
|  |                     local ch4 = (str[++i] & 0xFF); | ||
|  |                     res += format("%c%c%c%c", ch1, ch2, ch3, ch4); | ||
|  |                 } | ||
|  | 
 | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         return res; | ||
|  |     } | ||
|  | } |