void WriteSingleBlock(Block block) { switch (block.Tag) { case BlockTag.Document: WriteBlock(block.FirstChild); break; case BlockTag.ReferenceDefinition: // FIXME: these are completely broken in CommonMark.NET as it // only persists reference definitions on the document (root) // node, which means all context is lost regarding _where_ in // the document a reference definition exists. The block here // is completely empty. Additionally, it upper-cases the keys // the reference dictionary, which means they also cannot be // round-tripped correctly :( // // However - we can still write out _mostly_ semantically // identical (e.g. functional) markdown since CommonMark.NET's // link inlines will have the resolved URLs. break; case BlockTag.BlockQuote: writer.PushLinePrefix("> "); WriteBlock(block.FirstChild); writer.PopLinePrefix(); break; case BlockTag.Paragraph: WriteInline(block.InlineContent); break; case BlockTag.AtxHeading: writer.WriteLiteral('#', block.Heading.Level); writer.WriteLiteral(' '); WriteInline(block.InlineContent); break; case BlockTag.SetextHeading: var startPosition = writer.Position; WriteInline(block.InlineContent); var inlineLength = writer.Position - startPosition; var underlineLength = (int)block.Heading.SetextUnderlineLength; if (underlineLength < 1) { underlineLength = inlineLength; } writer.WriteLineLiteral(); writer.WriteLiteral(block.Heading.Level == 1 ? '=' : '-', underlineLength); break; case BlockTag.ThematicBreak: var breakChar = settings.ThematicBreakChar; var listData = block.Parent?.ListData; if (listData != null && listData.BulletChar == breakChar) { if (breakChar == '*') { breakChar = '-'; } else if (breakChar == '-') { breakChar = '*'; } } writer.WriteLiteral(breakChar, settings.ActualThematicBreakWidth); break; case BlockTag.FencedCode: case BlockTag.YamlBlock: var content = block.StringContent.ToString(); var fenceOffset = block.FencedCodeData.FenceOffset; var fenceChar = block.FencedCodeData.FenceChar; var fenceSize = Math.Max(3, MaxConsecutiveCharCount(content, fenceChar) + 1); writer.WriteLiteral(' ', fenceOffset); writer.WriteLiteral(fenceChar, fenceSize); if (!string.IsNullOrEmpty(block.FencedCodeData.Info)) { writer.WriteLiteral(block.FencedCodeData.Info); } writer.WriteLineLiteral(); if (fenceOffset > 0) { writer.PushLinePrefix(new string (' ', block.FencedCodeData.FenceOffset)); } writer.WriteLiteral(content); if (fenceOffset > 0) { writer.PopLinePrefix(); } writer.WriteLiteral(' ', fenceOffset); writer.WriteLiteral(fenceChar, fenceSize); break; case BlockTag.IndentedCode: writer.PushLinePrefix(" "); // FIXME: RemoveTrailingBlankLines appears to be broken. // TrimEnd works, but that's not exactly what we want. // Workaround is to convert to a string first, which is // is a bit of a perf hit. // // block.StringContent.RemoveTrailingBlankLines (); // block.StringContent.WriteTo (writer); writer.WriteLiteral(block.StringContent.ToString().TrimEnd('\n', '\r')); writer.PopLinePrefix(); break; case BlockTag.HtmlBlock: // FIXME: see comment for IndentedCode case writer.WriteLiteral(block.StringContent.ToString().TrimEnd('\n', '\r')); break; case BlockTag.List: WriteBlock(block.FirstChild); break; case BlockTag.ListItem: // FIXME: we could compute the ordered list information in the // BlockTag.List case above and pass via WriteBlock, but I wanted // to keep it simple. Could be an ever-so-slight perf improvement. var orderedIndex = block.ListData.Start; char orderedDelimiter; switch (block.ListData.Delimiter) { case ListDelimiter.Period: orderedDelimiter = '.'; break; case ListDelimiter.Parenthesis: orderedDelimiter = ')'; break; default: throw new NotImplementedException( $"{nameof (ListDelimiter)}.{block.ListData.Delimiter}"); } string marker; switch (block.ListData.ListType) { case ListType.Bullet: marker = block.ListData.BulletChar.ToString(); break; case ListType.Ordered: marker = (orderedIndex++).ToString(CultureInfo.InvariantCulture) + orderedDelimiter; break; default: throw new NotImplementedException( $"{nameof (ListType)}.{block.ListData.ListType}"); } writer.WriteLiteral(' ', block.ListData.MarkerOffset); writer.WriteLiteral(marker); writer.WriteLiteral(' ', block.ListData.Padding - marker.Length); var padding = block.ListData.Padding + block.ListData.MarkerOffset; if (padding > 0) { writer.PushLinePrefix(new string (' ', padding), false); } WriteBlock(block.FirstChild); if (padding > 0) { writer.PopLinePrefix(); } break; default: throw new NotImplementedException($"{block.Tag}"); } }