Files
SoulliesOfficial 7ee2894a63 整合SLSUtilities
2026-01-17 11:35:49 -05:00

379 lines
13 KiB
C#

using LunaWolfStudiosEditor.ScriptableSheets.Shared;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
namespace LunaWolfStudiosEditor.ScriptableSheets.Tables
{
public static class TableUtility
{
public static void FromFlatFileFormat(this Table<ITableProperty> propertyTable, string flatFileContent, FlatFileFormatSettings formatSettings)
{
var wrapper = formatSettings.GetWrapper();
if (formatSettings.HasHeaders)
{
var firstRowEndIndex = flatFileContent.IndexOf(formatSettings.RowDelimiter);
var joinedColumnHeaders = formatSettings.GetJoinedColumnHeaders(wrapper);
var joinedColumnHeadersSanitized = SanitizeHeader(joinedColumnHeaders);
if (firstRowEndIndex >= 0)
{
var headerRow = flatFileContent.Substring(0, firstRowEndIndex).Trim();
var headerRowSanitized = SanitizeHeader(headerRow);
// Validate column headers match the header row.
if (joinedColumnHeadersSanitized.Contains(headerRowSanitized) || headerRowSanitized.Contains(joinedColumnHeadersSanitized))
{
flatFileContent = flatFileContent.Substring(firstRowEndIndex + formatSettings.RowDelimiter.Length);
}
}
else
{
// Handle case where there's only a single row.
var flatFileContentSanitized = SanitizeHeader(flatFileContent);
if (joinedColumnHeadersSanitized.Contains(flatFileContentSanitized))
{
flatFileContent = string.Empty;
}
}
}
var stringSplitOptions = formatSettings.RemoveEmptyRows ? StringSplitOptions.RemoveEmptyEntries : StringSplitOptions.None;
string[] rowData;
if (formatSettings.HasWrapping)
{
var inverseWrap = wrapper.WrapInverse(formatSettings.RowDelimiter);
// Replace any trailing white space at end of line.
flatFileContent = Regex.Replace(flatFileContent, $@"{Regex.Escape(wrapper.Close.ToString())}\s*{Regex.Escape(formatSettings.RowDelimiter)}\s*{Regex.Escape(wrapper.Open.ToString())}", inverseWrap);
// Use new string array parameter to support older versions of C#.
rowData = flatFileContent.Split(new string[] { inverseWrap }, stringSplitOptions);
for (var i = 0; i < rowData.Length - 1; i++)
{
rowData[i] += wrapper.Close;
var nextRow = i + 1;
if (nextRow < rowData.Length)
{
rowData[nextRow] = wrapper.Open + rowData[nextRow];
}
}
var totalWrapChars = flatFileContent.Count(c => c == wrapper.Open || c == wrapper.Close);
var expectedWrapChars = rowData.Length * 2;
if (totalWrapChars < expectedWrapChars)
{
Debug.LogWarning($"Invalid {nameof(WrapOption)} {formatSettings.WrapOption}. Found {totalWrapChars} matching char(s) but expected at least {expectedWrapChars}. Defaulting to {nameof(WrapOption)} {WrapOption.None}.");
formatSettings.WrapOption = WrapOption.None;
rowData = flatFileContent.Split(new string[] { formatSettings.RowDelimiter }, stringSplitOptions);
}
}
else
{
rowData = flatFileContent.Split(new string[] { formatSettings.RowDelimiter }, stringSplitOptions);
}
var rowIndex = 0;
for (var row = formatSettings.FirstRowIndex; rowIndex < rowData.Length && row < propertyTable.Rows; row++)
{
string[] columnData;
if (formatSettings.HasWrapping)
{
var line = rowData[rowIndex].Trim();
columnData = line.Split(new string[] { wrapper.WrapInverse(formatSettings.ColumnDelimiter) }, StringSplitOptions.None);
if (columnData.Length > 0 && columnData[0].StartsWith(wrapper.Open.ToString()))
{
columnData[0] = columnData[0].Remove(0, 1);
var lastIndex = columnData.Length - 1;
if (columnData[lastIndex].EndsWith(wrapper.Close.ToString()))
{
columnData[lastIndex] = columnData[lastIndex].Remove(columnData[lastIndex].Length - 1, 1);
}
}
}
else
{
columnData = rowData[rowIndex].Split(new string[] { formatSettings.ColumnDelimiter }, StringSplitOptions.None);
}
var columnIndex = 0;
if (formatSettings.HasWrapping && formatSettings.EscapeOption != EscapeOption.None)
{
var escapedOpen = formatSettings.EscapeOption.GetEscapedWrapper(wrapper.Open, formatSettings.CustomEscapeSequence);
var escapedClose = formatSettings.EscapeOption.GetEscapedWrapper(wrapper.Close, formatSettings.CustomEscapeSequence);
for (var y = formatSettings.FirstColumnIndex; columnIndex < columnData.Length && y < propertyTable.Columns; y++)
{
var unescapedValue = wrapper.UnescapeContent(columnData[columnIndex], escapedOpen, escapedClose);
propertyTable.UpdateProperty(row, y, unescapedValue, formatSettings);
columnIndex++;
}
}
else
{
for (var y = formatSettings.FirstColumnIndex; columnIndex < columnData.Length && y < propertyTable.Columns; y++)
{
propertyTable.UpdateProperty(row, y, columnData[columnIndex], formatSettings);
columnIndex++;
}
}
rowIndex++;
}
}
private static ITableProperty UpdateProperty(this Table<ITableProperty> propertyTable, int row, int column, string value, FlatFileFormatSettings formatSettings)
{
var property = propertyTable.Get(row, column);
// Property could be null when mixing array sizes.
if (property != null)
{
property.SetProperty(value, formatSettings);
}
return property;
}
public static void FromJsonFormat(this Table<ITableProperty> propertyTable, string json, JsonSerializationFormat format, FlatFileFormatSettings formatSettings)
{
switch (format)
{
case JsonSerializationFormat.Flat:
propertyTable.FromJsonFlatFormat(json, formatSettings);
break;
case JsonSerializationFormat.Hierarchy:
propertyTable.FromJsonHierarchyFormat(json, formatSettings);
break;
default:
Debug.LogError($"Unsupported {nameof(JsonSerializationFormat)} {format}.");
break;
}
}
private static void FromJsonFlatFormat(this Table<ITableProperty> propertyTable, string json, FlatFileFormatSettings formatSettings)
{
try
{
var rowData = JsonConvert.DeserializeObject<Dictionary<string, Dictionary<string, string>>>(json);
var row = 0;
foreach (var rowEntry in rowData)
{
if (row >= propertyTable.Rows)
{
LogJSONRowWarning(rowData.Count, propertyTable.Rows);
break;
}
var column = formatSettings.FirstColumnIndex;
var rowValues = rowEntry.Value.ToArray();
for (var i = 0; i < rowValues.Length; i++)
{
if (column >= propertyTable.Columns)
{
// Add 1 for Actions column.
Debug.LogWarning($"Mismatched columns. JSON data has ({rowEntry.Value.Count + 1}) column(s) but the property table only has ({propertyTable.Columns}) column(s).");
break;
}
var property = propertyTable.UpdateProperty(row, column, rowValues[i].Value, formatSettings);
// Only go to the next element if the previous was found successfully. This handles edge cases with mismatched array sizes.
if (property == null)
{
i--;
}
else if (property.PropertyPath != rowValues[i].Key)
{
Debug.LogWarning($"Mismatched property path. Expected property path '{property.PropertyPath}' but used property path from JSON '{rowValues[i].Key}' for '{rowEntry.Key}'.");
}
column++;
}
row++;
}
}
catch (Exception ex)
{
Debug.LogError($"Error parsing JSON {ex.Message}");
}
}
private static void FromJsonHierarchyFormat(this Table<ITableProperty> propertyTable, string json, FlatFileFormatSettings formatSettings)
{
try
{
var rowData = JsonConvert.DeserializeObject<Dictionary<string, JObject>>(json);
var row = 0;
foreach (var rowEntry in rowData)
{
if (row >= propertyTable.Rows)
{
LogJSONRowWarning(rowData.Count, propertyTable.Rows);
break;
}
var rootObject = propertyTable.Get(row, formatSettings.FirstColumnIndex).RootObject;
var assetName = rowEntry.Key;
if (assetName != rootObject.name)
{
var assetPath = AssetDatabase.GetAssetPath(rootObject);
var assetRenameResponse = AssetDatabase.RenameAsset(assetPath, assetName);
if (!string.IsNullOrEmpty(assetRenameResponse))
{
Debug.LogWarning($"Cannot rename {rootObject.name} at asset path {assetPath} to new name '{assetName.GetEscapedText()}'.\n{assetRenameResponse}");
assetName = rootObject.name;
}
}
var serializedObjectJson = JsonConvert.SerializeObject(rowEntry.Value);
if (rootObject is ScriptableObject)
{
JsonUtility.FromJsonOverwrite(serializedObjectJson, rootObject);
}
else
{
EditorJsonUtility.FromJsonOverwrite(serializedObjectJson, rootObject);
// Ensure m_Name property matches filename after deserializing.
rootObject.name = assetName;
}
EditorUtility.SetDirty(rootObject);
row++;
}
}
catch (Exception ex)
{
Debug.LogError($"Error parsing JSON {ex.Message}");
}
}
private static void LogJSONRowWarning(int expectedRows, int actualRows)
{
Debug.LogWarning($"Mismatched rows. JSON data has ({expectedRows}) row(s) but the property table only has ({actualRows}) row(s). Create ({expectedRows - actualRows}) more row(s).");
}
public static string ToFlatFileFormat(this Table<ITableProperty> propertyTable, FlatFileFormatSettings formatSettings)
{
var lastColumn = propertyTable.Columns - 1;
var lastRow = propertyTable.Rows - 1;
var flatFile = new StringBuilder();
var wrapper = formatSettings.GetWrapper();
if (formatSettings.HasHeaders)
{
for (var column = formatSettings.FirstColumnIndex; column < formatSettings.ColumnHeaders.Length; column++)
{
var header = formatSettings.ColumnHeaders[column];
if (formatSettings.HasWrapping)
{
flatFile.Wrap(header, wrapper);
}
else
{
flatFile.Append(header);
}
if (formatSettings.FirstColumnOnly)
{
break;
}
if (column < lastColumn)
{
flatFile.Append(formatSettings.ColumnDelimiter);
}
}
if (propertyTable.Rows > formatSettings.FirstRowIndex)
{
flatFile.Append(formatSettings.RowDelimiter);
}
}
for (var row = formatSettings.FirstRowIndex; row < propertyTable.Rows; row++)
{
for (var column = formatSettings.FirstColumnIndex; column < propertyTable.Columns; column++)
{
var property = propertyTable.Get(row, column)?.GetProperty(formatSettings);
if (formatSettings.HasWrapping)
{
if (formatSettings.EscapeOption != EscapeOption.None)
{
var escapedOpen = formatSettings.EscapeOption.GetEscapedWrapper(wrapper.Open, formatSettings.CustomEscapeSequence);
var escapedClose = formatSettings.EscapeOption.GetEscapedWrapper(wrapper.Close, formatSettings.CustomEscapeSequence);
property = wrapper.EscapeContent(property, escapedOpen, escapedClose);
}
flatFile.Wrap(property, wrapper);
}
else
{
flatFile.Append(property);
}
if (formatSettings.FirstColumnOnly)
{
break;
}
if (column < lastColumn)
{
flatFile.Append(formatSettings.ColumnDelimiter);
}
}
if (formatSettings.FirstRowOnly)
{
break;
}
if (row < lastRow)
{
flatFile.Append(formatSettings.RowDelimiter);
}
}
return flatFile.ToString();
}
public static string ToJsonFormat(this Table<ITableProperty> propertyTable, JsonSerializationFormat format, FlatFileFormatSettings formatSettings)
{
switch (format)
{
case JsonSerializationFormat.Flat:
return propertyTable.ToJsonFlatFormat(formatSettings);
case JsonSerializationFormat.Hierarchy:
return propertyTable.ToJsonHierarchyFormat();
default:
Debug.LogError($"Unsupported {nameof(JsonSerializationFormat)} {format}.");
break;
}
return string.Empty;
}
private static string ToJsonFlatFormat(this Table<ITableProperty> propertyTable, FlatFileFormatSettings formatSettings)
{
var rowObjects = new Dictionary<string, Dictionary<string, string>>();
for (var row = 0; row < propertyTable.Rows; row++)
{
if (propertyTable.Columns > formatSettings.FirstColumnIndex)
{
var rowRootObjectName = propertyTable.Get(row, formatSettings.FirstColumnIndex).RootObject.name;
var rowData = new Dictionary<string, string>();
for (var column = formatSettings.FirstColumnIndex; column < propertyTable.Columns; column++)
{
var value = propertyTable.Get(row, column);
if (value != null)
{
rowData[value.PropertyPath] = value.GetProperty(formatSettings);
}
}
rowObjects[rowRootObjectName] = rowData;
}
}
return JsonConvert.SerializeObject(rowObjects, Formatting.Indented);
}
private static string ToJsonHierarchyFormat(this Table<ITableProperty> propertyTable)
{
var rowObjects = new Dictionary<string, JObject>();
for (var row = 0; row < propertyTable.Rows; row++)
{
if (propertyTable.Columns > 1)
{
var rowRootObject = propertyTable.Get(row, 1).RootObject;
var unityJson = rowRootObject is ScriptableObject ? JsonUtility.ToJson(rowRootObject) : EditorJsonUtility.ToJson(rowRootObject);
rowObjects[rowRootObject.name] = (JObject) JsonConvert.DeserializeObject(unityJson);
}
}
return JsonConvert.SerializeObject(rowObjects, Formatting.Indented);
}
private static string SanitizeHeader(string header)
{
return new string(header.Where(c => !char.IsWhiteSpace(c)).ToArray()).ToLower().Replace("m_", string.Empty);
}
}
}