diff --git a/ICSharpCode.Decompiler.Tests/TestCases/PdbGen/ForLoopTests.xml b/ICSharpCode.Decompiler.Tests/TestCases/PdbGen/ForLoopTests.xml index ad3e3b093..492f58104 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/PdbGen/ForLoopTests.xml +++ b/ICSharpCode.Decompiler.Tests/TestCases/PdbGen/ForLoopTests.xml @@ -1,7 +1,7 @@ - - - public static string CleanUpFileName(string text) { - return CleanUpFileName(text, separateAtDots: false); + return CleanUpName(text, separateAtDots: false, treatAsFileName: false); + } + + /// + /// Removes invalid characters from file names and reduces their length, + /// but keeps file extensions and path structure intact. + /// + public static string SanitizeFileName(string fileName) + { + return CleanUpName(fileName, separateAtDots: false, treatAsFileName: true); } /// /// Cleans up a node name for use as a file system name. If is active, - /// dots are seen as segment separators. Each segment is limited to 255 characters. + /// dots are seen as segment separators. Each segment is limited to maxSegmentLength characters. + /// (see ) If is active, + /// we check for file a extension and try to preserve it, if it's valid. /// - static string CleanUpFileName(string text, bool separateAtDots) + static string CleanUpName(string text, bool separateAtDots, bool treatAsFileName) { int pos = text.IndexOf(':'); if (pos > 0) @@ -587,23 +599,49 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler if (pos > 0) text = text.Substring(0, pos); text = text.Trim(); - // Whitelist allowed characters, replace everything else: - StringBuilder b = new StringBuilder(text.Length); + string extension = null; int currentSegmentLength = 0; var (supportsLongPaths, maxPathLength, maxSegmentLength) = longPathSupport.Value; + if (treatAsFileName) + { + // Check if input is a file name, i.e., has a valid extension + // If yes, preserve extension and append it at the end. + // But only, if the extension length does not exceed maxSegmentLength, + // if that's the case we just give up and treat the extension no different + // from the file name. + int lastDot = text.LastIndexOf('.'); + if (lastDot >= 0 && text.Length - lastDot < maxSegmentLength) + { + string originalText = text; + extension = text.Substring(lastDot); + text = text.Remove(lastDot); + foreach (var c in extension) + { + if (!(char.IsLetterOrDigit(c) || c == '-' || c == '_' || c == '.')) + { + // extension contains an invalid character, therefore cannot be a valid extension. + extension = null; + text = originalText; + break; + } + } + } + } + // Whitelist allowed characters, replace everything else: + StringBuilder b = new StringBuilder(text.Length + (extension?.Length ?? 0)); foreach (var c in text) { currentSegmentLength++; if (char.IsLetterOrDigit(c) || c == '-' || c == '_') { - // if the current segment exceeds 255 characters, + // if the current segment exceeds maxSegmentLength characters, // skip until the end of the segment. if (currentSegmentLength <= maxSegmentLength) b.Append(c); } else if (c == '.' && b.Length > 0 && b[b.Length - 1] != '.') { - // if the current segment exceeds 255 characters, + // if the current segment exceeds maxSegmentLength characters, // skip until the end of the segment. if (separateAtDots || currentSegmentLength <= maxSegmentLength) b.Append('.'); // allow dot, but never two in a row @@ -612,9 +650,15 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler if (separateAtDots) currentSegmentLength = 0; } + else if (treatAsFileName && (c == '/' || c == '\\') && currentSegmentLength > 0) + { + // if we treat this as a file name, we've started a new segment + b.Append(c); + currentSegmentLength = 0; + } else { - // if the current segment exceeds 255 characters, + // if the current segment exceeds maxSegmentLength characters, // skip until the end of the segment. if (currentSegmentLength <= maxSegmentLength) b.Append('-'); @@ -625,6 +669,8 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler if (b.Length == 0) b.Append('-'); string name = b.ToString(); + if (extension != null) + name += extension; if (IsReservedFileSystemName(name)) return name + "_"; else if (name == ".") @@ -638,7 +684,13 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler /// public static string CleanUpDirectoryName(string text) { - return CleanUpFileName(text, separateAtDots: true).Replace('.', Path.DirectorySeparatorChar); + return CleanUpName(text, separateAtDots: false, treatAsFileName: false); + } + + public static string CleanUpPath(string text) + { + return CleanUpName(text, separateAtDots: true, treatAsFileName: false) + .Replace('.', Path.DirectorySeparatorChar); } static bool IsReservedFileSystemName(string name) diff --git a/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs b/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs index 23223a805..ba010a45d 100644 --- a/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs +++ b/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs @@ -66,7 +66,10 @@ namespace ICSharpCode.Decompiler.DebugInfo string BuildFileNameFromTypeName(TypeDefinitionHandle handle) { var typeName = handle.GetFullTypeName(reader).TopLevelTypeName; - return Path.Combine(WholeProjectDecompiler.CleanUpDirectoryName(typeName.Namespace), WholeProjectDecompiler.CleanUpFileName(typeName.Name) + ".cs"); + string ns = settings.UseNestedDirectoriesForNamespaces + ? WholeProjectDecompiler.CleanUpPath(typeName.Namespace) + : WholeProjectDecompiler.CleanUpDirectoryName(typeName.Namespace); + return Path.Combine(ns, WholeProjectDecompiler.CleanUpFileName(typeName.Name) + ".cs"); } foreach (var sourceFile in reader.GetTopLevelTypeDefinitions().GroupBy(BuildFileNameFromTypeName)) diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index d76921cac..b7888ad30 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -1777,7 +1777,7 @@ namespace ICSharpCode.Decompiler /// Gets or sets a value indicating whether the new SDK style format /// shall be used for the generated project files. /// - [Category("DecompilerSettings.Other")] + [Category("DecompilerSettings.ProjectExport")] [Description("DecompilerSettings.UseSdkStyleProjectFormat")] public bool UseSdkStyleProjectFormat { get { return useSdkStyleProjectFormat; } @@ -1790,6 +1790,25 @@ namespace ICSharpCode.Decompiler } } + bool useNestedDirectoriesForNamespaces; + + /// + /// Gets/sets whether namespaces and namespace-like identifiers should be split at '.' + /// and each part should produce a new level of nesting in the output directory structure. + /// + [Category("DecompilerSettings.ProjectExport")] + [Description("DecompilerSettings.UseNestedDirectoriesForNamespaces")] + public bool UseNestedDirectoriesForNamespaces { + get { return useNestedDirectoriesForNamespaces; } + set { + if (useNestedDirectoriesForNamespaces != value) + { + useNestedDirectoriesForNamespaces = value; + OnPropertyChanged(); + } + } + } + bool aggressiveScalarReplacementOfAggregates = false; [Category("DecompilerSettings.Other")] diff --git a/ILSpy/Commands/ExtractPackageEntryContextMenuEntry.cs b/ILSpy/Commands/ExtractPackageEntryContextMenuEntry.cs index e41d055bd..79974c89b 100644 --- a/ILSpy/Commands/ExtractPackageEntryContextMenuEntry.cs +++ b/ILSpy/Commands/ExtractPackageEntryContextMenuEntry.cs @@ -23,6 +23,7 @@ using System.Linq; using System.Threading.Tasks; using ICSharpCode.Decompiler; +using ICSharpCode.Decompiler.CSharp.ProjectDecompiler; using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.TreeNodes; @@ -46,7 +47,7 @@ namespace ICSharpCode.ILSpy return; var assembly = selectedNodes[0].PackageEntry; SaveFileDialog dlg = new SaveFileDialog(); - dlg.FileName = Path.GetFileName(DecompilerTextView.CleanUpName(assembly.Name)); + dlg.FileName = Path.GetFileName(WholeProjectDecompiler.SanitizeFileName(assembly.Name)); dlg.Filter = ".NET assemblies|*.dll;*.exe;*.winmd" + Resources.AllFiles; dlg.InitialDirectory = Path.GetDirectoryName(bundleNode.LoadedAssembly.FileName); if (dlg.ShowDialog() != true) @@ -70,7 +71,7 @@ namespace ICSharpCode.ILSpy { foreach (var node in selectedNodes) { - var fileName = Path.GetFileName(DecompilerTextView.CleanUpName(node.PackageEntry.Name)); + var fileName = Path.GetFileName(WholeProjectDecompiler.SanitizeFileName(node.PackageEntry.Name)); SaveEntry(output, node.PackageEntry, Path.Combine(outputFolderOrFileName, fileName)); } } diff --git a/ILSpy/Commands/GeneratePdbContextMenuEntry.cs b/ILSpy/Commands/GeneratePdbContextMenuEntry.cs index 97101ece0..2577206e6 100644 --- a/ILSpy/Commands/GeneratePdbContextMenuEntry.cs +++ b/ILSpy/Commands/GeneratePdbContextMenuEntry.cs @@ -25,6 +25,7 @@ using System.Windows; using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.CSharp; +using ICSharpCode.Decompiler.CSharp.ProjectDecompiler; using ICSharpCode.Decompiler.DebugInfo; using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.TextView; @@ -63,7 +64,7 @@ namespace ICSharpCode.ILSpy return; } SaveFileDialog dlg = new SaveFileDialog(); - dlg.FileName = DecompilerTextView.CleanUpName(assembly.ShortName) + ".pdb"; + dlg.FileName = WholeProjectDecompiler.CleanUpFileName(assembly.ShortName) + ".pdb"; dlg.Filter = Resources.PortablePDBPdbAllFiles; dlg.InitialDirectory = Path.GetDirectoryName(assembly.FileName); if (dlg.ShowDialog() != true) diff --git a/ILSpy/Commands/SelectPdbContextMenuEntry.cs b/ILSpy/Commands/SelectPdbContextMenuEntry.cs index be9597b75..113122953 100644 --- a/ILSpy/Commands/SelectPdbContextMenuEntry.cs +++ b/ILSpy/Commands/SelectPdbContextMenuEntry.cs @@ -19,8 +19,8 @@ using System.IO; using System.Linq; +using ICSharpCode.Decompiler.CSharp.ProjectDecompiler; using ICSharpCode.ILSpy.Properties; -using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.TreeNodes; using Microsoft.Win32; @@ -35,7 +35,7 @@ namespace ICSharpCode.ILSpy if (assembly == null) return; OpenFileDialog dlg = new OpenFileDialog(); - dlg.FileName = DecompilerTextView.CleanUpName(assembly.ShortName) + ".pdb"; + dlg.FileName = WholeProjectDecompiler.CleanUpFileName(assembly.ShortName) + ".pdb"; dlg.Filter = Resources.PortablePDBPdbAllFiles; dlg.InitialDirectory = Path.GetDirectoryName(assembly.FileName); if (dlg.ShowDialog() != true) diff --git a/ILSpy/Options/MiscSettingsPanel.xaml b/ILSpy/Options/MiscSettingsPanel.xaml index ab5a84a8e..09a9c5485 100644 --- a/ILSpy/Options/MiscSettingsPanel.xaml +++ b/ILSpy/Options/MiscSettingsPanel.xaml @@ -6,9 +6,13 @@ xmlns:properties="clr-namespace:ICSharpCode.ILSpy.Properties" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> - - - -