private static void ReplaceTableOfContents(MarkdownSpec spec, Body body) { // We have to find the TOC, if one exists, and replace it... if (FindToc(body, out var tocFirst, out var tocLast, out var tocInstr, out var tocSec)) { for (int i = tocLast; i >= tocFirst; i--) { body.RemoveChild(body.ChildElements[i]); } var afterToc = body.ChildElements[tocFirst]; for (int i = 0; i < spec.Sections.Count; i++) { var section = spec.Sections[i]; if (section.Level > 2) { continue; } var p = new Paragraph(); p.AppendChild(new Hyperlink(new Run(new Text(section.Title))) { Anchor = section.BookmarkName }); p.ParagraphProperties = new ParagraphProperties(new ParagraphStyleId { Val = $"TOC{section.Level}" }); body.InsertBefore(p, afterToc); } if (tocSec != null) { body.InsertBefore(tocSec, afterToc); } } }
public static void ConvertToWord(MarkdownSpec spec, string templateFile, string outputFile) { using (var templateDoc = WordprocessingDocument.Open(templateFile, false)) using (var resultDoc = WordprocessingDocument.Create(outputFile, WordprocessingDocumentType.Document)) { foreach (var part in templateDoc.Parts) { resultDoc.AddPart(part.OpenXmlPart, part.RelationshipId); } var body = resultDoc.MainDocumentPart.Document.Body; ReplaceTableOfContents(spec, body); var context = new ConversionContext(); context.MaxBookmarkId.Value = 1 + body.Descendants <BookmarkStart>().Max(bookmark => int.Parse(bookmark.Id)); foreach (var src in spec.Sources) { var converter = new MarkdownSourceConverter( markdownDocument: src.Item2, wordDocument: resultDoc, spec: spec, context: context, filename: Path.GetFileName(src.Item1)); foreach (var p in converter.Paragraphs()) { body.AppendChild(p); } } } }
public static MarkdownSpec ReadFiles(IEnumerable <string> files) { var md = new MarkdownSpec { files = files }; md.Init(); return(md); }
public static MarkdownSpec ReadString(string s) { var md = new MarkdownSpec { s = s }; md.Init(); return(md); }
public MarkdownSourceConverter( MarkdownDocument markdownDocument, WordprocessingDocument wordDocument, MarkdownSpec spec, ConversionContext context, string filename) { this.markdownDocument = markdownDocument; this.wordDocument = wordDocument; sections = spec.Sections.ToDictionary(sr => sr.Url); productions = spec.Productions; this.context = context; this.filename = filename; reporter = new Reporter(filename); }
static int Main(string[] args) { // mdspec2docx *.md csharp.g4 template.docx -o spec.docx var ifiles = new List <string>(); var ofiles = new List <string>(); string argserror = ""; for (int i = 0; i < args.Length; i++) { var arg = args[i]; if (arg.StartsWith("-")) { if (arg == "-o" && i < args.Length - 1) { i++; ofiles.Add(args[i]); } else { argserror += $"Unrecognized '{arg}'\n"; } } else if (!arg.Contains("*") && !arg.Contains("?")) { if (!File.Exists(arg)) { Console.Error.WriteLine($"Not found - {arg}"); return(1); } ifiles.Add(arg); } else { // Windows command-shell doesn't do globbing, so we have to do it ourselves string dir = Path.GetDirectoryName(arg), filename = Path.GetFileName(arg); if (dir.Contains("*") || dir.Contains("?")) { Console.Error.WriteLine("Can't match wildcard directory names"); return(1); } if (dir == "") { dir = Directory.GetCurrentDirectory(); } if (!Directory.Exists(dir)) { Console.Error.WriteLine($"Not found - \"{dir}\""); return(1); } var fns2 = Directory.GetFiles(dir, filename); if (fns2.Length == 0) { Console.Error.WriteLine($"Not found - \"{arg}\""); return(1); } ifiles.AddRange(fns2); } } var imdfiles = new List <string>(); string ireadmefile = null, iantlrfile = null, idocxfile = null, odocfile = null; foreach (var ifile in ifiles) { var name = Path.GetFileName(ifile); var ext = Path.GetExtension(ifile).ToLower(); if (ext == ".g4") { if (iantlrfile != null) { argserror += "Multiple input .g4 files\n"; } iantlrfile = ifile; } else if (ext == ".docx") { if (idocxfile != null) { argserror += "Multiple input .docx files\n"; } idocxfile = ifile; } else if (ext != ".md") { argserror += $"Not .g4 or .docx or .md '{ifile}'\n"; continue; } else if (String.Equals(name, "readme.md", StringComparison.InvariantCultureIgnoreCase)) { if (ireadmefile != null) { argserror += "Multiple readme.md files\n"; } ireadmefile = ifile; } else { imdfiles.Add(ifile); } } foreach (var ofile in ofiles) { var ext = Path.GetExtension(ofile).ToLower(); if (ext == ".docx") { if (odocfile != null) { argserror += "Multiple output docx files\n"; } odocfile = ofile; } else { argserror += $"Unknown output file extension: {ext}\n"; } } if (odocfile == null) { argserror += "No output .docx file specified\n"; } if (ireadmefile == null && ifiles.Count == 0) { argserror += "No .md files supplied\n"; } if (idocxfile == null) { argserror += "No template.docx supplied\n"; } if (argserror != "") { Console.Error.WriteLine(argserror); Console.Error.WriteLine("mdspec2docx *.md grammar.g4 template.docx -o spec.docx"); Console.Error.WriteLine(); Console.Error.WriteLine("Turns the markdown files into a word document based on the template."); Console.Error.WriteLine("If readme.md and other files are given, then readme is used solely to"); Console.Error.WriteLine(" sort the docx based on its list of `* [Link](subfile.md)`."); Console.Error.WriteLine("If a .g4 is given, it verifies 1:1 correspondence with ```antlr blocks."); Console.Error.WriteLine("The -td temp directory will enable incremental (faster) runs in future."); return(1); } Console.WriteLine("mdspec2docx"); Console.WriteLine("Reading markdown files"); // Read input file. If it contains a load of linked filenames, then read them instead. List <string> ifiles_in_order = new List <string>(); List <Tuple <int, string, string, SourceLocation> > urls = null; if (ireadmefile == null) { ifiles_in_order.AddRange(ifiles); } else if (ireadmefile != null && ifiles.Count == 0) { ifiles_in_order.Add(ireadmefile); } else { var readme = FSharp.Markdown.Markdown.Parse(File.ReadAllText(ireadmefile)); urls = new List <Tuple <int, string, string, SourceLocation> >(); // is there a nicer way to get the URLs of all depth-1 and depth-2 URLs in this list? ... foreach (var list in readme.Paragraphs.OfType <FSharp.Markdown.MarkdownParagraph.ListBlock>()) { var pp = new List <Tuple <int, FSharp.Markdown.MarkdownParagraph> >(); foreach (var pars in list.items) { foreach (var par in pars) { pp.Add(Tuple.Create(1, par)); var sublist = par as FSharp.Markdown.MarkdownParagraph.ListBlock; if (sublist != null) { pp.AddRange(from subpars in sublist.items from subpar in subpars select Tuple.Create(2, subpar)); } } } foreach (var tpp in pp) { var level = tpp.Item1; var spanpar = tpp.Item2 as FSharp.Markdown.MarkdownParagraph.Span; if (spanpar == null) { continue; } var links = spanpar.body.OfType <FSharp.Markdown.MarkdownSpan.DirectLink>(); urls.AddRange(from link in links let title = string.Join("", link.body.OfType <FSharp.Markdown.MarkdownSpan.Literal>().Select(l => l.text)) let url = link.link where url.ToLower().EndsWith(".md") || url.ToLower().Contains(".md#") let loc = new SourceLocation(ireadmefile, null, list, link) select Tuple.Create(level, title, url, loc)); } } var filelinks = (from turl in urls let url = turl.Item3 let i = url.IndexOf('#') let url2 = (i == -1 ? url : url.Substring(0, i)) select url2).ToList().Distinct(); foreach (var link in filelinks) { var ifile = ifiles.FirstOrDefault(f => Path.GetFileName(f) == link); if (ifile == null) { Console.Error.WriteLine($"readme.md link '{link}' wasn't one of the files passed on the command line"); return(1); } ifiles_in_order.Add(ifile); } } var md = MarkdownSpec.ReadFiles(ifiles_in_order, urls); // Now md.Gramar contains the grammar as extracted out of the *.md files, and moreover has // correct references to within the spec. We'll check that it has the same productions as // in the corresponding ANTLR file if (iantlrfile != null) { Console.WriteLine($"Reading {Path.GetFileName(iantlrfile)}"); var grammar = Antlr.ReadFile(iantlrfile); foreach (var diff in CompareGrammars(grammar, md.Grammar)) { if (diff.authority == null) { Report("MD21", "error", $"markdown has superfluous production '{diff.productionName}'", "mdspec2docx"); } else if (diff.copy == null) { Report("MD22", "error", $"markdown lacks production '{diff.productionName}'", "mdspec2docx"); } else { Report("MD23", "error", $"production '{diff.productionName}' differs between markdown and antlr.g4", "mdspec2docx"); Report("MD23b", "error", "antlr.g4 says " + diff.authority.Replace("\r", "\\r").Replace("\n", "\\n"), "mdspec2docx"); Report("MD23c", "error", "markdown says " + diff.copy.Replace("\r", "\\r").Replace("\n", "\\n"), "mdspec2docx"); } } foreach (var p in grammar.Productions) { p.Link = md.Grammar.Productions.FirstOrDefault(mdp => mdp?.Name == p.Name)?.Link; p.LinkName = md.Grammar.Productions.FirstOrDefault(mdp => mdp?.Name == p.Name)?.LinkName; } } // Generate the Specification.docx file if (odocfile != null) { var odocfile2 = odocfile; if (odocfile2 != odocfile) { Report("MD26", "error", $"File '{odocfile}' was in use", "mdspec2docx"); } Console.WriteLine($"Writing '{Path.GetFileName(odocfile2)}'"); try { MarkdownSpecConverter.ConvertToWord(md, idocxfile, odocfile2); } catch (Exception ex) { Report("MD27", "error", ex.Message, "mdspec2docx"); return(1); } if (odocfile2 != odocfile) { return(1); } } return(0); }
private static IEnumerable <string> Span2Words(MarkdownSpan md) { if (md.IsLiteral) { var mdl = md as MarkdownSpan.Literal; foreach (var s in String2Words(MarkdownSpec.mdunescape(mdl))) { yield return(s); } } else if (md.IsStrong) { var mds = md as MarkdownSpan.Strong; foreach (var s in Spans2Words(mds.body)) { yield return(s); } } else if (md.IsEmphasis) { var mde = md as MarkdownSpan.Emphasis; foreach (var s in Spans2Words(mde.body)) { yield return(s); } } else if (md.IsInlineCode) { var mdi = md as MarkdownSpan.InlineCode; foreach (var s in String2Words(mdi.code)) { yield return(s); } } else if (md.IsDirectLink) { var mdl = md as MarkdownSpan.DirectLink; foreach (var s in Spans2Words(mdl.body)) { yield return(s); } foreach (var s in String2Words(mdl.title.Option())) { yield return(s); } } else if (md.IsIndirectLink) { var mdl = md as MarkdownSpan.DirectLink; foreach (var s in Spans2Words(mdl.body)) { yield return(s); } foreach (var s in String2Words(mdl.link)) { yield return(s); } foreach (var s in String2Words(mdl.title.Option())) { yield return(s); } } else if (md.IsAnchorLink) { var mdl = md as MarkdownSpan.AnchorLink; foreach (var s in String2Words(mdl.link)) { yield return(s); } } else if (md.IsDirectImage) { var mdi = md as MarkdownSpan.DirectImage; foreach (var s in String2Words(mdi.body)) { yield return(s); } foreach (var s in String2Words(mdi.link)) { yield return(s); } foreach (var s in String2Words(mdi.title.Option())) { yield return(s); } } else if (md.IsIndirectImage) { var mdi = md as MarkdownSpan.IndirectImage; foreach (var s in String2Words(mdi.body)) { yield return(s); } foreach (var s in String2Words(mdi.link)) { yield return(s); } foreach (var s in String2Words(mdi.key)) { yield return(s); } } else if (md.IsEmbedSpans) { } else if (md.IsHardLineBreak) { } else if (md.IsLatexDisplayMath) { var mdl = md as MarkdownSpan.LatexDisplayMath; foreach (var s in String2Words(mdl.code)) { yield return(s); } } else if (md.IsLatexInlineMath) { var mdl = md as MarkdownSpan.LatexInlineMath; foreach (var s in String2Words(mdl.code)) { yield return(s); } } }
static int Main(string[] args) { // mdspec2docx *.md csharp.g4 template.docx -o spec.docx var ifiles = new List <string>(); var ofiles = new List <string>(); string argserror = ""; for (int i = 0; i < args.Length; i++) { var arg = args[i]; if (arg.StartsWith("-")) { if (arg == "-o" && i < args.Length - 1) { i++; ofiles.Add(args[i]); } else { argserror += $"Unrecognized '{arg}'\n"; } } else if (!arg.Contains("*") && !arg.Contains("?")) { if (!File.Exists(arg)) { Console.Error.WriteLine($"Not found - {arg}"); return(1); } ifiles.Add(arg); } else { // Windows command-shell doesn't do globbing, so we have to do it ourselves string dir = Path.GetDirectoryName(arg), filename = Path.GetFileName(arg); if (dir.Contains("*") || dir.Contains("?")) { Console.Error.WriteLine("Can't match wildcard directory names"); return(1); } if (dir == "") { dir = Directory.GetCurrentDirectory(); } if (!Directory.Exists(dir)) { Console.Error.WriteLine($"Not found - \"{dir}\""); return(1); } var fns2 = Directory.GetFiles(dir, filename); if (fns2.Length == 0) { Console.Error.WriteLine($"Not found - \"{arg}\""); return(1); } ifiles.AddRange(fns2); } } var imdfiles = new List <string>(); string idocxfile = null, odocfile = null; foreach (var ifile in ifiles) { var name = Path.GetFileName(ifile); // Ignore the README, which is maintained separately and not part of the Standard. if (name.Equals("README.md", StringComparison.OrdinalIgnoreCase)) { continue; } var ext = Path.GetExtension(ifile).ToLower(); if (ext == ".docx") { if (idocxfile != null) { argserror += "Multiple input .docx files\n"; } idocxfile = ifile; } else if (ext != ".md") { argserror += $"Not .g4 or .docx or .md '{ifile}'\n"; continue; } else { imdfiles.Add(ifile); } } foreach (var ofile in ofiles) { var ext = Path.GetExtension(ofile).ToLower(); if (ext == ".docx") { if (odocfile != null) { argserror += "Multiple output docx files\n"; } odocfile = ofile; } else { argserror += $"Unknown output file extension: {ext}\n"; } } if (odocfile == null) { argserror += "No output .docx file specified\n"; } if (idocxfile == null) { argserror += "No template.docx supplied\n"; } if (argserror != "") { Console.Error.WriteLine(argserror); Console.Error.WriteLine("mdspec2docx *.md grammar.g4 template.docx -o spec.docx"); Console.Error.WriteLine(); Console.Error.WriteLine("Turns the markdown files into a word document based on the template."); Console.Error.WriteLine("If readme.md and other files are given, then readme is used solely to"); Console.Error.WriteLine(" sort the docx based on its list of `* [Link](subfile.md)`."); return(1); } Console.WriteLine("Reading markdown files"); // Read input file. If it contains a load of linked filenames, then read them instead. var md = MarkdownSpec.ReadFiles(imdfiles); // Generate the Specification.docx file if (odocfile != null) { var odocfile2 = odocfile; if (odocfile2 != odocfile) { Report("MD26", error: true, $"File '{odocfile}' was in use", "mdspec2docx"); } Console.WriteLine($"Writing '{Path.GetFileName(odocfile2)}'"); try { MarkdownSpecConverter.ConvertToWord(md, idocxfile, odocfile2); } catch (Exception ex) { Report("MD27", error: true, ex.Message, ex.StackTrace); return(1); } if (odocfile2 != odocfile) { return(1); } } Console.WriteLine($"Errors: {errors}"); Console.WriteLine($"Warnings: {warnings}"); return(errors == 0 ? 0 : 1); }
static int Main(string[] args) { var ifn = (args.Length >= 2 ? args[1] : "readme.md"); if (!File.Exists(ifn) || !File.Exists("template.docx") || Directory.GetFiles(".", "*.g4").Length > 1) { Console.Error.WriteLine("md2docx <filename>.md -- converts it to '<filename>.docx', based on 'template.docx'"); Console.Error.WriteLine(); Console.Error.WriteLine("If no file is specified:"); Console.Error.WriteLine(" it looks for readme.md instead"); Console.Error.WriteLine("If input file has a list with links of the form `* [Link](subfile.md)`:"); Console.Error.WriteLine(" it converts the listed subfiles instead of <filename>.md"); Console.Error.WriteLine("If the current directory contains one <grammar>.g4 file:"); Console.Error.WriteLine(" it verifies all ```antlr blocks correspond, and also generates <grammar>.html"); Console.Error.WriteLine("If 'template.docx' contains a Table of Contents:"); Console.Error.WriteLine(" it replaces it with one based on the markdown (but page numbers aren't supported)"); return(1); } // Read input file. If it contains a load of linked filenames, then read them instead. var readme = FSharp.Markdown.Markdown.Parse(File.ReadAllText(ifn)); var files = (from list in readme.Paragraphs.OfType <FSharp.Markdown.MarkdownParagraph.ListBlock>() let items = list.Item2 from par in items from spanpar in par.OfType <FSharp.Markdown.MarkdownParagraph.Span>() let spans = spanpar.Item from link in spans.OfType <FSharp.Markdown.MarkdownSpan.DirectLink>() let url = link.Item2.Item1 where url.EndsWith(".md", StringComparison.InvariantCultureIgnoreCase) select url).ToList().Distinct(); if (files.Count() == 0) { files = new[] { ifn } } ; var md = MarkdownSpec.ReadFiles(files); // Now md.Gramar contains the grammar as extracted out of the *.md files, and moreover has // correct references to within the spec. We'll check that it has the same productions as // in the corresponding ANTLR file var antlrfn = Directory.GetFiles(".", "*.g4").FirstOrDefault(); if (antlrfn != null) { var htmlfn = Path.ChangeExtension(antlrfn, ".html"); var grammar = Antlr.ReadFile(antlrfn); if (!AreProductionsSame(grammar, md.Grammar)) { throw new Exception("Grammar mismatch"); } foreach (var p in grammar.Productions) { p.Link = md.Grammar.Productions.FirstOrDefault(mdp => mdp?.ProductionName == p.ProductionName)?.Link; p.LinkName = md.Grammar.Productions.FirstOrDefault(mdp => mdp?.ProductionName == p.ProductionName)?.LinkName; } File.WriteAllText(htmlfn, grammar.ToHtml(), Encoding.UTF8); Process.Start(htmlfn); } // Generate the Specification.docx file var fn = PickUniqueFilename(Path.ChangeExtension(ifn, ".docx")); md.WriteFile("template.docx", fn); Process.Start(fn); return(0); }
static int Main(string[] args) { // mdspec2docx *.md csharp.g4 template.docx -o spec.docx var ifiles = new List <string>(); var ofiles = new List <string>(); string argserror = ""; for (int i = 0; i < args.Length; i++) { var arg = args[i]; if (arg.StartsWith("-")) { if (arg == "-o" && i < args.Length - 1) { i++; ofiles.Add(args[i]); } else { argserror += $"Unrecognized '{arg}'\n"; } } else if (!arg.Contains("*") && !arg.Contains("?")) { if (!File.Exists(arg)) { Console.Error.WriteLine($"Not found - {arg}"); return(1); } ifiles.Add(arg); } else { // Windows command-shell doesn't do globbing, so we have to do it ourselves string dir = Path.GetDirectoryName(arg), filename = Path.GetFileName(arg); if (dir.Contains("*") || dir.Contains("?")) { Console.Error.WriteLine("Can't match wildcard directory names"); return(1); } if (dir == "") { dir = Directory.GetCurrentDirectory(); } if (!Directory.Exists(dir)) { Console.Error.WriteLine($"Not found - \"{dir}\""); return(1); } var fns2 = Directory.GetFiles(dir, filename); if (fns2.Length == 0) { Console.Error.WriteLine($"Not found - \"{arg}\""); return(1); } ifiles.AddRange(fns2); } } var imdfiles = new List <string>(); string ireadmefile = null, idocxfile = null, odocfile = null; foreach (var ifile in ifiles) { var name = Path.GetFileName(ifile); var ext = Path.GetExtension(ifile).ToLower(); if (ext == ".docx") { if (idocxfile != null) { argserror += "Multiple input .docx files\n"; } idocxfile = ifile; } else if (ext != ".md") { argserror += $"Not .g4 or .docx or .md '{ifile}'\n"; continue; } else if (String.Equals(name, "readme.md", StringComparison.InvariantCultureIgnoreCase)) { if (ireadmefile != null) { argserror += "Multiple readme.md files\n"; } ireadmefile = ifile; } else { imdfiles.Add(ifile); } } foreach (var ofile in ofiles) { var ext = Path.GetExtension(ofile).ToLower(); if (ext == ".docx") { if (odocfile != null) { argserror += "Multiple output docx files\n"; } odocfile = ofile; } else { argserror += $"Unknown output file extension: {ext}\n"; } } if (odocfile == null) { argserror += "No output .docx file specified\n"; } if (ireadmefile == null && ifiles.Count == 0) { argserror += "No .md files supplied\n"; } if (idocxfile == null) { argserror += "No template.docx supplied\n"; } if (argserror != "") { Console.Error.WriteLine(argserror); Console.Error.WriteLine("mdspec2docx *.md grammar.g4 template.docx -o spec.docx"); Console.Error.WriteLine(); Console.Error.WriteLine("Turns the markdown files into a word document based on the template."); Console.Error.WriteLine("If readme.md and other files are given, then readme is used solely to"); Console.Error.WriteLine(" sort the docx based on its list of `* [Link](subfile.md)`."); return(1); } Console.WriteLine("Reading markdown files"); // Read input file. If it contains a load of linked filenames, then read them instead. List <string> ifiles_in_order = new List <string>(); List <Tuple <int, string, string, SourceLocation> > urls = null; if (ireadmefile == null) { ifiles_in_order.AddRange(imdfiles); } else if (ireadmefile != null && ifiles.Count == 0) { ifiles_in_order.Add(ireadmefile); } else { var readme = FSharp.Markdown.Markdown.Parse(File.ReadAllText(ireadmefile)); urls = new List <Tuple <int, string, string, SourceLocation> >(); // is there a nicer way to get the URLs of all depth-1 and depth-2 URLs in this list? ... foreach (var list in readme.Paragraphs.OfType <FSharp.Markdown.MarkdownParagraph.ListBlock>()) { var pp = new List <Tuple <int, FSharp.Markdown.MarkdownParagraph> >(); foreach (var pars in list.items) { foreach (var par in pars) { pp.Add(Tuple.Create(1, par)); var sublist = par as FSharp.Markdown.MarkdownParagraph.ListBlock; if (sublist != null) { pp.AddRange(from subpars in sublist.items from subpar in subpars select Tuple.Create(2, subpar)); } } } foreach (var tpp in pp) { var level = tpp.Item1; var spanpar = tpp.Item2 as FSharp.Markdown.MarkdownParagraph.Span; if (spanpar == null) { continue; } var links = spanpar.body.OfType <FSharp.Markdown.MarkdownSpan.DirectLink>(); urls.AddRange(from link in links let title = string.Join("", link.body.OfType <FSharp.Markdown.MarkdownSpan.Literal>().Select(l => l.text)) let url = link.link where url.ToLower().EndsWith(".md") || url.ToLower().Contains(".md#") let loc = new SourceLocation(ireadmefile, null, list, link) select Tuple.Create(level, title, url, loc)); } } var filelinks = (from turl in urls let url = turl.Item3 let i = url.IndexOf('#') let url2 = (i == -1 ? url : url.Substring(0, i)) select url2).ToList().Distinct(); foreach (var link in filelinks) { var ifile = ifiles.FirstOrDefault(f => Path.GetFileName(f) == link); if (ifile == null) { Console.Error.WriteLine($"readme.md link '{link}' wasn't one of the files passed on the command line"); return(1); } ifiles_in_order.Add(ifile); } } var md = MarkdownSpec.ReadFiles(ifiles_in_order, urls); // Generate the Specification.docx file if (odocfile != null) { var odocfile2 = odocfile; if (odocfile2 != odocfile) { Report("MD26", error: true, $"File '{odocfile}' was in use", "mdspec2docx"); } Console.WriteLine($"Writing '{Path.GetFileName(odocfile2)}'"); try { MarkdownSpecConverter.ConvertToWord(md, idocxfile, odocfile2); } catch (Exception ex) { Report("MD27", error: true, ex.Message, ex.StackTrace); return(1); } if (odocfile2 != odocfile) { return(1); } } Console.WriteLine($"Errors: {errors}"); Console.WriteLine($"Warnings: {warnings}"); return(errors == 0 ? 0 : 1); }