支持加载 XSD
This commit is contained in:
parent
289f6551c1
commit
ce26ef298e
@ -37,11 +37,6 @@ namespace HashCalculator.GUI
|
||||
|
||||
public static AssetEntry? TryParse(XElement element)
|
||||
{
|
||||
if (element == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (element.Name.Namespace != ModXml.EalaAsset)
|
||||
{
|
||||
TracerListener.WriteLine($"Unknown namespace: {element.Name.Namespace}");
|
||||
@ -59,6 +54,27 @@ namespace HashCalculator.GUI
|
||||
return null;
|
||||
}
|
||||
|
||||
public static AssetEntry? TryParseXsd(XElement element)
|
||||
{
|
||||
if (element.Name.Namespace != ModXsd.Schema)
|
||||
{
|
||||
TracerListener.WriteLine($"Unknown namespace: {element.Name.Namespace}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var type = element.Attribute("name")?.Value
|
||||
?? throw new NotSupportedException();
|
||||
return new AssetEntry(element.Name.LocalName, type);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
TracerListener.WriteLine($"Failed to parse element: {e}");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public AssetEntry(XElement element)
|
||||
{
|
||||
if (element == null)
|
||||
@ -107,6 +123,18 @@ namespace HashCalculator.GUI
|
||||
TypeIdString = $"{TypeId:x8} ({TypeId,10})";
|
||||
}
|
||||
|
||||
private AssetEntry(string kind, string type)
|
||||
{
|
||||
Type = type;
|
||||
Name = string.Empty;
|
||||
NameString = $"<{kind} {Type}>";
|
||||
DisplayLabels = Array.Empty<string>();
|
||||
InstanceId = 0;
|
||||
TypeId = SageHash.CalculateBinaryHash(Type);
|
||||
InstanceIdString = string.Empty;
|
||||
TypeIdString = $"{TypeId:x8} ({TypeId,10})";
|
||||
}
|
||||
|
||||
public bool Equals(AssetEntry? entry)
|
||||
{
|
||||
return this == entry;
|
||||
|
@ -46,14 +46,14 @@ namespace HashCalculator.GUI
|
||||
ViewModel.StatusText = string.Empty;
|
||||
return;
|
||||
case InputEntryType.BigFile:
|
||||
await LoadBig(selected.Value).ConfigureAwait(true);
|
||||
await LoadBig(selected.Value);
|
||||
return;
|
||||
case InputEntryType.ManifestFile:
|
||||
await LoadManifestFromFile(selected.Value).ConfigureAwait(true);
|
||||
await LoadManifestFromFile(selected.Value);
|
||||
return;
|
||||
case InputEntryType.XmlFile:
|
||||
ViewModel.IsXml = true;
|
||||
await LoadXml(selected.Value).ConfigureAwait(true);
|
||||
case InputEntryType.XsdFile:
|
||||
await LoadXml(selected);
|
||||
return;
|
||||
default:
|
||||
throw new NotSupportedException();
|
||||
@ -125,9 +125,8 @@ namespace HashCalculator.GUI
|
||||
var first = bigViewModel.AllManifests.FirstOrDefault();
|
||||
if (first != null)
|
||||
{
|
||||
var name = VirtualFileSystem.GetFileNameWithoutExtension(first.Value);
|
||||
if (name.StartsWith("mod", StringComparison.OrdinalIgnoreCase)
|
||||
|| name.StartsWith("mapmetadata", StringComparison.OrdinalIgnoreCase))
|
||||
var name = VirtualFileSystem.GetFileNameWithoutExtension(first.Value).ToLowerInvariant();
|
||||
if (new[] { "mod", "mapmetadata_mod", "static", "global" }.Any(name.StartsWith))
|
||||
{
|
||||
bigViewModel.SelectedItem = first;
|
||||
await bigViewModel.SelectCommand.ExecuteTask(ViewModel).ConfigureAwait(true);
|
||||
@ -189,7 +188,7 @@ namespace HashCalculator.GUI
|
||||
tokenSource.Cancel();
|
||||
try
|
||||
{
|
||||
await task.ConfigureAwait(true);
|
||||
await task;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@ -197,11 +196,21 @@ namespace HashCalculator.GUI
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadXml(string path)
|
||||
private async Task LoadXml(InputEntry entry)
|
||||
{
|
||||
await CancelLoadingXml().ConfigureAwait(true);
|
||||
await CancelLoadingXml();
|
||||
var path = entry.Text;
|
||||
var type = entry.Type;
|
||||
using var tokenSource = new CancellationTokenSource();
|
||||
var task = LoadXmlInternal(path, tokenSource.Token);
|
||||
var modXml = type switch
|
||||
{
|
||||
InputEntryType.XmlFile => LoadModXml(path, tokenSource.Token),
|
||||
InputEntryType.XsdFile => LoadModXsd(path, tokenSource.Token),
|
||||
_ => throw new NotSupportedException(type.ToString())
|
||||
};
|
||||
ViewModel.IsXml = true;
|
||||
ViewModel.IsXsd = type == InputEntryType.XsdFile;
|
||||
var task = LoadXmlInternal(await modXml, tokenSource.Token);
|
||||
_lastXmlTask = (tokenSource, task).ToTuple();
|
||||
|
||||
await ExceptionWrapepr(async () =>
|
||||
@ -229,23 +238,32 @@ namespace HashCalculator.GUI
|
||||
}, "SAGE FastHash 计算器 - XML 加载失败…", "XML 文件加载失败,也许,你选择的 XML 文件并不是红警3使用的那种……\r\n").ConfigureAwait(true);
|
||||
}
|
||||
|
||||
private async Task LoadXmlInternal(string path, CancellationToken token)
|
||||
private async Task<IModXml> LoadModXml(string path, CancellationToken cancelToken)
|
||||
{
|
||||
if (ModXml.LocateSdkFromRegistry() is not { } sdkRoot)
|
||||
{
|
||||
var studio = await RequestOpenFile("选择 Mod Studio", ("EALAModStudio.exe", null));
|
||||
sdkRoot = Path.GetDirectoryName(studio);
|
||||
}
|
||||
var modXml = new ModXml(path, sdkRoot, token);
|
||||
|
||||
var modXml = new ModXml(path, sdkRoot, cancelToken);
|
||||
try
|
||||
{
|
||||
await FindCsf(modXml.BaseDirectory).ConfigureAwait(true);
|
||||
await FindCsf(modXml.BaseDirectory, cancelToken);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
TracerListener.WriteLine($"[ModXml] Failed to automatically find and load CSF: {exception}");
|
||||
}
|
||||
return modXml;
|
||||
}
|
||||
|
||||
private Task<IModXml> LoadModXsd(string path, CancellationToken token)
|
||||
{
|
||||
return Task.FromResult<IModXml>(new ModXsd(path, token));
|
||||
}
|
||||
|
||||
private async Task LoadXmlInternal(IModXml modXml, CancellationToken token)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var start = DateTimeOffset.UtcNow;
|
||||
@ -333,9 +351,9 @@ namespace HashCalculator.GUI
|
||||
});
|
||||
}
|
||||
|
||||
private static Task FindCsf(DirectoryInfo baseDirectory)
|
||||
private static Task FindCsf(DirectoryInfo baseDirectory, CancellationToken cancelToken)
|
||||
{
|
||||
return Task.Run(() =>
|
||||
return Task.Run(async () =>
|
||||
{
|
||||
if (!baseDirectory.Exists)
|
||||
{
|
||||
@ -355,6 +373,7 @@ namespace HashCalculator.GUI
|
||||
from data in directories.GetDirectories("Data")
|
||||
from csf in data.GetFiles("*.csf")
|
||||
select csf;
|
||||
csfs = csfs.AsParallel().WithCancellation(cancelToken);
|
||||
var result =
|
||||
csfs.FirstOrDefault(x => x.Name.Equals("gamestrings.csf", StringComparison.OrdinalIgnoreCase))
|
||||
?? csfs.FirstOrDefault();
|
||||
@ -363,8 +382,13 @@ namespace HashCalculator.GUI
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
|
||||
using var fileStream = result.OpenRead();
|
||||
Translator.Provider = new CsfTranslationProvider(fileStream);
|
||||
using var memoryStream = new MemoryStream();
|
||||
using (var fileStream = result.OpenRead())
|
||||
{
|
||||
await fileStream.CopyToAsync(memoryStream, cancelToken);
|
||||
memoryStream.Position = 0;
|
||||
}
|
||||
Translator.Provider = new CsfTranslationProvider(memoryStream);
|
||||
TracerListener.WriteLine($"[ModXml]: Automatically loaded csf from `{result.FullName}`");
|
||||
});
|
||||
}
|
||||
|
20
Converters/MultiBooleanConjunctionConverter.cs
Normal file
20
Converters/MultiBooleanConjunctionConverter.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace HashCalculator.GUI.Converters
|
||||
{
|
||||
class MultiBooleanConjunctionConverter : IMultiValueConverter
|
||||
{
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return values.Cast<bool>().Aggregate((a, b) => a && b);
|
||||
}
|
||||
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
45
Converters/MultiValueAggregateConverter.cs
Normal file
45
Converters/MultiValueAggregateConverter.cs
Normal file
@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace HashCalculator.GUI.Converters
|
||||
{
|
||||
class MultiValueAggregateConverter : List<IValueConverter?>, IMultiValueConverter
|
||||
{
|
||||
public IMultiValueConverter? Converter { get; set; }
|
||||
public IValueConverter? PostProcess { get; set; }
|
||||
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (Converter is not { } converter)
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(Converter)} of {nameof(MultiValueAggregateConverter)} is null");
|
||||
}
|
||||
if (Count > 0)
|
||||
{
|
||||
values = values.ToArray(); // 复制一份
|
||||
var length = Math.Min(Count, values.Length);
|
||||
for (var i = 0; i < length; ++i)
|
||||
{
|
||||
if (this[i] is IValueConverter c)
|
||||
{
|
||||
values[i] = c.Convert(values[i], targetType, parameter, culture);
|
||||
}
|
||||
}
|
||||
}
|
||||
var result = converter.Convert(values, targetType, parameter, culture);
|
||||
if (PostProcess is not { } postConverter)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
return postConverter.Convert(result, targetType, parameter, culture);
|
||||
}
|
||||
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
@ -8,17 +8,17 @@ namespace HashCalculator.GUI
|
||||
{
|
||||
internal class FileSystemSuggestions
|
||||
{
|
||||
private static readonly Dictionary<string, InputEntryType> Mapping = new Dictionary<string, InputEntryType>(StringComparer.OrdinalIgnoreCase)
|
||||
private static readonly Dictionary<string, InputEntryType> Mapping = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ ".big", InputEntryType.BigFile },
|
||||
{ ".xml", InputEntryType.XmlFile },
|
||||
{ ".w3x", InputEntryType.XmlFile },
|
||||
{ ".manifest", InputEntryType.ManifestFile },
|
||||
{ ".xsd", InputEntryType.XsdFile },
|
||||
};
|
||||
|
||||
private Search _search = new Search();
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1031")]
|
||||
public IEnumerable<InputEntry> ProvideFileSystemSuggestions(string? path)
|
||||
{
|
||||
var empty = Enumerable.Empty<InputEntry>();
|
||||
|
15
IModXml.cs
Normal file
15
IModXml.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace HashCalculator.GUI
|
||||
{
|
||||
interface IModXml
|
||||
{
|
||||
public int TotalFilesProcessed { get; }
|
||||
public int TotalAssets { get; }
|
||||
IAsyncEnumerable<AssetEntry> ProcessDocument();
|
||||
}
|
||||
}
|
@ -1,9 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using TechnologyAssembler.Core.IO;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace HashCalculator.GUI
|
||||
{
|
||||
@ -13,6 +8,7 @@ namespace HashCalculator.GUI
|
||||
BigFile,
|
||||
ManifestFile,
|
||||
XmlFile,
|
||||
XsdFile,
|
||||
BinaryFile,
|
||||
Path,
|
||||
}
|
||||
@ -25,6 +21,7 @@ namespace HashCalculator.GUI
|
||||
{ InputEntryType.BinaryFile, "可以计算这个文件的哈希值" },
|
||||
{ InputEntryType.ManifestFile, "可以尝试读取这个 manifest 文件,显示各个素材的哈希值" },
|
||||
{ InputEntryType.XmlFile, "可以尝试读取这个 XML,显示 XML 里定义的各个素材的哈希值" },
|
||||
{ InputEntryType.XsdFile, "可以尝试读取 XSD,显示 XSD 里定义的各个素材的哈希值" },
|
||||
{ InputEntryType.Path, string.Empty },
|
||||
};
|
||||
|
||||
|
@ -6,6 +6,8 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:l="clr-namespace:HashCalculator.GUI"
|
||||
xmlns:c="clr-namespace:HashCalculator.GUI.Converters"
|
||||
xmlns:sys="clr-namespace:System;assembly=System.Runtime"
|
||||
mc:Ignorable="d"
|
||||
Title="SAGE FastHash 哈希计算器"
|
||||
Height="600"
|
||||
@ -152,30 +154,37 @@
|
||||
Height="25"
|
||||
Margin="0,0,0,5"
|
||||
>
|
||||
<Grid
|
||||
<StackPanel
|
||||
DockPanel.Dock="Right"
|
||||
Width="190"
|
||||
Orientation="Horizontal"
|
||||
d:Visibility="Visible"
|
||||
Visibility="{Binding IsXml,
|
||||
Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
>
|
||||
<Button
|
||||
Content="{Binding LoadCsfText}"
|
||||
Command="{Binding LoadCsf}"
|
||||
CommandParameter="{Binding ElementName=Self}"
|
||||
Width="92"
|
||||
HorizontalAlignment="Left"
|
||||
/>
|
||||
<Button
|
||||
Content="取消加载"
|
||||
Content="取消加载 XML"
|
||||
Command="{Binding CancelXml}"
|
||||
Width="92"
|
||||
HorizontalAlignment="Right"
|
||||
Padding="8,0"
|
||||
Margin="0,0,8,0"
|
||||
Visibility="{Binding IsLoadingXml,
|
||||
Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
/>
|
||||
</Grid>
|
||||
<CheckBox
|
||||
<Button
|
||||
Content="{Binding LoadCsfText}"
|
||||
Command="{Binding LoadCsf}"
|
||||
CommandParameter="{Binding ElementName=Self}"
|
||||
Padding="16,0"
|
||||
Visibility="{Binding IsXsd,
|
||||
Converter={StaticResource FalseToVisibilityConverter}}"
|
||||
/>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
DockPanel.Dock="Right"
|
||||
Orientation="Horizontal"
|
||||
Visibility="{Binding IsXsd,
|
||||
Converter={StaticResource FalseToVisibilityConverter}}"
|
||||
>
|
||||
<CheckBox
|
||||
Content="只显示 GameObject"
|
||||
IsChecked="{Binding GameObjectOnly}"
|
||||
Command="{Binding FilterDisplayEntries}"
|
||||
@ -184,15 +193,15 @@
|
||||
VerticalAlignment="Center"
|
||||
/>
|
||||
<CheckBox
|
||||
DockPanel.Dock="Right"
|
||||
Content="搜索类型 ID"
|
||||
IsChecked="{Binding ConsiderTypeId}"
|
||||
Command="{Binding FilterDisplayEntries}"
|
||||
Padding="4,0"
|
||||
Margin="8,0"
|
||||
VerticalAlignment="Center"
|
||||
Visibility="{Binding GameObjectOnly, Converter={StaticResource FalseToVisibilityConverter}}"
|
||||
/>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock
|
||||
Text="{Binding DisplayEntries.Count, StringFormat=列表里显示了{0}个素材}"
|
||||
Margin="5,0,0,0"
|
||||
@ -232,6 +241,18 @@
|
||||
EnableRowVirtualization="True"
|
||||
VirtualizingPanel.VirtualizationMode="Recycling"
|
||||
>
|
||||
<DataGrid.Resources>
|
||||
<c:MultiValueAggregateConverter x:Key="IsXmlNotXsd">
|
||||
<c:MultiValueAggregateConverter.Converter>
|
||||
<c:MultiBooleanConjunctionConverter />
|
||||
</c:MultiValueAggregateConverter.Converter>
|
||||
<c:MultiValueAggregateConverter.PostProcess>
|
||||
<BooleanToVisibilityConverter />
|
||||
</c:MultiValueAggregateConverter.PostProcess>
|
||||
<x:Null />
|
||||
<c:BooleanInvertConverter />
|
||||
</c:MultiValueAggregateConverter>
|
||||
</DataGrid.Resources>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn
|
||||
Header="类型/素材名称"
|
||||
@ -241,14 +262,27 @@
|
||||
Header="哈希(InstanceId)"
|
||||
Binding="{Binding InstanceIdString}"
|
||||
FontFamily="Courier New"
|
||||
Visibility="{Binding Source={x:Reference Name=ReferenceProvider},
|
||||
Path=DataContext.IsXsd,
|
||||
Converter={StaticResource FalseToVisibilityConverter}}"
|
||||
/>
|
||||
<DataGridTextColumn
|
||||
Header="名称"
|
||||
Binding="{Binding LocalizedNames}"
|
||||
Visibility="{Binding Source={x:Reference Name=ReferenceProvider},
|
||||
Path=DataContext.IsXml,
|
||||
Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
>
|
||||
<DataGridTextColumn.Visibility>
|
||||
<MultiBinding Converter="{StaticResource IsXmlNotXsd}">
|
||||
<Binding
|
||||
Source="{x:Reference Name=ReferenceProvider}"
|
||||
Path="DataContext.IsXml"
|
||||
/>
|
||||
<Binding
|
||||
Source="{x:Reference Name=ReferenceProvider}"
|
||||
Path="DataContext.IsXsd"
|
||||
/>
|
||||
</MultiBinding>
|
||||
</DataGridTextColumn.Visibility>
|
||||
</DataGridTextColumn>
|
||||
<DataGridTextColumn
|
||||
Header="类型 ID"
|
||||
Binding="{Binding TypeIdString}"
|
||||
|
11
ModXml.cs
11
ModXml.cs
@ -1,7 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Win32;
|
||||
using Mvp.Xml.XInclude;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
@ -9,12 +10,10 @@ using System.Threading.Tasks;
|
||||
using System.Threading.Tasks.Dataflow;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Win32;
|
||||
using Mvp.Xml.XInclude;
|
||||
|
||||
namespace HashCalculator.GUI
|
||||
{
|
||||
public class ModXml
|
||||
public class ModXml : IModXml
|
||||
{
|
||||
protected enum DocumentOption
|
||||
{
|
||||
|
164
ModXsd.cs
Normal file
164
ModXsd.cs
Normal file
@ -0,0 +1,164 @@
|
||||
using Mvp.Xml.XInclude;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks.Dataflow;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace HashCalculator.GUI
|
||||
{
|
||||
public class ModXsd : IModXml
|
||||
{
|
||||
protected enum DocumentOption
|
||||
{
|
||||
Normal,
|
||||
IsEntryPoint
|
||||
}
|
||||
|
||||
public static readonly IEnumerable<string> NotSupportedAttributes = new[]
|
||||
{
|
||||
"fragid", "set-xml-id", "encoding", "accept", "accept-language"
|
||||
};
|
||||
public static XNamespace Schema = "http://www.w3.org/2001/XMLSchema";
|
||||
public int TotalFilesProcessed => _processed.Count;
|
||||
public int TotalAssets => _assets.Count;
|
||||
private readonly ConcurrentDictionary<string, byte> _processed = new();
|
||||
private readonly ConcurrentDictionary<AssetEntry, string> _assets = new();
|
||||
private readonly BufferBlock<AssetEntry> _entries = new();
|
||||
private readonly string _xmlFullPath;
|
||||
private readonly CancellationToken _token;
|
||||
|
||||
public ModXsd(string xmlPath, CancellationToken token)
|
||||
{
|
||||
_xmlFullPath = Path.GetFullPath(xmlPath);
|
||||
_token = token;
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<AssetEntry> ProcessDocument()
|
||||
{
|
||||
if (_processed.Any() || _assets.Any())
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
var task = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await ProcessDocumentInternal(_xmlFullPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (!_token.IsCancellationRequested)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_entries.Complete();
|
||||
}
|
||||
});
|
||||
|
||||
while (await _entries.OutputAvailableAsync())
|
||||
{
|
||||
yield return _entries.Receive();
|
||||
}
|
||||
|
||||
await task;
|
||||
}
|
||||
|
||||
private async Task ProcessDocumentInternal(string fullPath)
|
||||
{
|
||||
_token.ThrowIfCancellationRequested();
|
||||
if (!_processed.TryAdd(fullPath.ToUpperInvariant(), default))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var document = await GetFileAsync(fullPath);
|
||||
var rootName = document.Root?.Name
|
||||
?? throw new InvalidDataException("Document doesn't have a root");
|
||||
if (rootName != Schema + "schema")
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
var includes = from include in document.Root.Elements(Schema + "include")
|
||||
let includePath = GetIncludePath(include, fullPath)
|
||||
where includePath != null
|
||||
select Task.Run(() => ProcessDocumentInternal(includePath), _token);
|
||||
var includesTasks = includes.ToArray();
|
||||
|
||||
var items = from element in document.Root.Elements()
|
||||
let name = element.Name
|
||||
where name == Schema + "simpleType" || name == Schema + "complexType"
|
||||
let entry = AssetEntry.TryParseXsd(element)
|
||||
where entry != null
|
||||
select entry;
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (_assets.TryAdd(item, fullPath))
|
||||
{
|
||||
_entries.Post(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
var previousPath = _assets[item];
|
||||
TracerListener.WriteLine($"[ModXsd] `{fullPath}`: Attempted to add item `{item.Type}:{item.Name}` multiple times - already processed in `{previousPath}`");
|
||||
}
|
||||
}
|
||||
|
||||
await Task.WhenAll(includesTasks);
|
||||
}
|
||||
|
||||
private string? GetIncludePath(XElement include, string includerPath)
|
||||
{
|
||||
if (Path.GetDirectoryName(includerPath) is not { } currentDirectory)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
if (include.Name != Schema + "include")
|
||||
{
|
||||
throw new InvalidDataException();
|
||||
}
|
||||
|
||||
var source = include.Attribute("schemaLocation")?.Value;
|
||||
if (source is null)
|
||||
{
|
||||
throw new InvalidDataException();
|
||||
}
|
||||
|
||||
var includedSource = Path.GetFullPath(source, currentDirectory);
|
||||
if (!File.Exists(includedSource))
|
||||
{
|
||||
TracerListener.WriteLine($"[ModXsd]: Warning, include path does not exist! It is: {includedSource}");
|
||||
return null;
|
||||
}
|
||||
|
||||
return includedSource;
|
||||
}
|
||||
|
||||
private async Task<XDocument> GetFileAsync(string normalizedPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
var xml = await File.ReadAllBytesAsync(normalizedPath, _token);
|
||||
using var xmlStream = new MemoryStream(xml);
|
||||
using var reader = new XIncludingReader(normalizedPath, xmlStream);
|
||||
|
||||
reader.MoveToContent();
|
||||
return XDocument.Load(reader);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new InvalidDataException($"Failed to process XML file: {normalizedPath} - {e.Message}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
39
ViewModel.cs
39
ViewModel.cs
@ -37,6 +37,23 @@ namespace HashCalculator.GUI
|
||||
|
||||
Notify(propertyName);
|
||||
}
|
||||
|
||||
protected void SetField<X>(ref X field, X value, Action onChange, [CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
if (propertyName == null)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
if (EqualityComparer<X>.Default.Equals(field, value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
field = value;
|
||||
onChange();
|
||||
Notify(propertyName);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
@ -53,25 +70,21 @@ namespace HashCalculator.GUI
|
||||
public string? Filter
|
||||
{
|
||||
get => _filter;
|
||||
set
|
||||
{
|
||||
SetField(ref _filter, value);
|
||||
FilterDisplayEntries.Execute(null);
|
||||
}
|
||||
set => SetField(ref _filter, value, () => FilterDisplayEntries.Execute(null));
|
||||
}
|
||||
|
||||
private bool _gameObjectOnly;
|
||||
public bool GameObjectOnly
|
||||
{
|
||||
get => _gameObjectOnly;
|
||||
set => SetField(ref _gameObjectOnly, value);
|
||||
set => SetField(ref _gameObjectOnly, value, () => ConsiderTypeId &= !value);
|
||||
}
|
||||
|
||||
private bool _considerTypeId;
|
||||
public bool ConsiderTypeId
|
||||
{
|
||||
get => _considerTypeId;
|
||||
set => SetField(ref _considerTypeId, value);
|
||||
set => SetField(ref _considerTypeId, value, () => GameObjectOnly &= !value);
|
||||
}
|
||||
|
||||
public Command FilterDisplayEntries { get; }
|
||||
@ -95,7 +108,7 @@ namespace HashCalculator.GUI
|
||||
public bool IsXml
|
||||
{
|
||||
get => _isXml;
|
||||
set => SetField(ref _isXml, value);
|
||||
set => SetField(ref _isXml, value, () => IsXsd &= value);
|
||||
}
|
||||
private bool _isLoadingXml;
|
||||
public bool IsLoadingXml
|
||||
@ -103,6 +116,16 @@ namespace HashCalculator.GUI
|
||||
get => _isLoadingXml;
|
||||
set => SetField(ref _isLoadingXml, value);
|
||||
}
|
||||
private bool _isXsd;
|
||||
public bool IsXsd
|
||||
{
|
||||
get => _isXsd;
|
||||
set => SetField(ref _isXsd, value, () =>
|
||||
{
|
||||
IsXml |= value;
|
||||
ConsiderTypeId |= value;
|
||||
});
|
||||
}
|
||||
public Command CancelXml { get; }
|
||||
[SuppressMessage("Microsoft.Performance", "CA1822")]
|
||||
public string LoadCsfText => $"{(Translator.HasProvider ? "更换" : "加载")} CSF";
|
||||
|
Loading…
x
Reference in New Issue
Block a user