diff --git a/Web.sln b/Web.sln
index 2a95b3cf..3b87c5d9 100644
--- a/Web.sln
+++ b/Web.sln
@@ -1,7 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
-VisualStudioVersion = 17.4.33122.133
VisualStudioVersion = 17.4.33103.184
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{C728C975-EEE4-4A54-ACD2-95E8BCA2F5CE}"
@@ -12,10 +11,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XsdToMarkdownTests", "src\t
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{1D3076DE-5378-4273-AB3B-C14B6F4C46BC}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FeedGenerator", "src\FeedGenerator\FeedGenerator.csproj", "{07F578E6-AE83-4615-821D-59B4F8D92B25}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FeedGenerator", "src\FeedGenerator\FeedGenerator.csproj", "{07F578E6-AE83-4615-821D-59B4F8D92B25}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XmlDocToMarkdown", "src\XmlDocToMarkdown\XmlDocToMarkdown.csproj", "{AD1815B0-1F4F-4CEE-961D-0AAFE21227BF}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SchemaValidator", "tools\SchemaValidator\SchemaValidator.csproj", "{20D9C939-933C-4F1F-8EF7-298CA16F136F}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -42,6 +43,10 @@ Global
{AD1815B0-1F4F-4CEE-961D-0AAFE21227BF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AD1815B0-1F4F-4CEE-961D-0AAFE21227BF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AD1815B0-1F4F-4CEE-961D-0AAFE21227BF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {20D9C939-933C-4F1F-8EF7-298CA16F136F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {20D9C939-933C-4F1F-8EF7-298CA16F136F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {20D9C939-933C-4F1F-8EF7-298CA16F136F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {20D9C939-933C-4F1F-8EF7-298CA16F136F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/tools/SchemaValidator/CommandLine.cs b/tools/SchemaValidator/CommandLine.cs
new file mode 100644
index 00000000..38199e50
--- /dev/null
+++ b/tools/SchemaValidator/CommandLine.cs
@@ -0,0 +1,97 @@
+// 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.SchemaValidator;
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+///
+/// Command-line parsing.
+///
+public class CommandLine
+{
+ private CommandLine()
+ {
+ }
+
+ ///
+ /// List of schema files to process.
+ ///
+ public List Schemas { get; private set; } = new List();
+
+ ///
+ /// List of source files to process.
+ ///
+ public List Sources { get; private set; } = new List();
+
+ ///
+ /// Show the help text.
+ ///
+ public static void ShowHelp()
+ {
+ Console.WriteLine("SchemaValidator.exe [-?] path/to/*.xsd path/to/*.wx?");
+ }
+
+ ///
+ /// Parses the command-line.
+ ///
+ /// Arguments from command-line.
+ /// Command line object created from command-line arguments
+ /// True if command-line is parsed, false if a failure was occurred.
+ 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
+ {
+ 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, SearchOption.AllDirectories);
+ if (files.Any())
+ {
+ commandLine.Schemas.AddRange(files.Where(f => Path.GetExtension(f).Equals(".xsd", StringComparison.OrdinalIgnoreCase)));
+ commandLine.Sources.AddRange(files.Where(f => Path.GetExtension(f).StartsWith(".wx", StringComparison.OrdinalIgnoreCase)));
+ }
+ else
+ {
+ Console.Error.WriteLine($"Source file '{sourcePath}' could not be found.");
+ success = false;
+ }
+ }
+ }
+ }
+
+ if (0 == commandLine.Schemas.Count)
+ {
+ Console.Error.WriteLine("No schema files specified. Specify at least one file.");
+ success = false;
+ }
+
+ if (0 == commandLine.Sources.Count)
+ {
+ Console.Error.WriteLine("No source files specified. Specify at least one file.");
+ success = false;
+ }
+
+ return success;
+ }
+}
diff --git a/tools/SchemaValidator/Program.cs b/tools/SchemaValidator/Program.cs
new file mode 100644
index 00000000..24060dc6
--- /dev/null
+++ b/tools/SchemaValidator/Program.cs
@@ -0,0 +1,75 @@
+// 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.SchemaValidator;
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Xml;
+using System.Xml.Linq;
+using System.Xml.Schema;
+
+public sealed class Program
+{
+ public static int Main(string[] args)
+ {
+ if (!CommandLine.TryParseArguments(args, out var commandLine))
+ {
+ CommandLine.ShowHelp();
+ return 1;
+ }
+
+ Console.WriteLine($"{DateTime.Now.ToLongTimeString()}: Start run. Validating {commandLine.Sources.Count} source files against {commandLine.Schemas.Count} schemas.");
+
+ var settings = GetReaderSettings(commandLine);
+
+ var errors = 0;
+
+ foreach (var source in commandLine.Sources)
+ {
+ try
+ {
+ Validate(source, settings);
+ }
+ catch (XmlSchemaException xse)
+ {
+ ++errors;
+ Console.WriteLine($"Encountered validation error in {source}({xse.LineNumber},{xse.LinePosition}): {xse.Message}");
+ }
+ }
+
+ Console.WriteLine($"{DateTime.Now.ToLongTimeString()}: Run complete. Encountered {errors} validation errors from {commandLine.Sources.Count} source files against {commandLine.Schemas.Count} schemas.");
+
+ return 0;
+ }
+
+ private static XmlReaderSettings GetReaderSettings(CommandLine commandLine)
+ {
+ var settings = new XmlReaderSettings
+ {
+ ValidationType = ValidationType.Schema,
+ ValidationFlags =
+ XmlSchemaValidationFlags.ReportValidationWarnings |
+ XmlSchemaValidationFlags.ProcessIdentityConstraints |
+ XmlSchemaValidationFlags.ProcessInlineSchema |
+ XmlSchemaValidationFlags.ProcessSchemaLocation
+ };
+
+ foreach (var schema in commandLine.Schemas)
+ {
+ settings.Schemas.Add(null, schema);
+ }
+
+ return settings;
+ }
+
+ static void Validate(string xmlFile, XmlReaderSettings settings)
+ {
+ var document = new XmlDocument();
+ var reader = XmlReader.Create(xmlFile, settings);
+ document.Load(reader);
+ document.Validate(null);
+ }
+}
diff --git a/tools/SchemaValidator/SchemaValidator.csproj b/tools/SchemaValidator/SchemaValidator.csproj
new file mode 100644
index 00000000..74abf5c9
--- /dev/null
+++ b/tools/SchemaValidator/SchemaValidator.csproj
@@ -0,0 +1,10 @@
+
+
+
+ Exe
+ net6.0
+ enable
+ enable
+
+
+