public static void Generate( string outputExcelFilePath, FeaturesAndEstimationsFileProcessor featuresAndEstimationsFileProcessor, SortedDictionary <Tuple <string, string>, HashSet <string> > unsupportedMethodsAndTheirAssemblyToLocationsWhereTheyAreUsed, IEnumerable <string> analyzedDlls) { ExcelEngine excelEngine = new ExcelEngine(); //Instantiate the Excel application object IApplication application = excelEngine.Excel; //Assigns default application version application.DefaultVersion = ExcelVersion.Excel2013; //A new workbook is created equivalent to creating a new workbook in Excel //Create a workbook with 1 worksheet IWorkbook workbook = application.Workbooks.Create(1); //Access first worksheet from the workbook IWorksheet worksheet = workbook.Worksheets[0]; // Set the columns width (note: columns here are one-based): worksheet.SetColumnWidth(3, 60); worksheet.SetColumnWidth(4, 45); worksheet.SetColumnWidth(5, 30); worksheet.SetColumnWidth(6, 13); // Prepare the styles: IStyle style_Remove, style_EasyWorkaround, style_RequiresImplementation, style_NonTrivialWorkaround; PrepareTheStyles(workbook, out style_Remove, out style_EasyWorkaround, out style_RequiresImplementation, out style_NonTrivialWorkaround); // Set the header text: worksheet.Range[5, 3].Text = "FEATURE:"; worksheet.Range[5, 4].Text = "CLASSES OR FILES WHERE THE FEATURE IS USED:"; // Set the header format: worksheet.Range[5, 3, 5, 6].CellStyle.Font.Bold = true; // Index of the first row that contains the feature: int currentRow = 7; var groupedResult = unsupportedMethodsAndTheirAssemblyToLocationsWhereTheyAreUsed.GroupBy((element) => element.Key.Item2); foreach (IGrouping <string, KeyValuePair <Tuple <string, string>, HashSet <string> > > item in groupedResult) { var currentCell = worksheet.Range["A" + currentRow.ToString()]; currentCell.Text = "From \"" + item.Key + "\":"; currentCell.CellStyle.Font.Bold = true; currentCell.CellStyle.Font.Size = 14; currentCell.CellStyle.Font.Underline = ExcelUnderline.Single; currentRow += 2; foreach (KeyValuePair <Tuple <string, string>, HashSet <string> > obj in item) { // Feature name: string featureName = obj.Key.Item1; IRange featureNameCell = worksheet.Range["C" + currentRow.ToString()]; featureNameCell.Text = featureName; // Locations where the feature is used: List <string> sortedLocations = new List <string>(obj.Value); sortedLocations.Sort(); string sortLocationsJoined = string.Join(", ", sortedLocations); IRange locationCell = worksheet.Range["D" + currentRow.ToString()]; locationCell.Text = sortLocationsJoined; // Other information: ExcelRowInfo excelRowInfo; if (featuresAndEstimationsFileProcessor.TryGetInfo(featureName, out excelRowInfo)) { // Row style: string recommendedActionCode = excelRowInfo.RecommendedActionCode; if (!string.IsNullOrEmpty(recommendedActionCode)) { IStyle styleToApply = null; switch (recommendedActionCode) { case "REMOVE": styleToApply = style_Remove; break; case "EASYWORKAROUND": styleToApply = style_EasyWorkaround; break; case "REQUIRESIMPLEMENTATION": styleToApply = style_RequiresImplementation; break; case "NONTRIVIALWORKAROUND": styleToApply = style_NonTrivialWorkaround; break; default: break; } if (styleToApply != null) { worksheet.Range["A" + currentRow.ToString() + ":" + "G" + currentRow.ToString()].CellStyle = styleToApply; } ; } // Recommended action: string recommendedAction = excelRowInfo.RecommendedAction; if (!string.IsNullOrEmpty(recommendedAction)) { worksheet.Range["E" + currentRow.ToString()].Text = recommendedAction; } // Estimation of workload: string estimation = excelRowInfo.Estimation; if (!string.IsNullOrEmpty(estimation)) { worksheet.Range["F" + currentRow.ToString()].Number = double.Parse(estimation); } // Optional title: string optionalTitle = excelRowInfo.OptionalTitle; if (!string.IsNullOrEmpty(optionalTitle)) { featureNameCell.Text = optionalTitle + ": " + featureName; } } // Feature name font size: if (featureName.Length > 2700) { featureNameCell.CellStyle.Font.Size = 7; } else if (featureName.Length > 2000) { featureNameCell.CellStyle.Font.Size = 8; } else if (featureName.Length > 1300) { featureNameCell.CellStyle.Font.Size = 9; } else if (featureName.Length > 600) { featureNameCell.CellStyle.Font.Size = 10; } else { featureNameCell.CellStyle.Font.Size = 11; } // Locations font size: if (sortLocationsJoined.Length > 2700) { locationCell.CellStyle.Font.Size = 7; } else if (sortLocationsJoined.Length > 2000) { locationCell.CellStyle.Font.Size = 8; } else if (sortLocationsJoined.Length > 1300) { locationCell.CellStyle.Font.Size = 9; } else if (sortLocationsJoined.Length > 600) { locationCell.CellStyle.Font.Size = 10; } else { locationCell.CellStyle.Font.Size = 11; } // Increase the row index: ++currentRow; } // Insert a blank row: ++currentRow; } // Set "Wrap Text" on the columns 2, 3, and 5 (note: columns here are one-based): worksheet.Columns[2].WrapText = true; worksheet.Columns[3].WrapText = true; try { worksheet.Columns[4].WrapText = true; worksheet.Columns[5].WrapText = true; } catch (Exception ex) { Debug.WriteLine(ex.ToString()); } // Vertical align the content of the cells: worksheet.Columns[2].VerticalAlignment = ExcelVAlign.VAlignTop; worksheet.Columns[3].VerticalAlignment = ExcelVAlign.VAlignTop; try { worksheet.Columns[4].VerticalAlignment = ExcelVAlign.VAlignTop; worksheet.Columns[5].VerticalAlignment = ExcelVAlign.VAlignTop; } catch (Exception ex) { Debug.WriteLine(ex.ToString()); } // Add information at the end about the DLLs that have been analized: currentRow += 5; string analyzedDllsText = "(Analyzed DLLs: " + string.Join(", ", analyzedDlls) + ")"; worksheet.Range["A" + currentRow.ToString()].Text = analyzedDllsText; // Saving the workbook to disk in XLSX format workbook.SaveAs(outputExcelFilePath); }
private void Button_Click(object sender, RoutedEventArgs e) { string coreAssemblyPath = CoreAssemblyPathTextBox.Text; string supportedElementsPath = SupportedElementsPathTextBox.Text; string mscorlibFolderPath = MscorlibFolderPathTextBox.Text; string sdkFolderPath = SDKFolderPathTextBox.Text; string otherFoldersPath = OtherFoldersPathTextBox.Text; HashSet <string> xamlFilesToIgnore = null; try { xamlFilesToIgnore = new HashSet <string>(XamlFilesToIgnoreTextBox.Text.Split(',').Select(s => s.Trim().ToLower())); } catch (Exception ex) { MessageBox.Show("Invalid list of XAML files to exclude." + Environment.NewLine + Environment.NewLine + ex.ToString()); return; } if (string.IsNullOrEmpty(coreAssemblyPath) || string.IsNullOrEmpty(supportedElementsPath) || string.IsNullOrEmpty(mscorlibFolderPath)) { MessageBox.Show("Please fill all the fields before continuing."); return; } if (!File.Exists(supportedElementsPath)) { MessageBox.Show("File not found: " + supportedElementsPath); return; } var featuresAndEstimationsFileProcessor = new FeaturesAndEstimationsFileProcessor(FeaturesAndEstimationsPathTextBox.Text); // Save the content of the text boxes for reuse when relaunching the application: SaveContentOfTextBoxes(); #if ASK_USER_TO_CHOOSE_ASSEMBLIES_TO_ANALYZE // Create OpenFileDialog Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog(); // Set filter for file extension and default file extension dlg.DefaultExt = ".dll"; dlg.Filter = "Assemblies and executables (*.dll,*.exe)|*.dll;*.exe"; dlg.Multiselect = true; // Display OpenFileDialog by calling ShowDialog method Nullable <bool> result = dlg.ShowDialog(); if (result == true) { string[] fileNames = dlg.FileNames; #else string[] fileNames = Configuration.AssembliesToAnalyze; if (true) { #endif if (_coreSupportedMethods == null) { _coreSupportedMethods = new CoreSupportedMethodsContainer(coreAssemblyPath); } var logger = new LoggerThatAggregatesAllErrors(); var watch = Stopwatch.StartNew(); var listOfUnsupportedMethods = new List <UnsupportedMethodInfo>(); // Get the path of the folder where the first assembly is located, and add that path to the list of places where to look in for resolving referenced assemblies: string firstFileToAnalyze = fileNames.FirstOrDefault(); string additionalFolderWhereToResolveAssemblies = null; if (firstFileToAnalyze != null) { additionalFolderWhereToResolveAssemblies = System.IO.Path.GetDirectoryName(firstFileToAnalyze); } // Do the analysis: try { foreach (var filename in fileNames) { CompatibilityAnalyzer.Analyze( filename, logger, listOfUnsupportedMethods, _coreSupportedMethods, fileNames, Configuration.UrlNamespacesThatBelongToUserCode, Configuration.AttributesToIgnoreInXamlBecauseTheyAreFromBaseClasses, xamlFilesToIgnore, supportedElementsPath, mscorlibFolderPath, sdkFolderPath, otherFoldersPath, skipTypesWhereNoMethodIsActuallyCalled: true, additionalFolderWhereToResolveAssemblies: additionalFolderWhereToResolveAssemblies);; } } catch (Exception ex) { MessageBox.Show(ex.ToString()); } // Replace all telerik assembly names with "http://schemas.telerik.com/2008/xaml/presentation" so as to avoid differences between namespaces from .xaml files and namespaces from .cs files: foreach (UnsupportedMethodInfo unsupportedMethodInfo in listOfUnsupportedMethods) { if (unsupportedMethodInfo.MethodAssemblyName.StartsWith("Telerik.")) { unsupportedMethodInfo.MethodAssemblyName = "http://schemas.telerik.com/2008/xaml/presentation"; } } // Replace all Expression Blend-related namespaces with "http://schemas.microsoft.com/expression/2010/..." so as to avoid differences between namespaces from .xaml files and namespaces from .cs files: foreach (UnsupportedMethodInfo unsupportedMethodInfo in listOfUnsupportedMethods) { if (unsupportedMethodInfo.MethodAssemblyName.StartsWith("Microsoft.Expression.") || unsupportedMethodInfo.MethodAssemblyName.StartsWith("System.Windows.Interactivity") || unsupportedMethodInfo.MethodAssemblyName == "http://schemas.microsoft.com/expression/2010/drawing" || unsupportedMethodInfo.MethodAssemblyName == "http://schemas.microsoft.com/expression/2010/interactivity" || unsupportedMethodInfo.MethodAssemblyName == "http://schemas.microsoft.com/expression/2010/interactions" ) { unsupportedMethodInfo.MethodAssemblyName = "http://schemas.microsoft.com/expression/2010/..."; } } // Aggregate all MEF-related namespaces: foreach (UnsupportedMethodInfo unsupportedMethodInfo in listOfUnsupportedMethods) { if (unsupportedMethodInfo.MethodAssemblyName.StartsWith("System.ComponentModel.Composition.")) { unsupportedMethodInfo.MethodAssemblyName = "System.ComponentModel.Composition"; } } // Aggregate all DomainServices-related namespaces: foreach (UnsupportedMethodInfo unsupportedMethodInfo in listOfUnsupportedMethods) { if (unsupportedMethodInfo.MethodAssemblyName.StartsWith("System.ServiceModel.DomainServices.Client.")) { unsupportedMethodInfo.MethodAssemblyName = "System.ServiceModel.DomainServices.Client"; } } // Replace all the native MS UI-related assembly names with "http://schemas.microsoft.com/winfx/2006/xaml/presentation" so as to avoid differences between namespaces from .xaml files and namespaces from .cs files: foreach (UnsupportedMethodInfo unsupportedMethodInfo in listOfUnsupportedMethods) { if (unsupportedMethodInfo.MethodAssemblyName == "System.Windows" || unsupportedMethodInfo.MethodAssemblyName == "System.Windows.Controls" || unsupportedMethodInfo.MethodAssemblyName == "System.Windows.Controls.Data" || unsupportedMethodInfo.MethodAssemblyName == "System.Windows.Browser" || unsupportedMethodInfo.MethodAssemblyName == "System.Windows.Controls.Navigation" || unsupportedMethodInfo.MethodAssemblyName == "System.Windows.Controls.Toolkit") { unsupportedMethodInfo.MethodAssemblyName = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"; } } // Process the list of unsupported methods: SortedDictionary <Tuple <string, string>, HashSet <string> > unsupportedMethodsAndTheirAssemblyToLocationsWhereTheyAreUsed; ProcessUnsupportedMethods(listOfUnsupportedMethods, out unsupportedMethodsAndTheirAssemblyToLocationsWhereTheyAreUsed, rectifyMethodName: true); // Remove some stuff: foreach (Tuple <string, string> additionalEntryToRemove in Configuration.AdditionalEntriesToRemoveBeforeAggregation) { unsupportedMethodsAndTheirAssemblyToLocationsWhereTheyAreUsed.Remove(additionalEntryToRemove); } // For classes that are not defined at all (ie. missing constructor), aggregate all the methods into the same line: List <Tuple <string, string> > entriesToRemoveFromDictionary = new List <Tuple <string, string> >(); // This is here because we cannot remove the keys while iterating in the dictionary, so we do it afterwards. List <KeyValuePair <Tuple <string, string>, HashSet <string> > > entriesToAddToDictionary = new List <KeyValuePair <Tuple <string, string>, HashSet <string> > >(); // This is here because we cannot add entries while iterating in the dictionary, so we do it afterwards. HashSet <Tuple <string, string> > classesForWhichTheMethodsHaveAlreadyBeenAggregated = new HashSet <Tuple <string, string> >(); foreach (KeyValuePair <Tuple <string, string>, HashSet <string> > pair in unsupportedMethodsAndTheirAssemblyToLocationsWhereTheyAreUsed) { string theFullMethodName = pair.Key.Item1; string theAssemblyName = pair.Key.Item2; HashSet <string> whereItIsUsed = pair.Value; if (!Configuration.EntriesToNotAggregateWithOtherEntries.Contains(theFullMethodName)) { #if ALWAYS_AGGREGATE_METHODS if (theFullMethodName.Contains(".")) #else if (theFullMethodName.Contains("..ctor")) #endif { #if ALWAYS_AGGREGATE_METHODS string className = theFullMethodName.Substring(0, theFullMethodName.IndexOf(".")); #else string className = theFullMethodName.Substring(0, theFullMethodName.IndexOf("..ctor")); #endif if (!classesForWhichTheMethodsHaveAlreadyBeenAggregated.Contains(new Tuple <string, string>(className, theAssemblyName))) { classesForWhichTheMethodsHaveAlreadyBeenAggregated.Add(new Tuple <string, string>(className, theAssemblyName)); List <string> methodNames = new List <string>(); // Find all other methods that concern the same class: foreach (KeyValuePair <Tuple <string, string>, HashSet <string> > pair2 in unsupportedMethodsAndTheirAssemblyToLocationsWhereTheyAreUsed) { //todo-perf: optimize performance by creating a dictionary beforehand instead of nesting two iterations! string theFullMethodName2 = pair2.Key.Item1; string theAssemblyName2 = pair2.Key.Item2; HashSet <string> whereItIsUsed2 = pair.Value; if (!Configuration.EntriesToNotAggregateWithOtherEntries.Contains(theFullMethodName2)) { if (theFullMethodName2.Contains('.') && !theFullMethodName2.Contains("..ctor") ) { string className2 = theFullMethodName2.Substring(0, theFullMethodName2.IndexOf('.')); if (className2 == className && theAssemblyName2 == theAssemblyName) { string methodName2 = theFullMethodName2.Substring(theFullMethodName2.IndexOf('.') + 1); methodNames.Add(methodName2); HashSetHelpers.AddItemsFromOneHashSetToAnother(whereItIsUsed2, whereItIsUsed); entriesToRemoveFromDictionary.Add(pair2.Key); } } } } // Aggregate them: string newMethodName; if (methodNames.Count > 0) { if (theFullMethodName.Contains("..ctor")) { newMethodName = className + " (members used: " + string.Join(", ", methodNames) + ")"; } else { newMethodName = className + "." + string.Join(", .", methodNames); } } else { newMethodName = className; } entriesToRemoveFromDictionary.Add(pair.Key); entriesToAddToDictionary.Add(new KeyValuePair <Tuple <string, string>, HashSet <string> >( new Tuple <string, string>(newMethodName, theAssemblyName), whereItIsUsed)); } } } } // Perform the actual removal and addition (note: this cannot be done before because it is not possible to remove or add an item from a collection while iterating it with "foreach"); foreach (var key in entriesToRemoveFromDictionary) { unsupportedMethodsAndTheirAssemblyToLocationsWhereTheyAreUsed.Remove(key); } foreach (var entry in entriesToAddToDictionary) { unsupportedMethodsAndTheirAssemblyToLocationsWhereTheyAreUsed.Add(entry.Key, entry.Value); } //------------------------------------- // Merge classes that are related to each other: //------------------------------------- MergingRelatedClasses.MergeRelatedClasses(Configuration.ClassesRelatedToEachOther, unsupportedMethodsAndTheirAssemblyToLocationsWhereTheyAreUsed); //------------------------------------- // Remove elements with an empty assembly name because they are due to "clr-namespace:..." without any assembly being specified, which means that the assembly is the user assembly itself, so we should remove the entry: //------------------------------------- entriesToRemoveFromDictionary = new List <Tuple <string, string> >(); foreach (var entry in unsupportedMethodsAndTheirAssemblyToLocationsWhereTheyAreUsed) { if (entry.Key.Item2 == "") { entriesToRemoveFromDictionary.Add(entry.Key); } } foreach (var key in entriesToRemoveFromDictionary) { unsupportedMethodsAndTheirAssemblyToLocationsWhereTheyAreUsed.Remove(key); } //------------------------------------- // Remove some additional stuff: //------------------------------------- foreach (Tuple <string, string> additionalEntryToRemove in Configuration.AdditionalEntriesToRemoveAfterAggregation) { unsupportedMethodsAndTheirAssemblyToLocationsWhereTheyAreUsed.Remove(additionalEntryToRemove); } //------------------------------------- // Save the result: //------------------------------------- // Save as Excel document: ExcelGenerator.Generate( OutputExcelFilePathTextBox.Text, featuresAndEstimationsFileProcessor, unsupportedMethodsAndTheirAssemblyToLocationsWhereTheyAreUsed, fileNames.Select(fileName => System.IO.Path.GetFileName(fileName))); #if SAVE_CSV_DOCUMENT // Save as CSV document: CsvGenerator.Generate(OutputCsvFilePathTextBox.Text, unsupportedMethodsAndTheirAssemblyToLocationsWhereTheyAreUsed); #endif var elapsedMs = watch.ElapsedMilliseconds; MessageBox.Show(string.Format("Operation completed in {0} seconds.", Math.Floor(elapsedMs / 1000d).ToString())); //------------------------------------- // Open the result: //------------------------------------- Process.Start(OutputExcelFilePathTextBox.Text); //------------------------------------- // Close this app: //------------------------------------- System.Windows.Application.Current.Shutdown(); } }