mirror of https://github.com/icsharpcode/ILSpy.git
Browse Source
Five new IResourceNodeFactory implementations route .xml/.xsd/.xslt to a shared XmlResourceEntryNode (text + XML highlighting via SyntaxExtensionOverride on the output), .xaml to XamlResourceEntryNode (same pattern), and .png/.gif/.bmp/.jpg / .ico / .cur to image-rendering nodes that inline the bitmap with a Save button. SyntaxExtensionOverride is read after Decompile to switch the editor's highlighter without changing the active language. Cursor handling flips byte 2 of the on-disk header so Skia decodes the .cur as an icon for preview while Save preserves the original bytes. ResourceFactoryTests pin the dispatch — including .xsd — and the existing assembly-tree assertion is relaxed since typed entries are no longer all ResourceTreeNodes. Assisted-by: Claude:claude-opus-4-7:Claude Codepull/3755/head
9 changed files with 518 additions and 2 deletions
@ -0,0 +1,76 @@
@@ -0,0 +1,76 @@
|
||||
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System.Text; |
||||
|
||||
using Avalonia.Headless.NUnit; |
||||
|
||||
using AwesomeAssertions; |
||||
|
||||
using ICSharpCode.Decompiler.Metadata; |
||||
|
||||
using ILSpy.AppEnv; |
||||
using ILSpy.TreeNodes; |
||||
using ILSpy.Views; |
||||
|
||||
using NUnit.Framework; |
||||
|
||||
namespace ICSharpCode.ILSpy.Tests; |
||||
|
||||
[TestFixture] |
||||
public class ResourceFactoryTests |
||||
{ |
||||
// Touch the composition host once so ILSpyTreeNode's static `ResourceNodeFactories`
|
||||
// initialiser succeeds. AvaloniaTest already boots the app builder; resolving the
|
||||
// MainWindow ensures App.Initialize has run and AppComposition.Current is wired.
|
||||
static void EnsureComposition() => AppComposition.Current.GetExport<MainWindow>(); |
||||
|
||||
[AvaloniaTest] |
||||
[TestCase("schema.xsd")] |
||||
[TestCase("config.xml")] |
||||
[TestCase("transform.xslt")] |
||||
[TestCase("Window.xaml")] |
||||
[TestCase("logo.png")] |
||||
[TestCase("logo.gif")] |
||||
[TestCase("logo.bmp")] |
||||
[TestCase("logo.jpg")] |
||||
[TestCase("favicon.ico")] |
||||
[TestCase("pointer.cur")] |
||||
[TestCase("strings.resources")] |
||||
public void Typed_Resource_Names_Route_To_Specialised_Node(string name) |
||||
{ |
||||
EnsureComposition(); |
||||
// Tiny payload — node creation must not depend on stream contents being parseable.
|
||||
var payload = Encoding.UTF8.GetBytes("<root />"); |
||||
var node = ResourceEntryNode.Create(new ByteArrayResource(name, payload)); |
||||
|
||||
// Anything ending in a known extension must NOT fall back to the generic node — the
|
||||
// dispatcher's whole job is to pick the right typed handler. Compare by exact type
|
||||
// (not BeOfType) since SharpTreeNode has a custom .Should() extension in this assembly.
|
||||
node.GetType().Should().NotBe(typeof(ResourceTreeNode), |
||||
$"resource '{name}' should be routed to a specialised node, not the generic fallback"); |
||||
} |
||||
|
||||
[AvaloniaTest] |
||||
public void Unknown_Extension_Falls_Back_To_Generic_Resource_Node() |
||||
{ |
||||
EnsureComposition(); |
||||
var node = ResourceEntryNode.Create(new ByteArrayResource("data.bin", new byte[] { 1, 2, 3 })); |
||||
node.GetType().Should().Be(typeof(ResourceTreeNode)); |
||||
} |
||||
} |
||||
@ -0,0 +1,96 @@
@@ -0,0 +1,96 @@
|
||||
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System; |
||||
using System.Composition; |
||||
using System.IO; |
||||
|
||||
using global::Avalonia.Controls; |
||||
using global::Avalonia.Media.Imaging; |
||||
|
||||
using ICSharpCode.Decompiler; |
||||
using ICSharpCode.Decompiler.CSharp.ProjectDecompiler; |
||||
using ICSharpCode.Decompiler.Metadata; |
||||
using ICSharpCode.ILSpyX.Abstractions; |
||||
|
||||
using ILSpy.Commands; |
||||
using ILSpy.Languages; |
||||
using ILSpy.TextView; |
||||
|
||||
namespace ILSpy.TreeNodes |
||||
{ |
||||
[Export(typeof(IResourceNodeFactory))] |
||||
[Shared] |
||||
sealed class CursorResourceNodeFactory : IResourceNodeFactory |
||||
{ |
||||
public ITreeNode? CreateNode(Resource resource) |
||||
{ |
||||
if (resource.Name.EndsWith(".cur", StringComparison.OrdinalIgnoreCase)) |
||||
return new CursorResourceEntryNode(resource.Name, resource.TryOpenStream); |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
sealed class CursorResourceEntryNode : ResourceEntryNode |
||||
{ |
||||
public CursorResourceEntryNode(string key, Func<Stream?> openStream) |
||||
: base(key, () => openStream() ?? Stream.Null) |
||||
{ |
||||
} |
||||
|
||||
public override object Icon => Images.Images.Resource; |
||||
|
||||
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) |
||||
{ |
||||
if (output is not ISmartTextOutput smart) |
||||
return; |
||||
byte[] cursor; |
||||
using (var src = OpenStream()) |
||||
{ |
||||
if (src == Stream.Null) |
||||
{ |
||||
output.WriteLine("ILSpy: Failed opening resource stream."); |
||||
return; |
||||
} |
||||
using var ms = new MemoryStream(); |
||||
src.CopyTo(ms); |
||||
cursor = ms.ToArray(); |
||||
} |
||||
// CUR and ICO share the same on-disk layout; the only difference is byte 2 of the
|
||||
// header (1=icon, 2=cursor). Skia's image decoder only accepts the icon variant, so
|
||||
// flip the bit before handing it off — preview only, doesn't affect Save (which
|
||||
// writes the original snapshot).
|
||||
byte[] iconView = cursor.Length >= 3 ? (byte[])cursor.Clone() : cursor; |
||||
if (iconView.Length >= 3) |
||||
iconView[2] = 1; |
||||
smart.AddUIElement(() => new Image { Source = new Bitmap(new MemoryStream(iconView)) }); |
||||
smart.WriteLine(); |
||||
smart.AddButton(Images.Images.Save, "Save", async (_, _) => await SaveSnapshotAsync(cursor).ConfigureAwait(false)); |
||||
smart.WriteLine(); |
||||
} |
||||
|
||||
async System.Threading.Tasks.Task SaveSnapshotAsync(byte[] snapshot) |
||||
{ |
||||
var defaultName = Path.GetFileName(WholeProjectDecompiler.SanitizeFileName((string)Text)); |
||||
var path = await FilePickers.SaveAsync("All files|*.*", defaultName).ConfigureAwait(false); |
||||
if (path == null) |
||||
return; |
||||
await File.WriteAllBytesAsync(path, snapshot).ConfigureAwait(false); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,91 @@
@@ -0,0 +1,91 @@
|
||||
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System; |
||||
using System.Composition; |
||||
using System.IO; |
||||
|
||||
using global::Avalonia.Controls; |
||||
using global::Avalonia.Media.Imaging; |
||||
|
||||
using ICSharpCode.Decompiler; |
||||
using ICSharpCode.Decompiler.CSharp.ProjectDecompiler; |
||||
using ICSharpCode.Decompiler.Metadata; |
||||
using ICSharpCode.ILSpyX.Abstractions; |
||||
|
||||
using ILSpy.Commands; |
||||
using ILSpy.Languages; |
||||
using ILSpy.TextView; |
||||
|
||||
namespace ILSpy.TreeNodes |
||||
{ |
||||
[Export(typeof(IResourceNodeFactory))] |
||||
[Shared] |
||||
sealed class IconResourceNodeFactory : IResourceNodeFactory |
||||
{ |
||||
public ITreeNode? CreateNode(Resource resource) |
||||
{ |
||||
if (resource.Name.EndsWith(".ico", StringComparison.OrdinalIgnoreCase)) |
||||
return new IconResourceEntryNode(resource.Name, resource.TryOpenStream); |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
sealed class IconResourceEntryNode : ResourceEntryNode |
||||
{ |
||||
public IconResourceEntryNode(string key, Func<Stream?> openStream) |
||||
: base(key, () => openStream() ?? Stream.Null) |
||||
{ |
||||
} |
||||
|
||||
public override object Icon => Images.Images.Resource; |
||||
|
||||
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) |
||||
{ |
||||
if (output is not ISmartTextOutput smart) |
||||
return; |
||||
byte[] snapshot; |
||||
using (var src = OpenStream()) |
||||
{ |
||||
if (src == Stream.Null) |
||||
{ |
||||
output.WriteLine("ILSpy: Failed opening resource stream."); |
||||
return; |
||||
} |
||||
using var ms = new MemoryStream(); |
||||
src.CopyTo(ms); |
||||
snapshot = ms.ToArray(); |
||||
} |
||||
// Avalonia's Bitmap decodes .ico via Skia and picks the largest frame. Per-frame
|
||||
// rendering (with size/bpp labels) needs ICONDIR/ICONDIRENTRY parsing — TODO follow-up.
|
||||
smart.AddUIElement(() => new Image { Source = new Bitmap(new MemoryStream(snapshot)) }); |
||||
smart.WriteLine(); |
||||
smart.AddButton(Images.Images.Save, "Save", async (_, _) => await SaveSnapshotAsync(snapshot).ConfigureAwait(false)); |
||||
smart.WriteLine(); |
||||
} |
||||
|
||||
async System.Threading.Tasks.Task SaveSnapshotAsync(byte[] snapshot) |
||||
{ |
||||
var defaultName = Path.GetFileName(WholeProjectDecompiler.SanitizeFileName((string)Text)); |
||||
var path = await FilePickers.SaveAsync("All files|*.*", defaultName).ConfigureAwait(false); |
||||
if (path == null) |
||||
return; |
||||
await File.WriteAllBytesAsync(path, snapshot).ConfigureAwait(false); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,96 @@
@@ -0,0 +1,96 @@
|
||||
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System; |
||||
using System.Composition; |
||||
using System.IO; |
||||
|
||||
using global::Avalonia.Controls; |
||||
using global::Avalonia.Media.Imaging; |
||||
|
||||
using ICSharpCode.Decompiler; |
||||
using ICSharpCode.Decompiler.Metadata; |
||||
using ICSharpCode.ILSpyX.Abstractions; |
||||
|
||||
using ILSpy.Languages; |
||||
using ILSpy.TextView; |
||||
|
||||
namespace ILSpy.TreeNodes |
||||
{ |
||||
[Export(typeof(IResourceNodeFactory))] |
||||
[Shared] |
||||
sealed class ImageResourceNodeFactory : IResourceNodeFactory |
||||
{ |
||||
static readonly string[] imageFileExtensions = { ".png", ".gif", ".bmp", ".jpg" }; |
||||
|
||||
public ITreeNode? CreateNode(Resource resource) |
||||
{ |
||||
foreach (var ext in imageFileExtensions) |
||||
{ |
||||
if (resource.Name.EndsWith(ext, StringComparison.OrdinalIgnoreCase)) |
||||
return new ImageResourceEntryNode(resource.Name, resource.TryOpenStream); |
||||
} |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
sealed class ImageResourceEntryNode : ResourceEntryNode |
||||
{ |
||||
public ImageResourceEntryNode(string key, Func<Stream?> openStream) |
||||
: base(key, () => openStream() ?? Stream.Null) |
||||
{ |
||||
} |
||||
|
||||
// TODO: ship a ResourceImage.svg asset; the generic resource glyph is used for now.
|
||||
public override object Icon => Images.Images.Resource; |
||||
|
||||
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) |
||||
{ |
||||
if (output is not ISmartTextOutput smart) |
||||
return; |
||||
byte[] snapshot; |
||||
using (var src = OpenStream()) |
||||
{ |
||||
if (src == Stream.Null) |
||||
{ |
||||
output.WriteLine("ILSpy: Failed opening resource stream."); |
||||
return; |
||||
} |
||||
using var ms = new MemoryStream(); |
||||
src.CopyTo(ms); |
||||
snapshot = ms.ToArray(); |
||||
} |
||||
// Bitmap consumes the stream, so capture into a byte[] first — the original may be
|
||||
// one-shot, and AddUIElement runs lazily on the UI thread well after Decompile returns.
|
||||
smart.AddUIElement(() => new Image { Source = new Bitmap(new MemoryStream(snapshot)) }); |
||||
smart.WriteLine(); |
||||
smart.AddButton(Images.Images.Save, "Save", async (_, _) => await SaveSnapshotAsync(snapshot).ConfigureAwait(false)); |
||||
smart.WriteLine(); |
||||
} |
||||
|
||||
async System.Threading.Tasks.Task SaveSnapshotAsync(byte[] snapshot) |
||||
{ |
||||
var defaultName = Path.GetFileName(global::ICSharpCode.Decompiler.CSharp.ProjectDecompiler |
||||
.WholeProjectDecompiler.SanitizeFileName((string)Text)); |
||||
var path = await Commands.FilePickers.SaveAsync("All files|*.*", defaultName).ConfigureAwait(false); |
||||
if (path == null) |
||||
return; |
||||
await File.WriteAllBytesAsync(path, snapshot).ConfigureAwait(false); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,65 @@
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System; |
||||
using System.Composition; |
||||
using System.IO; |
||||
|
||||
using ICSharpCode.Decompiler; |
||||
using ICSharpCode.Decompiler.Metadata; |
||||
using ICSharpCode.ILSpyX.Abstractions; |
||||
|
||||
using ILSpy.Languages; |
||||
using ILSpy.TextView; |
||||
|
||||
namespace ILSpy.TreeNodes |
||||
{ |
||||
[Export(typeof(IResourceNodeFactory))] |
||||
[Shared] |
||||
sealed class XamlResourceNodeFactory : IResourceNodeFactory |
||||
{ |
||||
public ITreeNode? CreateNode(Resource resource) |
||||
{ |
||||
if (resource.Name.EndsWith(".xaml", StringComparison.OrdinalIgnoreCase)) |
||||
return new XamlResourceEntryNode(resource.Name, resource.TryOpenStream); |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
sealed class XamlResourceEntryNode : ResourceEntryNode |
||||
{ |
||||
public XamlResourceEntryNode(string key, Func<Stream?> openStream) |
||||
: base(key, () => openStream() ?? Stream.Null) |
||||
{ |
||||
} |
||||
|
||||
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) |
||||
{ |
||||
using var data = OpenStream(); |
||||
if (data == Stream.Null) |
||||
{ |
||||
output.WriteLine("ILSpy: Failed opening resource stream."); |
||||
return; |
||||
} |
||||
using var reader = new StreamReader(data); |
||||
output.Write(reader.ReadToEnd()); |
||||
if (output is AvaloniaEditTextOutput aeto) |
||||
aeto.SyntaxExtensionOverride = ".xml"; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,78 @@
@@ -0,0 +1,78 @@
|
||||
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System; |
||||
using System.Composition; |
||||
using System.IO; |
||||
|
||||
using ICSharpCode.Decompiler; |
||||
using ICSharpCode.Decompiler.Metadata; |
||||
using ICSharpCode.ILSpyX.Abstractions; |
||||
|
||||
using ILSpy.Languages; |
||||
using ILSpy.TextView; |
||||
|
||||
namespace ILSpy.TreeNodes |
||||
{ |
||||
[Export(typeof(IResourceNodeFactory))] |
||||
[Shared] |
||||
sealed class XmlResourceNodeFactory : IResourceNodeFactory |
||||
{ |
||||
static readonly string[] xmlFileExtensions = { ".xml", ".xsd", ".xslt" }; |
||||
|
||||
public ITreeNode? CreateNode(Resource resource) |
||||
{ |
||||
foreach (var ext in xmlFileExtensions) |
||||
{ |
||||
if (resource.Name.EndsWith(ext, StringComparison.OrdinalIgnoreCase)) |
||||
return new XmlResourceEntryNode(resource.Name, resource.TryOpenStream); |
||||
} |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
sealed class XmlResourceEntryNode : ResourceEntryNode |
||||
{ |
||||
public XmlResourceEntryNode(string key, Func<Stream?> openStream) |
||||
: base(key, () => openStream() ?? Stream.Null) |
||||
{ |
||||
} |
||||
|
||||
// TODO: ship Resource{Xml,Xsd,Xslt}.svg assets and pick by extension. Until then the
|
||||
// generic resource glyph is fine — the file name in the tree disambiguates.
|
||||
public override object Icon => Images.Images.Resource; |
||||
|
||||
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) |
||||
{ |
||||
using var data = OpenStream(); |
||||
if (data == Stream.Null) |
||||
{ |
||||
output.WriteLine("ILSpy: Failed opening resource stream."); |
||||
return; |
||||
} |
||||
using var reader = new StreamReader(data); |
||||
output.Write(reader.ReadToEnd()); |
||||
if (output is AvaloniaEditTextOutput aeto) |
||||
{ |
||||
// Force XML highlighting regardless of the active language. AvaloniaEdit's bundled
|
||||
// XmlHighlighting.xshd handles .xml / .xsd / .xslt identically.
|
||||
aeto.SyntaxExtensionOverride = ".xml"; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
Loading…
Reference in new issue