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

436 lines
15 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
}
}
}
}
}
}
}
}
}
}
// 制御文字かどうか判定
is_control_char(ch) {
// ASCII制御文字0x00-0x1Fの簡易判定
// TODO: より完全な制御文字判定
local code = this.char_code(ch)
return code >= 0 and code <= 31
}
// 文字のASCIIコードを取得簡易版
char_code(ch) {
// 主要な文字のASCIIコード簡易実装
if ch == "\0" { return 0 } else { if ch == "\t" { return 9 } else { if ch == "\n" { return 10 } else { if ch == "\r" { return 13 } else {
if ch == " " { return 32 } else { if ch == "!" { return 33 } else { if ch == "\"" { return 34 } else { if ch == "#" { return 35 } else {
if ch == "$" { return 36 } else { if ch == "%" { return 37 } else { if ch == "&" { return 38 } else { if ch == "'" { return 39 } else {
if ch == "(" { return 40 } else { if ch == ")" { return 41 } else { if ch == "*" { return 42 } else { if ch == "+" { return 43 } else {
if ch == "," { return 44 } else { if ch == "-" { return 45 } else { if ch == "." { return 46 } else { if ch == "/" { return 47 } else {
if ch == "0" { return 48 } else { if ch == "1" { return 49 } else { if ch == "2" { return 50 } else { if ch == "3" { return 51 } else {
if ch == "4" { return 52 } else { if ch == "5" { return 53 } else { if ch == "6" { return 54 } else { if ch == "7" { return 55 } else {
if ch == "8" { return 56 } else { if ch == "9" { return 57 } else {
return 0 // その他の文字は0
} } } } } } } } } } } } } } } } } } } } } } } } } } } } } }
}
// Unicodeエスケープ形式に変換簡易版
escape_unicode(ch) {
local code = this.char_code(ch)
return "\\u" + this.int_to_hex4(code)
}
// 整数を4桁の16進数文字列に変換
int_to_hex4(n) {
// 簡易実装: 0-127のASCII文字のみ対応
if n >= 0 and n <= 15 {
return "000" + this.int_to_hex_digit(n)
} else {
if n >= 16 and n <= 255 {
local high = n / 16
local low = n % 16
return "00" + this.int_to_hex_digit(high) + this.int_to_hex_digit(low)
} else {
// より大きな値は後で実装
return "0000"
}
}
}
// 整数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: BMPの基本ASCIIとサロゲート検知
hex_to_char(hex) {
// サロゲート半の範囲は '?' に置換(結合は現段階で未対応)
if hex >= "D800" and hex <= "DFFF" {
return "?"
}
// 簡易: よく使う範囲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) {
local code = this.char_code(ch)
return code >= 32 and code <= 126 // 基本的な印刷可能ASCII文字
}
}