diff --git a/PowerDocu.AppDocumenter/AppDocumentationContent.cs b/PowerDocu.AppDocumenter/AppDocumentationContent.cs new file mode 100644 index 0000000..6dfc78e --- /dev/null +++ b/PowerDocu.AppDocumenter/AppDocumentationContent.cs @@ -0,0 +1,161 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using PowerDocu.Common; + +namespace PowerDocu.AppDocumenter +{ + class AppDocumentationContent + { + public string folderPath, filename; + public string ID; + public string Name; + public AppProperties appProperties; + public AppResources appResources; + public AppDataSources appDataSources; + public AppVariablesInfo appVariablesInfo; + public AppControls appControls; + public Dictionary ResourceStreams; + // App Properties that are colours + public readonly string[] ColourProperties = new string[] { "BorderColor", "Color", "DisabledBorderColor", "DisabledColor", "DisabledFill", "DisabledSectionColor", + "DisabledSelectionFill", "Fill", "FocusedBorderColor", "HoverBorderColor", "HoverColor", "HoverFill", "PressedBorderColor","PressedColor", + "PressedFill", "SelectionColor", "SelectionFill" }; + + public AppDocumentationContent(AppEntity app, string path) + { + NotificationHelper.SendNotification("Preparing documentation content for " + app.Name); + folderPath = path + CharsetHelper.GetSafeName(@"\AppDoc - " + app.Name + @"\"); + Directory.CreateDirectory(folderPath); + filename = CharsetHelper.GetSafeName(app.Name) + ((app.ID != null) ? ("(" + app.ID + ")") : ""); + ResourceStreams = app.ResourceStreams; + ID = app.ID; + Name = app.Name; + appProperties = new AppProperties(app); + appVariablesInfo = new AppVariablesInfo(app); + appDataSources = new AppDataSources(app); + appResources = new AppResources(app); + appControls = new AppControls(app); + } + } + + public class AppVariablesInfo + { + public string header = "Variables & Collections"; + public string headerGlobalVariables = "Global Variables"; + public string headerContextVariables = "Context Variables"; + public string headerCollections = "Collections"; + public string infoText = ""; + public Dictionary> variableCollectionControlReferences; + public HashSet globalVariables, contextVariables, collections; + public AppVariablesInfo(AppEntity app) + { + infoText = $"There are {app.GlobalVariables.Count} Global Variables, {app.ContextVariables.Count} Context Variables and {app.Collections.Count} Collections."; + variableCollectionControlReferences = app.VariableCollectionControlReferences; + globalVariables = app.GlobalVariables.OrderBy(o => o).ToHashSet(); + contextVariables = app.ContextVariables.OrderBy(o => o).ToHashSet(); + collections = app.Collections.OrderBy(o => o).ToHashSet(); + } + } + + public class AppDataSources + { + public string header = "DataSources"; + public string infoText = ""; + public List dataSources; + public AppDataSources(AppEntity app) + { + infoText = $"A total of {app.DataSources.Count} DataSources are located in the app:"; + dataSources = app.DataSources.OrderBy(o => o.Name).ToList(); + } + } + + public class AppResources + { + public string header = "Resources"; + public string infoText = ""; + public List resources; + + public AppResources(AppEntity app) + { + infoText = $"A total of {app.Resources.Count} Resources are located in the app:"; + resources = app.Resources; + } + } + + public class AppProperties + { + public string header = ""; + public string appLogo; + public string appBackgroundColour; + public string headerAppProperties = "App Properties"; + public string headerAppStatistics = "App Statistics"; + public string headerAppPreviewFlags = "App Preview Flags"; + public string headerDocumentationGenerated = "Documentation generated at"; + public List appProperties; + public Expression appPreviewsFlagProperty; + public string[] propertiesToSkip = new string[] { "AppPreviewFlagsMap", "ControlCount" }; + public string[] OverviewProperties = new string[] {"AppCreationSource", "AppDescription", "AppName", "BackgroundColor", "DocumentAppType", "DocumentLayoutHeight", "DocumentLayoutOrientation", + "DocumentLayoutWidth", "IconColor", "IconName", "Id", "LastSavedDateTimeUTC", "LogoFileName", "Name"}; + public Dictionary statisticsTable = new Dictionary(); + public AppProperties(AppEntity app) + { + header = "Power App Documentation - " + app.Name; + Expression appLogoExp = app.Properties.Find(o => o.expressionOperator == "LogoFileName"); + appLogo = appLogoExp?.expressionOperands[0].ToString(); + Expression bgColour = app.Properties.Find(o => o.expressionOperator == "BackgroundColor"); + appBackgroundColour = bgColour?.expressionOperands[0].ToString(); + statisticsTable.Add("Screens", "" + app.Controls.Where(o => o.Type == "screen").ToList().Count); + List allControls = new List(); + foreach (ControlEntity control in app.Controls) + { + allControls.Add(control); + allControls.AddRange(AppDocumentationHelper.getAllChildControls(control)); + } + statisticsTable.Add("Controls (excluding Screens)", "" + (allControls.Count - app.Controls.Where(o => o.Type == "screen").ToList().Count)); + statisticsTable.Add("Variables", "" + (app.GlobalVariables.Count + app.ContextVariables.Count)); + statisticsTable.Add("Collections", "" + app.Collections.Count); + statisticsTable.Add("Data Sources", "" + app.DataSources.Count); + statisticsTable.Add("Resources", "" + app.Resources.Count); + appProperties = app.Properties.OrderBy(o => o.expressionOperator).ToList(); + appPreviewsFlagProperty = app.Properties.Find(o => o.expressionOperator == "AppPreviewFlagsMap"); + } + } + + public class AppControls + { + public string headerOverview = "Controls Overview"; + public string headerDetails = "Detailed Controls"; + public string headerScreenNavigation = "Screen Navigation"; + public string infoTextScreens = ""; + public string infoTextControls = ""; + public string infoTextScreenNavigation = "The following diagram shows the navigation between the different screens."; + public string imageScreenNavigation = "ScreenNavigation"; + public List controls; + public List allControls = new List(); + public AppControls(AppEntity app) + { + controls = app.Controls.OrderBy(o => o.Name).ToList(); + foreach (ControlEntity control in controls) + { + allControls.Add(control); + allControls.AddRange(ControlEntity.getAllChildControls(control)); + } + infoTextScreens = $"A total of {controls.Where(o => o.Type == "screen").ToList().Count} Screens are located in the app."; + infoTextControls = $"A total of {allControls.Count} Controls are located in the app."; + } + } + + public static class AppDocumentationHelper + { + public static List getAllChildControls(ControlEntity control) + { + List childControls = new List(); + foreach (ControlEntity childControl in control.Children) + { + childControls.Add(childControl); + childControls.AddRange(getAllChildControls(childControl)); + } + return childControls; + } + } +} \ No newline at end of file diff --git a/PowerDocu.AppDocumenter/AppDocumentationGenerator.cs b/PowerDocu.AppDocumenter/AppDocumentationGenerator.cs index b27c2ee..9ee924f 100644 --- a/PowerDocu.AppDocumenter/AppDocumentationGenerator.cs +++ b/PowerDocu.AppDocumenter/AppDocumentationGenerator.cs @@ -10,7 +10,7 @@ namespace PowerDocu.AppDocumenter { public static class AppDocumentationGenerator { - public static void GenerateWordDocumentation(string filePath, string wordTemplate = null) + public static void GenerateDocumentation(string filePath, string fileFormat, string wordTemplate = null) { if (File.Exists(filePath)) { @@ -59,7 +59,7 @@ public static void GenerateWordDocumentation(string filePath, string wordTemplat { Node source = rootGraph.GetOrAddNode("App"); source.SetAttributeHtml("label", "
App
"); - source.SetAttribute("shape","oval"); + source.SetAttribute("shape", "oval"); Node dest = rootGraph.GetOrAddNode(CharsetHelper.GetSafeName(destination)); dest.SetAttributeHtml("label", "
" + CharsetHelper.GetSafeName(destination) + "
"); rootGraph.GetOrAddEdge(source, dest, "App -" + destination); @@ -95,15 +95,24 @@ public static void GenerateWordDocumentation(string filePath, string wordTemplat { bitmap?.Save(folderPath + "ScreenNavigation.png"); } - - //create the Word document - if (String.IsNullOrEmpty(wordTemplate) || !File.Exists(wordTemplate)) + AppDocumentationContent content = new AppDocumentationContent(app, path); + if (fileFormat.Equals(OutputFormatHelper.Word) || fileFormat.Equals(OutputFormatHelper.All)) { - AppWordDocBuilder wordzip = new AppWordDocBuilder(app, path, null); + //create the Word document + NotificationHelper.SendNotification("Creating Word documentation"); + if (String.IsNullOrEmpty(wordTemplate) || !File.Exists(wordTemplate)) + { + AppWordDocBuilder wordzip = new AppWordDocBuilder(content, null); + } + else + { + AppWordDocBuilder wordzip = new AppWordDocBuilder(content, wordTemplate); + } } - else + if (fileFormat.Equals(OutputFormatHelper.Markdown) || fileFormat.Equals(OutputFormatHelper.All)) { - AppWordDocBuilder wordzip = new AppWordDocBuilder(app, path, wordTemplate); + NotificationHelper.SendNotification("Creating Markdown documentation"); + AppMarkdownBuilder markdownFile = new AppMarkdownBuilder(content); } } DateTime endDocGeneration = DateTime.Now; diff --git a/PowerDocu.AppDocumenter/AppDocumenter.cs b/PowerDocu.AppDocumenter/AppDocumenter.cs index 7f30262..5614655 100644 --- a/PowerDocu.AppDocumenter/AppDocumenter.cs +++ b/PowerDocu.AppDocumenter/AppDocumenter.cs @@ -18,9 +18,9 @@ static void Main(string[] args) else { if (args.Length == 1) - AppDocumentationGenerator.GenerateWordDocumentation(args[0]); + AppDocumentationGenerator.GenerateDocumentation(args[0], "All"); if (args.Length == 2) - AppDocumentationGenerator.GenerateWordDocumentation(args[0], args[1]); + AppDocumentationGenerator.GenerateDocumentation(args[0], "All", args[1]); } } } diff --git a/PowerDocu.AppDocumenter/AppMarkdownBuilder.cs b/PowerDocu.AppDocumenter/AppMarkdownBuilder.cs new file mode 100644 index 0000000..e94fe67 --- /dev/null +++ b/PowerDocu.AppDocumenter/AppMarkdownBuilder.cs @@ -0,0 +1,448 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using PowerDocu.Common; +using Grynwald.MarkdownGenerator; +using Svg; + +namespace PowerDocu.AppDocumenter +{ + class AppMarkdownBuilder : MarkdownBuilder + { + private readonly AppDocumentationContent content; + private readonly string mainDocumentFileName, appDetailsFileName, variablesDocumentFileName, dataSourcesFileName, resourcesFileName, controlsFileName; + private readonly MdDocument mainDocument, appDetailsDocument, variablesDocument, dataSourcesDocument, resourcesDocument, controlsDocument; + private readonly Dictionary screenDocuments = new Dictionary(); + private readonly DocumentSet set; + private MdTable metadataTable; + private readonly int appLogoWidth = 250; + + public AppMarkdownBuilder(AppDocumentationContent contentdocumentation) + { + content = contentdocumentation; + Directory.CreateDirectory(content.folderPath); + mainDocumentFileName = ("index " + content.filename + ".md").Replace(" ", "-"); + appDetailsFileName = ("appdetails " + content.filename + ".md").Replace(" ", "-"); + variablesDocumentFileName = ("variables " + content.filename + ".md").Replace(" ", "-"); + dataSourcesFileName = ("datasources " + content.filename + ".md").Replace(" ", "-"); + resourcesFileName = ("resources " + content.filename + ".md").Replace(" ", "-"); + controlsFileName = ("controls " + content.filename + ".md").Replace(" ", "-"); + set = new DocumentSet(); + mainDocument = set.CreateMdDocument(mainDocumentFileName); + appDetailsDocument = set.CreateMdDocument(appDetailsFileName); + variablesDocument = set.CreateMdDocument(variablesDocumentFileName); + dataSourcesDocument = set.CreateMdDocument(dataSourcesFileName); + resourcesDocument = set.CreateMdDocument(resourcesFileName); + controlsDocument = set.CreateMdDocument(controlsFileName); + //a dedicated document for each screen + foreach (ControlEntity screen in contentdocumentation.appControls.controls.Where(o => o.Type == "screen").OrderBy(o => o.Name).ToList()) + { + screenDocuments.Add(screen.Name, set.CreateMdDocument(("screen " + screen.Name + " " + content.filename + ".md").Replace(" ", "-"))); + } + addAppMetadata(); + addAppOverview(); + addAppDetails(); + addAppVariablesInfo(); + addAppDataSources(); + addAppResources(); + addAppControlsOverview(); + addDetailedAppControls(); + set.Save(content.folderPath); + NotificationHelper.SendNotification("Created Markdown documentation for " + content.Name); + } + + private void addAppOverview() + { + List tableRows = new List(); + mainDocument.Root.Add(new MdHeading(content.appProperties.headerAppStatistics, 2)); + foreach (KeyValuePair kvp2 in content.appProperties.statisticsTable) + { + tableRows.Add(new MdTableRow(kvp2.Key, kvp2.Value)); + } + mainDocument.Root.Add(new MdTable(new MdTableRow("Component Type", "Count"), tableRows)); + } + + private MdBulletList getNavigationLinks(bool topLevel = true) + { + MdListItem[] navItems = new MdListItem[] { + new MdListItem(new MdLinkSpan("Overview", topLevel ? mainDocumentFileName : "../" + mainDocumentFileName)), + new MdListItem(new MdLinkSpan("App Details", topLevel ? appDetailsFileName : "../" + appDetailsFileName)), + new MdListItem(new MdLinkSpan("Variables", topLevel ? variablesDocumentFileName : "../" + variablesDocumentFileName)), + new MdListItem(new MdLinkSpan("DataSources", topLevel ? dataSourcesFileName : "../" + dataSourcesFileName)), + new MdListItem(new MdLinkSpan("Resources", topLevel ? resourcesFileName : "../" + resourcesFileName)), + new MdListItem(new MdLinkSpan("Controls", topLevel ? controlsFileName : "../" + controlsFileName)), + }; + return new MdBulletList(navItems); + } + + private void addAppMetadata() + { + List tableRows = new List + { + new MdTableRow("App Name", content.Name) + }; + if (!String.IsNullOrEmpty(content.appProperties.appLogo)) + { + if (content.ResourceStreams.TryGetValue(content.appProperties.appLogo, out MemoryStream resourceStream)) + { + Directory.CreateDirectory(content.folderPath + "resources"); + Bitmap appLogo; + if (!String.IsNullOrEmpty(content.appProperties.appBackgroundColour)) + { + Color c = ColorTranslator.FromHtml(ColourHelper.ParseColor(content.appProperties.appBackgroundColour)); + Bitmap bmp = new Bitmap(resourceStream); + appLogo = new Bitmap(bmp.Width, bmp.Height); + Rectangle rect = new Rectangle(Point.Empty, bmp.Size); + using (Graphics G = Graphics.FromImage(appLogo)) + { + G.Clear(c); + G.DrawImageUnscaledAndClipped(bmp, rect); + } + appLogo.Save(content.folderPath + @"resources\applogo.png"); + } + else + { + using Stream streamToWriteTo = File.Open(content.folderPath + @"resources\applogo.png", FileMode.Create); + resourceStream.CopyTo(streamToWriteTo); + resourceStream.Position = 0; + appLogo = new Bitmap(resourceStream); + } + resourceStream.Position = 0; + if (appLogo.Width > appLogoWidth) + { + Bitmap resized = new Bitmap(appLogo, new Size(appLogoWidth, appLogoWidth * appLogo.Height / appLogo.Width)); + resized.Save(content.folderPath + @"resources\appLogoSmall.png"); + tableRows.Add(new MdTableRow("App Logo", new MdImageSpan("App Logo", "resources/appLogoSmall.png"))); + } + else + { + tableRows.Add(new MdTableRow("App Logo", new MdImageSpan("App Logo", "resources/appLogo.png"))); + } + } + } + tableRows.Add(new MdTableRow(content.appProperties.headerDocumentationGenerated, DateTime.Now.ToLongDateString() + " " + DateTime.Now.ToShortTimeString())); + metadataTable = new MdTable(new MdTableRow(new List() { "Property", "Value" }), tableRows); + // prepare the common sections for all documents + foreach (MdDocument doc in set.Documents) + { + doc.Root.Add(new MdHeading(content.appProperties.header, 1)); + doc.Root.Add(metadataTable); + doc.Root.Add(getNavigationLinks()); + } + } + + private void addAppDetails() + { + List tableRows = new List(); + appDetailsDocument.Root.Add(new MdHeading(content.appProperties.headerAppProperties, 2)); + foreach (Expression property in content.appProperties.appProperties) + { + if (!content.appProperties.propertiesToSkip.Contains(property.expressionOperator)) + { + tableRows.Add(new MdTableRow(property.expressionOperator, property.expressionOperands[0].ToString())); + } + } + if (tableRows.Count > 0) + { + appDetailsDocument.Root.Add(new MdTable(new MdTableRow("App Property", "Value"), tableRows)); + } + appDetailsDocument.Root.Add(new MdHeading(content.appProperties.headerAppPreviewFlags, 2)); + tableRows = new List(); + if (content.appProperties.appPreviewsFlagProperty != null) + { + foreach (Expression flagProp in content.appProperties.appPreviewsFlagProperty.expressionOperands) + { + tableRows.Add(new MdTableRow(flagProp.expressionOperator, flagProp.expressionOperands[0].ToString())); + } + if (tableRows.Count > 0) + { + appDetailsDocument.Root.Add(new MdTable(new MdTableRow("Preview Flag", "Value"), tableRows)); + } + } + } + + private void addAppVariablesInfo() + { + variablesDocument.Root.Add(new MdHeading(content.appVariablesInfo.header, 2)); + variablesDocument.Root.Add(new MdParagraph(new MdTextSpan(content.appVariablesInfo.infoText))); + variablesDocument.Root.Add(new MdHeading(content.appVariablesInfo.headerGlobalVariables, 3)); + foreach (string var in content.appVariablesInfo.globalVariables) + { + variablesDocument.Root.Add(new MdHeading(var, 4)); + content.appVariablesInfo.variableCollectionControlReferences.TryGetValue(var, out List references); + if (references != null) + { + variablesDocument.Root.Add(new MdParagraph(new MdTextSpan("Variable used in:"))); + List tableRows = new List(); + foreach (ControlPropertyReference reference in references.OrderBy(o => o.Control.Name).ThenBy(o => o.RuleProperty)) + { + //link to the screen instead of the control directly for the moment, as the directly generated anchor link (#" + control.Name.ToLower()) doesn't work the same way in DevOps and GitHub + tableRows.Add(new MdTableRow(new MdLinkSpan(reference.Control.Name + " (" + reference.Control.Screen()?.Name + ")", + ("screen " + reference.Control.Screen()?.Name + " " + content.filename + ".md").Replace(" ", "-")), + reference.RuleProperty)); + } + variablesDocument.Root.Add(new MdTable(new MdTableRow("Control", "Property"), tableRows)); + } + } + variablesDocument.Root.Add(new MdHeading(content.appVariablesInfo.headerContextVariables, 3)); + foreach (string var in content.appVariablesInfo.contextVariables) + { + variablesDocument.Root.Add(new MdHeading(var, 4)); + content.appVariablesInfo.variableCollectionControlReferences.TryGetValue(var, out List references); + if (references != null) + { + variablesDocument.Root.Add(new MdParagraph(new MdTextSpan("Variable used in:"))); + List tableRows = new List(); + foreach (ControlPropertyReference reference in references.OrderBy(o => o.Control.Name).ThenBy(o => o.RuleProperty)) + { + tableRows.Add(new MdTableRow(reference.Control.Name + " (" + reference.Control.Screen()?.Name + ")", reference.RuleProperty)); + } + variablesDocument.Root.Add(new MdTable(new MdTableRow("Control", "Property"), tableRows)); + } + } + variablesDocument.Root.Add(new MdHeading(content.appVariablesInfo.headerCollections, 3)); + foreach (string var in content.appVariablesInfo.collections) + { + variablesDocument.Root.Add(new MdHeading(var, 4)); + content.appVariablesInfo.variableCollectionControlReferences.TryGetValue(var, out List references); + if (references != null) + { + variablesDocument.Root.Add(new MdParagraph(new MdTextSpan("Variable used in:"))); + List tableRows = new List(); + foreach (ControlPropertyReference reference in references.OrderBy(o => o.Control.Name).ThenBy(o => o.RuleProperty)) + { + tableRows.Add(new MdTableRow(reference.Control.Name + " (" + reference.Control.Screen()?.Name + ")", reference.RuleProperty)); + } + variablesDocument.Root.Add(new MdTable(new MdTableRow("Control", "Property"), tableRows)); + } + } + } + + private void addAppControlsOverview() + { + controlsDocument.Root.Add(new MdHeading(content.appControls.headerOverview, 2)); + controlsDocument.Root.Add(new MdParagraph(new MdTextSpan(content.appControls.infoTextScreens))); + controlsDocument.Root.Add(new MdParagraph(new MdTextSpan(content.appControls.infoTextControls))); + foreach (ControlEntity control in content.appControls.controls) + { + if (control.Type != "appinfo") + { + controlsDocument.Root.Add(new MdHeading(new MdLinkSpan("Screen: " + control.Name, ("screen " + control.Name + " " + content.filename + ".md").Replace(" ", "-")), 3)); + controlsDocument.Root.Add(CreateControlList(control)); + } + } + controlsDocument.Root.Add(new MdHeading(content.appControls.headerScreenNavigation, 2)); + controlsDocument.Root.Add(new MdParagraph(new MdTextSpan(content.appControls.infoTextScreenNavigation))); + controlsDocument.Root.Add(new MdParagraph(new MdImageSpan(content.appControls.headerScreenNavigation, content.appControls.imageScreenNavigation + ".svg"))); + } + + private MdBulletList CreateControlList(ControlEntity control) + { + var svgDocument = SvgDocument.FromSvg(AppControlIcons.GetControlIcon(control.Type)); + //generating the PNG from the SVG with a width of 16px because some SVGs are huge and downscaled, thus can't be shown directly + using (var bitmap = svgDocument.Draw(16, 0)) + { + bitmap?.Save(content.folderPath + @"resources\" + control.Type + ".png"); + } + //link to the screen instead of the control directly for the moment, as the directly generated anchor link (#" + control.Name.ToLower()) doesn't work the same way in DevOps and GitHub + MdBulletList list = new MdBulletList(){ + new MdListItem(new MdLinkSpan( + new MdCompositeSpan( + new MdImageSpan(control.Type, "resources/"+control.Type+".png"), + new MdTextSpan(" "+control.Name)) + ,("screen " + control.Screen().Name + " " + content.filename + ".md").Replace(" ", "-")))}; + + foreach (ControlEntity child in control.Children.OrderBy(o => o.Name).ToList()) + { + list.Add(new MdListItem(CreateControlList(child))); + } + return list; + } + + private void addDetailedAppControls() + { + foreach (ControlEntity screen in content.appControls.controls.Where(o => o.Type == "screen").OrderBy(o => o.Name).ToList()) + { + screenDocuments.TryGetValue(screen.Name, out MdDocument screenDoc); + screenDoc.Root.Add(new MdHeading(screen.Name, 2)); + addAppControlsTable(screen, screenDoc); + foreach (ControlEntity control in content.appControls.allControls.Where(o => o.Type != "appinfo" && o.Type != "screen" && o.Screen().Equals(screen)).OrderBy(o => o.Name).ToList()) + { + screenDoc.Root.Add(new MdHeading(control.Name, 2)); + addAppControlsTable(control, screenDoc); + } + } + } + + private void addAppControlsTable(ControlEntity control, MdDocument screenDoc) + { + List tableRows = new List(); + var svgDocument = SvgDocument.FromSvg(AppControlIcons.GetControlIcon(control.Type)); + //generating the PNG from the SVG with a width of 16px because some SVGs are huge and downscaled, thus can't be shown directly + using (var bitmap = svgDocument.Draw(16, 0)) + { + bitmap?.Save(content.folderPath + @"resources\" + control.Type + ".png"); + } + tableRows.Add(new MdTableRow(new MdImageSpan(control.Type, "resources/" + control.Type + ".png"), new MdTextSpan("Type: " + control.Type))); + + string category = ""; + foreach (Rule rule in control.Rules.OrderBy(o => o.Category).ThenBy(o => o.Property).ToList()) + { + if (!content.ColourProperties.Contains(rule.Property)) + { + if (rule.Category != category) + { + if (tableRows.Count > 0) + { + screenDoc.Root.Add(new MdTable(new MdTableRow("Property", "Value"), tableRows)); + tableRows = new List(); + } + category = rule.Category; + screenDoc.Root.Add(new MdHeading(category, 3)); + } + if (rule.InvariantScript.StartsWith("RGBA(")) + { + string colour = ColourHelper.ParseColor(rule.InvariantScript[..(rule.InvariantScript.IndexOf(')') + 1)]); + if (!String.IsNullOrEmpty(colour)) + { + StringBuilder colorTable = new StringBuilder(""); + colorTable.Append(""); + colorTable.Append("
").Append(rule.InvariantScript).Append("
"); + tableRows.Add(new MdTableRow(rule.Property, new MdRawMarkdownSpan(colorTable.ToString()))); + } + else + { + tableRows.Add(new MdTableRow(rule.Property, rule.InvariantScript)); + } + } + else + { + tableRows.Add(new MdTableRow(rule.Property, rule.InvariantScript)); + } + } + } + if (tableRows.Count > 0) + screenDoc.Root.Add(new MdTable(new MdTableRow("Property", "Value"), tableRows)); + //Colour properties + tableRows = new List(); + screenDoc.Root.Add(new MdHeading("Color Properties", 3)); + foreach (string property in content.ColourProperties) + { + Rule rule = control.Rules.Find(o => o.Property == property); + if (rule != null) + { + if (rule.InvariantScript.StartsWith("RGBA(")) + { + string colour = ColourHelper.ParseColor(rule.InvariantScript[..(rule.InvariantScript.IndexOf(')') + 1)]); + if (!String.IsNullOrEmpty(colour)) + { + StringBuilder colorTable = new StringBuilder(""); + colorTable.Append(""); + colorTable.Append("
").Append(rule.InvariantScript).Append("
"); + tableRows.Add(new MdTableRow(rule.Property, new MdRawMarkdownSpan(colorTable.ToString()))); + } + else + { + tableRows.Add(new MdTableRow(rule.Property, rule.InvariantScript)); + } + } + else + { + tableRows.Add(new MdTableRow(rule.Property, rule.InvariantScript)); + } + } + } + if (tableRows.Count > 0) + screenDoc.Root.Add(new MdTable(new MdTableRow("Property", "Value"), tableRows)); + tableRows = new List(); + screenDoc.Root.Add(new MdHeading("Child & Parent Controls", 3)); + + foreach (ControlEntity childControl in control.Children) + { + tableRows.Add(new MdTableRow("Child Control", childControl.Name)); + } + if (control.Parent != null) + { + tableRows.Add(new MdTableRow("Parent Control", control.Parent.Name)); + } + if (tableRows.Count > 0) + screenDoc.Root.Add(new MdTable(new MdTableRow("Property", "Value"), tableRows)); + } + + private void addAppDataSources() + { + dataSourcesDocument.Root.Add(new MdHeading(content.appDataSources.header, 2)); + dataSourcesDocument.Root.Add(new MdParagraph(new MdTextSpan(content.appDataSources.infoText))); + + foreach (DataSource datasource in content.appDataSources.dataSources) + { + dataSourcesDocument.Root.Add(new MdHeading(datasource.Name, 3)); + List tableRows = new List + { + new MdTableRow("Name", datasource.Name), + new MdTableRow("Type", datasource.Type) + }; + dataSourcesDocument.Root.Add(new MdTable(new MdTableRow("Property", "Value"), tableRows)); + tableRows = new List(); + dataSourcesDocument.Root.Add(new MdHeading("DataSource Properties", 4)); + foreach (Expression expression in datasource.Properties.OrderBy(o => o.expressionOperator)) + { + if (expression.expressionOperands.Count > 1) + { + tableRows.Add(new MdTableRow(expression.expressionOperator, new MdRawMarkdownSpan(AddExpressionDetails(new List { expression })))); + } + else + { + tableRows.Add(new MdTableRow(expression.expressionOperator, expression.expressionOperands?[0].ToString())); + } + } + dataSourcesDocument.Root.Add(new MdTable(new MdTableRow("Property", "Value"), tableRows)); + } + } + + private void addAppResources() + { + Directory.CreateDirectory(content.folderPath + "resources"); + resourcesDocument.Root.Add(new MdHeading(content.appResources.header, 2)); + resourcesDocument.Root.Add(new MdParagraph(new MdTextSpan(content.appResources.infoText))); + foreach (Resource resource in content.appResources.resources) + { + resourcesDocument.Root.Add(new MdHeading(resource.Name, 3)); + List tableRows = new List + { + new MdTableRow("Name", resource.Name), + new MdTableRow("Content", resource.Content), + new MdTableRow("Resource Kind", resource.ResourceKind) + }; + if (resource.ResourceKind == "LocalFile") + { + if (content.ResourceStreams.TryGetValue(resource.Name, out MemoryStream resourceStream)) + { + Expression fileName = resource.Properties.First(o => o.expressionOperator == "FileName"); + using Stream streamToWriteTo = File.Open(content.folderPath + @"resources\" + fileName.expressionOperands[0].ToString(), FileMode.Create); + + resourceStream.Position = 0; + resourceStream.CopyTo(streamToWriteTo); + int imageWidth = 400; + if (!fileName.expressionOperands[0].ToString().EndsWith("svg", StringComparison.OrdinalIgnoreCase)) + { + using var image = Image.FromStream(resourceStream, false, false); + imageWidth = (image.Width > 400) ? 400 : image.Width; + } + //todo consider showing a resized copy of the image if it is wider than 400px + tableRows.Add(new MdTableRow("Resource Preview", new MdImageSpan(resource.Name, "resources/" + fileName.expressionOperands[0].ToString()))); + } + } + foreach (Expression expression in resource.Properties.OrderBy(o => o.expressionOperator)) + { + tableRows.Add(new MdTableRow(expression.expressionOperator, expression.expressionOperands?[0].ToString())); + } + + resourcesDocument.Root.Add(new MdTable(new MdTableRow("Property", "Value"), tableRows)); + } + } + } +} diff --git a/PowerDocu.AppDocumenter/AppParser.cs b/PowerDocu.AppDocumenter/AppParser.cs index 673c985..d87e4f4 100644 --- a/PowerDocu.AppDocumenter/AppParser.cs +++ b/PowerDocu.AppDocumenter/AppParser.cs @@ -18,7 +18,7 @@ public enum PackageType SolutionPackage } private readonly List apps = new List(); - private AppEntity currentApp; + private readonly AppEntity currentApp; public PackageType packageType; public AppParser(string filename) @@ -26,29 +26,27 @@ public AppParser(string filename) NotificationHelper.SendNotification(" - Processing " + filename); if (filename.EndsWith(".zip")) { - using (FileStream stream = new FileStream(filename, FileMode.Open)) + using FileStream stream = new FileStream(filename, FileMode.Open); + List definitions = ZipHelper.getFilesInPathFromZip(stream, "", ".msapp"); + packageType = PackageType.SolutionPackage; + foreach (ZipArchiveEntry definition in definitions) { - List definitions = ZipHelper.getFilesInPathFromZip(stream, "", ".msapp"); - packageType = PackageType.SolutionPackage; - foreach (ZipArchiveEntry definition in definitions) + string tempFile = Path.GetDirectoryName(filename) + @"\" + definition.Name; + definition.ExtractToFile(tempFile, true); + NotificationHelper.SendNotification(" - Processing app " + definition.FullName); + using (FileStream appDefinition = new FileStream(tempFile, FileMode.Open)) { - string tempFile = Path.GetDirectoryName(filename) + @"\" + definition.Name; - definition.ExtractToFile(tempFile, true); - NotificationHelper.SendNotification(" - Processing app " + definition.FullName); - using (FileStream appDefinition = new FileStream(tempFile, FileMode.Open)) { - { - AppEntity app = new AppEntity(); - currentApp = app; - parseAppProperties(appDefinition); - parseAppControls(appDefinition); - parseAppDataSources(appDefinition); - parseAppResources(appDefinition); - apps.Add(app); - } + AppEntity app = new AppEntity(); + currentApp = app; + parseAppProperties(appDefinition); + parseAppControls(appDefinition); + parseAppDataSources(appDefinition); + parseAppResources(appDefinition); + apps.Add(app); } - File.Delete(tempFile); } + File.Delete(tempFile); } } else if (filename.EndsWith(".msapp")) @@ -57,14 +55,12 @@ public AppParser(string filename) packageType = PackageType.AppPackage; AppEntity app = new AppEntity(); currentApp = app; - using (FileStream stream = new FileStream(filename, FileMode.Open)) - { - parseAppProperties(stream); - parseAppControls(stream); - parseAppDataSources(stream); - parseAppResources(stream); - apps.Add(app); - } + using FileStream stream = new FileStream(filename, FileMode.Open); + parseAppProperties(stream); + parseAppControls(stream); + parseAppDataSources(stream); + parseAppResources(stream); + apps.Add(app); } else { @@ -77,31 +73,29 @@ private void parseAppProperties(Stream appArchive) string[] filesToParse = new string[] { "Resources\\PublishInfo.json", "Header.json", "Properties.json" }; foreach (string fileToParse in filesToParse) { - using (StreamReader reader = new StreamReader(ZipHelper.getFileFromZip(appArchive, fileToParse).Open())) + using StreamReader reader = new StreamReader(ZipHelper.getFileFromZip(appArchive, fileToParse).Open()); + string appJSON = reader.ReadToEnd(); + var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All, MaxDepth = 128 }; + var _jsonSerializer = JsonSerializer.Create(settings); + dynamic propertiesDefinition = JsonConvert.DeserializeObject(appJSON, settings).ToObject(typeof(object), _jsonSerializer); + foreach (JToken property in propertiesDefinition.Children()) { - string appJSON = reader.ReadToEnd(); - var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All, MaxDepth = 128 }; - var _jsonSerializer = JsonSerializer.Create(settings); - dynamic propertiesDefinition = JsonConvert.DeserializeObject(appJSON, settings).ToObject(typeof(object), _jsonSerializer); - foreach (JToken property in propertiesDefinition.Children()) + JProperty prop = (JProperty)property; + currentApp.Properties.Add(Expression.parseExpressions(prop)); + if (prop.Name.Equals("AppName")) { - JProperty prop = (JProperty)property; - currentApp.Properties.Add(Expression.parseExpressions(prop)); - if (prop.Name.Equals("AppName")) - { - currentApp.Name = prop.Value.ToString(); - } - if (prop.Name.Equals("ID")) - { - currentApp.ID = prop.Value.ToString(); - } - if (prop.Name.Equals("LogoFileName") && !String.IsNullOrEmpty(prop.Value.ToString())) - { - ZipArchiveEntry resourceFile = ZipHelper.getFileFromZip(appArchive, "Resources\\" + prop.Value.ToString()); - MemoryStream ms = new MemoryStream(); - resourceFile.Open().CopyTo(ms); - currentApp.ResourceStreams.Add(prop.Value.ToString(), ms); - } + currentApp.Name = prop.Value.ToString(); + } + if (prop.Name.Equals("ID")) + { + currentApp.ID = prop.Value.ToString(); + } + if (prop.Name.Equals("LogoFileName") && !String.IsNullOrEmpty(prop.Value.ToString())) + { + ZipArchiveEntry resourceFile = ZipHelper.getFileFromZip(appArchive, "Resources\\" + prop.Value.ToString()); + MemoryStream ms = new MemoryStream(); + resourceFile.Open().CopyTo(ms); + currentApp.ResourceStreams.Add(prop.Value.ToString(), ms); } } } @@ -113,14 +107,12 @@ private void parseAppControls(Stream appArchive) //parse the controls. each controlFile represents a screen foreach (ZipArchiveEntry controlEntry in controlFiles) { - using (StreamReader reader = new StreamReader(controlEntry.Open())) - { - string appJSON = reader.ReadToEnd(); - var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All, MaxDepth = 128 }; - var _jsonSerializer = JsonSerializer.Create(settings); - dynamic controlsDefinition = JsonConvert.DeserializeObject(appJSON, settings).ToObject(typeof(object), _jsonSerializer); - currentApp.Controls.Add(parseControl(((JObject)controlsDefinition.TopParent).Children().ToList())); - } + using StreamReader reader = new StreamReader(controlEntry.Open()); + string appJSON = reader.ReadToEnd(); + var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All, MaxDepth = 128 }; + var _jsonSerializer = JsonSerializer.Create(settings); + dynamic controlsDefinition = JsonConvert.DeserializeObject(appJSON, settings).ToObject(typeof(object), _jsonSerializer); + currentApp.Controls.Add(parseControl(((JObject)controlsDefinition.TopParent).Children().ToList())); } foreach (ControlEntity control in currentApp.Controls) { @@ -532,32 +524,30 @@ private int findClosingCharacter(string content, char open, char close) private void parseAppDataSources(Stream appArchive) { ZipArchiveEntry dataSourceFile = ZipHelper.getFileFromZip(appArchive, "References\\DataSources.json"); - using (StreamReader reader = new StreamReader(dataSourceFile.Open())) - { - string appJSON = reader.ReadToEnd(); - var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All, MaxDepth = 128 }; - var _jsonSerializer = JsonSerializer.Create(settings); - dynamic datasourceDefinition = JsonConvert.DeserializeObject(appJSON, settings).ToObject(typeof(object), _jsonSerializer); - foreach (JToken datasource in datasourceDefinition.DataSources.Children()) + using StreamReader reader = new StreamReader(dataSourceFile.Open()); + string appJSON = reader.ReadToEnd(); + var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All, MaxDepth = 128 }; + var _jsonSerializer = JsonSerializer.Create(settings); + dynamic datasourceDefinition = JsonConvert.DeserializeObject(appJSON, settings).ToObject(typeof(object), _jsonSerializer); + foreach (JToken datasource in datasourceDefinition.DataSources.Children()) + { + DataSource ds = new DataSource(); + foreach (JProperty prop in datasource.Children()) { - DataSource ds = new DataSource(); - foreach (JProperty prop in datasource.Children()) + switch (prop.Name) { - switch (prop.Name) - { - case "Name": - ds.Name = prop.Value.ToString(); - break; - case "Type": - ds.Type = prop.Value.ToString(); - break; - default: - ds.Properties.Add(Expression.parseExpressions(prop)); - break; - } + case "Name": + ds.Name = prop.Value.ToString(); + break; + case "Type": + ds.Type = prop.Value.ToString(); + break; + default: + ds.Properties.Add(Expression.parseExpressions(prop)); + break; } - currentApp.DataSources.Add(ds); } + currentApp.DataSources.Add(ds); } } @@ -565,48 +555,46 @@ private void parseAppResources(Stream appArchive) { string[] ResourceExtensions = new string[] { "jpg", "jpeg", "gif", "png", "bmp", "tif", "tiff", "svg" }; ZipArchiveEntry dataSourceFile = ZipHelper.getFileFromZip(appArchive, "References\\Resources.json"); - using (StreamReader reader = new StreamReader(dataSourceFile.Open())) - { - string appJSON = reader.ReadToEnd(); - var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All, MaxDepth = 128 }; - var _jsonSerializer = JsonSerializer.Create(settings); - dynamic resourceDefinition = JsonConvert.DeserializeObject(appJSON, settings).ToObject(typeof(object), _jsonSerializer); - foreach (JToken resource in resourceDefinition.Resources.Children()) + using StreamReader reader = new StreamReader(dataSourceFile.Open()); + string appJSON = reader.ReadToEnd(); + var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All, MaxDepth = 128 }; + var _jsonSerializer = JsonSerializer.Create(settings); + dynamic resourceDefinition = JsonConvert.DeserializeObject(appJSON, settings).ToObject(typeof(object), _jsonSerializer); + foreach (JToken resource in resourceDefinition.Resources.Children()) + { + Resource res = new Resource(); + string pathToResource = ""; + foreach (JProperty prop in resource.Children()) { - Resource res = new Resource(); - string pathToResource = ""; - foreach (JProperty prop in resource.Children()) + switch (prop.Name) { - switch (prop.Name) - { - case "Name": - res.Name = prop.Value.ToString(); - break; - case "Content": - res.Content = prop.Value.ToString(); - break; - case "ResourceKind": - res.ResourceKind = prop.Value.ToString(); - break; - case "Path": - pathToResource = prop.Value.ToString(); - break; - default: - res.Properties.Add(Expression.parseExpressions(prop)); - break; - } + case "Name": + res.Name = prop.Value.ToString(); + break; + case "Content": + res.Content = prop.Value.ToString(); + break; + case "ResourceKind": + res.ResourceKind = prop.Value.ToString(); + break; + case "Path": + pathToResource = prop.Value.ToString(); + break; + default: + res.Properties.Add(Expression.parseExpressions(prop)); + break; } - currentApp.Resources.Add(res); - if (res.ResourceKind == "LocalFile") + } + currentApp.Resources.Add(res); + if (res.ResourceKind == "LocalFile") + { + string extension = pathToResource[(pathToResource.LastIndexOf('.') + 1)..].ToLower(); + if (ResourceExtensions.Contains(extension)) { - string extension = pathToResource.Substring(pathToResource.LastIndexOf('.') + 1).ToLower(); - if (ResourceExtensions.Contains(extension)) - { - ZipArchiveEntry resourceFile = ZipHelper.getFileFromZip(appArchive, pathToResource); - MemoryStream ms = new MemoryStream(); - resourceFile.Open().CopyTo(ms); - currentApp.ResourceStreams.Add(res.Name, ms); - } + ZipArchiveEntry resourceFile = ZipHelper.getFileFromZip(appArchive, pathToResource); + MemoryStream ms = new MemoryStream(); + resourceFile.Open().CopyTo(ms); + currentApp.ResourceStreams.Add(res.Name, ms); } } } diff --git a/PowerDocu.AppDocumenter/AppWordDocBuilder.cs b/PowerDocu.AppDocumenter/AppWordDocBuilder.cs index 8d621aa..3ab7de7 100644 --- a/PowerDocu.AppDocumenter/AppWordDocBuilder.cs +++ b/PowerDocu.AppDocumenter/AppWordDocBuilder.cs @@ -3,73 +3,56 @@ using System.Drawing; using System.IO; using System.Linq; -using System.Security.Cryptography; using System.Text; using DocumentFormat.OpenXml; using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Wordprocessing; -using A = DocumentFormat.OpenXml.Drawing; -using A14 = DocumentFormat.OpenXml.Office2010.Drawing; -using DW = DocumentFormat.OpenXml.Drawing.Wordprocessing; -using PIC = DocumentFormat.OpenXml.Drawing.Pictures; using PowerDocu.Common; namespace PowerDocu.AppDocumenter { class AppWordDocBuilder : WordDocBuilder { - private readonly AppEntity app; + private readonly AppDocumentationContent content; private bool DetailedDocumentation = false; - public AppWordDocBuilder(AppEntity appToDocument, string path, string template) + public AppWordDocBuilder(AppDocumentationContent contentDocumentation, string template) { - this.app = appToDocument; - folderPath = path + CharsetHelper.GetSafeName(@"\AppDoc - " + app.Name + @"\"); - Directory.CreateDirectory(folderPath); + content = contentDocumentation; + Directory.CreateDirectory(content.folderPath); do { - string filename = CharsetHelper.GetSafeName(app.Name) + ((app.ID != null) ? ("(" + app.ID + ")") : "") + (DetailedDocumentation ? " detailed" : "") + ".docx"; - filename = filename.Replace(":", "-"); - filename = folderPath + filename; - InitializeWordDocument(filename, template); - using (WordprocessingDocument wordDocument = WordprocessingDocument.Open(filename, true)) - { - mainPart = wordDocument.MainDocumentPart; - body = mainPart.Document.Body; - PrepareDocument(!String.IsNullOrEmpty(template)); - addAppProperties(); - addAppGeneralInfo(); - addAppDataSources(); - addAppResources(); - addAppControlsOverview(wordDocument); - if (DetailedDocumentation) addDetailedAppControls(); - } + string filename = InitializeWordDocument(content.folderPath + content.filename + (DetailedDocumentation ? " detailed" : ""), template); + using WordprocessingDocument wordDocument = WordprocessingDocument.Open(filename, true); + mainPart = wordDocument.MainDocumentPart; + body = mainPart.Document.Body; + PrepareDocument(!String.IsNullOrEmpty(template)); + addAppProperties(); + addAppVariablesInfo(); + addAppDataSources(); + addAppResources(); + addAppControlsOverview(wordDocument); + if (DetailedDocumentation) addDetailedAppControls(); DetailedDocumentation = !DetailedDocumentation; } while (DetailedDocumentation); - NotificationHelper.SendNotification("Created Word documentation for " + app.Name); + NotificationHelper.SendNotification("Created Word documentation for " + contentDocumentation.Name); } private void addAppProperties() { - string[] propertiesToSkip = new string[] { "AppPreviewFlagsMap", "ControlCount" }; - string[] OverviewProperties = new string[] {"AppCreationSource", "AppDescription", "AppName", "BackgroundColor", "DocumentAppType", "DocumentLayoutHeight", "DocumentLayoutOrientation", - "DocumentLayoutWidth", "IconColor", "IconName", "Id", "LastSavedDateTimeUTC", "LogoFileName", "Name"}; Paragraph para = body.AppendChild(new Paragraph()); Run run = para.AppendChild(new Run()); - run.AppendChild(new Text("Power App Documentation - " + app.Name)); + run.AppendChild(new Text(content.appProperties.header)); ApplyStyleToParagraph("Heading1", para); body.AppendChild(new Paragraph(new Run())); Table table = CreateTable(); - table.Append(CreateRow(new Text("App Name"), new Text(app.Name))); + table.Append(CreateRow(new Text("App Name"), new Text(content.Name))); //if there is a custom logo we add it to the documentation as well. Icon based logos currently not supported - Expression appLogo = app.Properties.FirstOrDefault(o => o.expressionOperator == "LogoFileName"); - if (appLogo != null) + if (!String.IsNullOrEmpty(content.appProperties.appLogo)) { - MemoryStream resourceStream; - if (app.ResourceStreams.TryGetValue(appLogo.expressionOperands[0].ToString(), out resourceStream)) + if (content.ResourceStreams.TryGetValue(content.appProperties.appLogo, out MemoryStream resourceStream)) { - Drawing icon = null; - + Drawing icon; ImagePart imagePart = mainPart.AddImagePart(ImagePartType.Jpeg); int imageWidth, imageHeight; using (var image = Image.FromStream(resourceStream, false, false)) @@ -82,14 +65,13 @@ private void addAppProperties() int usedWidth = (imageWidth > 400) ? 400 : imageWidth; icon = InsertImage(mainPart.GetIdOfPart(imagePart), usedWidth, (int)(usedWidth * imageHeight / imageWidth)); TableRow tr = CreateRow(new Text("App Logo"), icon); - Expression bgColour = app.Properties.FirstOrDefault(o => o.expressionOperator == "BackgroundColor"); - if (bgColour != null) + if (!String.IsNullOrEmpty(content.appProperties.appBackgroundColour)) { TableCell tc = (TableCell)tr.LastChild; var shading = new Shading() { Color = "auto", - Fill = ColourHelper.ParseColor(bgColour.expressionOperands[0].ToString()), + Fill = ColourHelper.ParseColor(content.appProperties.appBackgroundColour), Val = ShadingPatternValues.Clear }; tc.TableCellProperties.Append(shading); @@ -97,32 +79,24 @@ private void addAppProperties() table.Append(tr); } } - table.Append(CreateRow(new Text("Documentation generated at"), new Text(DateTime.Now.ToLongDateString() + " " + DateTime.Now.ToShortTimeString()))); + table.Append(CreateRow(new Text(content.appProperties.headerDocumentationGenerated), new Text(DateTime.Now.ToLongDateString() + " " + DateTime.Now.ToShortTimeString()))); Table statisticsTable = CreateTable(); - statisticsTable.Append(CreateRow(new Text("Screens"), new Text("" + app.Controls.Where(o => o.Type == "screen").ToList().Count))); - List allControls = new List(); - foreach (ControlEntity control in app.Controls) + foreach (KeyValuePair stats in content.appProperties.statisticsTable) { - allControls.Add(control); - allControls.AddRange(getAllChildControls(control)); + statisticsTable.Append(CreateRow(new Text(stats.Key), new Text(stats.Value))); } - statisticsTable.Append(CreateRow(new Text("Controls (excluding Screens)"), new Text("" + (allControls.Count - app.Controls.Where(o => o.Type == "screen").ToList().Count)))); - statisticsTable.Append(CreateRow(new Text("Variables"), new Text("" + (app.GlobalVariables.Count + app.ContextVariables.Count)))); - statisticsTable.Append(CreateRow(new Text("Collections"), new Text("" + app.Collections.Count))); - statisticsTable.Append(CreateRow(new Text("Data Sources"), new Text("" + app.DataSources.Count))); - statisticsTable.Append(CreateRow(new Text("Resources"), new Text("" + app.Resources.Count))); - table.Append(CreateRow(new Text("App Statistics"), statisticsTable)); + table.Append(CreateRow(new Text(content.appProperties.headerAppStatistics), statisticsTable)); body.Append(table); body.AppendChild(new Paragraph(new Run(new Break()))); para = body.AppendChild(new Paragraph()); run = para.AppendChild(new Run()); - run.AppendChild(new Text("App Properties")); - ApplyStyleToParagraph("Heading2", para); + run.AppendChild(new Text(content.appProperties.headerAppProperties)); + ApplyStyleToParagraph("Heading1", para); body.AppendChild(new Paragraph(new Run())); table = CreateTable(); - foreach (Expression property in app.Properties.OrderBy(o => o.expressionOperator).ToList()) + foreach (Expression property in content.appProperties.appProperties) { - if (!propertiesToSkip.Contains(property.expressionOperator) && (OverviewProperties.Contains(property.expressionOperator) || DetailedDocumentation)) + if (!content.appProperties.propertiesToSkip.Contains(property.expressionOperator) && (content.appProperties.OverviewProperties.Contains(property.expressionOperator) || DetailedDocumentation)) { AddExpressionTable(property, table, 1, false, true); } @@ -133,11 +107,11 @@ private void addAppProperties() { para = body.AppendChild(new Paragraph()); run = para.AppendChild(new Run()); - run.AppendChild(new Text("App Preview Flags")); - ApplyStyleToParagraph("Heading2", para); + run.AppendChild(new Text(content.appProperties.headerAppPreviewFlags)); + ApplyStyleToParagraph("Heading1", para); body.AppendChild(new Paragraph(new Run())); table = CreateTable(); - Expression appPreviewsFlagProperty = app.Properties.Find(o => o.expressionOperator == "AppPreviewFlagsMap"); + Expression appPreviewsFlagProperty = content.appProperties.appPreviewsFlagProperty; if (appPreviewsFlagProperty != null) { foreach (Expression flagProp in appPreviewsFlagProperty.expressionOperands) @@ -150,23 +124,23 @@ private void addAppProperties() } } - private void addAppGeneralInfo() + private void addAppVariablesInfo() { Paragraph para = body.AppendChild(new Paragraph()); Run run = para.AppendChild(new Run()); - run.AppendChild(new Text("Variables & Collections")); - ApplyStyleToParagraph("Heading2", para); - body.AppendChild(new Paragraph(new Run(new Text($"There are {app.GlobalVariables.Count} Global Variables, {app.ContextVariables.Count} Context Variables and {app.Collections.Count} Collections.")))); + run.AppendChild(new Text(content.appVariablesInfo.header)); + ApplyStyleToParagraph("Heading1", para); + body.AppendChild(new Paragraph(new Run(new Text(content.appVariablesInfo.infoText)))); para = body.AppendChild(new Paragraph()); run = para.AppendChild(new Run()); - run.AppendChild(new Text("Global Variables")); - ApplyStyleToParagraph("Heading3", para); + run.AppendChild(new Text(content.appVariablesInfo.headerGlobalVariables)); + ApplyStyleToParagraph("Heading2", para); Table table = CreateTable(); table.Append(CreateHeaderRow(new Text("Variable Name"), new Text("Used In"))); - foreach (string var in app.GlobalVariables.OrderBy(o => o).ToHashSet()) + foreach (string var in content.appVariablesInfo.globalVariables) { Table varReferenceTable = CreateTable(); - app.VariableCollectionControlReferences.TryGetValue(var, out List references); + content.appVariablesInfo.variableCollectionControlReferences.TryGetValue(var, out List references); if (references != null) { varReferenceTable.Append(CreateHeaderRow(new Text("Control"), new Text("Property"))); @@ -181,14 +155,14 @@ private void addAppGeneralInfo() body.AppendChild(new Paragraph(new Run(new Break()))); para = body.AppendChild(new Paragraph()); run = para.AppendChild(new Run()); - run.AppendChild(new Text("Context Variables")); - ApplyStyleToParagraph("Heading3", para); + run.AppendChild(new Text(content.appVariablesInfo.headerContextVariables)); + ApplyStyleToParagraph("Heading2", para); table = CreateTable(); table.Append(CreateHeaderRow(new Text("Variable Name"), new Text("Used In"))); - foreach (string var in app.ContextVariables.OrderBy(o => o).ToHashSet()) + foreach (string var in content.appVariablesInfo.contextVariables) { Table varReferenceTable = CreateTable(); - app.VariableCollectionControlReferences.TryGetValue(var, out List references); + content.appVariablesInfo.variableCollectionControlReferences.TryGetValue(var, out List references); if (references != null) { varReferenceTable.Append(CreateHeaderRow(new Text("Control"), new Text("Property"))); @@ -203,14 +177,14 @@ private void addAppGeneralInfo() body.AppendChild(new Paragraph(new Run(new Break()))); para = body.AppendChild(new Paragraph()); run = para.AppendChild(new Run()); - run.AppendChild(new Text("Collections")); - ApplyStyleToParagraph("Heading3", para); + run.AppendChild(new Text(content.appVariablesInfo.headerCollections)); + ApplyStyleToParagraph("Heading2", para); table = CreateTable(); table.Append(CreateHeaderRow(new Text("Collection Name"), new Text("Used In"))); - foreach (string coll in app.Collections.OrderBy(o => o).ToHashSet()) + foreach (string coll in content.appVariablesInfo.collections) { Table collReferenceTable = CreateTable(); - app.VariableCollectionControlReferences.TryGetValue(coll, out List references); + content.appVariablesInfo.variableCollectionControlReferences.TryGetValue(coll, out List references); if (references != null) { collReferenceTable.Append(CreateHeaderRow(new Text("Control"), new Text("Property"))); @@ -229,29 +203,41 @@ private void addAppControlsOverview(WordprocessingDocument wordDoc) { Paragraph para = body.AppendChild(new Paragraph()); Run run = para.AppendChild(new Run()); - run.AppendChild(new Text("Controls Overview")); - ApplyStyleToParagraph("Heading2", para); - body.AppendChild(new Paragraph(new Run(new Text($"A total of {app.Controls.Where(o => o.Type == "screen").ToList().Count} Screens are located in the app:")))); - foreach (ControlEntity control in app.Controls.OrderBy(o => o.Name).ToList()) + run.AppendChild(new Text(content.appControls.headerOverview)); + ApplyStyleToParagraph("Heading1", para); + body.AppendChild(new Paragraph(new Run(new Text(content.appControls.infoTextScreens)))); + body.AppendChild(new Paragraph(new Run(new Text(content.appControls.infoTextControls)))); + foreach (ControlEntity control in content.appControls.controls) { if (control.Type != "appinfo") { para = body.AppendChild(new Paragraph()); run = para.AppendChild(new Run()); - run.AppendChild(new Text("Screen: " + control.Name)); - ApplyStyleToParagraph("Heading3", para); + if (DetailedDocumentation) + { + run.AppendChild(new Hyperlink(new Text("Screen: " + control.Name)) + { + Anchor = CreateMD5Hash(control.Name), + DocLocation = "" + }); + } + else + { + run.AppendChild(new Text("Screen: " + control.Name)); + } + ApplyStyleToParagraph("Heading2", para); body.AppendChild(CreateControlTable(control)); } } body.AppendChild(new Paragraph(new Run(new Break()))); para = body.AppendChild(new Paragraph()); run = para.AppendChild(new Run()); - run.AppendChild(new Text("Screen Navigation")); - ApplyStyleToParagraph("Heading3", para); - body.AppendChild(new Paragraph(new Run(new Text("The following diagram shows the navigation between the different screens.")))); + run.AppendChild(new Text(content.appControls.headerScreenNavigation)); + ApplyStyleToParagraph("Heading2", para); + body.AppendChild(new Paragraph(new Run(new Text(content.appControls.infoTextScreenNavigation)))); ImagePart imagePart = wordDoc.MainDocumentPart.AddImagePart(ImagePartType.Png); int imageWidth, imageHeight; - using (FileStream stream = new FileStream(folderPath + "ScreenNavigation.png", FileMode.Open)) + using (FileStream stream = new FileStream(content.folderPath + content.appControls.imageScreenNavigation + ".png", FileMode.Open)) { using (var image = Image.FromStream(stream, false, false)) { @@ -262,7 +248,7 @@ private void addAppControlsOverview(WordprocessingDocument wordDoc) imagePart.FeedData(stream); } ImagePart svgPart = wordDoc.MainDocumentPart.AddNewPart("image/svg+xml", "rId" + (new Random()).Next(100000, 999999)); - using (FileStream stream = new FileStream(folderPath + "ScreenNavigation.svg", FileMode.Open)) + using (FileStream stream = new FileStream(content.folderPath + content.appControls.imageScreenNavigation + ".svg", FileMode.Open)) { svgPart.FeedData(stream); } @@ -283,7 +269,21 @@ private Table CreateControlTable(ControlEntity control, BorderValues borderType SetDefaultTableBorderStyle(new InsideHorizontalBorder(), BorderValues.None), SetDefaultTableBorderStyle(new InsideVerticalBorder(), BorderValues.None) ); - table.Append(CreateRow(InsertSvgImage(mainPart, AppControlIcons.GetControlIcon(control.Type), 32, 32), new Text(control.Name + " [" + control.Type + "]"))); + string controlType = control.Type; + OpenXmlElement controlElement; + if (DetailedDocumentation) + { + controlElement = new Hyperlink(new Run(new Text(control.Name + " [" + controlType + "]"))) + { + Anchor = CreateMD5Hash(control.Name), + DocLocation = "" + }; + } + else + { + controlElement = new Text(control.Name + " [" + controlType + "]"); + } + table.Append(CreateRow(InsertSvgImage(mainPart, AppControlIcons.GetControlIcon(controlType), 32, 32), controlElement)); foreach (ControlEntity child in control.Children.OrderBy(o => o.Name).ToList()) { table.Append(CreateRow(new Text(""), CreateControlTable(child, BorderValues.None))); @@ -293,131 +293,131 @@ private Table CreateControlTable(ControlEntity control, BorderValues borderType private void addDetailedAppControls() { - string[] ColourProperties = new string[] { "BorderColor", "Color", "DisabledBorderColor", "DisabledColor", "DisabledFill", "DisabledSectionColor", - "DisabledSelectionFill", "Fill", "FocusedBorderColor", "HoverBorderColor", "HoverColor", "HoverFill", "PressedBorderColor","PressedColor", - "PressedFill", "SelectionColor", "SelectionFill" }; - Paragraph para = body.AppendChild(new Paragraph()); Run run = para.AppendChild(new Run()); - run.AppendChild(new Text("Detailed Controls")); - ApplyStyleToParagraph("Heading2", para); - - List allControls = new List(); - foreach (ControlEntity control in app.Controls) - { - allControls.Add(control); - allControls.AddRange(getAllChildControls(control)); - } - body.AppendChild(new Paragraph(new Run(new Text($"A total of {allControls.Count} Controls are located in the app:")))); - foreach (ControlEntity control in allControls.OrderBy(o => o.Name).ToList()) + run.AppendChild(new Text(content.appControls.headerDetails)); + ApplyStyleToParagraph("Heading1", para); + foreach (ControlEntity screen in content.appControls.controls.Where(o => o.Type == "screen").OrderBy(o => o.Name).ToList()) { para = body.AppendChild(new Paragraph()); run = para.AppendChild(new Run()); - run.AppendChild(new Text(control.Name)); - ApplyStyleToParagraph("Heading3", para); + run.AppendChild(new Text(screen.Name)); + string bookmarkID = (new Random()).Next(100000, 999999).ToString(); + BookmarkStart start = new BookmarkStart() { Name = CreateMD5Hash(screen.Name), Id = bookmarkID }; + BookmarkEnd end = new BookmarkEnd() { Id = bookmarkID }; + para.Append(start, end); + ApplyStyleToParagraph("Heading2", para); body.AppendChild(new Paragraph(new Run())); - Table table = CreateTable(); - Table typeTable = CreateTable(BorderValues.None); - typeTable.Append(CreateRow(InsertSvgImage(mainPart, AppControlIcons.GetControlIcon(control.Type), 16, 16), new Text(control.Type))); - table.Append(CreateRow(new Text("Type"), typeTable)); - string category = ""; - foreach (Rule rule in control.Rules.OrderBy(o => o.Category).ThenBy(o => o.Property).ToList()) + addAppControlsTable(screen); + foreach (ControlEntity control in content.appControls.allControls.Where(o => o.Type != "appinfo" && o.Type != "screen" && o.Screen()?.Equals(screen) == true).OrderBy(o => o.Name).ToList()) + { + para = body.AppendChild(new Paragraph()); + run = para.AppendChild(new Run()); + run.AppendChild(new Text(control.Name)); + bookmarkID = (new Random()).Next(100000, 999999).ToString(); + start = new BookmarkStart() { Name = CreateMD5Hash(control.Name), Id = bookmarkID }; + end = new BookmarkEnd() { Id = bookmarkID }; + para.Append(start, end); + ApplyStyleToParagraph("Heading3", para); + body.AppendChild(new Paragraph(new Run())); + addAppControlsTable(control); + } + } + body.AppendChild(new Paragraph(new Run(new Break()))); + } + + private void addAppControlsTable(ControlEntity control) + { + Table table = CreateTable(); + Table typeTable = CreateTable(BorderValues.None); + typeTable.Append(CreateRow(InsertSvgImage(mainPart, AppControlIcons.GetControlIcon(control.Type), 16, 16), new Text(control.Type))); + table.Append(CreateRow(new Text("Type"), typeTable)); + string category = ""; + foreach (Rule rule in control.Rules.OrderBy(o => o.Category).ThenBy(o => o.Property).ToList()) + { + if (!content.ColourProperties.Contains(rule.Property)) { - if (!ColourProperties.Contains(rule.Property)) + if (rule.Category != category) { - if (rule.Category != category) - { - category = rule.Category; - table.Append(CreateMergedRow(new Text(category), 2, WordDocBuilder.cellHeaderBackground)); - } - if (rule.InvariantScript.StartsWith("RGBA(")) - { - Table colorTable = CreateTable(BorderValues.None); - colorTable.Append(CreateRow(new Text(rule.InvariantScript))); - string colour = ColourHelper.ParseColor(rule.InvariantScript.Substring(0, rule.InvariantScript.IndexOf(')') + 1)); - if (!String.IsNullOrEmpty(colour)) - { - colorTable.Append(CreateMergedRow(new Text(""), 1, colour)); - table.Append(CreateRow(new Text(rule.Property), colorTable)); - } - } - else + category = rule.Category; + table.Append(CreateMergedRow(new Text(category), 2, WordDocBuilder.cellHeaderBackground)); + } + if (rule.InvariantScript.StartsWith("RGBA(")) + { + Table colorTable = CreateTable(BorderValues.None); + colorTable.Append(CreateRow(new Text(rule.InvariantScript))); + string colour = ColourHelper.ParseColor(rule.InvariantScript[..(rule.InvariantScript.IndexOf(')') + 1)]); + if (!String.IsNullOrEmpty(colour)) { - table.Append(CreateRow(new Text(rule.Property), new Text(rule.InvariantScript))); + colorTable.Append(CreateMergedRow(new Text(""), 1, colour)); + table.Append(CreateRow(new Text(rule.Property), colorTable)); } } + else + { + table.Append(CreateRow(new Text(rule.Property), new Text(rule.InvariantScript))); + } } - table.Append(CreateMergedRow(new Text("Color Properties"), 2, WordDocBuilder.cellHeaderBackground)); - foreach (string property in ColourProperties) + } + table.Append(CreateMergedRow(new Text("Color Properties"), 2, WordDocBuilder.cellHeaderBackground)); + foreach (string property in content.ColourProperties) + { + Rule rule = control.Rules.Find(o => o.Property == property); + if (rule != null) { - Rule rule = control.Rules.FirstOrDefault(o => o.Property == property); - if (rule != null) + if (rule.InvariantScript.StartsWith("RGBA(")) { - if (rule.InvariantScript.StartsWith("RGBA(")) + Table colorTable = CreateTable(BorderValues.None); + colorTable.Append(CreateRow(new Text(rule.InvariantScript))); + string colour = ColourHelper.ParseColor(rule.InvariantScript[..(rule.InvariantScript.IndexOf(')') + 1)]); + if (!String.IsNullOrEmpty(colour)) { - Table colorTable = CreateTable(BorderValues.None); - colorTable.Append(CreateRow(new Text(rule.InvariantScript))); - string colour = ColourHelper.ParseColor(rule.InvariantScript.Substring(0, rule.InvariantScript.IndexOf(')') + 1)); - if (!String.IsNullOrEmpty(colour)) - { - colorTable.Append(CreateMergedRow(new Text(""), 1, colour)); - table.Append(CreateRow(new Text(rule.Property), colorTable)); - } - } - else - { - table.Append(CreateRow(new Text(rule.Property), new Text(rule.InvariantScript))); + colorTable.Append(CreateMergedRow(new Text(""), 1, colour)); + table.Append(CreateRow(new Text(rule.Property), colorTable)); } } - } - foreach (ControlEntity childControl in control.Children) - { - /*Table childtable = CreateTable(); - childtable.Append(CreateMergedRow(new Text("Child Controls"), 2, WordDocBuilder.cellHeaderBackground)); - foreach (Expression expression in childControl.Properties) + else { - AddExpressionTable(expression, childtable); + table.Append(CreateRow(new Text(rule.Property), new Text(rule.InvariantScript))); } - table.Append(CreateRow(new Text("childcontrol"), childtable)); - */ } - /* //Other properties are likely not needed for documentation, still keeping this code in case we want to show them at some point - table.Append(CreateMergedRow(new Text("Properties"), 2, WordDocBuilder.cellHeaderBackground)); - foreach (Expression expression in control.Properties) - { - AddExpressionTable(expression, table); - }*/ - - body.Append(table); - body.AppendChild(new Paragraph(new Run(new Break()))); } - body.AppendChild(new Paragraph(new Run(new Break()))); - } - - private List getAllChildControls(ControlEntity control) - { - List childControls = new List(); + table.Append(CreateMergedRow(new Text("Child & Parent Controls"), 2, WordDocBuilder.cellHeaderBackground)); + Table childtable = CreateTable(BorderValues.None); foreach (ControlEntity childControl in control.Children) { - childControls.Add(childControl); - childControls.AddRange(getAllChildControls(childControl)); + childtable.Append(CreateRow(new Text(childControl.Name))); + } + table.Append(CreateRow(new Text("Child Controls"), childtable)); + if (control.Parent != null) + { + table.Append(CreateRow(new Text("Parent Control"), new Text(control.Parent.Name))); } - return childControls; + //todo isLocked property could be documented + /* //Other properties are likely not needed for documentation, still keeping this code in case we want to show them at some point + table.Append(CreateMergedRow(new Text("Properties"), 2, WordDocBuilder.cellHeaderBackground)); + foreach (Expression expression in control.Properties) + { + AddExpressionTable(expression, table); + }*/ + + body.Append(table); + body.AppendChild(new Paragraph(new Run(new Break()))); } private void addAppDataSources() { Paragraph para = body.AppendChild(new Paragraph()); Run run = para.AppendChild(new Run()); - run.AppendChild(new Text("DataSources")); - ApplyStyleToParagraph("Heading2", para); - body.AppendChild(new Paragraph(new Run(new Text($"A total of {app.DataSources.Count} DataSources are located in the app:")))); - foreach (DataSource datasource in app.DataSources.OrderBy(o => o.Name)) + run.AppendChild(new Text(content.appDataSources.header)); + ApplyStyleToParagraph("Heading1", para); + body.AppendChild(new Paragraph(new Run(new Text(content.appDataSources.infoText)))); + foreach (DataSource datasource in content.appDataSources.dataSources) { para = body.AppendChild(new Paragraph()); run = para.AppendChild(new Run()); run.AppendChild(new Text(datasource.Name)); - ApplyStyleToParagraph("Heading3", para); + ApplyStyleToParagraph("Heading2", para); body.AppendChild(new Paragraph(new Run())); Table table = CreateTable(); table.Append(CreateRow(new Text("Name"), new Text(datasource.Name))); @@ -440,48 +440,44 @@ private void addAppResources() { Paragraph para = body.AppendChild(new Paragraph()); Run run = para.AppendChild(new Run()); - run.AppendChild(new Text("Resources")); - ApplyStyleToParagraph("Heading2", para); - body.AppendChild(new Paragraph(new Run(new Text($"A total of {app.Resources.Count} Resources are located in the app:")))); - foreach (Resource resource in app.Resources) + run.AppendChild(new Text(content.appResources.header)); + ApplyStyleToParagraph("Heading1", para); + body.AppendChild(new Paragraph(new Run(new Text(content.appResources.infoText)))); + foreach (Resource resource in content.appResources.resources) { para = body.AppendChild(new Paragraph()); run = para.AppendChild(new Run()); run.AppendChild(new Text(resource.Name)); - ApplyStyleToParagraph("Heading3", para); + ApplyStyleToParagraph("Heading2", para); body.AppendChild(new Paragraph(new Run())); Table table = CreateTable(); table.Append(CreateRow(new Text("Name"), new Text(resource.Name))); table.Append(CreateRow(new Text("Content"), new Text(resource.Content))); table.Append(CreateRow(new Text("Resource Kind"), new Text(resource.ResourceKind))); - if (resource.ResourceKind == "LocalFile") + if (resource.ResourceKind == "LocalFile" && content.ResourceStreams.TryGetValue(resource.Name, out MemoryStream resourceStream)) { - MemoryStream resourceStream; - if (app.ResourceStreams.TryGetValue(resource.Name, out resourceStream)) + Drawing icon = null; + Expression fileName = resource.Properties.First(o => o.expressionOperator == "FileName"); + if (fileName.expressionOperands[0].ToString().EndsWith("svg", StringComparison.OrdinalIgnoreCase)) { - Drawing icon = null; - Expression fileName = resource.Properties.First(o => o.expressionOperator == "FileName"); - if (fileName.expressionOperands.First().ToString().ToLower().EndsWith("svg")) - { - string svg = Encoding.Default.GetString(resourceStream.ToArray()); - icon = InsertSvgImage(mainPart, svg, 400, 400); - } - else + string svg = Encoding.Default.GetString(resourceStream.ToArray()); + icon = InsertSvgImage(mainPart, svg, 400, 400); + } + else + { + ImagePart imagePart = mainPart.AddImagePart(ImagePartType.Jpeg); + int imageWidth, imageHeight; + using (var image = Image.FromStream(resourceStream, false, false)) { - ImagePart imagePart = mainPart.AddImagePart(ImagePartType.Jpeg); - int imageWidth, imageHeight; - using (var image = Image.FromStream(resourceStream, false, false)) - { - imageWidth = image.Width; - imageHeight = image.Height; - } - resourceStream.Position = 0; - imagePart.FeedData(resourceStream); - int usedWidth = (imageWidth > 400) ? 400 : imageWidth; - icon = InsertImage(mainPart.GetIdOfPart(imagePart), usedWidth, (int)(usedWidth * imageHeight / imageWidth)); + imageWidth = image.Width; + imageHeight = image.Height; } - table.Append(CreateRow(new Text("Resource Preview"), icon)); + resourceStream.Position = 0; + imagePart.FeedData(resourceStream); + int usedWidth = (imageWidth > 400) ? 400 : imageWidth; + icon = InsertImage(mainPart.GetIdOfPart(imagePart), usedWidth, (int)(usedWidth * imageHeight / imageWidth)); } + table.Append(CreateRow(new Text("Resource Preview"), icon)); } if (DetailedDocumentation) { diff --git a/PowerDocu.Common/PowerDocuReleaseHelper.cs b/PowerDocu.Common/PowerDocuReleaseHelper.cs index 9eb1317..dc4df7a 100644 --- a/PowerDocu.Common/PowerDocuReleaseHelper.cs +++ b/PowerDocu.Common/PowerDocuReleaseHelper.cs @@ -9,7 +9,7 @@ namespace PowerDocu.Common { public static class PowerDocuReleaseHelper { - public static Version currentVersion = new Version(0, 10, 0); + public static Version currentVersion = new Version(1, 0, 0); public static string latestVersionTag = currentVersion.ToString(); public static string latestVersionUrl; private static bool hasReleaseBeenChecked = false; diff --git a/PowerDocu.Common/WordDocBuilder.cs b/PowerDocu.Common/WordDocBuilder.cs index 2749fed..0068959 100644 --- a/PowerDocu.Common/WordDocBuilder.cs +++ b/PowerDocu.Common/WordDocBuilder.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Drawing; using System.IO; using System.Linq; using System.Security.Cryptography; @@ -12,7 +11,6 @@ using A14 = DocumentFormat.OpenXml.Office2010.Drawing; using DW = DocumentFormat.OpenXml.Drawing.Wordprocessing; using PIC = DocumentFormat.OpenXml.Drawing.Pictures; -using PowerDocu.Common; namespace PowerDocu.Common { @@ -32,42 +30,57 @@ public abstract class WordDocBuilder protected int maxImageHeight = PageHeight - PageMarginTop - PageMarginBottom; protected const string cellHeaderBackground = "E5E5FF"; protected readonly Random random = new Random(); - protected string folderPath; protected MainDocumentPart mainPart; protected Body body; protected Dictionary SVGImages; public HashSet UsedRandomNumbers = new HashSet(); - protected void InitializeWordDocument(string filename, string template) + protected string InitializeWordDocument(string filename, string template) { SVGImages = new Dictionary(); + if (!string.IsNullOrEmpty(template) && template.EndsWith("docm")) + { + filename += ".docm"; + } + else + { + filename += ".docx"; + } //create a new document if no template is provided if (String.IsNullOrEmpty(template)) { - using (WordprocessingDocument wordDocument = - WordprocessingDocument.Create(filename, WordprocessingDocumentType.Document)) - { - MainDocumentPart mainPart = wordDocument.AddMainDocumentPart(); - - // Create the document structure and add some text. - mainPart.Document = new Document(); - AddStylesPartToPackage(wordDocument); - Body body = mainPart.Document.AppendChild(new Body()); - - // Set Page Size and Page Margin so that we can place the image as desired. - // Available Width = PageWidth - PageMarginLeft - PageMarginRight (= 17000 - 1000 - 1000 = 15000 for default values) - var sectionProperties = new SectionProperties(); - sectionProperties.AppendChild(new PageSize { Width = PageWidth, Height = PageHeight }); - sectionProperties.AppendChild(new PageMargin { Left = PageMarginLeft, Bottom = PageMarginBottom, Top = PageMarginTop, Right = PageMarginRight }); - body.AppendChild(sectionProperties); - } + using WordprocessingDocument wordDocument = WordprocessingDocument.Create(filename, WordprocessingDocumentType.Document); + MainDocumentPart mainPart = wordDocument.AddMainDocumentPart(); + + // Create the document structure and add some text. + mainPart.Document = new Document(); + AddStylesPartToPackage(wordDocument); + Body body = mainPart.Document.AppendChild(new Body()); + + // Set Page Size and Page Margin so that we can place the image as desired. + // Available Width = PageWidth - PageMarginLeft - PageMarginRight (= 17000 - 1000 - 1000 = 15000 for default values) + var sectionProperties = new SectionProperties(); + sectionProperties.AppendChild(new PageSize { Width = PageWidth, Height = PageHeight }); + sectionProperties.AppendChild(new PageMargin { Left = PageMarginLeft, Bottom = PageMarginBottom, Top = PageMarginTop, Right = PageMarginRight }); + body.AppendChild(sectionProperties); } else { //Create a copy of the Word template that will be used to add the documentation File.Copy(template, filename, true); + using WordprocessingDocument wordDocument = WordprocessingDocument.Open(filename, true); + if (template.EndsWith("dotx")) + { + wordDocument.ChangeDocumentType(WordprocessingDocumentType.Document); + } + if (template.EndsWith("docm")) + { + wordDocument.ChangeDocumentType(WordprocessingDocumentType.MacroEnabledDocument); + } + wordDocument.Close(); } + return filename; } protected TableRow CreateHeaderRow(params OpenXmlElement[] cellValues) @@ -376,7 +389,7 @@ protected StyleDefinitionsPart AddStylesPartToPackage(WordprocessingDocument doc return part; } - /* helper class to add the givens style to the provided paragraph */ + /* helper class to add the given style to the provided paragraph */ protected void ApplyStyleToParagraph(string styleid, Paragraph p) { // If the paragraph has no ParagraphProperties object, create one. @@ -472,8 +485,7 @@ protected TableRow CreateRow(params OpenXmlElement[] cellValues) protected Table AddExpressionTable(Expression expression, Table table = null, double factor = 1, bool showShading = true, bool firstColumnBold = false) { - if (table == null) - table = CreateTable(BorderValues.Single, factor); + table ??= CreateTable(BorderValues.Single, factor); if (expression?.expressionOperator != null) { var tr = new TableRow(); @@ -587,18 +599,32 @@ protected void PrepareDocument(bool templateUsed) private void AddSettingsToMainDocumentPart() { - DocumentSettingsPart settingsPart = mainPart.AddNewPart(); - settingsPart.Settings = new Settings( - new Compatibility( - //Compatibility for Office 2013 onwards, which helps with processing larger documents - new CompatibilitySetting() - { - Name = CompatSettingNameValues.CompatibilityMode, - Val = new StringValue("15"), - Uri = new StringValue("http://schemas.microsoft.com/office/word") - } - ) - ); + DocumentSettingsPart settingsPart = mainPart.DocumentSettingsPart ?? mainPart.AddNewPart(); + Compatibility compatibility = new Compatibility( + //Compatibility for Office 2013 onwards, which helps with processing larger documents + new CompatibilitySetting() + { + Name = CompatSettingNameValues.CompatibilityMode, + Val = new StringValue("15"), + Uri = new StringValue("http://schemas.microsoft.com/office/word") + } + ); + if (settingsPart.Settings == null) + { + settingsPart.Settings = new Settings(compatibility); + } + else + { + Compatibility c = (Compatibility)settingsPart.Settings.ChildElements.FirstOrDefault(o => o.GetType().Equals(typeof(Compatibility))); + if (c != null) + { + c = compatibility; + } + else + { + settingsPart.Settings.AddChild(compatibility); + } + } settingsPart.Settings.Save(); } @@ -659,8 +685,8 @@ protected string GetRandomHexNumber(int digits) protected string CreateMD5Hash(string input) { // Step 1, calculate MD5 hash from input - MD5 md5 = System.Security.Cryptography.MD5.Create(); - byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(input); + MD5 md5 = MD5.Create(); + byte[] inputBytes = Encoding.ASCII.GetBytes(input); byte[] hashBytes = md5.ComputeHash(inputBytes); // Step 2, convert byte array to hex string diff --git a/PowerDocu.FlowDocumenter/FlowWordDocBuilder.cs b/PowerDocu.FlowDocumenter/FlowWordDocBuilder.cs index 028d81d..4e0babc 100644 --- a/PowerDocu.FlowDocumenter/FlowWordDocBuilder.cs +++ b/PowerDocu.FlowDocumenter/FlowWordDocBuilder.cs @@ -22,8 +22,7 @@ public FlowWordDocBuilder(FlowDocumentationContent contentDocumentation, string { this.content = contentDocumentation; Directory.CreateDirectory(content.folderPath); - string filename = content.folderPath + content.filename + ".docx"; - InitializeWordDocument(filename, template); + string filename = InitializeWordDocument(content.folderPath + content.filename, template); using (WordprocessingDocument wordDocument = WordprocessingDocument.Open(filename, true)) { mainPart = wordDocument.MainDocumentPart; diff --git a/PowerDocu.GUI/PowerDocu.GUI.csproj b/PowerDocu.GUI/PowerDocu.GUI.csproj index 38be339..2fd18f1 100644 --- a/PowerDocu.GUI/PowerDocu.GUI.csproj +++ b/PowerDocu.GUI/PowerDocu.GUI.csproj @@ -15,7 +15,7 @@ - 0.10.0 + 1.0.0 Rene Modery WinExe netcoreapp3.1 diff --git a/PowerDocu.GUI/PowerDocuForm.Designer.cs b/PowerDocu.GUI/PowerDocuForm.Designer.cs index 303b183..04bac88 100644 --- a/PowerDocu.GUI/PowerDocuForm.Designer.cs +++ b/PowerDocu.GUI/PowerDocuForm.Designer.cs @@ -51,7 +51,7 @@ private void InitializeComponent() openWordTemplateDialog = new OpenFileDialog() { FileName = "", - Filter = "Word Documents (*.docx)|*.docx", + Filter = "Word Documents (*.docx, *.docm, *.dotx)|*.docx;*.docm;*.dotx", Title = "Select the Word document to use as template" }; selectWordTemplateButton = new IconButton() @@ -69,7 +69,7 @@ private void InitializeComponent() outputFormatComboBox = new ComboBox() { Location = new Point(15, 25 + selectWordTemplateButton.Height), - Size = new System.Drawing.Size(85, 21), + Size = new System.Drawing.Size(convertToDPISpecific(85), convertToDPISpecific(21)), DropDownStyle = ComboBoxStyle.DropDownList }; outputFormatComboBox.Items.AddRange(new object[] {OutputFormatHelper.Word, @@ -115,7 +115,7 @@ private void InitializeComponent() outputFormatInfoLabel = new Label() { Location = new Point(30 + outputFormatComboBox.Width, outputFormatComboBox.Location.Y + 5), - Text = "Select output file format", + Text = "Select output format", Width = convertToDPISpecific(300), Height = convertToDPISpecific(30) }; diff --git a/PowerDocu.GUI/PowerDocuForm.cs b/PowerDocu.GUI/PowerDocuForm.cs index bf795c2..efec939 100644 --- a/PowerDocu.GUI/PowerDocuForm.cs +++ b/PowerDocu.GUI/PowerDocuForm.cs @@ -68,8 +68,9 @@ private void selectZIPFileButton_Click(object sender, EventArgs e) : null ); NotificationHelper.SendNotification("Trying to process Power Apps"); - AppDocumentationGenerator.GenerateWordDocumentation( + AppDocumentationGenerator.GenerateDocumentation( fileName, + outputFormatComboBox.SelectedItem.ToString(), (openWordTemplateDialog.FileName != "") ? openWordTemplateDialog.FileName : null @@ -77,8 +78,9 @@ private void selectZIPFileButton_Click(object sender, EventArgs e) } else if (fileName.EndsWith(".msapp")) { - AppDocumentationGenerator.GenerateWordDocumentation( + AppDocumentationGenerator.GenerateDocumentation( fileName, + outputFormatComboBox.SelectedItem.ToString(), (openWordTemplateDialog.FileName != "") ? openWordTemplateDialog.FileName : null