Files
hakorune/apps/lib/json_native/utils/escape.hako

436 lines
14 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// EscapeUtils — JSON文字列エスケープ処理美しいモジュラー設計
// 責務: JSON文字列のエスケープ・アンエスケープ・妥当性検証
static box EscapeUtils {
// ===== JSON文字列エスケープ =====
// 文字列をJSON用にエスケープ
escape_string(s) {
local result = ""
local i = 0
loop(i < s.length()) {
local ch = s.substring(i, i + 1)
result = result + this.escape_char(ch)
i = i + 1
}
return result
}
// 1文字をエスケープ
escape_char(ch) {
if ch == "\"" {
return "\\\""
} else {
if ch == "\\" {
return "\\\\"
} else {
if ch == "/" {
return "\\/"
} else {
if ch == "\b" {
return "\\b"
} else {
if ch == "\f" {
return "\\f"
} else {
if ch == "\n" {
return "\\n"
} else {
if ch == "\r" {
return "\\r"
} else {
if ch == "\t" {
return "\\t"
} else {
if this.is_control_char(ch) {
return this.escape_unicode(ch)
} else {
return ch
}
}
}
}
}
}
}
}
}
}
// 制御文字かどうか判定MVP: 代表的な制御のみ)
is_control_char(ch) {
return ch == "\0" or ch == "\t" or ch == "\n" or ch == "\r" or ch == "\f" or ch == "\b"
}
// 文字のASCIIコードを取得簡易: 必要最小のケースのみ
char_code(ch) {
if ch == "\0" { return 0 }
if ch == "\t" { return 9 }
if ch == "\n" { return 10 }
if ch == "\r" { return 13 }
if ch == "\f" { return 12 }
if ch == " " { return 32 }
if ch == "\"" { return 34 }
if ch == "\\" { return 92 }
return -1
}
// Unicodeエスケープ形式に変換簡易版
escape_unicode(ch) {
local code = this.char_code(ch)
return "\\u" + this.int_to_hex4(code)
}
// 整数を4桁の16進数文字列に変換0..65535にクランプ)
int_to_hex4(n) {
local v = n
if v < 0 { v = 0 }
if v > 65535 { v = 65535 }
// 4096=16^3, 256=16^2, 16=16^1
local d0 = v / 4096
local r0 = v % 4096
local d1 = r0 / 256
local r1 = r0 % 256
local d2 = r1 / 16
local d3 = r1 % 16
return this.int_to_hex_digit(d0) + this.int_to_hex_digit(d1) + this.int_to_hex_digit(d2) + this.int_to_hex_digit(d3)
}
// 整数0-15を16進数文字に変換
int_to_hex_digit(n) {
return match n {
0 => "0", 1 => "1", 2 => "2", 3 => "3",
4 => "4", 5 => "5", 6 => "6", 7 => "7",
8 => "8", 9 => "9", 10 => "a", 11 => "b",
12 => "c", 13 => "d", 14 => "e", 15 => "f",
_ => "0"
}
}
// ===== JSON文字列アンエスケープ =====
// エスケープされたJSON文字列を元に戻す
unescape_string(s) {
local result = ""
local i = 0
loop(i < s.length()) {
local ch = s.substring(i, i + 1)
if ch == "\\" and i + 1 < s.length() {
local next_ch = s.substring(i + 1, i + 2)
local unescaped = this.unescape_sequence(next_ch, s, i)
result = result + unescaped.value
i = i + unescaped.advance
} else {
result = result + ch
i = i + 1
}
}
return result
}
// エスケープシーケンスを解釈(オブジェクトリテラル未対応環境のため MapBox で返す)
unescape_sequence(next_ch, full_string, pos) {
local out = new MapBox()
if next_ch == "\"" {
out.set("value", "\"")
out.set("advance", 2)
return out
}
if next_ch == "\\" {
out.set("value", "\\")
out.set("advance", 2)
return out
}
if next_ch == "/" {
out.set("value", "/")
out.set("advance", 2)
return out
}
if next_ch == "b" {
out.set("value", "\b")
out.set("advance", 2)
return out
}
if next_ch == "f" {
out.set("value", "\f")
out.set("advance", 2)
return out
}
if next_ch == "n" {
out.set("value", "\n")
out.set("advance", 2)
return out
}
if next_ch == "r" {
out.set("value", "\r")
out.set("advance", 2)
return out
}
if next_ch == "t" {
out.set("value", "\t")
out.set("advance", 2)
return out
}
if next_ch == "u" {
// Unicodeエスケープ \\uXXXX
if pos + 5 < full_string.length() {
local hex = full_string.substring(pos + 2, pos + 6)
if this.is_valid_hex4(hex) {
out.set("value", this.hex_to_char(hex))
out.set("advance", 6)
return out
} else {
out.set("value", "\\u")
out.set("advance", 2)
return out
}
} else {
out.set("value", "\\u")
out.set("advance", 2)
return out
}
}
// 不明なエスケープはそのまま残す
out.set("value", "\\" + next_ch)
out.set("advance", 2)
return out
}
// 4桁の16進数文字列が有効かどうか判定
is_valid_hex4(s) {
if s.length() != 4 {
return false
}
local i = 0
loop(i < 4) {
local ch = s.substring(i, i + 1)
if not this.is_hex_digit(ch) {
return false
}
i = i + 1
}
return true
}
// 16進数文字かどうか判定
is_hex_digit(ch) {
return (ch >= "0" and ch <= "9") or
(ch >= "a" and ch <= "f") or
(ch >= "A" and ch <= "F")
}
// 4桁の16進数文字列を文字に変換MVP: ASCII + 一部制御 + サロゲート検知)
hex_to_char(hex) {
// サロゲート半の範囲は '?' に置換(結合は現段階で未対応)
if hex >= "D800" and hex <= "DFFF" {
return "?"
}
// 制御文字(代表的なもの)
if hex == "0000" { return "\0" }
if hex == "0008" { return "\b" }
if hex == "0009" { return "\t" }
if hex == "000A" { return "\n" }
if hex == "000C" { return "\f" }
if hex == "000D" { return "\r" }
// 簡易: よく使う範囲0x20-0x7Eを網羅
if hex == "005C" { return "\\" }
if hex == "0022" { return "\"" }
// 0-9, A-Z, a-z, 空白と基本記号
if hex == "0020" { return " " }
if hex == "0021" { return "!" }
if hex == "0023" { return "#" }
if hex == "0024" { return "$" }
if hex == "0025" { return "%" }
if hex == "0026" { return "&" }
if hex == "0027" { return "'" }
if hex == "0028" { return "(" }
if hex == "0029" { return ")" }
if hex == "002A" { return "*" }
if hex == "002B" { return "+" }
if hex == "002C" { return "," }
if hex == "002D" { return "-" }
if hex == "002E" { return "." }
if hex == "002F" { return "/" }
if hex == "0030" { return "0" }
if hex == "0031" { return "1" }
if hex == "0032" { return "2" }
if hex == "0033" { return "3" }
if hex == "0034" { return "4" }
if hex == "0035" { return "5" }
if hex == "0036" { return "6" }
if hex == "0037" { return "7" }
if hex == "0038" { return "8" }
if hex == "0039" { return "9" }
if hex == "003A" { return ":" }
if hex == "003B" { return ";" }
if hex == "003C" { return "<" }
if hex == "003D" { return "=" }
if hex == "003E" { return ">" }
if hex == "003F" { return "?" }
if hex == "0040" { return "@" }
if hex == "0041" { return "A" }
if hex == "0042" { return "B" }
if hex == "0043" { return "C" }
if hex == "0044" { return "D" }
if hex == "0045" { return "E" }
if hex == "0046" { return "F" }
if hex == "0047" { return "G" }
if hex == "0048" { return "H" }
if hex == "0049" { return "I" }
if hex == "004A" { return "J" }
if hex == "004B" { return "K" }
if hex == "004C" { return "L" }
if hex == "004D" { return "M" }
if hex == "004E" { return "N" }
if hex == "004F" { return "O" }
if hex == "0050" { return "P" }
if hex == "0051" { return "Q" }
if hex == "0052" { return "R" }
if hex == "0053" { return "S" }
if hex == "0054" { return "T" }
if hex == "0055" { return "U" }
if hex == "0056" { return "V" }
if hex == "0057" { return "W" }
if hex == "0058" { return "X" }
if hex == "0059" { return "Y" }
if hex == "005A" { return "Z" }
if hex == "005B" { return "[" }
if hex == "005D" { return "]" }
if hex == "005E" { return "^" }
if hex == "005F" { return "_" }
if hex == "0060" { return "`" }
if hex == "0061" { return "a" }
if hex == "0062" { return "b" }
if hex == "0063" { return "c" }
if hex == "0064" { return "d" }
if hex == "0065" { return "e" }
if hex == "0066" { return "f" }
if hex == "0067" { return "g" }
if hex == "0068" { return "h" }
if hex == "0069" { return "i" }
if hex == "006A" { return "j" }
if hex == "006B" { return "k" }
if hex == "006C" { return "l" }
if hex == "006D" { return "m" }
if hex == "006E" { return "n" }
if hex == "006F" { return "o" }
if hex == "0070" { return "p" }
if hex == "0071" { return "q" }
if hex == "0072" { return "r" }
if hex == "0073" { return "s" }
if hex == "0074" { return "t" }
if hex == "0075" { return "u" }
if hex == "0076" { return "v" }
if hex == "0077" { return "w" }
if hex == "0078" { return "x" }
if hex == "0079" { return "y" }
if hex == "007A" { return "z" }
if hex == "007B" { return "{" }
if hex == "007C" { return "|" }
if hex == "007D" { return "}" }
if hex == "007E" { return "~" }
return "?"
}
// ===== 妥当性検証 =====
// JSON文字列が妥当かどうか検証
validate_string(s) {
local i = 0
loop(i < s.length()) {
local ch = s.substring(i, i + 1)
// 制御文字のチェック
if this.is_control_char(ch) and ch != "\t" and ch != "\n" and ch != "\r" {
return false // エスケープされていない制御文字
}
// エスケープシーケンスのチェック
if ch == "\\" {
if i + 1 >= s.length() {
return false // 不完全なエスケープ
}
local next_ch = s.substring(i + 1, i + 2)
if not this.is_valid_escape_char(next_ch) {
return false // 無効なエスケープ文字
}
// Unicodeエスケープの特別処理
if next_ch == "u" {
if i + 5 >= s.length() {
return false // 不完全なUnicodeエスケープ
}
local hex = s.substring(i + 2, i + 6)
if not this.is_valid_hex4(hex) {
return false // 無効な16進数
}
i = i + 6 // Unicodeエスケープをスキップ
} else {
i = i + 2 // 通常のエスケープをスキップ
}
} else {
i = i + 1
}
}
return true
}
// 有効なエスケープ文字かどうか判定
is_valid_escape_char(ch) {
return ch == "\"" or ch == "\\" or ch == "/" or
ch == "b" or ch == "f" or ch == "n" or
ch == "r" or ch == "t" or ch == "u"
}
// ===== 便利メソッド =====
// 文字列をJSON文字列リテラルとしてクォート
quote_string(s) {
return "\"" + this.escape_string(s) + "\""
}
// JSON文字列リテラルからクォートを除去してアンエスケープ
unquote_string(s) {
if s.length() >= 2 and s.substring(0, 1) == "\"" and s.substring(s.length() - 1, s.length()) == "\"" {
local content = s.substring(1, s.length() - 1)
return this.unescape_string(content)
} else {
return s // クォートされていない場合はそのまま
}
}
// 安全な文字列表示(デバッグ用)
safe_display(s) {
local result = "\""
local i = 0
loop(i < s.length()) {
local ch = s.substring(i, i + 1)
if this.is_printable(ch) {
result = result + ch
} else {
result = result + this.escape_char(ch)
}
i = i + 1
}
return result + "\""
}
// 印刷可能文字かどうか判定
is_printable(ch) {
// MVP: 制御文字でなければ表示可能とみなす(デバッグ用)
return not this.is_control_char(ch)
}
}