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);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 |