zc
This commit is contained in:
427
Source/WulaFallenEmpire/Utils/DefInjectedExportUtility.cs
Normal file
427
Source/WulaFallenEmpire/Utils/DefInjectedExportUtility.cs
Normal file
@@ -0,0 +1,427 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user