394 lines
12 KiB
C#
394 lines
12 KiB
C#
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);
|
||
}
|
||
} |