XsdToMarkdown reads schema xsds and generates Markdown doc.

This commit is contained in:
Bob Arnson 2020-09-03 21:14:44 -04:00
Родитель 912fcd4f42
Коммит 49b5486bbc
13 изменённых файлов: 16358 добавлений и 0 удалений

66
WixToolset.Doc.sln Normal file
Просмотреть файл

@ -0,0 +1,66 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30309.148
MinimumVisualStudioVersion = 15.0.26124.0
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{EBFD1BC6-A6D1-4EF5-B045-2D86CE5FEC35}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{AB67F86B-F263-4620-BAA7-89C3B8CC16E5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XsdToMarkdown", "src\tools\XsdToMarkdown\XsdToMarkdown.csproj", "{638D0024-AFC0-4700-B112-13E8AD6BB705}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XsdToMarkdownTests", "src\test\XsdToMarkDownTests\XsdToMarkdownTests.csproj", "{B4C62B62-8727-47D3-BF6A-1B8C03C44996}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{209E1F24-3452-4269-A2BD-A953F3F7BD1E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3435EEA7-79D8-4B2C-8484-94A044017747}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{638D0024-AFC0-4700-B112-13E8AD6BB705}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{638D0024-AFC0-4700-B112-13E8AD6BB705}.Debug|Any CPU.Build.0 = Debug|Any CPU
{638D0024-AFC0-4700-B112-13E8AD6BB705}.Debug|x64.ActiveCfg = Debug|Any CPU
{638D0024-AFC0-4700-B112-13E8AD6BB705}.Debug|x64.Build.0 = Debug|Any CPU
{638D0024-AFC0-4700-B112-13E8AD6BB705}.Debug|x86.ActiveCfg = Debug|Any CPU
{638D0024-AFC0-4700-B112-13E8AD6BB705}.Debug|x86.Build.0 = Debug|Any CPU
{638D0024-AFC0-4700-B112-13E8AD6BB705}.Release|Any CPU.ActiveCfg = Release|Any CPU
{638D0024-AFC0-4700-B112-13E8AD6BB705}.Release|Any CPU.Build.0 = Release|Any CPU
{638D0024-AFC0-4700-B112-13E8AD6BB705}.Release|x64.ActiveCfg = Release|Any CPU
{638D0024-AFC0-4700-B112-13E8AD6BB705}.Release|x64.Build.0 = Release|Any CPU
{638D0024-AFC0-4700-B112-13E8AD6BB705}.Release|x86.ActiveCfg = Release|Any CPU
{638D0024-AFC0-4700-B112-13E8AD6BB705}.Release|x86.Build.0 = Release|Any CPU
{B4C62B62-8727-47D3-BF6A-1B8C03C44996}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B4C62B62-8727-47D3-BF6A-1B8C03C44996}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B4C62B62-8727-47D3-BF6A-1B8C03C44996}.Debug|x64.ActiveCfg = Debug|Any CPU
{B4C62B62-8727-47D3-BF6A-1B8C03C44996}.Debug|x64.Build.0 = Debug|Any CPU
{B4C62B62-8727-47D3-BF6A-1B8C03C44996}.Debug|x86.ActiveCfg = Debug|Any CPU
{B4C62B62-8727-47D3-BF6A-1B8C03C44996}.Debug|x86.Build.0 = Debug|Any CPU
{B4C62B62-8727-47D3-BF6A-1B8C03C44996}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B4C62B62-8727-47D3-BF6A-1B8C03C44996}.Release|Any CPU.Build.0 = Release|Any CPU
{B4C62B62-8727-47D3-BF6A-1B8C03C44996}.Release|x64.ActiveCfg = Release|Any CPU
{B4C62B62-8727-47D3-BF6A-1B8C03C44996}.Release|x64.Build.0 = Release|Any CPU
{B4C62B62-8727-47D3-BF6A-1B8C03C44996}.Release|x86.ActiveCfg = Release|Any CPU
{B4C62B62-8727-47D3-BF6A-1B8C03C44996}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{AB67F86B-F263-4620-BAA7-89C3B8CC16E5} = {EBFD1BC6-A6D1-4EF5-B045-2D86CE5FEC35}
{B4C62B62-8727-47D3-BF6A-1B8C03C44996} = {209E1F24-3452-4269-A2BD-A953F3F7BD1E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {49354629-B8AD-455D-A379-708577975BF5}
EndGlobalSection
EndGlobal

Просмотреть файл

@ -0,0 +1,266 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xse="http://wixtoolset.org/schemas/XmlSchemaExtension"
xmlns:html="http://www.w3.org/1999/xhtml"
targetNamespace="http://wixtoolset.org/schemas/v4/wxs/bal"
xmlns="http://wixtoolset.org/schemas/v4/wxs/bal">
<xs:annotation>
<xs:documentation>
The source code schema for the WiX Toolset Burn User Experience Extension.
</xs:documentation>
</xs:annotation>
<xs:import namespace="http://wixtoolset.org/schemas/v4/wxs" />
<xs:element name="Condition">
<xs:annotation>
<xs:documentation>
Conditions for a bundle. The condition is specified in the inner text of the element.
</xs:documentation>
<xs:appinfo>
<xse:parent namespace="http://wixtoolset.org/schemas/v4/wxs" ref="Bundle" />
<xse:parent namespace="http://wixtoolset.org/schemas/v4/wxs" ref="Fragment" />
</xs:appinfo>
</xs:annotation>
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:annotation>
<xs:documentation>
The condition that must evaluate to true for the installation to continue.
</xs:documentation>
</xs:annotation>
<xs:attribute name="Message" type="xs:string" use="required">
<xs:annotation>
<xs:documentation>
Set the value to the text to display when the condition fails and the installation must be terminated.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
<xs:element name="WixStandardBootstrapperApplication">
<xs:annotation>
<xs:documentation>
Configures WixStandardBootstrapperApplication for a Bundle.
</xs:documentation>
<xs:appinfo>
<xse:parent namespace="http://wixtoolset.org/schemas/v4/wxs" ref="BootstrapperApplicationRef" />
</xs:appinfo>
</xs:annotation>
<xs:complexType>
<xs:attribute name="LaunchTarget" type="xs:string">
<xs:annotation>
<xs:documentation>
If set, the success page will show a Launch button the user can use to launch the application being installed.
The string value can be formatted using Burn variables enclosed in brackets,
to refer to installation directories and so forth.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="LaunchTargetElevatedId" type="xs:string">
<xs:annotation>
<xs:documentation>
Id of the target ApprovedExeForElevation element.
If set with LaunchTarget, WixStdBA will launch the application through the Engine's LaunchApprovedExe method instead of through ShellExecute.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="LaunchArguments" type="xs:string">
<xs:annotation>
<xs:documentation>
If set, WixStdBA will supply these arguments when launching the application specified by the LaunchTarget attribute.
The string value can be formatted using Burn variables enclosed in brackets,
to refer to installation directories and so forth.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="LaunchHidden" type="YesNoType">
<xs:annotation>
<xs:documentation>
If set to "yes", WixStdBA will launch the application specified by the LaunchTarget attribute with the SW_HIDE flag.
This attribute is ignored when the LaunchTargetElevatedId attribute is specified.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="LaunchWorkingFolder" type="xs:string">
<xs:annotation>
<xs:documentation>
WixStdBA will use this working folder when launching the specified application.
The string value can be formatted using Burn variables enclosed in brackets,
to refer to installation directories and so forth.
This attribute is ignored when the LaunchTargetElevatedId attribute is specified.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="LicenseFile" type="xs:string">
<xs:annotation>
<xs:documentation>Source file of the RTF license file. Cannot be used simultaneously with LicenseUrl.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="LicenseUrl" type="xs:string">
<xs:annotation>
<xs:documentation>URL target of the license link. Cannot be used simultaneously with LicenseFile. This attribute can be empty to hide the license link completely.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="LogoFile" type="xs:string">
<xs:annotation>
<xs:documentation>Source file of the logo graphic.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="LogoSideFile" type="xs:string">
<xs:annotation>
<xs:documentation>Source file of the side logo graphic.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="ThemeFile" type="xs:string">
<xs:annotation>
<xs:documentation>Source file of the theme XML.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="LocalizationFile" type="xs:string">
<xs:annotation>
<xs:documentation>Source file of the theme localization .wxl file.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="SuppressOptionsUI" type="YesNoType">
<xs:annotation>
<xs:documentation>If set to "yes", the Options button will not be shown and the user will not be able to choose an installation directory.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="SuppressDowngradeFailure" type="YesNoType">
<xs:annotation>
<xs:documentation>If set to "yes", attempting to installer a downgraded version of a bundle will be treated as a successful do-nothing operation.
The default behavior (or when explicitly set to "no") is to treat downgrade attempts as failures. </xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="SuppressRepair" type="YesNoType">
<xs:annotation>
<xs:documentation>If set to "yes", the Repair button will not be shown in the maintenance-mode UI.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="ShowVersion" type="YesNoType">
<xs:annotation>
<xs:documentation>If set to "yes", the application version will be displayed on the UI.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="SupportCacheOnly" type="YesNoType">
<xs:annotation>
<xs:documentation>If set to "yes", the bundle can be pre-cached using the /cache command line argument.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="WixManagedBootstrapperApplicationHost">
<xs:annotation>
<xs:documentation>
Configures the ManagedBootstrapperApplicationHost for a Bundle.
</xs:documentation>
<xs:appinfo>
<xse:parent namespace="http://wixtoolset.org/schemas/v4/wxs" ref="BootstrapperApplicationRef" />
</xs:appinfo>
</xs:annotation>
<xs:complexType>
<xs:attribute name="LogoFile" type="xs:string">
<xs:annotation>
<xs:documentation>Source file of the logo graphic.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="ThemeFile" type="xs:string">
<xs:annotation>
<xs:documentation>Source file of the theme XML.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="LocalizationFile" type="xs:string">
<xs:annotation>
<xs:documentation>Source file of the theme localization .wxl file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:attribute name="BAFunctions" type="YesNoType">
<xs:annotation>
<xs:documentation>
When set to "yes", WixStdBA will load the DLL and work with it to handle BA messages.
</xs:documentation>
<xs:appinfo>
<xse:parent namespace="http://wixtoolset.org/schemas/v4/wxs" ref="Payload" />
</xs:appinfo>
</xs:annotation>
</xs:attribute>
<xs:attribute name="Overridable" type="YesNoType">
<xs:annotation>
<xs:documentation>
When set to "yes", lets the user override the variable's default value by specifying another value on the command line,
in the form Variable=Value. Otherwise, WixStdBA won't overwrite the default value and will log
"Ignoring attempt to set non-overridable variable: 'BAR'."
</xs:documentation>
<xs:appinfo>
<xse:parent namespace="http://wixtoolset.org/schemas/v4/wxs" ref="Variable" />
</xs:appinfo>
</xs:annotation>
</xs:attribute>
<xs:attribute name="PrereqLicenseFile" type="xs:string">
<xs:annotation>
<xs:documentation>
Source file of the RTF license file.
There may only be one package in the bundle that has either the PrereqLicenseFile attribute or the PrereqLicenseUrl attribute.
</xs:documentation>
<xs:appinfo>
<xse:parent namespace="http://wixtoolset.org/schemas/v4/wxs" ref="ExePackage" />
<xse:parent namespace="http://wixtoolset.org/schemas/v4/wxs" ref="MsiPackage" />
<xse:parent namespace="http://wixtoolset.org/schemas/v4/wxs" ref="MspPackage" />
<xse:parent namespace="http://wixtoolset.org/schemas/v4/wxs" ref="MsuPackage" />
</xs:appinfo>
</xs:annotation>
</xs:attribute>
<xs:attribute name="PrereqLicenseUrl" type="xs:string">
<xs:annotation>
<xs:documentation>
URL target of the license link.
There may only be one package in the bundle that has either the PrereqLicenseFile attribute or the PrereqLicenseUrl attribute.
</xs:documentation>
<xs:appinfo>
<xse:parent namespace="http://wixtoolset.org/schemas/v4/wxs" ref="ExePackage" />
<xse:parent namespace="http://wixtoolset.org/schemas/v4/wxs" ref="MsiPackage" />
<xse:parent namespace="http://wixtoolset.org/schemas/v4/wxs" ref="MspPackage" />
<xse:parent namespace="http://wixtoolset.org/schemas/v4/wxs" ref="MsuPackage" />
</xs:appinfo>
</xs:annotation>
</xs:attribute>
<xs:attribute name="PrereqPackage" type="YesNoType">
<xs:annotation>
<xs:documentation>
When set to "yes", the Prereq BA will plan the package to be installed if its InstallCondition is "true" or empty.
</xs:documentation>
<xs:appinfo>
<xse:parent namespace="http://wixtoolset.org/schemas/v4/wxs" ref="ExePackage" />
<xse:parent namespace="http://wixtoolset.org/schemas/v4/wxs" ref="MsiPackage" />
<xse:parent namespace="http://wixtoolset.org/schemas/v4/wxs" ref="MspPackage" />
<xse:parent namespace="http://wixtoolset.org/schemas/v4/wxs" ref="MsuPackage" />
</xs:appinfo>
</xs:annotation>
</xs:attribute>
<xs:simpleType name="YesNoType">
<xs:annotation>
<xs:documentation>Values of this type will either be "yes" or "no".</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:NMTOKEN">
<xs:enumeration value="no"/>
<xs:enumeration value="yes"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,161 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
namespace XsdToMarkDownTests
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using Markdig;
using WixBuildTools.TestSupport;
using WixBuildTools.XsdToMarkdown;
using Xunit;
public class XsdFixture
{
[Fact]
public void CommandLineParsingFailsOnMissingFile()
{
using var fs = new DisposableFileSystem();
var output = fs.GetFolder(create: true);
var folder = TestData.Get(@"TestData");
var args = new[] { "-out", output, Path.Combine(folder, "wix5.xsd") };
Assert.False(CommandLine.TryParseArguments(args, out var commandLine));
Assert.Equal(output, commandLine.OutputFolder);
Assert.Empty(commandLine.Files);
}
[Fact]
public void CommandLineParsingWorksWell()
{
using var fs = new DisposableFileSystem();
var output = fs.GetFolder(create: true);
var folder = TestData.Get(@"TestData");
var args = new[] { "-out", output, Path.Combine(folder, "wix.xsd") };
Assert.True(CommandLine.TryParseArguments(args, out var commandLine));
Assert.Equal(output, commandLine.OutputFolder);
Assert.Single(commandLine.Files);
Assert.Equal(new[] { Path.Combine(folder, "wix.xsd") }, commandLine.Files.OrderBy(s => s));
}
[Fact]
public void CommandLineParsingHandlesWildcards()
{
using var fs = new DisposableFileSystem();
var output = fs.GetFolder(create: true);
var folder = TestData.Get(@"TestData");
var args = new[] { "-out", output, Path.Combine(folder, "*.xsd") };
Assert.True(CommandLine.TryParseArguments(args, out var commandLine));
Assert.Equal(output, commandLine.OutputFolder);
Assert.Equal(3, commandLine.Files.Count);
Assert.Equal(new[] { Path.Combine(folder, "bal.xsd"), Path.Combine(folder, "util.xsd"), Path.Combine(folder, "wix.xsd") }, commandLine.Files.OrderBy(s => s));
}
[Fact]
public void SpotChecksOnXsdAnalysisLookGood()
{
var folder = TestData.Get(@"TestData");
var document = XDocument.Load(Path.Combine(folder, "wix.xsd"));
var xsd = new Xsd(document);
Assert.True(xsd.IsMainSchema);
Assert.Equal("Wxs", xsd.SchemaName);
Assert.Equal("http://wixtoolset.org/schemas/v4/wxs", xsd.TargetNamespace);
Assert.Equal(28, xsd.SimpleTypes.Count());
Assert.Empty(xsd.RootAttributes);
Assert.Single(xsd.AttributeGroups);
Assert.Equal(271, xsd.Elements.Count);
var componentElement = xsd.Elements["Component"];
Assert.Equal(17, componentElement.Attributes.Count);
Assert.Equal(31, componentElement.Children.Count);
Assert.Equal(3, componentElement.MsiRefs.Count());
Assert.Equal("Component", componentElement.Name);
Assert.Equal("http://wixtoolset.org/schemas/v4/wxs", componentElement.Namespace);
Assert.Empty(componentElement.Parents);
Assert.Equal(2, componentElement.SeeAlsos.Count());
}
[Fact]
public void SimpleXsdConvertsToMarkdown()
{
using var fs = new DisposableFileSystem();
var output = fs.GetFolder(create: true).Replace('\\', '/');
var finalizedXsds = GetFinalizedXsds();
var converter = new ConvertXsdToMarkdownCommand();
var pages = finalizedXsds.SelectMany(xsd => converter.Convert(xsd)).ToList();
var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build();
foreach (var page in pages)
{
var markdown = String.Join(Environment.NewLine, page.Content);
var html = Markdown.ToHtml(markdown, pipeline);
var dir = Path.Combine(output, page.Id);
Directory.CreateDirectory(dir);
var mdPath = Path.Combine(dir, "index.md");
File.WriteAllText(mdPath, markdown);
var htmlPath = Path.Combine(dir, "index.html");
File.WriteAllText(htmlPath, html);
}
}
[Fact]
public void FinalizerFinalizes()
{
var finalizedXsds = GetFinalizedXsds();
var xsd = finalizedXsds.Single(x => "Wxs" == x.SchemaName);
Assert.True(xsd.IsMainSchema);
Assert.Equal("Wxs", xsd.SchemaName);
Assert.Equal("http://wixtoolset.org/schemas/v4/wxs", xsd.TargetNamespace);
Assert.Equal(28, xsd.SimpleTypes.Count());
Assert.Empty(xsd.RootAttributes);
Assert.Single(xsd.AttributeGroups);
Assert.Equal(271, xsd.Elements.Count);
var componentElement = xsd.Elements["Component"];
Assert.Equal(17, componentElement.Attributes.Count);
Assert.Equal(41, componentElement.Children.Count);
Assert.Equal(3, componentElement.MsiRefs.Count());
Assert.Equal("Component", componentElement.Name);
Assert.Equal("http://wixtoolset.org/schemas/v4/wxs", componentElement.Namespace);
Assert.Equal(2, componentElement.SeeAlsos.Count());
Assert.Equal(new[]
{
"ComponentGroup",
"Directory",
"DirectoryRef",
"Feature",
"FeatureGroup",
"FeatureRef",
"Fragment",
"Module",
"Product",
}, componentElement.Parents.Values.Select(p => p.Name).OrderBy(p => p));
}
private static IEnumerable<Xsd> GetFinalizedXsds()
{
var folder = TestData.Get(@"TestData");
var paths = new[]
{
Path.Combine(folder, "bal.xsd"),
Path.Combine(folder, "wix.xsd"),
Path.Combine(folder, "util.xsd"),
};
var xsds = paths.Select(path => new Xsd(XDocument.Load(path))).ToList();
return XsdFinalizer.Finalize(xsds);
}
}
}

Просмотреть файл

@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<Content Include="TestData\bal.xsd" CopyToOutputDirectory="PreserveNewest" />
<Content Include="TestData\wix.xsd" CopyToOutputDirectory="PreserveNewest" />
<Content Include="TestData\util.xsd" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Markdig" Version="0.20.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.0-preview-20200519-01" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="1.3.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="WixBuildTools.TestSupport" Version="4.0.41" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\tools\XsdToMarkdown\XsdToMarkdown.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,111 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
namespace WixBuildTools.XsdToMarkdown
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
/// <summary>
/// Command-line parsing.
/// </summary>
public class CommandLine
{
private CommandLine()
{
}
/// <summary>
/// List of files to process.
/// </summary>
public List<string> Files { get; private set; } = new List<string>();
/// <summary>
/// Output folder.
/// </summary>
public string OutputFolder { get; set; }
/// <summary>
/// Show the help text.
/// </summary>
public static void ShowHelp()
{
Console.WriteLine("XsdToMarkdown.exe [-?] -out folder xsd1 xsd2 ... xsdN");
}
/// <summary>
/// Parses the command-line.
/// </summary>
/// <param name="args">Arguments from command-line.</param>
/// <param name="commandLine">Command line object created from command-line arguments</param>
/// <returns>True if command-line is parsed, false if a failure was occurred.</returns>
public static bool TryParseArguments(string[] args, out CommandLine commandLine)
{
var success = true;
commandLine = new CommandLine();
for (var i = 0; i < args.Length; ++i)
{
if ('-' == args[i][0] || '/' == args[i][0])
{
var arg = args[i].Substring(1).ToLowerInvariant();
if ("?" == arg || "help" == arg)
{
return false;
}
else if ("o" == arg || "out" == arg)
{
++i;
if (args.Length == i)
{
Console.Error.WriteLine("Missing file specification for '-out' option.");
success = false;
}
else
{
var outputPath = Path.GetFullPath(args[i]);
commandLine.OutputFolder = outputPath;
}
}
}
else
{
var paths = args[i].Split(new string[] { ";" }, StringSplitOptions.RemoveEmptyEntries);
foreach (var path in paths)
{
var sourcePath = Path.GetFullPath(path);
var sourceDir = Path.GetDirectoryName(sourcePath);
var wildcard = sourcePath.Substring(sourceDir.Length + 1);
var files = Directory.EnumerateFiles(sourceDir, wildcard);
if (files.Any())
{
commandLine.Files.AddRange(files);
}
else
{
Console.Error.WriteLine($"Source file '{sourcePath}' could not be found.");
success = false;
}
}
}
}
if (0 == commandLine.Files.Count)
{
Console.Error.WriteLine("No inputs specified. Specify at least one file.");
success = false;
}
if (String.IsNullOrEmpty(commandLine.OutputFolder))
{
Console.Error.WriteLine("Ouput folder was not specified. Specify an output folder using the '-out' switch.");
success = false;
}
return success;
}
}
}

Просмотреть файл

@ -0,0 +1,264 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
namespace WixBuildTools.XsdToMarkdown
{
using System;
using System.Collections.Generic;
using System.Linq;
// todo: handle embedded HTML: convert xsd html to markdown?
public class ConvertXsdToMarkdownCommand
{
public Xsd Xsd { get; private set; }
public IEnumerable<Page> Convert(Xsd xsd)
{
this.Xsd = xsd;
var pages = new List<Page>() { this.GenerateSchemaRootPage() };
pages.AddRange(xsd.SimpleTypes.Values.OrderBy(t => t.Name).Select(t => this.SimpleTypeDocumentation(t)));
pages.AddRange(xsd.Elements.Values.OrderBy(el => el.Name).Select(el => this.ElementDocumentation(el)));
return pages;
}
private string PageId(PageType type, string name = null)
{
return type switch
{
PageType.SchemaRoot => $"{this.Xsd.SchemaName}/".ToLowerInvariant(),
PageType.Element => $"{this.Xsd.SchemaName}/elements/{name}/".ToLowerInvariant(),
PageType.Type => $"{this.Xsd.SchemaName}/types/{name}/".ToLowerInvariant(),
_ => throw new ArgumentOutOfRangeException(),
};
}
private Page GenerateSchemaRootPage()
{
var content = new List<string>
{
"---",
$"title: {this.Xsd.SchemaName} schema reference",
$"layout: documentation",
"---",
//$"# {this.Xsd.SchemaName} schema",
this.Xsd.SchemaDocumentation,
"## Target namespace",
this.Xsd.TargetNamespace
};
if (this.Xsd.RootElements.Any())
{
content.Add("## Root elements");
content.AddRange(this.Xsd.RootElements.OrderBy(el => el.Name).Select(el => $"- [{el.Name}]({this.LinkForElement(PageType.SchemaRoot, el.Namespace, el.Name)})"));
}
content.Add("## Elements");
content.AddRange(this.Xsd.Elements.Values.OrderBy(el => el.Name).Select(el => $"- [{el.Name}]({this.LinkForElement(PageType.SchemaRoot, el.Namespace, el.Name)})"));
if (this.Xsd.SimpleTypes.Any())
{
content.Add("## Types");
content.AddRange(this.Xsd.SimpleTypes.Values.OrderBy(t => t.Name).Select(t => $"- [{t.Name}]({this.LinkForType(PageType.SchemaRoot, t.Name)})"));
}
return new Page(this.PageId(PageType.SchemaRoot), content);
}
private Page SimpleTypeDocumentation(SimpleType simpleType)
{
var title = this.Xsd.IsMainSchema switch
{
true => $"{simpleType.Name} type",
false => $"{simpleType.Name} type ({this.Xsd.SchemaName} extension)",
};
var content = new List<string>()
{
"---",
$"title: {title}",
$"layout: documentation",
"---",
//$"## {title}",
simpleType.Documentation,
};
if (simpleType.EnumValues?.Any() == true)
{
content.Add("### Enumeration values");
content.AddRange(EnumValuesDocumentation(simpleType.EnumValues));
}
return new Page(this.PageId(PageType.Type, simpleType.Name), content);
}
private Page ElementDocumentation(Element element)
{
var title = this.Xsd.IsMainSchema switch
{
true => $"{element.Name} element",
false => $"{element.Name} element ({this.Xsd.SchemaName} extension)",
};
var content = new List<string>()
{
"---",
$"title: {title}",
$"layout: documentation",
"---",
//$"## {title}",
element.Documentation,
};
if (!element.MsiRefs.IsNullOrEmpty())
{
content.Add("### Windows Installer references");
content.Add(String.Join(", ", element.MsiRefs.SelectMany(msiRef => FormatMsiRefElement(msiRef))));
}
// todo: how-to refs
if (!element.Parents.IsNullOrEmpty())
{
content.Add("### Parents");
content.Add(String.Join(", ", element.Parents.Values.Select(parent => this.FormatParentElement(parent))));
}
if (!element.Children.IsNullOrEmpty())
{
content.Add("### Children");
content.AddRange(element.Children.Values.OrderBy(child => child.Name).Select(child => this.FormatChildElement(child)));
}
if (!String.IsNullOrEmpty(element.Remarks))
{
content.Add("### Remarks");
content.Add(element.Remarks);
}
// attributes
if (element.Attributes.Any())
{
content.Add("### Attributes");
foreach (var attribute in element.Attributes.Values)
{
var required = attribute.Required ? ", required" : String.Empty /*", optional" default*/;
if (attribute.EnumValues?.Any() == true)
{
content.Add($"**{attribute.Name}** (enumeration{required})");
content.Add($" : {attribute.Description} This attribute's value must be one of the following:");
content.AddRange(EnumValuesDocumentation(attribute.EnumValues));
}
else
{
if (String.IsNullOrEmpty(attribute.TypeDocumentation))
{
content.Add($"**{attribute.Name}** ({attribute.Type}{required})");
}
else
{
content.Add($"**{attribute.Name}** ([{attribute.Type}]({this.LinkForType(PageType.Element, attribute.Type)} '{attribute.TypeDocumentation}'){required})");
}
content.Add($" : {attribute.Description}");
}
content.Add(String.Empty);
}
}
// see also
if (!element.SeeAlsos.IsNullOrEmpty())
{
content.Add("### See also");
content.Add(String.Join(", ", element.SeeAlsos.Select(seeAlso => this.FormatSeeAlsoElement(seeAlso))));
}
return new Page(this.PageId(PageType.Element, element.Name), content);
}
private static IEnumerable<string> EnumValuesDocumentation(IEnumerable<EnumValue> enumValues)
{
foreach (var enumValue in enumValues)
{
if (String.IsNullOrEmpty(enumValue.Documentation))
{
yield return $"- *{enumValue.Name}*";
}
else
{
yield return $"- *{enumValue.Name}*: {enumValue.Documentation}";
}
}
}
private string FormatChildElement(Child child)
{
var extension = child.Namespace == this.Xsd.TargetNamespace ? String.Empty : $" ({Xsd.GetSchemaNameFromNamespace(child.Namespace)} extension)";
return $"* [{child.Name}{extension}]({this.LinkForElement(PageType.Element, child.Namespace, child.Name)}) {child.Cardinality}{child.Documentation}";
}
private string FormatParentElement(Parent parent) => $"[{parent.Name}]({this.LinkForElement(PageType.Element, parent.Namespace, parent.Name)})";
private string FormatSeeAlsoElement(Element seeAlso) => $"[{seeAlso.Name}]({this.LinkForElement(PageType.Element, seeAlso.Namespace, seeAlso.Name)})";
private static IEnumerable<string> FormatMsiRefElement(MsiRef msiRef)
{
if (!String.IsNullOrEmpty(msiRef.Action))
{
yield return $"[{msiRef.Action} Action](https://docs.microsoft.com/en-us/windows/win32/msi/{msiRef.Action.ToLowerInvariant()}-action)";
}
if (!String.IsNullOrEmpty(msiRef.Table))
{
yield return $"[{msiRef.Table} Table](https://docs.microsoft.com/en-us/windows/win32/msi/{msiRef.Table.ToLowerInvariant()}-table)";
}
}
private string RelativeLinkForPage(PageType sourcePageType, PageType destinationPageType, string targetNamespace = null, string name = null)
{
var namespaceId = targetNamespace == this.Xsd.TargetNamespace || targetNamespace == null ? null : Xsd.GetSchemaNameFromNamespace(targetNamespace);
var link = (sourcePageType, destinationPageType, !String.IsNullOrEmpty(namespaceId)) switch
{
(PageType.SchemaRoot, PageType.SchemaRoot, true) => $"../{namespaceId}/",
(PageType.SchemaRoot, PageType.SchemaRoot, false) => throw new ArgumentOutOfRangeException(),
(PageType.SchemaRoot, PageType.Element, true) => $"../{namespaceId}/elements/{name}/",
(PageType.SchemaRoot, PageType.Element, false) => $"elements/{name}/",
(PageType.SchemaRoot, PageType.Type, true) => $"../{namespaceId}/types/{name}/",
(PageType.SchemaRoot, PageType.Type, false) => $"types/{name}/",
(PageType.Element, PageType.SchemaRoot, true) => $"../{namespaceId}/",
(PageType.Element, PageType.SchemaRoot, false) => throw new ArgumentOutOfRangeException(),
(PageType.Element, PageType.Element, true) => $"../../../{namespaceId}/elements/{name}/",
(PageType.Element, PageType.Element, false) => $"../{name}/",
(PageType.Element, PageType.Type, true) => throw new ArgumentOutOfRangeException(),
(PageType.Element, PageType.Type, false) => $"../../types/{name}/",
(PageType.Type, PageType.SchemaRoot, true) => $"../{namespaceId}/",
(PageType.Type, PageType.SchemaRoot, false) => throw new ArgumentOutOfRangeException(),
(PageType.Type, PageType.Element, true) => $"../../../{namespaceId}/elements/{name}/",
(PageType.Type, PageType.Element, false) => $"../../elements/{name}/",
(PageType.Type, PageType.Type, true) => throw new ArgumentOutOfRangeException(),
(PageType.Type, PageType.Type, false) => $"../../types/{name}/",
_ => throw new NotImplementedException(),
};
return link.ToLowerInvariant();
}
private string LinkForElement(PageType sourcePageType, string targetNamespace, string elementName) =>
this.RelativeLinkForPage(sourcePageType, PageType.Element, targetNamespace, elementName);
private string LinkForType(PageType sourcePageType, string typeName) =>
$"{this.RelativeLinkForPage(sourcePageType, PageType.Type, targetNamespace: null, typeName.EndsWith("TypeUnion") ? typeName.Replace("TypeUnion", "Type") : typeName)}";
}
public static class Extensions
{
public static bool IsNullOrEmpty<TSource>(this IEnumerable<TSource> enumerable) => enumerable == null || !enumerable.Any();
}
}

Просмотреть файл

@ -0,0 +1,28 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
namespace WixBuildTools.XsdToMarkdown
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
enum PageType
{
SchemaRoot,
Element,
Type
}
[DebuggerDisplay("{Id}")]
public class Page
{
public string Id { get; }
public IEnumerable<string> Content { get; }
public Page(string id, IEnumerable<string> content)
{
this.Id = id;
this.Content = content;
}
}
}

Просмотреть файл

@ -0,0 +1,45 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
namespace WixBuildTools.XsdToMarkdown
{
using System;
using System.IO;
using System.Linq;
using System.Xml.Linq;
public sealed class Program
{
public static int Main(string[] args)
{
if (!CommandLine.TryParseArguments(args, out var commandLine))
{
CommandLine.ShowHelp();
return 1;
}
// Load 'em up.
var xsds = commandLine.Files.Select(path => new Xsd(XDocument.Load(path)));
Console.WriteLine($"XsdToMarkdown: Processing {xsds.Count()} XSDs.");
// Put 'em together.
var finalizedXsds = XsdFinalizer.Finalize(xsds);
// Convert 'em to Markdown.
var converter = new ConvertXsdToMarkdownCommand();
var pages = finalizedXsds.SelectMany(xsd => converter.Convert(xsd));
foreach (var page in pages)
{
var markdown = String.Join(Environment.NewLine, page.Content);
var dir = Path.Combine(commandLine.OutputFolder, page.Id);
var mdPath = Path.Combine(dir, "index.html.md");
Directory.CreateDirectory(dir);
File.WriteAllText(mdPath, markdown);
}
Console.WriteLine($"XsdToMarkdown: Wrote {pages.Count()} Markdown pages.");
return 0;
}
}
}

Просмотреть файл

@ -0,0 +1,419 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
namespace WixBuildTools.XsdToMarkdown
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Xml.Linq;
[DebuggerDisplay("SchemaName={SchemaName,nq} TargetNamespace={TargetNamespace,nq}")]
public class Xsd
{
private static readonly XNamespace XmlSchemaNamespace = "http://www.w3.org/2001/XMLSchema";
private static readonly XNamespace XmlSchemaExtensionNamespace = "http://wixtoolset.org/schemas/XmlSchemaExtension";
private static readonly XName AnnotationElement = XmlSchemaNamespace + "annotation";
private static readonly XName DocumentationElement = XmlSchemaNamespace + "documentation";
private static readonly XName ElementElement = XmlSchemaNamespace + "element";
private static readonly XName AppInfoElement = XmlSchemaNamespace + "appinfo";
private static readonly XName ParentElement = XmlSchemaExtensionNamespace + "parent";
private static readonly XName MsiRefElement = XmlSchemaExtensionNamespace + "msiRef";
private static readonly XName RemarksElement = XmlSchemaExtensionNamespace + "remarks";
private static readonly XName SeeAlsoElement = XmlSchemaExtensionNamespace + "seeAlso";
private static readonly XName ComplexTypeElement = XmlSchemaNamespace + "complexType";
private static readonly XName AttributeElement = XmlSchemaNamespace + "attribute";
private static readonly XName AttributeGroupElement = XmlSchemaNamespace + "attributeGroup";
private static readonly XName SimpleTypeElement = XmlSchemaNamespace + "simpleType";
private static readonly XName RestrictionElement = XmlSchemaNamespace + "restriction";
private static readonly XName EnumerationElement = XmlSchemaNamespace + "enumeration";
private static readonly XName SimpleContentElement = XmlSchemaNamespace + "simpleContent";
private static readonly XName ExtensionElement = XmlSchemaNamespace + "extension";
private static readonly XName MainElement = XmlSchemaExtensionNamespace + "main";
private static readonly XName DeprecatedElement = XmlSchemaExtensionNamespace + "deprecated";
public XDocument Document { get; private set; }
public bool IsMainSchema { get; private set; }
public string TargetNamespace { get; private set; }
public string SchemaName { get; private set; }
public string SchemaDocumentation { get; private set; }
public IDictionary<string, Element> Elements { get; }
public IEnumerable<Element> RootElements { get; }
public IDictionary<string, IEnumerable<Attribute>> AttributeGroups { get; private set; }
public IDictionary<string, Attribute> RootAttributes { get; private set; }
public IDictionary<string, SimpleType> SimpleTypes { get; }
public Xsd(XDocument xsd)
{
this.Document = xsd;
this.IsMainSchema = xsd.Root.Element(AnnotationElement)?.Element(AppInfoElement)?.Element(MainElement) != null;
this.TargetNamespace = xsd.Root.Attribute("targetNamespace").Value;
this.SchemaName = GetSchemaNameFromNamespace(this.TargetNamespace);
this.SchemaDocumentation = this.GetSchemaDocumentation();
var xSimpleTypes = xsd.Root.Elements(SimpleTypeElement);
this.SimpleTypes = xSimpleTypes.Select(x => ParseSimpleType(x)).ToDictionary(t => t.Name);
var xAttributes = xsd.Root.Elements(AttributeElement);
this.RootAttributes = xAttributes?.Select(x => this.ParseAttribute(x)).ToDictionary(a => a.Name);
this.AttributeGroups = this.ParseAttributeGroups(xsd.Root);
var xElements = xsd.Root.Elements(ElementElement);
this.Elements = xElements?.Select(x => this.CreateElement(x)).ToDictionary(el => el.Name);
// bugbugbug: fix wix.xsd so that parentless elements are actually parentless
// this.RootElements = xElements.Where(x => x.Element(AnnotationElement)?.Element(AppInfoElement)?.Elements(ParentElement) == null)?.Select(x => this.CreateElement(x));
this.RootElements = Enumerable.Empty<Element>();
}
public static string GetSchemaNameFromNamespace(string targetNamespace)
{
var namespaceParts = targetNamespace.Split('/');
var schemaName = namespaceParts[^1];
return Capitalize(schemaName);
}
private static SimpleType ParseSimpleType(XElement xSimpleType)
{
var name = xSimpleType.Attribute("name")?.Value;
var doc = GetDocumentationFromAnnotation(xSimpleType.Element(AnnotationElement));
var enumValues = CreateEmuerationValues(xSimpleType);
return new SimpleType(name, doc, enumValues);
}
private static IEnumerable<EnumValue> CreateEmuerationValues(XElement xSimpleType)
{
var xRestriction = xSimpleType?.Element(RestrictionElement);
var enumerations = xRestriction?.Elements(EnumerationElement);
return enumerations == null
? Enumerable.Empty<EnumValue>()
: enumerations.Select(x => new EnumValue(x.Attribute("value")?.Value, GetDocumentationFromAnnotation(x.Element(AnnotationElement))));
}
private Element CreateElement(XElement xElement)
{
var name = xElement.Attribute("name")?.Value;
var xAnnotation = xElement.Element(AnnotationElement);
var xAppInfo = xAnnotation?.Element(AppInfoElement);
var xComplexType = xElement.Element(ComplexTypeElement);
var deprecation = GetDeprecation(xAnnotation, "element");
var documentation = String.IsNullOrEmpty(deprecation) ? GetDocumentationFromAnnotation(xAnnotation) : deprecation;
var msiRefs = xAppInfo?.Elements(MsiRefElement).Select(x => CreateMsiRef(x));
var parents = xAppInfo?.Elements(ParentElement).Select(x => CreateParent(x));
var children = xComplexType?.Descendants(ElementElement).Select(x => this.CreateChild(x));
// TODO: handle embedded HTML
var remarks = xAppInfo?.Element(RemarksElement)?.Value;
var attributes = this.CreateAttributes(xComplexType);
var seeAlsos = xAppInfo?.Elements(SeeAlsoElement).SelectMany(x => this.CreateSeeAlsoElement(x));
return new Element(name, this.TargetNamespace, documentation, remarks, attributes, parents, children, msiRefs, seeAlsos);
}
private IEnumerable<Element> CreateSeeAlsoElement(XElement xSeeAlso)
{
var element = xSeeAlso.Attribute("ref")?.Value;
var @namespace = xSeeAlso.Attribute("namespace")?.Value;
yield return new Element(element, @namespace);
}
private IEnumerable<Attribute> CreateAttributes(XElement xComplexType)
{
if (xComplexType != null)
{
var attributeGroupReferences = xComplexType.Elements(AttributeGroupElement).Select(x => x.Attribute("ref")?.Value);
var attributeGroupAttributes = attributeGroupReferences.SelectMany(a => this.AttributeGroups[a]);
var attributes = attributeGroupAttributes.Concat(this.ParseAttributes(xComplexType));
return attributes.OrderBy(attr => attr.Name);
}
return Enumerable.Empty<Attribute>();
}
private Child CreateChild(XElement xParent)
{
var childElement = xParent.Attribute("ref")?.Value;
var minOccurs = xParent.Attribute("minOccurs")?.Value ?? xParent.Parent.Attribute("minOccurs")?.Value ?? "1";
var maxOccurs = xParent.Attribute("maxOccurs")?.Value ?? xParent.Parent.Attribute("maxOccurs")?.Value ?? "1";
var doc = GetDocumentationFromAnnotation(xParent.Element(AnnotationElement), prefix: ": ");
var cardinality = Cardinality(minOccurs, maxOccurs);
return new Child(this.TargetNamespace, childElement, String.IsNullOrEmpty(doc) ? String.Empty : doc, cardinality);
static string Cardinality(string minOccurs, string maxOccurs)
{
var optional = Int32.TryParse(minOccurs, out var min) && min == 0 ? /*"optional" default*/ String.Empty : "required";
var unbounded = !String.IsNullOrEmpty(maxOccurs) && maxOccurs.Equals("unbounded", StringComparison.OrdinalIgnoreCase);
var max = unbounded ? /*"unlimited" default*/ String.Empty : $"no more than {maxOccurs}";
var any = !String.IsNullOrEmpty(optional) || !String.IsNullOrEmpty(max);
return String.Concat(any ? "(" : null, optional, String.IsNullOrEmpty(optional) ? null : ", ", max, any ? ") " : null);
}
}
private static Parent CreateParent(XElement xParent)
{
var parentNamespace = xParent.Attribute("namespace")?.Value;
var parentElement = xParent.Attribute("ref")?.Value;
return new Parent(parentNamespace, parentElement);
}
private static MsiRef CreateMsiRef(XElement xMsiRef)
{
var action = xMsiRef.Attribute("action")?.Value;
var table = xMsiRef.Attribute("table")?.Value;
return new MsiRef(action, table);
}
private string GetSchemaDocumentation()
{
var xAnnotation = this.Document.Root.Element(AnnotationElement);
var documentation = GetDocumentationFromAnnotation(xAnnotation);
return documentation;
}
private Dictionary<string, IEnumerable<Attribute>> ParseAttributeGroups(XElement root)
{
var xAttributeGroups = root.Elements(AttributeGroupElement);
var result = xAttributeGroups.ToDictionary(
x => x.Attribute("name")?.Value,
x => this.ParseAttributes(x));
return result;
}
private IEnumerable<Attribute> ParseAttributes(XElement parent)
{
// Use simple content if it exists.
parent = parent.Element(SimpleContentElement)?.Element(ExtensionElement) ?? parent;
var xAttributes = parent.Elements(AttributeElement);
return xAttributes.Select(x => this.ParseAttribute(x));
}
private Attribute ParseAttribute(XElement xAttribute)
{
var name = xAttribute.Attribute("name")?.Value;
var required = String.Equals(xAttribute.Attribute("use")?.Value, "required", StringComparison.OrdinalIgnoreCase);
var type = this.GetAttributeType(xAttribute);
var typeDocumentation = String.Empty;
IEnumerable<EnumValue> enumValues = null;
if (String.IsNullOrEmpty(type))
{
var xSimpleType = xAttribute.Element(SimpleTypeElement);
enumValues = CreateEmuerationValues(xSimpleType);
}
else if (this.SimpleTypes.TryGetValue(type, out var simpleType))
{
typeDocumentation = simpleType.Documentation;
enumValues = simpleType.EnumValues;
}
var xAnnotation = xAttribute.Element(AnnotationElement);
var deprecation = GetDeprecation(xAnnotation, "attribute");
var documentation = deprecation ?? GetDocumentationFromAnnotation(xAnnotation);
var xAppInfo = xAnnotation?.Element(AppInfoElement);
var xParents = xAppInfo?.Elements(ParentElement);
var parents = xParents?.Select(x => CreateParent(x));
return new Attribute(name, documentation, type, typeDocumentation, required, enumValues, parents);
}
private static string Capitalize(string value)
{
if (value.Length > 0)
{
value = String.Concat(Char.ToUpperInvariant(value[0]), value.Substring(1));
}
return value;
}
private static string GetDocumentationFromAnnotation(XElement xAnnotation, string prefix = null)
{
// TODO: handle embedded HTML
var doc = xAnnotation?.Element(DocumentationElement)?.Value ?? String.Empty;
// Join split lines else Markdown tables go crazy.
var lines = doc.Split('\n');
doc = String.Join(" ", lines.Select(line => line.Trim())).Trim();
return String.IsNullOrWhiteSpace(doc) ? String.Empty : (prefix ?? String.Empty) + doc;
}
private string GetAttributeType(XElement xAttribute)
{
var type = xAttribute.Attribute("type")?.Value;
return type switch
{
"xs:string" => "String",
"xs:integer" => "Integer",
_ => type,
};
}
private static string GetDeprecation(XElement xAnnotation, string type)
{
string deprecation = null;
var xDeprecated = xAnnotation?.Element(AppInfoElement)?.Element(DeprecatedElement);
if (xDeprecated != null)
{
deprecation = "Deprecated";
var replacement = xDeprecated.Attribute("ref")?.Value;
if (!String.IsNullOrEmpty(replacement))
{
deprecation += $": Use the {replacement} {type} instead.";
}
}
return deprecation;
}
}
[DebuggerDisplay("Name={Name,nq}")]
public class Element
{
public string Name { get; }
public string Namespace { get; }
public string Documentation { get; }
public string Remarks { get; set; }
public IEnumerable<MsiRef> MsiRefs { get; set; }
public IDictionary<string, Parent> Parents { get; set; }
public IDictionary<string, Child> Children { get; }
public IDictionary<string, Attribute> Attributes { get; set; }
public IEnumerable<Element> SeeAlsos { get; set; }
public Element(string name, string @namespace)
{
this.Name = name;
this.Namespace = @namespace;
}
public Element(string name, string @namespace, string documentation, string remarks, IEnumerable<Attribute> attributes, IEnumerable<Parent> parents, IEnumerable<Child> children, IEnumerable<MsiRef> msiRefs, IEnumerable<Element> seeAlsos)
{
this.Name = name;
this.Namespace = @namespace;
this.Documentation = documentation;
this.Remarks = remarks;
this.Attributes = attributes?.ToDictionary(x => x.Name) ?? new Dictionary<string, Attribute>();
this.Parents = parents?.ToDictionary(x => x.Name) ?? new Dictionary<string, Parent>();
this.Children = children?.ToDictionary(x => x.Name) ?? new Dictionary<string, Child>();
this.MsiRefs = msiRefs;
this.SeeAlsos = seeAlsos;
}
}
[DebuggerDisplay("Name={Name,nq} Type={Type,nq} Required={Required,nq}")]
public class Attribute
{
public string Name { get; }
public string Description { get; }
public string Type { get; }
public string TypeDocumentation { get; }
public bool Required { get; }
public IEnumerable<EnumValue> EnumValues { get; set; }
public IEnumerable<Parent> Parents { get; set; }
public Attribute(string name, string description, string type, string typeDocumentation, bool required, IEnumerable<EnumValue> enumValues, IEnumerable<Parent> parents)
{
this.Name = name;
this.Description = description;
this.Type = type;
this.TypeDocumentation = typeDocumentation;
this.Required = required;
this.EnumValues = enumValues;
this.Parents = parents;
}
}
[DebuggerDisplay("Name={Name,nq}")]
public class SimpleType
{
public string Name { get; }
public string Documentation { get; }
public IEnumerable<EnumValue> EnumValues { get; }
public SimpleType(string name, string documentation, IEnumerable<EnumValue> enumValues)
{
this.Name = name;
this.Documentation = documentation;
this.EnumValues = enumValues;
}
}
[DebuggerDisplay("Name={Name,nq}")]
public class EnumValue
{
public string Name { get; }
public string Documentation { get; }
public EnumValue(string name, string documentation)
{
this.Name = name;
this.Documentation = documentation;
}
}
[DebuggerDisplay("Action={Action,nq} Table={Table,nq}")]
public class MsiRef
{
public string Action { get; }
public string Table { get; }
public MsiRef(string action, string table)
{
this.Action = action;
this.Table = table;
}
}
[DebuggerDisplay("Name={Name,nq} Namespace={Namespace,nq}")]
public class Parent
{
public string Namespace { get; }
public string Name { get; }
public Parent(string @namespace, string element)
{
this.Namespace = @namespace;
this.Name = element;
}
}
[DebuggerDisplay("Name={Name,nq} '{Cardinality,nq}'")]
public class Child
{
public string Namespace { get; }
public string Name { get; }
public string Documentation { get; }
public string Cardinality { get; }
public Child(string @namespace, string name, string documentation, string cardinality)
{
this.Namespace = @namespace;
this.Name = name;
this.Documentation = documentation;
this.Cardinality = cardinality;
}
}
}

Просмотреть файл

@ -0,0 +1,59 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
namespace WixBuildTools.XsdToMarkdown
{
using System;
using System.Collections.Generic;
using System.Linq;
/// <summary>
/// Updates Xsd objects so that parents and children across xsds are reflected everywhere.
/// </summary>
public static class XsdFinalizer
{
public static IEnumerable<Xsd> Finalize(IEnumerable<Xsd> xsds)
{
var xsdByNamespace = xsds.ToDictionary(x => x.TargetNamespace);
foreach (var xsd in xsdByNamespace.Values)
{
foreach (var attr in xsd.RootAttributes?.Values)
{
foreach (var parent in attr.Parents)
{
if (xsdByNamespace.TryGetValue(parent.Namespace, out var targetXsd)
&& targetXsd.Elements.TryGetValue(parent.Name, out var targetElement))
{
targetElement.Attributes[parent.Name] = attr;
}
}
}
foreach (var element in xsd.Elements.Values)
{
foreach (var parent in element.Parents.Values)
{
if (xsdByNamespace.TryGetValue(parent.Namespace, out var targetXsd)
&& targetXsd.Elements.TryGetValue(parent.Name, out var targetElement))
{
// TODO: Stretch: dig up cardinality and documentation. v3 doc doesn't but maybe we can do better, eh?
targetElement.Children[element.Name] = new Child(element.Namespace, element.Name, String.Empty, String.Empty);
}
}
// add this element as a parent to each child (e.g., Product child-> Component <-parent Product)
foreach (var child in element.Children.Values)
{
if (xsdByNamespace.TryGetValue(child.Namespace, out var targetXsd)
&& targetXsd.Elements.TryGetValue(child.Name, out var targetElement))
{
targetElement.Parents[element.Name] = new Parent(element.Namespace, element.Name);
}
}
}
}
return xsdByNamespace.Values;
}
}
}

Просмотреть файл

@ -0,0 +1,12 @@
<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<OutputType>Exe</OutputType>
<Description>XsdToMarkdown</Description>
<Title>WiX Toolset XsdToMarkdown doc generator</Title>
<DebugType>embedded</DebugType>
<RollForward>Major</RollForward>
</PropertyGroup>
</Project>