using System.Collections.Generic; using System.Text; using System.Text.RegularExpressions; using Superlazy; public static class SLString { private static readonly List tags = new List() { 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 koreanPostpositionStrTags = new List() { 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 = $"[{sb.ToString(splitEnd + 1, bracketEnd - splitEnd - 1)}]"; sb.Remove(bracketStart, bracketEnd - bracketStart + 1); sb.Insert(bracketStart, formattedText); currentSearchPosition = bracketStart + formattedText.Length; } return sb.ToString(); } public static List GetTags(string text, SLEntity context) { var list = new List(); 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); } }