using System; using System.Xml; using System.Xml.Linq; using CommandLine; using Sprache; using XNeedle.Parser; using XNeedle.Parser.Elements; using XNeedle.Transformation; using XNeedle.Formats; namespace XNeedle { class Program { public class Options { [Value(0, MetaName = "xn", Required = true, HelpText = "XN transformation definition")] public required string XnDefinition { get; set; } [Option('i', "input", HelpText = "Input XML file", Required = true)] public required string Input { get; set; } [Option('o', "output", HelpText = "Output XML file", Required = true)] public required string Output { get; set; } [Option('v', "verbose", Required = false, HelpText = "Set output to verbose messages.")] public bool Verbose { get; set; } } static void Main(string[] args) { CommandLine.Parser.Default.ParseArguments(args) .WithParsed(RunOptions) .WithNotParsed(HandleParseError); } static void RunOptions(Options opts) { // Auto-discover operations via reflection BangOperation.RegisterAll(); var rawSource = File.ReadAllText(opts.XnDefinition); var xnSource = XnParser.StripComments(rawSource); IEnumerable rules; try { rules = XnParser.Rules.Parse(xnSource); } catch (ParseException ex) { var lines = rawSource.Split('\n'); int line = ex.Position?.Line ?? -1; int col = ex.Position?.Column ?? -1; bool showPosition = (line != -1) && (col != -1); if (showPosition) { var msg = ex.Message; var hint = (msg.Contains("expected }") && !msg.Contains("expected ';'")) ? "Hint: a missing ';' terminator may be located just after this position" : ""; Console.Error.WriteLine($"{opts.XnDefinition}({line},{col}): parse error: {msg}"); var srcLine = line <= lines.Length ? lines[line - 1].TrimEnd() : ""; var pointer = new string(' ', Math.Max(0, col - 1)) + "^"; Console.Error.WriteLine($" {srcLine}"); Console.Error.WriteLine($" {pointer}"); if (!string.IsNullOrEmpty(hint)) { Console.Error.WriteLine($"\n{hint}"); } } else { Console.Error.WriteLine($"{opts.XnDefinition}: parse error: {ex.Message}"); } return; } var doc = XDocument.Load(opts.Input, LoadOptions.PreserveWhitespace); var encoding = XmlRoundtripWriter.DetectEncoding(File.ReadAllBytes(opts.Input)); var sloc = new SourceLocation(opts.Input, encoding); try { foreach (var rule in rules.OfType()) rule.Transform(doc, sloc); } catch (RequireException ex) { Console.Error.WriteLine($"{ex.SLoc.FilePath}({ex.SLoc.LineNumber}): require error: {ex.UserMessage}"); return; } using var writer = XmlRoundtripWriter.Create(opts.Input, opts.Output); doc.WriteTo(writer); Console.WriteLine($"Applied \"{opts.XnDefinition}\" to \"{opts.Input}\" and saved result to \"{opts.Output}\""); } static void HandleParseError(IEnumerable errs) {} } }