165 lines
5.4 KiB
C#
165 lines
5.4 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|
|
}
|