IEnumerable <OpenXmlCompositeElement> Paragraph2Paragraphs(MarkdownParagraph md) { if (md.IsHeading) { var mdh = md as MarkdownParagraph.Heading; var level = mdh.Item1; var spans = mdh.Item2; var sr = sections[new SectionRef(mdh, filename).Url]; var props = new ParagraphProperties(new ParagraphStyleId() { Val = $"Heading{level}" }); var p = new Paragraph { ParagraphProperties = props }; maxBookmarkId.Value += 1; p.AppendChild(new BookmarkStart { Name = sr.BookmarkName, Id = maxBookmarkId.Value.ToString() }); p.Append(Spans2Elements(spans)); p.AppendChild(new BookmarkEnd { Id = maxBookmarkId.Value.ToString() }); yield return(p); Console.WriteLine(new string(' ', level * 4 - 4) + sr.Number + " " + sr.Title); yield break; } else if (md.IsParagraph) { var mdp = md as MarkdownParagraph.Paragraph; var spans = mdp.Item; yield return(new Paragraph(Spans2Elements(spans))); yield break; } else if (md.IsListBlock) { var mdl = md as MarkdownParagraph.ListBlock; var flat = FlattenList(mdl); // Let's figure out what kind of list it is - ordered or unordered? nested? var format0 = new[] { "1", "1", "1", "1" }; foreach (var item in flat) { format0[item.Level] = (item.IsBulletOrdered ? "1" : "o"); } var format = string.Join("", format0); var numberingPart = wdoc.MainDocumentPart.NumberingDefinitionsPart ?? wdoc.MainDocumentPart.AddNewPart <NumberingDefinitionsPart>("NumberingDefinitionsPart001"); if (numberingPart.Numbering == null) { numberingPart.Numbering = new Numbering(); } Func <int, bool, Level> createLevel; createLevel = (level, isOrdered) => { var numformat = NumberFormatValues.Bullet; var levelText = new[] { "·", "o", "·", "o" }[level]; if (isOrdered && level == 0) { numformat = NumberFormatValues.Decimal; levelText = "%1."; } if (isOrdered && level == 1) { numformat = NumberFormatValues.LowerLetter; levelText = "%2."; } if (isOrdered && level == 2) { numformat = NumberFormatValues.LowerRoman; levelText = "%3."; } if (isOrdered && level == 3) { numformat = NumberFormatValues.LowerRoman; levelText = "%4."; } var r = new Level { LevelIndex = level }; r.Append(new StartNumberingValue { Val = 1 }); r.Append(new NumberingFormat { Val = numformat }); r.Append(new LevelText { Val = levelText }); r.Append(new ParagraphProperties(new Indentation { Left = (540 + 360 * level).ToString(), Hanging = "360" })); if (levelText == "·") { r.Append(new NumberingSymbolRunProperties(new RunFonts { Hint = FontTypeHintValues.Default, Ascii = "Symbol", HighAnsi = "Symbol", EastAsia = "Times new Roman", ComplexScript = "Times new Roman" })); } if (levelText == "o") { r.Append(new NumberingSymbolRunProperties(new RunFonts { Hint = FontTypeHintValues.Default, Ascii = "Courier New", HighAnsi = "Courier New", ComplexScript = "Courier New" })); } return(r); }; var level0 = createLevel(0, format[0] == '1'); var level1 = createLevel(1, format[1] == '1'); var level2 = createLevel(2, format[2] == '1'); var level3 = createLevel(3, format[3] == '1'); var abstracts = numberingPart.Numbering.OfType <AbstractNum>().Select(an => an.AbstractNumberId.Value).ToList(); var aid = (abstracts.Count == 0 ? 1 : abstracts.Max() + 1); var aabstract = new AbstractNum(new MultiLevelType() { Val = MultiLevelValues.Multilevel }, level0, level1, level2, level3) { AbstractNumberId = aid }; numberingPart.Numbering.InsertAt(aabstract, 0); var instances = numberingPart.Numbering.OfType <NumberingInstance>().Select(ni => ni.NumberID.Value); var nid = (instances.Count() == 0 ? 1 : instances.Max() + 1); var numInstance = new NumberingInstance(new AbstractNumId { Val = aid }) { NumberID = nid }; numberingPart.Numbering.AppendChild(numInstance); // We'll also figure out the indentation(for the benefit of those paragraphs that should be // indendent with the list but aren't numbered). I'm not sure what the indent comes from. // in the docx, each AbstractNum that I created has an indent for each of its levels, // defaulted at 900, 1260, 1620, ... but I can't see where in the above code that's created? Func <int, string> calcIndent = level => (540 + level * 360).ToString(); foreach (var item in flat) { var content = item.Paragraph; if (content.IsParagraph || content.IsSpan) { var spans = (content.IsParagraph ? (content as MarkdownParagraph.Paragraph).Item : (content as MarkdownParagraph.Span).Item); if (item.HasBullet) { yield return new Paragraph(Spans2Elements(spans)) { ParagraphProperties = new ParagraphProperties(new NumberingProperties(new ParagraphStyleId { Val = "ListParagraph" }, new NumberingLevelReference { Val = item.Level }, new NumberingId { Val = nid })) } } ; else { yield return new Paragraph(Spans2Elements(spans)) { ParagraphProperties = new ParagraphProperties(new Indentation { Left = calcIndent(item.Level) }) } }; } else if (content.IsQuotedBlock || content.IsCodeBlock) { foreach (var p in Paragraph2Paragraphs(content)) { var props = p.GetFirstChild <ParagraphProperties>(); if (props == null) { props = new ParagraphProperties(); p.InsertAt(props, 0); } var indent = props?.GetFirstChild <Indentation>(); if (indent == null) { indent = new Indentation(); props.Append(indent); } indent.Left = calcIndent(item.Level); yield return(p); } } else if (content.IsTableBlock) { foreach (var p in Paragraph2Paragraphs(content)) { var table = p as Table; if (table == null) { yield return(p); continue; } var tprops = table.GetFirstChild <TableProperties>(); var tindent = tprops?.GetFirstChild <TableIndentation>(); if (tindent == null) { throw new Exception("Ooops! Table is missing indentation"); } tindent.Width = int.Parse(calcIndent(item.Level)); yield return(table); } } else { throw new Exception("Unexpected item in list"); } } } else if (md.IsCodeBlock) { var mdc = md as MarkdownParagraph.CodeBlock; var code = mdc.Item1; var lang = mdc.Item2; code = BugWorkaroundDecode(code); var runs = new List <Run>(); var onFirstLine = true; IEnumerable <ColorizedLine> lines; if (lang == "csharp" || lang == "c#" || lang == "cs") { lines = Colorize.CSharp(code); } else if (lang == "vb" || lang == "vbnet" || lang == "vb.net") { lines = Colorize.VB(code); } else if (lang == "" || lang == "xml") { lines = Colorize.PlainText(code); } else if (lang == "antlr") { lines = Antlr.ColorizeAntlr(code); } else { throw new NotSupportedException($"unrecognized language {lang}"); } foreach (var line in lines) { if (onFirstLine) { onFirstLine = false; } else { runs.Add(new Run(new Break())); } foreach (var word in line.Words) { var run = new Run(); var props = new RunProperties(); if (word.Red != 0 || word.Green != 0 || word.Blue != 0) { props.Append(new Color { Val = $"{word.Red:X2}{word.Green:X2}{word.Blue:X2}" }); } if (word.IsItalic) { props.Append(new Italic()); } if (props.HasChildren) { run.Append(props); } run.Append(new Text(word.Text) { Space = SpaceProcessingModeValues.Preserve }); runs.Add(run); } } var style = new ParagraphStyleId { Val = (lang == "antlr" ? "Grammar" : "Code") }; yield return(new Paragraph(runs) { ParagraphProperties = new ParagraphProperties(style) }); } else if (md.IsQuotedBlock) { var mdq = md as MarkdownParagraph.QuotedBlock; var quoteds = mdq.Item; var kind = ""; foreach (var quoted0 in quoteds) { var quoted = quoted0; if (quoted.IsParagraph) { var p = quoted as MarkdownParagraph.Paragraph; var spans = p.Item; if (spans.Any() && spans.First().IsStrong) { var strong = (spans.First() as MarkdownSpan.Strong).Item; if (strong.Any() && strong.First().IsLiteral) { var literal = mdunescape(strong.First() as MarkdownSpan.Literal); if (literal == "Note") { kind = "AlertText"; } else if (literal == "Annotation") { kind = "Annotation"; yield return(new Paragraph(Span2Elements(spans.Head)) { ParagraphProperties = new ParagraphProperties(new ParagraphStyleId { Val = kind }) }); if (spans.Tail.Any() && spans.Tail.First().IsLiteral) { quoted = MarkdownParagraph.NewParagraph(new Microsoft.FSharp.Collections.FSharpList <MarkdownSpan>(MarkdownSpan.NewLiteral(mdunescape(spans.Tail.First() as MarkdownSpan.Literal).TrimStart()), spans.Tail.Tail)); } else { quoted = MarkdownParagraph.NewParagraph(spans.Tail); } } } } // foreach (var qp in Paragraph2Paragraphs(quoted)) { var qpp = qp as Paragraph; if (qpp != null) { var props = new ParagraphProperties(new ParagraphStyleId() { Val = kind }); qpp.ParagraphProperties = props; } yield return(qp); } } else if (quoted.IsCodeBlock) { var mdc = quoted as MarkdownParagraph.CodeBlock; var code = mdc.Item1; var lang = mdc.Item2; var ignoredAfterLang = mdc.Item3; var lines = code.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None).ToList(); if (string.IsNullOrWhiteSpace(lines.Last())) { lines.RemoveAt(lines.Count - 1); } var run = new Run() { RunProperties = new RunProperties(new RunStyle { Val = "CodeEmbedded" }) }; foreach (var line in lines) { if (run.ChildElements.Count() > 1) { run.Append(new Break()); } run.Append(new Text(" " + line) { Space = SpaceProcessingModeValues.Preserve }); } yield return(new Paragraph(run) { ParagraphProperties = new ParagraphProperties(new ParagraphStyleId { Val = kind }) }); } else if (quoted.IsListBlock) { if (!(quoted as MarkdownParagraph.ListBlock).Item1.IsOrdered) { throw new NotImplementedException("unordered list inside annotation"); } var count = 1; foreach (var qp in Paragraph2Paragraphs(quoted)) { var qpp = qp as Paragraph; if (qpp == null) { yield return(qp); continue; } qp.InsertAt(new Run(new Text($"{count}. ") { Space = SpaceProcessingModeValues.Preserve }), 0); count += 1; var props = new ParagraphProperties(new ParagraphStyleId() { Val = kind }); qpp.ParagraphProperties = props; yield return(qp); } } } } else if (md.IsTableBlock) { var mdt = md as MarkdownParagraph.TableBlock; var header = mdt.Item1.Option(); var align = mdt.Item2; var rows = mdt.Item3; var table = new Table(); var tstyle = new TableStyle { Val = "TableGrid" }; var tindent = new TableIndentation { Width = 360, Type = TableWidthUnitValues.Dxa }; var tborders = new TableBorders(); tborders.TopBorder = new TopBorder { Val = BorderValues.Single }; tborders.BottomBorder = new BottomBorder { Val = BorderValues.Single }; tborders.LeftBorder = new LeftBorder { Val = BorderValues.Single }; tborders.RightBorder = new RightBorder { Val = BorderValues.Single }; tborders.InsideHorizontalBorder = new InsideHorizontalBorder { Val = BorderValues.Single }; tborders.InsideVerticalBorder = new InsideVerticalBorder { Val = BorderValues.Single }; var tcellmar = new TableCellMarginDefault(); tcellmar.Append(); table.Append(new TableProperties(tstyle, tindent, tborders)); var ncols = align.Length; for (int irow = -1; irow < rows.Length; irow++) { if (irow == -1 && header == null) { continue; } var mdrow = (irow == -1 ? header : rows[irow]); var row = new TableRow(); for (int icol = 0; icol < Math.Min(ncols, mdrow.Length); icol++) { var mdcell = mdrow[icol]; var cell = new TableCell(); var pars = Paragraphs2Paragraphs(mdcell).ToList(); for (int ip = 0; ip < pars.Count; ip++) { var p = pars[ip] as Paragraph; if (p == null) { cell.Append(pars[ip]); continue; } var props = new ParagraphProperties(new ParagraphStyleId { Val = "TableCellNormal" }); if (align[icol].IsAlignCenter) { props.Append(new Justification { Val = JustificationValues.Center }); } if (align[icol].IsAlignRight) { props.Append(new Justification { Val = JustificationValues.Right }); } p.InsertAt(props, 0); cell.Append(pars[ip]); } if (pars.Count == 0) { cell.Append(new Paragraph(new ParagraphProperties(new SpacingBetweenLines { After = "0" }), new Run(new Text("")))); } row.Append(cell); } table.Append(row); } yield return(new Paragraph(new Run(new Text(""))) { ParagraphProperties = new ParagraphProperties(new ParagraphStyleId { Val = "TableLineBefore" }) }); yield return(table); yield return(new Paragraph(new Run(new Text(""))) { ParagraphProperties = new ParagraphProperties(new ParagraphStyleId { Val = "TableLineAfter" }) }); } else { yield return(new Paragraph(new Run(new Text($"[{md.GetType().Name}]")))); } }
IEnumerable <OpenXmlCompositeElement> Paragraph2Paragraphs(MarkdownParagraph md) { reporter.CurrentParagraph = md; if (md.IsHeading) { var mdh = md as MarkdownParagraph.Heading; var level = mdh.size; var spans = mdh.body; var sr = sections[new SectionRef(mdh, filename).Url]; reporter.CurrentSection = sr; var properties = new List <OpenXmlElement> { new ParagraphStyleId { Val = $"Heading{level}" } }; if (sr.Number is null) { properties.Add(new NumberingProperties(new NumberingLevelReference { Val = 0 }, new NumberingId { Val = 0 })); } var props = new ParagraphProperties(properties); var p = new Paragraph { ParagraphProperties = props }; context.MaxBookmarkId.Value += 1; p.AppendChild(new BookmarkStart { Name = sr.BookmarkName, Id = context.MaxBookmarkId.Value.ToString() }); p.Append(Span2Elements(MarkdownSpan.NewLiteral(sr.TitleWithoutNumber, FSharpOption <MarkdownRange> .None))); p.AppendChild(new BookmarkEnd { Id = context.MaxBookmarkId.Value.ToString() }); yield return(p); var i = sr.Url.IndexOf("#"); string currentSection = $"{sr.Url.Substring(0, i)} {new string('#', level)} {sr.Title} [{sr.Number}]"; reporter.Log(currentSection); yield break; } else if (md.IsParagraph) { var mdp = md as MarkdownParagraph.Paragraph; var spans = mdp.body; yield return(new Paragraph(Spans2Elements(spans))); yield break; } else if (md.IsQuotedBlock) { var mdq = md as MarkdownParagraph.QuotedBlock; // TODO: Actually make this a block quote. // See https://github.com/ECMA-TC49-TG2/conversion-to-markdown/issues/123 foreach (var paragraph in mdq.paragraphs.SelectMany(Paragraph2Paragraphs)) { yield return(paragraph); } yield break; } else if (md.IsListBlock) { var mdl = md as MarkdownParagraph.ListBlock; var flat = FlattenList(mdl); // Let's figure out what kind of list it is - ordered or unordered? nested? var format0 = new[] { "1", "1", "1", "1" }; foreach (var item in flat) { format0[item.Level] = (item.IsBulletOrdered ? "1" : "o"); } var format = string.Join("", format0); var numberingPart = wordDocument.MainDocumentPart.NumberingDefinitionsPart ?? wordDocument.MainDocumentPart.AddNewPart <NumberingDefinitionsPart>("NumberingDefinitionsPart001"); if (numberingPart.Numbering == null) { numberingPart.Numbering = new Numbering(); } Func <int, bool, Level> createLevel; createLevel = (level, isOrdered) => { var numformat = NumberFormatValues.Bullet; var levelText = new[] { "·", "o", "·", "o" }[level]; if (isOrdered && level == 0) { numformat = NumberFormatValues.Decimal; levelText = "%1."; } if (isOrdered && level == 1) { numformat = NumberFormatValues.LowerLetter; levelText = "%2."; } if (isOrdered && level == 2) { numformat = NumberFormatValues.LowerRoman; levelText = "%3."; } if (isOrdered && level == 3) { numformat = NumberFormatValues.LowerRoman; levelText = "%4."; } var r = new Level { LevelIndex = level }; r.Append(new StartNumberingValue { Val = 1 }); r.Append(new NumberingFormat { Val = numformat }); r.Append(new LevelText { Val = levelText }); r.Append(new ParagraphProperties(new Indentation { Left = (540 + 360 * level).ToString(), Hanging = "360" })); if (levelText == "·") { r.Append(new NumberingSymbolRunProperties(new RunFonts { Hint = FontTypeHintValues.Default, Ascii = "Symbol", HighAnsi = "Symbol", EastAsia = "Times new Roman", ComplexScript = "Times new Roman" })); } if (levelText == "o") { r.Append(new NumberingSymbolRunProperties(new RunFonts { Hint = FontTypeHintValues.Default, Ascii = "Courier New", HighAnsi = "Courier New", ComplexScript = "Courier New" })); } return(r); }; var level0 = createLevel(0, format[0] == '1'); var level1 = createLevel(1, format[1] == '1'); var level2 = createLevel(2, format[2] == '1'); var level3 = createLevel(3, format[3] == '1'); var abstracts = numberingPart.Numbering.OfType <AbstractNum>().Select(an => an.AbstractNumberId.Value).ToList(); var aid = (abstracts.Count == 0 ? 1 : abstracts.Max() + 1); var aabstract = new AbstractNum(new MultiLevelType() { Val = MultiLevelValues.Multilevel }, level0, level1, level2, level3) { AbstractNumberId = aid }; numberingPart.Numbering.InsertAt(aabstract, 0); var instances = numberingPart.Numbering.OfType <NumberingInstance>().Select(ni => ni.NumberID.Value); var nid = (instances.Count() == 0 ? 1 : instances.Max() + 1); var numInstance = new NumberingInstance(new AbstractNumId { Val = aid }) { NumberID = nid }; numberingPart.Numbering.AppendChild(numInstance); // We'll also figure out the indentation(for the benefit of those paragraphs that should be // indendent with the list but aren't numbered). I'm not sure what the indent comes from. // in the docx, each AbstractNum that I created has an indent for each of its levels, // defaulted at 900, 1260, 1620, ... but I can't see where in the above code that's created? Func <int, string> calcIndent = level => (540 + level * 360).ToString(); foreach (var item in flat) { var content = item.Paragraph; if (content.IsParagraph || content.IsSpan) { var spans = (content.IsParagraph ? (content as MarkdownParagraph.Paragraph).body : (content as MarkdownParagraph.Span).body); if (item.HasBullet) { yield return(new Paragraph(Spans2Elements(spans)) { ParagraphProperties = new ParagraphProperties(new NumberingProperties(new ParagraphStyleId { Val = "ListParagraph" }, new NumberingLevelReference { Val = item.Level }, new NumberingId { Val = nid })) }); } else { yield return(new Paragraph(Spans2Elements(spans)) { ParagraphProperties = new ParagraphProperties(new Indentation { Left = calcIndent(item.Level) }) }); } } else if (content.IsQuotedBlock || content.IsCodeBlock) { foreach (var p in Paragraph2Paragraphs(content)) { var props = p.GetFirstChild <ParagraphProperties>(); if (props == null) { props = new ParagraphProperties(); p.InsertAt(props, 0); } var indent = props?.GetFirstChild <Indentation>(); if (indent == null) { indent = new Indentation(); props.Append(indent); } indent.Left = calcIndent(item.Level); yield return(p); } } else if (content.IsTableBlock) { foreach (var p in Paragraph2Paragraphs(content)) { var table = p as Table; if (table == null) { yield return(p); continue; } var tprops = table.GetFirstChild <TableProperties>(); var tindent = tprops?.GetFirstChild <TableIndentation>(); if (tindent == null) { throw new Exception("Ooops! Table is missing indentation"); } tindent.Width = int.Parse(calcIndent(item.Level)); yield return(table); } } else { reporter.Error("MD08", $"Unexpected item in list '{content.GetType().Name}'"); } } } else if (md.IsCodeBlock) { var mdc = md as MarkdownParagraph.CodeBlock; var code = mdc.code; var lang = mdc.language; code = BugWorkaroundDecode(code); var runs = new List <Run>(); var onFirstLine = true; IEnumerable <ColorizedLine> lines; switch (lang) { case "csharp": case "c#": case "cs": lines = Colorize.CSharp(code); break; case "vb": case "vbnet": case "vb.net": lines = Colorize.VB(code); break; case "": case "console": case "xml": lines = Colorize.PlainText(code); break; case "ANTLR": case "antlr": lines = Antlr.ColorizeAntlr(code); break; default: reporter.Error("MD09", $"unrecognized language {lang}"); lines = Colorize.PlainText(code); break; } foreach (var line in lines) { if (onFirstLine) { onFirstLine = false; } else { runs.Add(new Run(new Break())); } foreach (var word in line.Words) { var run = new Run(); var props = new RunProperties(); if (word.Red != 0 || word.Green != 0 || word.Blue != 0) { props.Append(new Color { Val = $"{word.Red:X2}{word.Green:X2}{word.Blue:X2}" }); } if (word.IsItalic) { props.Append(new Italic()); } if (props.HasChildren) { run.Append(props); } run.Append(new Text(word.Text) { Space = SpaceProcessingModeValues.Preserve }); runs.Add(run); } } if (lang == "antlr") { var p = new Paragraph() { ParagraphProperties = new ParagraphProperties(new ParagraphStyleId { Val = "Grammar" }) }; var prodref = productions.Single(prod => prod.Code == code); context.MaxBookmarkId.Value += 1; p.AppendChild(new BookmarkStart { Name = prodref.BookmarkName, Id = context.MaxBookmarkId.Value.ToString() }); p.Append(runs); p.AppendChild(new BookmarkEnd { Id = context.MaxBookmarkId.Value.ToString() }); yield return(p); } else { var p = new Paragraph() { ParagraphProperties = new ParagraphProperties(new ParagraphStyleId { Val = "Code" }) }; p.Append(runs); yield return(p); } } else if (md.IsTableBlock) { var mdt = md as MarkdownParagraph.TableBlock; var header = mdt.headers.Option(); var align = mdt.alignments; var rows = mdt.rows; var table = new Table(); if (header == null) { reporter.Error("MD10", "Github requires all tables to have header rows"); } if (!header.Any(cell => cell.Length > 0)) { header = null; // even if Github requires an empty header, we can at least cull it from Docx } var tstyle = new TableStyle { Val = "TableGrid" }; var tindent = new TableIndentation { Width = 360, Type = TableWidthUnitValues.Dxa }; var tborders = new TableBorders(); tborders.TopBorder = new TopBorder { Val = BorderValues.Single }; tborders.BottomBorder = new BottomBorder { Val = BorderValues.Single }; tborders.LeftBorder = new LeftBorder { Val = BorderValues.Single }; tborders.RightBorder = new RightBorder { Val = BorderValues.Single }; tborders.InsideHorizontalBorder = new InsideHorizontalBorder { Val = BorderValues.Single }; tborders.InsideVerticalBorder = new InsideVerticalBorder { Val = BorderValues.Single }; var tcellmar = new TableCellMarginDefault(); tcellmar.Append(); table.Append(new TableProperties(tstyle, tindent, tborders)); var ncols = align.Length; for (int irow = -1; irow < rows.Length; irow++) { if (irow == -1 && header == null) { continue; } var mdrow = (irow == -1 ? header : rows[irow]); var row = new TableRow(); for (int icol = 0; icol < Math.Min(ncols, mdrow.Length); icol++) { var mdcell = mdrow[icol]; var cell = new TableCell(); var pars = Paragraphs2Paragraphs(mdcell).ToList(); for (int ip = 0; ip < pars.Count; ip++) { var p = pars[ip] as Paragraph; if (p == null) { cell.Append(pars[ip]); continue; } var props = new ParagraphProperties(new ParagraphStyleId { Val = "TableCellNormal" }); if (align[icol].IsAlignCenter) { props.Append(new Justification { Val = JustificationValues.Center }); } if (align[icol].IsAlignRight) { props.Append(new Justification { Val = JustificationValues.Right }); } p.InsertAt(props, 0); cell.Append(pars[ip]); } if (pars.Count == 0) { cell.Append(new Paragraph(new ParagraphProperties(new SpacingBetweenLines { After = "0" }), new Run(new Text("")))); } row.Append(cell); } table.Append(row); } yield return(new Paragraph(new Run(new Text(""))) { ParagraphProperties = new ParagraphProperties(new ParagraphStyleId { Val = "TableLineBefore" }) }); yield return(table); yield return(new Paragraph(new Run(new Text(""))) { ParagraphProperties = new ParagraphProperties(new ParagraphStyleId { Val = "TableLineAfter" }) }); } else { reporter.Error("MD11", $"Unrecognized markdown element {md.GetType().Name}"); yield return(new Paragraph(new Run(new Text($"[{md.GetType().Name}]")))); } }