ProjectDDD/Packages/SLUnity/SLString.cs
2025-07-08 19:46:31 +09:00

394 lines
12 KiB
C#
Raw Permalink 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.

using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using Superlazy;
public static class SLString
{
private static readonly List<char[]> tags = new List<char[]>()
{
new char[]{ 's', 'c' },
new char[]{ '/', 's', 'c' },
new char[]{ 's', 'c', 'o', 'l', 'o', 'r' },
new char[]{ '/', 's', 'c', 'o', 'l', 'o', 'r' },
new char[]{ 'n', 'l', },
new char[]{ 's', 't', 'r' }
};
private static readonly List<string[]> koreanPostpositionStrTags = new List<string[]>()
{
new string[] { "을(를)", "을", "를" },
new string[] { "을/를", "을", "를" },
new string[] { "이(가)", "이", "가" },
new string[] { "이/가", "이", "가" },
new string[] { "은(는)", "은", "는" },
new string[] { "은/는", "은", "는" },
new string[] { "과(와)", "과", "와" },
new string[] { "과/와", "과", "와" }
};
private static readonly string englishPluralStrTags = "(s)";
public static string GetString(string str, SLEntity context)
{
return Translation(str, context);
}
private static string Translation(string str, SLEntity context)
{
if (string.IsNullOrEmpty(str)) return string.Empty;
if (str.IsLeft("STR_") == false) return Format(str, context);
var strKey = SLSystem.Data["Strings"][str];
var lang = SLGame.Session["Option"]["Language"];
if (strKey[lang])
{
return Format(strKey[lang], context);
}
else if (strKey["EN"])
{
return Format(strKey["EN"], context);
}
else if (strKey["KR"])
{
return Format(strKey["KR"], context);
}
else
{
return Format(str, context);
}
}
private static string Format(string value, SLEntity context)
{
var tagStart = value.LastIndexOf('{', value.Length - 1);
if (tagStart != -1)
{
var tagEnd = value.IndexOf('}', tagStart);
if (tagEnd == -1)
{
SLLog.Error($"Tagging Error [{value}]");
return value;
}
var sb = new StringBuilder(value);
var tagParamIdx = value.IndexOf(':', tagStart + 1, tagEnd - tagStart - 1);
string tagAttribute;
string tagType;
if (tagParamIdx != -1)
{
tagAttribute = sb.ToString(tagStart + 1, tagParamIdx - tagStart - 1);
tagType = sb.ToString(tagParamIdx + 1, tagEnd - tagParamIdx - 1);
}
else
{
tagAttribute = sb.ToString(tagStart + 1, tagEnd - tagStart - 1);
tagType = string.Empty;
}
var format = Translation(SLTag.Apply(context.Get(tagAttribute), tagType), context);
sb.Remove(tagStart, tagEnd - tagStart + 1);
sb.Insert(tagStart, format);
return Format(sb.ToString(), context); // 태그를 반복적으로 적용
}
else
{
return CustomTag(value, context);
}
}
private static string CustomTag(string str, SLEntity context)
{
if (string.IsNullOrEmpty(str)) return str;
var sb = new StringBuilder(GetTagApplyString(str, context));
return PostPositionCheck(sb);
}
private static string GetTagApplyString(string str, SLEntity context)
{
if (string.IsNullOrEmpty(str)) return str;
var tagStart = str.LastIndexOf('<', str.Length - 1);
var hasTag = tagStart != -1;
// end
if (hasTag == false)
{
return str;
}
var tagEnd = str.IndexOf('>', tagStart);
if (tagEnd == -1)
{
SLLog.Error($"Tagging Error [{str}]");
return str;
}
var sb = new StringBuilder(str);
if (TagCheck(sb, tagStart, tagEnd, tags[0]))
{
var colorTag = SLSystem.Data["SpecialColors"][sb.ToString(tagStart + 4, tagEnd - tagStart - 4)].ToString();
sb.Remove(tagStart + 1, tagEnd - tagStart - 1);
sb.Insert(tagStart + 1, colorTag);
sb.Insert(tagStart + 1, "color=");
}
else if (TagCheck(sb, tagStart, tagEnd, tags[1]))
{
sb.Remove(tagStart + 2, tagEnd - tagStart - 2);
sb.Insert(tagStart + 2, "color");
}
else if (TagCheck(sb, tagStart, tagEnd, tags[2]))
{
var colorTag = SLSystem.Data["SpecialColors"][sb.ToString(tagStart + 8, tagEnd - tagStart - 8)].ToString();
sb.Remove(tagStart + 1, tagEnd - tagStart - 1);
sb.Insert(tagStart + 1, colorTag);
sb.Insert(tagStart + 1, "color=");
}
else if (TagCheck(sb, tagStart, tagEnd, tags[3]))
{
sb.Remove(tagStart + 2, tagEnd - tagStart - 2);
sb.Insert(tagStart + 2, "color");
}
else if (TagCheck(sb, tagStart, tagEnd, tags[4]))
{
sb.Remove(tagStart, tagEnd - tagStart + 1);
sb.Insert(tagStart, "\n");
}
else if (TagCheck(sb, tagStart, tagEnd, tags[5]))
{
var newString = sb.ToString(tagStart + 5, tagEnd - tagStart - 5);
sb.Remove(tagStart, tagEnd - tagStart + 1);
sb.Insert(tagStart, Translation(newString, context));
}
var left = GetTagApplyString(sb.ToString(0, tagStart), context); // 태그 반복
sb.Remove(0, tagStart);
sb.Insert(0, left);
return sb.ToString();
}
private static string PostPositionCheck(StringBuilder sb)
{
var lang = SLGame.Session["Option"]["Language"];
if (lang == "KR")
{
foreach (var tag in koreanPostpositionStrTags)
{
int index = LastIndexOf(sb, tag[0]);
while (index != -1)
{
// index가 0보다 작으면 이전 문자가 없으므로 안전하게 체크
if (index - 1 < 0)
break;
char priorWord = sb[index - 1];
string correction = HasEndConsonant(priorWord) ? tag[1] : tag[2];
int tagLength = tag[0].Length;
sb.Remove(index, tagLength);
sb.Insert(index, correction);
// sb를 직접 검색하여 갱신
index = LastIndexOf(sb, tag[0]);
}
}
}
else if (lang == "EN")
{
int index = LastIndexOf(sb, englishPluralStrTags);
while (index != -1)
{
int blankEndIndex = LastIndexOf(sb, ' ', index - 1);
int blankStartIndex = LastIndexOf(sb, ' ', blankEndIndex - 1);
if (blankEndIndex == -1 || blankStartIndex == -1)
break;
string numberStr = Substring(sb, blankStartIndex + 1, blankEndIndex - blankStartIndex - 1);
// 공백 및 기타 whitespace 처리
numberStr = Regex.Replace(numberStr, @"\s+", " ");
int blankIndex = numberStr.LastIndexOf(' ');
if (blankIndex != -1)
{
numberStr = numberStr.Substring(blankIndex + 1);
}
bool plural;
string numberStrLower = numberStr.ToLower();
if (numberStrLower == "one" || numberStrLower == "a")
{
plural = false;
}
else
{
if (!int.TryParse(numberStr, out var number))
{
plural = true;
}
else
{
plural = number != 1;
}
}
sb.Remove(index, englishPluralStrTags.Length);
if (plural)
{
sb.Insert(index, 's');
}
index = LastIndexOf(sb, englishPluralStrTags);
}
}
return ColorTagCheck(sb);
}
private static bool HasEndConsonant(char word)
{
if (word < 0xAC00) return false;
return (word - 0xAC00) % 28 + 0x11a7 != 4519;
}
private static bool TagCheck(StringBuilder sb, int tagStart, int tagEnd, char[] tag)
{
var cnt = tag.Length;
if (tagEnd - tagStart - 1 < tag.Length) return false;
for (var i = 0; i < cnt; ++i)
{
if (sb[i + tagStart + 1] != tag[i] && sb[i + tagStart + 1] != char.ToUpper(tag[i])) return false;
}
return true;
}
private static string ColorTagCheck(StringBuilder sb)
{
int bracketStart;
var currentSearchPosition = 0;
while (currentSearchPosition < sb.Length && (bracketStart = IndexOf(sb, '[', currentSearchPosition)) != -1)
{
var bracketEnd = IndexOf(sb, ']', bracketStart);
if (bracketEnd == -1)
{
SLLog.Error($"Bracket Tagging Error [{sb}]");
break;
}
var colorCode = SLSystem.Data["SpecialColors"]["Default"];
var splitEnd = IndexOf(sb, '|', bracketStart);
if (splitEnd == -1 || splitEnd > bracketEnd) // 구분선이 없다면
{
splitEnd = bracketStart;
}
else
{
colorCode = SLSystem.Data["SpecialColors"][sb.ToString(bracketStart + 1, splitEnd - bracketStart - 1)];
}
var formattedText = $"<color={colorCode}>[{sb.ToString(splitEnd + 1, bracketEnd - splitEnd - 1)}]</color>";
sb.Remove(bracketStart, bracketEnd - bracketStart + 1);
sb.Insert(bracketStart, formattedText);
currentSearchPosition = bracketStart + formattedText.Length;
}
return sb.ToString();
}
public static List<string> GetTags(string text, SLEntity context)
{
var list = new List<string>();
int bracketStart;
var currentSearchPosition = 0;
text = GetString(text, context);
while (currentSearchPosition < text.Length && (bracketStart = text.IndexOf('[', currentSearchPosition)) != -1)
{
var bracketEnd = text.IndexOf(']', bracketStart);
if (bracketEnd == -1)
{
SLLog.Error($"Bracket Tagging Error [{text}]");
break;
}
var originalText = text.Substring(bracketStart + 1, bracketEnd - bracketStart - 1);
if (originalText.Contains('|')) // 태그붙음
{
originalText = originalText.Substring(originalText.IndexOf('|') + 1);
}
var tagID = Regex.Replace(originalText, @"[0-9\s!\""#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~×]", ""); // TODO: 추후에는 실제 태그 ID 변경
if (string.IsNullOrEmpty(tagID) == false)
{
var tag = SLSystem.Data["Tags"][tagID];
if (tag["Desc"])
{
list.Add(tagID);
}
}
currentSearchPosition = bracketStart + bracketEnd - bracketStart + 1;
}
return list;
}
private static int LastIndexOf(StringBuilder sb, string value)
{
if (value.Length == 0) return sb.Length;
for (int i = sb.Length - value.Length; i >= 0; i--)
{
bool found = true;
for (int j = 0; j < value.Length; j++)
{
if (sb[i + j] != value[j])
{
found = false;
break;
}
}
if (found) return i;
}
return -1;
}
private static int IndexOf(StringBuilder sb, char value, int startIndex = 0)
{
for (int i = startIndex; i < sb.Length; i++)
{
if (sb[i] == value)
return i;
}
return -1;
}
private static int LastIndexOf(StringBuilder sb, char c, int startIndex)
{
for (int i = startIndex; i >= 0; i--)
{
if (sb[i] == c) return i;
}
return -1;
}
private static string Substring(StringBuilder sb, int start, int length)
{
return sb.ToString(start, length);
}
}