void WriteSingleInline(ref Inline inline, char [] escapeChars) { switch (inline.Tag) { case InlineTag.Emphasis: case InlineTag.Strong: var delimeterChar = inline.Emphasis.DelimiterCharacter; if (delimeterChar == char.MinValue) { delimeterChar = '_'; } var delimeter = new string ( delimeterChar, inline.Tag == InlineTag.Emphasis ? 1 : 2); writer.WriteLiteral(delimeter); WriteInline(inline.FirstChild); writer.WriteLiteral(delimeter); break; case InlineTag.Strikethrough: writer.WriteLiteral("~~"); WriteInline(inline.FirstChild); writer.WriteLiteral("~~"); break; case InlineTag.Placeholder: writer.WriteLiteral('['); writer.WriteEscaped(inline.TargetUrl); writer.WriteLiteral(']'); break; case InlineTag.Link: case InlineTag.Image: // for writing out autolinks, we use this potentially stripped // URL so we can round-trip email address autolinks. var targetUrl = inline.TargetUrl; if (targetUrl.StartsWith("mailto:", StringComparison.Ordinal)) { targetUrl = targetUrl.Substring(7); } // translate links where the target URL and the content are // the same (and no title is specified) into <autolinks> if (inline.Tag == InlineTag.Link && string.IsNullOrEmpty(inline.LiteralContent) && inline.FirstChild != null && inline.FirstChild.Tag == InlineTag.String && inline.FirstChild.NextSibling == null && inline.FirstChild.LiteralContent == targetUrl) { writer.WriteLiteral('<'); writer.WriteEscaped(targetUrl); writer.WriteLiteral('>'); break; } // the only difference between a link and an image is ^! if (inline.Tag == InlineTag.Image) { writer.WriteLiteral('!'); } writer.WriteLiteral('['); WriteInline(inline.FirstChild, '[', ']'); writer.WriteLiteral(']'); writer.WriteLiteral('('); writer.WriteEscaped(inline.TargetUrl, '(', ')'); if (!string.IsNullOrEmpty(inline.LiteralContent)) { writer.WriteLiteral(" \""); writer.WriteEscaped(inline.LiteralContent, '"', '(', ')'); writer.WriteLiteral('"'); } writer.WriteLiteral(')'); break; case InlineTag.Code: if (string.IsNullOrEmpty(inline.LiteralContent)) { writer.WriteLiteral("` `"); break; } var contentLength = inline.LiteralContent.Length; var wrapSize = MaxConsecutiveCharCount(inline.LiteralContent, '`') + 1; writer.WriteLiteral('`', wrapSize); if (contentLength > 0 && inline.LiteralContent [0] == '`') { writer.WriteLiteral(' '); } writer.WriteLiteral(inline.LiteralContent); if (contentLength > 0 && inline.LiteralContent [contentLength - 1] == '`') { writer.WriteLiteral(' '); } writer.WriteLiteral('`', wrapSize); break; case InlineTag.RawHtml: writer.WriteLiteral(inline.LiteralContent); break; case InlineTag.LineBreak: writer.WriteLiteral('\\'); writer.WriteLineLiteral(); break; case InlineTag.SoftBreak: writer.WriteLineLiteral(); var next = inline.NextSibling; if (next != null && next.Tag == InlineTag.String && !string.IsNullOrEmpty(next.LiteralContent)) { switch (next.LiteralContent [0]) { case '#': case '-': case '>': case '*': case '=': writer.WriteLiteral(' ', 4); break; } } break; case InlineTag.String: var content = inline.LiteralContent; if (string.IsNullOrEmpty(content)) { break; } // CommonMark.NET and the CommonMark JS reference parser appear // to yield single-character string inlines for characters that // need escaping. Therefore, we only attempt to escape these // strings, and not the first character on a string of an arbitray // length. switch (content) { case "*": case "_": case ">": case "-": case "#": case "\\": case "[": case "`": case "<": writer.WriteEscaped(content [0]); break; case "!": if (inline.NextSibling != null && inline.NextSibling.Tag == InlineTag.Link) { writer.WriteEscaped('!'); } else { writer.WriteLiteral('!'); } break; default: // match this inline and a next for escaped ordered list // syntax: ^\d+[\.\)]$ - if the two sibling inlines match, // we want to escape the list delimeter. if (inline.NextSibling != null && content.Length < 9 && ( inline.NextSibling.LiteralContent == "." || inline.NextSibling.LiteralContent == ")")) { var allDigits = true; for (int i = 0; i < content.Length; i++) { if (!char.IsDigit(content [i])) { allDigits = false; break; } } if (allDigits) { writer.WriteLiteral(content); // this will skip over the delimeter inline on the // next outer loop iteration since we're handling it // here (escaping it). inline = inline.NextSibling; writer.WriteEscaped(inline.LiteralContent [0]); break; } } writer.WriteEscaped(content, escapeChars); break; } break; default: throw new NotImplementedException($"{inline.Tag}"); } }