Files
WulaFallenEmpireRW/Source/WulaFallenEmpire/Utils/DefInjectedExportUtility.cs
2025-12-17 15:48:23 +08:00

428 lines
15 KiB
C#
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.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using RimWorld;
using Verse;
namespace WulaFallenEmpire.Utils
{
public static class DefInjectedExportUtility
{
private sealed class InjectionValue
{
public string Key;
public bool IsCollection;
public List<string> Values;
}
public static void ExportDefInjectedTemplateFromDefs(ModContentPack content)
{
try
{
if (content?.ModMetaData == null)
{
Messages.Message("Export failed: Mod content metadata not found.", MessageTypeDefOf.RejectInput);
return;
}
string outRoot = Path.Combine(
GenFilePaths.SaveDataFolderPath,
"WulaFallenEmpire_DefInjectedExport",
DateTime.Now.ToString("yyyyMMdd_HHmmss"));
string outDefInjected = Path.Combine(outRoot, "English", "DefInjected");
Directory.CreateDirectory(outDefInjected);
string outTsvPath = Path.Combine(outRoot, "worklist.tsv");
var entriesByFolder = new Dictionary<string, Dictionary<string, InjectionValue>>(StringComparer.OrdinalIgnoreCase);
var seenKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
List<Type> defTypes = GenTypes.AllSubclassesNonAbstract(typeof(Def)).ToList();
defTypes.Sort((a, b) => string.Compare(a.FullName, b.FullName, StringComparison.OrdinalIgnoreCase));
foreach (Type defType in defTypes)
{
DefInjectionUtility.ForEachPossibleDefInjection(
defType,
(string suggestedPath,
string normalizedPath,
bool isCollection,
string currentValue,
IEnumerable<string> currentValueCollection,
bool translationAllowed,
bool fullListTranslationAllowed,
FieldInfo fieldInfo,
Def def) =>
{
if (!translationAllowed)
{
return;
}
if (string.IsNullOrWhiteSpace(suggestedPath))
{
return;
}
if (suggestedPath.IndexOf(".modContentPack.", StringComparison.OrdinalIgnoreCase) >= 0)
{
return;
}
if (!isCollection && string.Equals(fieldInfo?.Name, "defName", StringComparison.OrdinalIgnoreCase))
{
return;
}
List<string> values;
bool collectionOut;
if (isCollection)
{
values = currentValueCollection?.Where(v => KeepValue(suggestedPath, fieldInfo, v)).ToList() ?? new List<string>();
if (values.Count == 0)
{
return;
}
collectionOut = true;
}
else
{
if (!KeepValue(suggestedPath, fieldInfo, currentValue))
{
return;
}
values = new List<string> { currentValue };
collectionOut = false;
}
if (!seenKeys.Add(suggestedPath))
{
return;
}
string folderName = GetDefInjectedFolderName(def?.GetType() ?? defType);
if (!entriesByFolder.TryGetValue(folderName, out Dictionary<string, InjectionValue> folderEntries))
{
folderEntries = new Dictionary<string, InjectionValue>(StringComparer.OrdinalIgnoreCase);
entriesByFolder.Add(folderName, folderEntries);
}
folderEntries.Add(
suggestedPath,
new InjectionValue
{
Key = suggestedPath,
IsCollection = collectionOut,
Values = values
});
},
content.ModMetaData);
}
WriteDefInjectedXmlOutputs(entriesByFolder, outDefInjected);
WriteWorklistTsv(entriesByFolder, outTsvPath);
Messages.Message($"DefInjected export written to: {outRoot}", MessageTypeDefOf.TaskCompletion);
}
catch (Exception ex)
{
Log.Error($"[WulaFallenEmpire] DefInjected export failed: {ex}");
Messages.Message("DefInjected export failed (see log).", MessageTypeDefOf.RejectInput);
}
}
private static void WriteDefInjectedXmlOutputs(
Dictionary<string, Dictionary<string, InjectionValue>> entriesByFolder,
string outDefInjectedRoot)
{
foreach (KeyValuePair<string, Dictionary<string, InjectionValue>> folderPair in entriesByFolder)
{
string folder = folderPair.Key;
Dictionary<string, InjectionValue> entries = folderPair.Value;
if (entries.Count == 0)
{
continue;
}
string safeFolder = SanitizePathSegment(folder);
string folderPath = Path.Combine(outDefInjectedRoot, safeFolder);
Directory.CreateDirectory(folderPath);
WriteXmlUtf8Indented(
BuildLanguageDataDocument(entries, todoMode: false),
Path.Combine(folderPath, "Auto_CN.xml"));
WriteXmlUtf8Indented(
BuildLanguageDataDocument(entries, todoMode: true),
Path.Combine(folderPath, "Auto_TODO.xml"));
}
}
private static XDocument BuildLanguageDataDocument(
Dictionary<string, InjectionValue> entries,
bool todoMode)
{
var languageData = new XElement("LanguageData");
foreach (InjectionValue entry in entries.Values.OrderBy(e => e.Key, StringComparer.OrdinalIgnoreCase))
{
if (entry.IsCollection)
{
var element = new XElement(entry.Key);
foreach (string value in entry.Values)
{
element.Add(new XElement("li", todoMode ? ToTodoListItem(value) : (value ?? string.Empty)));
}
languageData.Add(element);
}
else
{
string value = entry.Values.FirstOrDefault() ?? string.Empty;
languageData.Add(new XElement(entry.Key, todoMode ? "TODO" : value));
}
}
return new XDocument(new XDeclaration("1.0", "utf-8", null), languageData);
}
private static void WriteWorklistTsv(
Dictionary<string, Dictionary<string, InjectionValue>> entriesByFolder,
string outTsvPath)
{
var lines = new List<string> { "Folder\tKey\tCNSourceType\tCNSource" };
foreach (KeyValuePair<string, Dictionary<string, InjectionValue>> folderPair in entriesByFolder)
{
string folder = folderPair.Key;
foreach (InjectionValue entry in folderPair.Value.Values.OrderBy(e => e.Key, StringComparer.OrdinalIgnoreCase))
{
if (entry.IsCollection)
{
string joined = string.Join("\\n", entry.Values.Select(v => v ?? string.Empty));
lines.Add($"{EscapeTsv(folder)}\t{EscapeTsv(entry.Key)}\tlist\t{EscapeTsv(joined)}");
}
else
{
string value = entry.Values.FirstOrDefault() ?? string.Empty;
lines.Add($"{EscapeTsv(folder)}\t{EscapeTsv(entry.Key)}\ttext\t{EscapeTsv(value)}");
}
}
}
string dir = Path.GetDirectoryName(outTsvPath);
if (!string.IsNullOrEmpty(dir))
{
Directory.CreateDirectory(dir);
}
File.WriteAllLines(outTsvPath, lines, new UTF8Encoding(encoderShouldEmitUTF8Identifier: true));
}
private static string GetDefInjectedFolderName(Type defType)
{
if (defType == null)
{
return "UnknownDefType";
}
string ns = defType.Namespace ?? string.Empty;
if (ns == "Verse" || ns == "RimWorld")
{
return defType.Name;
}
return defType.FullName ?? defType.Name;
}
private static string SanitizePathSegment(string segment)
{
if (string.IsNullOrEmpty(segment))
{
return "_";
}
foreach (char c in Path.GetInvalidFileNameChars())
{
segment = segment.Replace(c, '_');
}
return segment;
}
private static string ToTodoListItem(string original)
{
string s = original ?? string.Empty;
int idx = s.IndexOf("->", StringComparison.Ordinal);
if (idx >= 0)
{
return s.Substring(0, idx) + "->TODO";
}
return "TODO";
}
private static bool KeepValue(string key, FieldInfo fieldInfo, string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return false;
}
if (!string.IsNullOrEmpty(key))
{
if (key.IndexOf(".defName", StringComparison.OrdinalIgnoreCase) >= 0)
{
return false;
}
if (key.IndexOf(".fileName", StringComparison.OrdinalIgnoreCase) >= 0)
{
return false;
}
}
if (LooksLikeFilePath(value))
{
return false;
}
if (LooksLikeAssetPath(value))
{
return false;
}
string fieldName = fieldInfo?.Name ?? string.Empty;
if (fieldName.IndexOf("label", StringComparison.OrdinalIgnoreCase) >= 0)
{
return true;
}
if (fieldName.IndexOf("description", StringComparison.OrdinalIgnoreCase) >= 0)
{
return true;
}
if (fieldName.IndexOf("title", StringComparison.OrdinalIgnoreCase) >= 0)
{
return true;
}
if (fieldName.IndexOf("text", StringComparison.OrdinalIgnoreCase) >= 0)
{
return true;
}
for (int i = 0; i < value.Length; i++)
{
char c = value[i];
if (c >= 0x4E00 && c <= 0x9FFF)
{
return true;
}
}
if (value.IndexOfAny(new[] { ' ', '\n', '\r', '\t' }) >= 0)
{
return true;
}
if (value.IndexOfAny(new[] { '', '。', '', '', '', '', '、', '', '', '《', '》', '“', '”', '"', '\'', ':', ';', ',', '.', '!', '?' }) >= 0)
{
return true;
}
return false;
}
private static bool LooksLikeFilePath(string value)
{
if (string.IsNullOrEmpty(value))
{
return false;
}
if (value.Length >= 3 && char.IsLetter(value[0]) && value[1] == ':' && (value[2] == '\\' || value[2] == '/'))
{
return true;
}
if (value.Contains("\\\\") || value.Contains(":\\"))
{
return true;
}
return false;
}
private static bool LooksLikeAssetPath(string value)
{
if (string.IsNullOrEmpty(value))
{
return false;
}
if (value.IndexOf('/') < 0 && value.IndexOf('\\') < 0)
{
return false;
}
for (int i = 0; i < value.Length; i++)
{
char c = value[i];
if (c >= 0x4E00 && c <= 0x9FFF)
{
return false;
}
}
for (int i = 0; i < value.Length; i++)
{
char c = value[i];
if (char.IsWhiteSpace(c))
{
return false;
}
}
return true;
}
private static void WriteXmlUtf8Indented(XDocument doc, string path)
{
var settings = new XmlWriterSettings
{
Indent = true,
IndentChars = " ",
NewLineChars = "\n",
NewLineHandling = NewLineHandling.Replace,
OmitXmlDeclaration = false,
Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)
};
using XmlWriter writer = XmlWriter.Create(path, settings);
doc.Save(writer);
}
private static string EscapeTsv(string value)
{
if (value == null)
{
return string.Empty;
}
if (value.IndexOfAny(new[] { '\t', '\n', '\r', '"' }) >= 0)
{
return "\"" + value.Replace("\"", "\"\"") + "\"";
}
return value;
}
}
}