844 lines
30 KiB
C#
844 lines
30 KiB
C#
#if UNITY_EDITOR
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using ModelRenderer.Scripts.Common;
|
|
using PerfectWorld.Scripts.Task;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
|
|
namespace BrewMonster.Scripts.Task
|
|
{
|
|
public static class TaskCsvExporter
|
|
{
|
|
private const string MenuPath = "Tools/Task/Export Tasks To CSV";
|
|
private const string OutputAssetPath = "Assets/PerfectWorld/Exports/tasks_export.csv";
|
|
private const string KnownTaskContainerAssetPath = "Assets/PerfectWorld/SO/TaskTemplContainerSO.asset";
|
|
private const int MaxSerializationDepth = 12;
|
|
|
|
private static readonly string[] CustomHeaders =
|
|
{
|
|
"TaskId",
|
|
"ParentId",
|
|
"PrevSiblingId",
|
|
"NextSiblingId",
|
|
"FirstChildId",
|
|
"Depth",
|
|
"SubCount",
|
|
"Name",
|
|
"Description",
|
|
"OkText",
|
|
"NoText",
|
|
"Tribute",
|
|
"Signature",
|
|
"DelvTaskTalkJson",
|
|
"UnqualifiedTalkJson",
|
|
"DelvItemTalkJson",
|
|
"ExeTalkJson",
|
|
"AwardTalkJson"
|
|
};
|
|
|
|
private static readonly FieldInfo AllTaskTemplatesField = typeof(TaskTemplContainerSO).GetField(
|
|
"_allTaskTemplates",
|
|
BindingFlags.Instance | BindingFlags.NonPublic);
|
|
|
|
[MenuItem(MenuPath)]
|
|
public static void ExportTasksToCsv()
|
|
{
|
|
TaskTemplContainerSO container = null;
|
|
bool destroyTemporaryContainer = false;
|
|
|
|
try
|
|
{
|
|
container = LoadOrBuildTaskContainer(out destroyTemporaryContainer);
|
|
if (container == null)
|
|
{
|
|
Debug.LogError("[TaskCsvExporter] Could not load task data from SO or pack file.");
|
|
return;
|
|
}
|
|
|
|
container.BuildTaskTemplateMap();
|
|
|
|
List<ATaskTempl> allTemplates = GetAllTaskTemplates(container);
|
|
if (allTemplates.Count == 0)
|
|
{
|
|
Debug.LogError("[TaskCsvExporter] No task templates were found to export.");
|
|
return;
|
|
}
|
|
|
|
PrepareTemplatesForExport(container, allTemplates);
|
|
|
|
List<ATaskTempl> orderedTasks = BuildOrderedTaskList(container, allTemplates);
|
|
List<FieldInfo> fixedDataFields = GetOrderedPublicFields(typeof(ATaskTemplFixedData));
|
|
List<Dictionary<string, string>> rows = BuildRows(orderedTasks, fixedDataFields);
|
|
|
|
ValidateExport(rows, orderedTasks.Count, allTemplates.Count);
|
|
WriteCsv(rows, fixedDataFields, OutputAssetPath);
|
|
|
|
AssetDatabase.ImportAsset(OutputAssetPath, ImportAssetOptions.ForceUpdate);
|
|
AssetDatabase.Refresh();
|
|
|
|
int readableNameCount = rows.Count(row =>
|
|
row.TryGetValue("Name", out string value) && !string.IsNullOrWhiteSpace(value));
|
|
|
|
Debug.Log(
|
|
$"[TaskCsvExporter] Exported {rows.Count} task rows to {OutputAssetPath}. " +
|
|
$"SourceCount={allTemplates.Count}, NamedRows={readableNameCount}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogException(ex);
|
|
}
|
|
finally
|
|
{
|
|
if (destroyTemporaryContainer && container != null)
|
|
{
|
|
UnityEngine.Object.DestroyImmediate(container);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static TaskTemplContainerSO LoadOrBuildTaskContainer(out bool destroyTemporaryContainer)
|
|
{
|
|
destroyTemporaryContainer = false;
|
|
|
|
TaskTemplContainerSO existing = LoadExistingTaskContainer();
|
|
if (HasTaskData(existing))
|
|
{
|
|
return existing;
|
|
}
|
|
|
|
string taskPackPath = Path.Combine(Application.streamingAssetsPath, "data/tasks.data");
|
|
if (!File.Exists(taskPackPath))
|
|
{
|
|
throw new FileNotFoundException(
|
|
$"Task pack file not found at '{taskPackPath}'.",
|
|
taskPackPath);
|
|
}
|
|
|
|
TaskTemplContainerSO temporaryContainer = ScriptableObject.CreateInstance<TaskTemplContainerSO>();
|
|
temporaryContainer.LoadAllTasksFromPack();
|
|
destroyTemporaryContainer = true;
|
|
return temporaryContainer;
|
|
}
|
|
|
|
private static TaskTemplContainerSO LoadExistingTaskContainer()
|
|
{
|
|
TaskTemplContainerSO container = AssetDatabase.LoadAssetAtPath<TaskTemplContainerSO>(KnownTaskContainerAssetPath);
|
|
if (container != null)
|
|
{
|
|
return container;
|
|
}
|
|
|
|
string[] guids = AssetDatabase.FindAssets("t:TaskTemplContainerSO");
|
|
foreach (string guid in guids)
|
|
{
|
|
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
|
|
container = AssetDatabase.LoadAssetAtPath<TaskTemplContainerSO>(assetPath);
|
|
if (container != null)
|
|
{
|
|
return container;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static bool HasTaskData(TaskTemplContainerSO container)
|
|
{
|
|
return container != null && GetAllTaskTemplates(container).Count > 0;
|
|
}
|
|
|
|
private static List<ATaskTempl> GetAllTaskTemplates(TaskTemplContainerSO container)
|
|
{
|
|
if (container == null || AllTaskTemplatesField == null)
|
|
{
|
|
return new List<ATaskTempl>();
|
|
}
|
|
|
|
List<ATaskTempl> templates = AllTaskTemplatesField.GetValue(container) as List<ATaskTempl>;
|
|
return templates?.Where(template => template != null).ToList() ?? new List<ATaskTempl>();
|
|
}
|
|
|
|
private static void PrepareTemplatesForExport(TaskTemplContainerSO container, IEnumerable<ATaskTempl> templates)
|
|
{
|
|
foreach (ATaskTempl template in templates)
|
|
{
|
|
template.FillUnserializableDataWhenPlayGame(container);
|
|
}
|
|
}
|
|
|
|
private static List<ATaskTempl> BuildOrderedTaskList(TaskTemplContainerSO container, IReadOnlyCollection<ATaskTempl> allTemplates)
|
|
{
|
|
var ordered = new List<ATaskTempl>(allTemplates.Count);
|
|
var visitedIds = new HashSet<uint>();
|
|
|
|
foreach (ATaskTempl topTask in container.TopTaskTemplates)
|
|
{
|
|
TraverseTaskTree(topTask, ordered, visitedIds);
|
|
}
|
|
|
|
foreach (ATaskTempl template in allTemplates.OrderBy(template => template.m_ID))
|
|
{
|
|
TraverseTaskTree(template, ordered, visitedIds);
|
|
}
|
|
|
|
return ordered;
|
|
}
|
|
|
|
private static void TraverseTaskTree(ATaskTempl task, ICollection<ATaskTempl> ordered, ISet<uint> visitedIds)
|
|
{
|
|
if (task == null || !visitedIds.Add(task.m_ID))
|
|
{
|
|
return;
|
|
}
|
|
|
|
ordered.Add(task);
|
|
|
|
ATaskTempl child = task.m_pFirstChild;
|
|
while (child != null)
|
|
{
|
|
TraverseTaskTree(child, ordered, visitedIds);
|
|
child = child.m_pNextSibling;
|
|
}
|
|
}
|
|
|
|
private static List<Dictionary<string, string>> BuildRows(IEnumerable<ATaskTempl> tasks, IReadOnlyList<FieldInfo> fixedDataFields)
|
|
{
|
|
var rows = new List<Dictionary<string, string>>();
|
|
|
|
foreach (ATaskTempl task in tasks)
|
|
{
|
|
var row = new Dictionary<string, string>(StringComparer.Ordinal)
|
|
{
|
|
["TaskId"] = task.m_ID.ToString(CultureInfo.InvariantCulture),
|
|
["ParentId"] = task.ParentID.ToString(CultureInfo.InvariantCulture),
|
|
["PrevSiblingId"] = task.PrevSiblingID.ToString(CultureInfo.InvariantCulture),
|
|
["NextSiblingId"] = task.NextSiblingID.ToString(CultureInfo.InvariantCulture),
|
|
["FirstChildId"] = task.FirstChildID.ToString(CultureInfo.InvariantCulture),
|
|
["Depth"] = task.m_uDepth.ToString(CultureInfo.InvariantCulture),
|
|
["SubCount"] = task.m_nSubCount.ToString(CultureInfo.InvariantCulture),
|
|
["Name"] = ByteToStringUtils.UshortArrayToUnicodeString(task.m_FixedData.m_szName),
|
|
["Description"] = ByteToStringUtils.UshortArrayToUnicodeString(task.m_pwstrDescript),
|
|
["OkText"] = ByteToStringUtils.UshortArrayToUnicodeString(task.m_pwstrOkText),
|
|
["NoText"] = ByteToStringUtils.UshortArrayToUnicodeString(task.m_pwstrNoText),
|
|
["Tribute"] = ByteToStringUtils.UshortArrayToUnicodeString(task.m_pwstrTribute),
|
|
["Signature"] = ByteToStringUtils.UshortArrayToUnicodeString(task.m_FixedData.m_pszSignature),
|
|
["DelvTaskTalkJson"] = SerializeJsonValue(task.m_DelvTaskTalk, typeof(talk_proc), "m_DelvTaskTalk", 0),
|
|
["UnqualifiedTalkJson"] = SerializeJsonValue(task.m_UnqualifiedTalk, typeof(talk_proc), "m_UnqualifiedTalk", 0),
|
|
["DelvItemTalkJson"] = SerializeJsonValue(task.m_DelvItemTalk, typeof(talk_proc), "m_DelvItemTalk", 0),
|
|
["ExeTalkJson"] = SerializeJsonValue(task.m_ExeTalk, typeof(talk_proc), "m_ExeTalk", 0),
|
|
["AwardTalkJson"] = SerializeJsonValue(task.m_AwardTalk, typeof(talk_proc), "m_AwardTalk", 0)
|
|
};
|
|
|
|
object boxedFixedData = task.m_FixedData;
|
|
foreach (FieldInfo field in fixedDataFields)
|
|
{
|
|
object value = field.GetValue(boxedFixedData);
|
|
row[field.Name] = SerializeColumnValue(value, field.FieldType, field.Name);
|
|
}
|
|
|
|
rows.Add(row);
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
private static void ValidateExport(IReadOnlyCollection<Dictionary<string, string>> rows, int traversedCount, int sourceCount)
|
|
{
|
|
if (rows.Count != traversedCount)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"CSV row count mismatch. Rows={rows.Count}, Traversed={traversedCount}.");
|
|
}
|
|
|
|
if (traversedCount != sourceCount)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Task traversal mismatch. Traversed={traversedCount}, Source={sourceCount}.");
|
|
}
|
|
}
|
|
|
|
private static void WriteCsv(IEnumerable<Dictionary<string, string>> rows, IReadOnlyList<FieldInfo> fixedDataFields, string assetPath)
|
|
{
|
|
string absolutePath = GetAbsoluteProjectPath(assetPath);
|
|
string directory = Path.GetDirectoryName(absolutePath);
|
|
if (!string.IsNullOrEmpty(directory))
|
|
{
|
|
Directory.CreateDirectory(directory);
|
|
}
|
|
|
|
var headers = new List<string>(CustomHeaders.Length + fixedDataFields.Count);
|
|
headers.AddRange(CustomHeaders);
|
|
headers.AddRange(fixedDataFields.Select(field => field.Name));
|
|
|
|
var builder = new StringBuilder(1024 * 1024);
|
|
builder.AppendLine(string.Join(",", headers.Select(CsvEscape)));
|
|
|
|
foreach (Dictionary<string, string> row in rows)
|
|
{
|
|
string[] values = headers
|
|
.Select(header => row.TryGetValue(header, out string value) ? value : string.Empty)
|
|
.Select(CsvEscape)
|
|
.ToArray();
|
|
|
|
builder.AppendLine(string.Join(",", values));
|
|
}
|
|
|
|
File.WriteAllText(absolutePath, builder.ToString(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: true));
|
|
}
|
|
|
|
private static string GetAbsoluteProjectPath(string assetPath)
|
|
{
|
|
string projectRoot = Directory.GetParent(Application.dataPath)?.FullName ?? Directory.GetCurrentDirectory();
|
|
return Path.Combine(projectRoot, assetPath);
|
|
}
|
|
|
|
private static string CsvEscape(string value)
|
|
{
|
|
value ??= string.Empty;
|
|
|
|
bool needsQuotes =
|
|
value.IndexOf(',') >= 0 ||
|
|
value.IndexOf('"') >= 0 ||
|
|
value.IndexOf('\n') >= 0 ||
|
|
value.IndexOf('\r') >= 0;
|
|
|
|
if (!needsQuotes)
|
|
{
|
|
return value;
|
|
}
|
|
|
|
return "\"" + value.Replace("\"", "\"\"") + "\"";
|
|
}
|
|
|
|
private static string SerializeColumnValue(object value, Type declaredType, string path)
|
|
{
|
|
if (value == null)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
Type type = Nullable.GetUnderlyingType(declaredType) ?? declaredType;
|
|
|
|
if (TrySerializeLeafValue(value, type, path, false, out string leafValue))
|
|
{
|
|
return leafValue;
|
|
}
|
|
|
|
if (type.IsArray)
|
|
{
|
|
return SerializeJsonValue(value, type, path, 0);
|
|
}
|
|
|
|
if (typeof(IEnumerable).IsAssignableFrom(type) && type != typeof(string))
|
|
{
|
|
return SerializeEnumerableValue((IEnumerable)value, path, 0);
|
|
}
|
|
|
|
return SerializeJsonValue(value, type, path, 0);
|
|
}
|
|
|
|
private static string SerializeJsonValue(object value, Type declaredType, string path, int depth)
|
|
{
|
|
if (value == null)
|
|
{
|
|
return "null";
|
|
}
|
|
|
|
if (depth >= MaxSerializationDepth)
|
|
{
|
|
return QuoteJson("<max-depth>");
|
|
}
|
|
|
|
Type type = Nullable.GetUnderlyingType(declaredType) ?? declaredType;
|
|
|
|
if (TrySerializeLeafValue(value, type, path, true, out string leafValue))
|
|
{
|
|
return leafValue;
|
|
}
|
|
|
|
if (type.IsArray)
|
|
{
|
|
return SerializeArrayValue((Array)value, path, depth + 1);
|
|
}
|
|
|
|
if (typeof(IEnumerable).IsAssignableFrom(type) && type != typeof(string))
|
|
{
|
|
return SerializeEnumerableValue((IEnumerable)value, path, depth + 1);
|
|
}
|
|
|
|
return SerializeObjectValue(value, type, path, depth + 1);
|
|
}
|
|
|
|
private static string SerializeObjectValue(object value, Type type, string path, int depth)
|
|
{
|
|
List<FieldInfo> fields = GetOrderedPublicFields(type);
|
|
if (fields.Count == 0)
|
|
{
|
|
return QuoteJson(Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty);
|
|
}
|
|
|
|
var builder = new StringBuilder();
|
|
builder.Append('{');
|
|
|
|
for (int i = 0; i < fields.Count; i++)
|
|
{
|
|
FieldInfo field = fields[i];
|
|
if (i > 0)
|
|
{
|
|
builder.Append(',');
|
|
}
|
|
|
|
object fieldValue = field.GetValue(value);
|
|
string fieldPath = string.IsNullOrEmpty(path) ? field.Name : path + "." + field.Name;
|
|
|
|
builder.Append(QuoteJson(field.Name));
|
|
builder.Append(':');
|
|
builder.Append(SerializeJsonValue(fieldValue, field.FieldType, fieldPath, depth));
|
|
}
|
|
|
|
builder.Append('}');
|
|
return builder.ToString();
|
|
}
|
|
|
|
private static string SerializeArrayValue(Array array, string path, int depth)
|
|
{
|
|
if (array == null)
|
|
{
|
|
return "null";
|
|
}
|
|
|
|
Type elementType = array.GetType().GetElementType() ?? typeof(object);
|
|
|
|
if (array.Rank == 2)
|
|
{
|
|
return SerializeMatrixValue(array, elementType, path, depth);
|
|
}
|
|
|
|
var builder = new StringBuilder();
|
|
builder.Append('[');
|
|
|
|
int index = 0;
|
|
foreach (object item in array)
|
|
{
|
|
if (index++ > 0)
|
|
{
|
|
builder.Append(',');
|
|
}
|
|
|
|
builder.Append(SerializeJsonValue(item, elementType, path + "[]", depth));
|
|
}
|
|
|
|
builder.Append(']');
|
|
return builder.ToString();
|
|
}
|
|
|
|
private static string SerializeMatrixValue(Array matrix, Type elementType, string path, int depth)
|
|
{
|
|
if (elementType == typeof(ushort) && IsTaskCharField(path))
|
|
{
|
|
return SerializeUnicodeRowArray(ReadUnicodeRows((ushort[,])matrix));
|
|
}
|
|
|
|
if (elementType == typeof(byte) && IsByteTextField(path))
|
|
{
|
|
return SerializeUnicodeRowArray(ReadUnicodeRows((byte[,])matrix));
|
|
}
|
|
|
|
var builder = new StringBuilder();
|
|
builder.Append('[');
|
|
|
|
int rows = matrix.GetLength(0);
|
|
int cols = matrix.GetLength(1);
|
|
|
|
for (int row = 0; row < rows; row++)
|
|
{
|
|
if (row > 0)
|
|
{
|
|
builder.Append(',');
|
|
}
|
|
|
|
builder.Append('[');
|
|
for (int col = 0; col < cols; col++)
|
|
{
|
|
if (col > 0)
|
|
{
|
|
builder.Append(',');
|
|
}
|
|
|
|
object elementValue = matrix.GetValue(row, col);
|
|
builder.Append(SerializeJsonValue(elementValue, elementType, path + "[]", depth));
|
|
}
|
|
|
|
builder.Append(']');
|
|
}
|
|
|
|
builder.Append(']');
|
|
return builder.ToString();
|
|
}
|
|
|
|
private static string SerializeEnumerableValue(IEnumerable enumerable, string path, int depth)
|
|
{
|
|
var builder = new StringBuilder();
|
|
builder.Append('[');
|
|
|
|
bool first = true;
|
|
foreach (object item in enumerable)
|
|
{
|
|
if (!first)
|
|
{
|
|
builder.Append(',');
|
|
}
|
|
|
|
first = false;
|
|
Type itemType = item?.GetType() ?? typeof(object);
|
|
builder.Append(SerializeJsonValue(item, itemType, path + "[]", depth));
|
|
}
|
|
|
|
builder.Append(']');
|
|
return builder.ToString();
|
|
}
|
|
|
|
private static bool TrySerializeLeafValue(object value, Type type, string path, bool jsonContext, out string serializedValue)
|
|
{
|
|
if (type == typeof(string))
|
|
{
|
|
serializedValue = jsonContext
|
|
? QuoteJson((string)value)
|
|
: (string)value ?? string.Empty;
|
|
return true;
|
|
}
|
|
|
|
if (type == typeof(bool))
|
|
{
|
|
serializedValue = ((bool)value) ? "true" : "false";
|
|
return true;
|
|
}
|
|
|
|
if (IsNumericType(type))
|
|
{
|
|
serializedValue = Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty;
|
|
return true;
|
|
}
|
|
|
|
if (type.IsEnum)
|
|
{
|
|
string enumValue = $"{Convert.ToUInt64(value, CultureInfo.InvariantCulture)}:{value}";
|
|
serializedValue = jsonContext ? QuoteJson(enumValue) : enumValue;
|
|
return true;
|
|
}
|
|
|
|
if (type == typeof(ushort[]))
|
|
{
|
|
string text = SerializeUshortArray((ushort[])value, path);
|
|
serializedValue = jsonContext && LooksLikeJson(text) ? text : (jsonContext ? QuoteJson(text) : text);
|
|
return true;
|
|
}
|
|
|
|
if (type == typeof(byte[]))
|
|
{
|
|
string text = SerializeByteArray((byte[])value, path);
|
|
serializedValue = jsonContext && LooksLikeJson(text) ? text : (jsonContext ? QuoteJson(text) : text);
|
|
return true;
|
|
}
|
|
|
|
if (type == typeof(char))
|
|
{
|
|
string charValue = Convert.ToString(value, CultureInfo.InvariantCulture);
|
|
serializedValue = jsonContext ? QuoteJson(charValue) : charValue;
|
|
return true;
|
|
}
|
|
|
|
serializedValue = null;
|
|
return false;
|
|
}
|
|
|
|
private static string SerializeUshortArray(ushort[] values, string path)
|
|
{
|
|
if (values == null || values.Length == 0)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
if (IsTaskCharField(path))
|
|
{
|
|
return SerializeUnicodeRowArray(ReadUnicodeRows(values, TaskTemplConstants.TASK_AWARD_MAX_DISPLAY_CHAR_LEN));
|
|
}
|
|
|
|
if (IsLikelyTextField(path))
|
|
{
|
|
return ByteToStringUtils.UshortArrayToUnicodeString(values);
|
|
}
|
|
|
|
return SerializePrimitiveArray(values);
|
|
}
|
|
|
|
private static string SerializeByteArray(byte[] values, string path)
|
|
{
|
|
if (values == null || values.Length == 0)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
if (IsByteTextField(path))
|
|
{
|
|
return SerializeUnicodeRowArray(ReadUnicodeRows(values, TaskTemplConstants.TASK_AWARD_MAX_DISPLAY_CHAR_LEN));
|
|
}
|
|
|
|
if (IsLikelyTextField(path))
|
|
{
|
|
return DecodeByteArrayAsUnicode(values);
|
|
}
|
|
|
|
return SerializePrimitiveArray(values);
|
|
}
|
|
|
|
private static string SerializePrimitiveArray<T>(IEnumerable<T> values)
|
|
{
|
|
return "[" + string.Join(",", values.Select(SerializePrimitiveElement)) + "]";
|
|
}
|
|
|
|
private static string SerializePrimitiveElement<T>(T value)
|
|
{
|
|
if (value is null)
|
|
{
|
|
return "null";
|
|
}
|
|
|
|
Type type = value.GetType();
|
|
if (type == typeof(bool))
|
|
{
|
|
return ((bool)(object)value) ? "true" : "false";
|
|
}
|
|
|
|
return Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty;
|
|
}
|
|
|
|
private static List<string> ReadUnicodeRows(ushort[] flatValues, int rowWidth)
|
|
{
|
|
var rows = new List<string>();
|
|
if (flatValues == null || flatValues.Length == 0 || rowWidth <= 0)
|
|
{
|
|
return rows;
|
|
}
|
|
|
|
int rowCount = flatValues.Length / rowWidth;
|
|
for (int row = 0; row < rowCount; row++)
|
|
{
|
|
var buffer = new ushort[rowWidth];
|
|
Array.Copy(flatValues, row * rowWidth, buffer, 0, rowWidth);
|
|
rows.Add(ByteToStringUtils.UshortArrayToUnicodeString(buffer));
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
private static List<string> ReadUnicodeRows(ushort[,] matrix)
|
|
{
|
|
var rows = new List<string>();
|
|
if (matrix == null)
|
|
{
|
|
return rows;
|
|
}
|
|
|
|
int rowCount = matrix.GetLength(0);
|
|
int colCount = matrix.GetLength(1);
|
|
|
|
for (int row = 0; row < rowCount; row++)
|
|
{
|
|
var buffer = new ushort[colCount];
|
|
for (int col = 0; col < colCount; col++)
|
|
{
|
|
buffer[col] = matrix[row, col];
|
|
}
|
|
|
|
rows.Add(ByteToStringUtils.UshortArrayToUnicodeString(buffer));
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
private static List<string> ReadUnicodeRows(byte[] flatValues, int rowWidth)
|
|
{
|
|
var rows = new List<string>();
|
|
if (flatValues == null || flatValues.Length == 0 || rowWidth <= 0)
|
|
{
|
|
return rows;
|
|
}
|
|
|
|
int rowCount = flatValues.Length / rowWidth;
|
|
for (int row = 0; row < rowCount; row++)
|
|
{
|
|
var buffer = new byte[rowWidth];
|
|
Array.Copy(flatValues, row * rowWidth, buffer, 0, rowWidth);
|
|
rows.Add(DecodeByteArrayAsUnicode(buffer));
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
private static List<string> ReadUnicodeRows(byte[,] matrix)
|
|
{
|
|
var rows = new List<string>();
|
|
if (matrix == null)
|
|
{
|
|
return rows;
|
|
}
|
|
|
|
int rowCount = matrix.GetLength(0);
|
|
int colCount = matrix.GetLength(1);
|
|
|
|
for (int row = 0; row < rowCount; row++)
|
|
{
|
|
var buffer = new byte[colCount];
|
|
for (int col = 0; col < colCount; col++)
|
|
{
|
|
buffer[col] = matrix[row, col];
|
|
}
|
|
|
|
rows.Add(DecodeByteArrayAsUnicode(buffer));
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
private static string DecodeByteArrayAsUnicode(byte[] values)
|
|
{
|
|
if (values == null || values.Length == 0)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
var widened = new ushort[values.Length];
|
|
for (int i = 0; i < values.Length; i++)
|
|
{
|
|
widened[i] = values[i];
|
|
}
|
|
|
|
return ByteToStringUtils.UshortArrayToUnicodeString(widened);
|
|
}
|
|
|
|
private static string SerializeUnicodeRowArray(IEnumerable<string> rows)
|
|
{
|
|
return "[" + string.Join(",", rows.Select(QuoteJson)) + "]";
|
|
}
|
|
|
|
private static string QuoteJson(string value)
|
|
{
|
|
value ??= string.Empty;
|
|
|
|
var builder = new StringBuilder(value.Length + 8);
|
|
builder.Append('"');
|
|
foreach (char ch in value)
|
|
{
|
|
switch (ch)
|
|
{
|
|
case '\\':
|
|
builder.Append("\\\\");
|
|
break;
|
|
case '"':
|
|
builder.Append("\\\"");
|
|
break;
|
|
case '\n':
|
|
builder.Append("\\n");
|
|
break;
|
|
case '\r':
|
|
builder.Append("\\r");
|
|
break;
|
|
case '\t':
|
|
builder.Append("\\t");
|
|
break;
|
|
default:
|
|
builder.Append(ch);
|
|
break;
|
|
}
|
|
}
|
|
|
|
builder.Append('"');
|
|
return builder.ToString();
|
|
}
|
|
|
|
private static bool LooksLikeJson(string value)
|
|
{
|
|
if (string.IsNullOrEmpty(value))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return value[0] == '[' || value[0] == '{' || value == "null";
|
|
}
|
|
|
|
private static bool IsTaskCharField(string path)
|
|
{
|
|
return path?.IndexOf("TaskChar", StringComparison.OrdinalIgnoreCase) >= 0;
|
|
}
|
|
|
|
private static bool IsByteTextField(string path)
|
|
{
|
|
return path?.IndexOf("pszExp", StringComparison.OrdinalIgnoreCase) >= 0;
|
|
}
|
|
|
|
private static bool IsLikelyTextField(string path)
|
|
{
|
|
if (string.IsNullOrEmpty(path))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
string lastSegment = path;
|
|
int separatorIndex = path.LastIndexOf('.');
|
|
if (separatorIndex >= 0 && separatorIndex < path.Length - 1)
|
|
{
|
|
lastSegment = path.Substring(separatorIndex + 1);
|
|
}
|
|
|
|
return lastSegment.IndexOf("name", StringComparison.OrdinalIgnoreCase) >= 0
|
|
|| lastSegment.IndexOf("text", StringComparison.OrdinalIgnoreCase) >= 0
|
|
|| lastSegment.IndexOf("desc", StringComparison.OrdinalIgnoreCase) >= 0
|
|
|| lastSegment.IndexOf("sign", StringComparison.OrdinalIgnoreCase) >= 0
|
|
|| lastSegment.IndexOf("tribute", StringComparison.OrdinalIgnoreCase) >= 0
|
|
|| lastSegment.IndexOf("str", StringComparison.OrdinalIgnoreCase) >= 0
|
|
|| lastSegment.IndexOf("psz", StringComparison.OrdinalIgnoreCase) >= 0
|
|
|| lastSegment.IndexOf("wsz", StringComparison.OrdinalIgnoreCase) >= 0;
|
|
}
|
|
|
|
private static bool IsNumericType(Type type)
|
|
{
|
|
type = Nullable.GetUnderlyingType(type) ?? type;
|
|
|
|
switch (Type.GetTypeCode(type))
|
|
{
|
|
case TypeCode.Byte:
|
|
case TypeCode.SByte:
|
|
case TypeCode.UInt16:
|
|
case TypeCode.UInt32:
|
|
case TypeCode.UInt64:
|
|
case TypeCode.Int16:
|
|
case TypeCode.Int32:
|
|
case TypeCode.Int64:
|
|
case TypeCode.Decimal:
|
|
case TypeCode.Double:
|
|
case TypeCode.Single:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static List<FieldInfo> GetOrderedPublicFields(Type type)
|
|
{
|
|
return type
|
|
.GetFields(BindingFlags.Instance | BindingFlags.Public)
|
|
.OrderBy(field => field.MetadataToken)
|
|
.ToList();
|
|
}
|
|
}
|
|
}
|
|
#endif
|