/// <summary> /// Parses a column header equivalent to the regexp: <code>\s*:\s*[delimiterChar]+\s*:\s*</code> /// </summary> /// <param name="slice">The text slice.</param> /// <param name="delimiterChar">The delimiter character (either `-` or `=`). If `\0`, it will detect the character (either `-` or `=`)</param> /// <param name="align">The alignment of the column.</param> /// <returns> /// <c>true</c> if parsing was successfull /// </returns> public static bool ParseColumnHeaderDetect(ref StringSlice slice, ref char delimiterChar, out TableColumnAlign?align) { align = null; slice.TrimStart(); var c = slice.CurrentChar; bool hasLeft = false; bool hasRight = false; if (c == ':') { hasLeft = true; slice.NextChar(); } slice.TrimStart(); c = slice.CurrentChar; // if we want to automatically detect if (delimiterChar == '\0') { if (c == '=' || c == '-') { delimiterChar = c; } else { return(false); } } // We expect at least one `-` delimiter char if (slice.CountAndSkipChar(delimiterChar) == 0) { return(false); } slice.TrimStart(); c = slice.CurrentChar; if (c == ':') { hasRight = true; slice.NextChar(); } slice.TrimStart(); align = hasLeft && hasRight ? TableColumnAlign.Center : hasRight ? TableColumnAlign.Right : hasLeft ? TableColumnAlign.Left : (TableColumnAlign?)null; return(true); }
public override bool Match(InlineProcessor processor, ref StringSlice slice) { // First, some definitions. // A delimiter run is a sequence of one or more delimiter characters that is not preceded or followed by the same delimiter character // The amount of delimiter characters in the delimiter run may exceed emphasisDesc.MaximumCount, as that is handeled in `ProcessEmphasis` var delimiterChar = slice.CurrentChar; var emphasisDesc = emphasisMap[delimiterChar]; char pc = (char)0; if (processor.Inline is HtmlEntityInline htmlEntityInline) { if (htmlEntityInline.Transcoded.Length > 0) { pc = htmlEntityInline.Transcoded[htmlEntityInline.Transcoded.End]; } } if (pc == 0) { pc = slice.PeekCharExtra(-1); if (pc == delimiterChar && slice.PeekCharExtra(-2) != '\\') { // If we get here, we determined that either: // a) there weren't enough delimiters in the delimiter run to satisfy the MinimumCount condition // b) the previous character couldn't open/close return(false); } } var startPosition = slice.Start; int delimiterCount = slice.CountAndSkipChar(delimiterChar); // If the emphasis doesn't have the minimum required character if (delimiterCount < emphasisDesc.MinimumCount) { return(false); } char c = slice.CurrentChar; // The following character is actually an entity, we need to decode it if (HtmlEntityParser.TryParse(ref slice, out string htmlString, out int htmlLength)) { c = htmlString[0]; } // Calculate Open-Close for current character CharHelper.CheckOpenCloseDelimiter(pc, c, emphasisDesc.EnableWithinWord, out bool canOpen, out bool canClose); // We have potentially an open or close emphasis if (canOpen || canClose) { var delimiterType = DelimiterType.Undefined; if (canOpen) { delimiterType |= DelimiterType.Open; } if (canClose) { delimiterType |= DelimiterType.Close; } var delimiter = new EmphasisDelimiterInline(this, emphasisDesc) { DelimiterCount = delimiterCount, Type = delimiterType, Span = new SourceSpan(processor.GetSourcePosition(startPosition, out int line, out int column), processor.GetSourcePosition(slice.Start - 1)), Column = column, Line = line, }; processor.Inline = delimiter; return(true); } // We don't have an emphasis return(false); }
public override bool Match(InlineProcessor processor, ref StringSlice slice) { var match = slice.CurrentChar; if (slice.PeekCharExtra(-1) == match) { return(false); } var startPosition = slice.Start; // Match the opened sticks int openSticks = slice.CountAndSkipChar(match); int contentStart = slice.Start; int closeSticks = 0; char c = slice.CurrentChar; var builder = StringBuilderCache.Local(); // A backtick string is a string of one or more backtick characters (`) that is neither preceded nor followed by a backtick. // A code span begins with a backtick string and ends with a backtick string of equal length. // The contents of the code span are the characters between the two backtick strings, normalized in the following ways: // 1. line endings are converted to spaces. // 2. If the resulting string both begins AND ends with a space character, but does not consist entirely // of space characters, a single space character is removed from the front and back. // This allows you to include code that begins or ends with backtick characters, which must be separated by // whitespace from the opening or closing backtick strings. bool allSpace = true; var contentEnd = -1; while (c != '\0') { // Transform '\n' into a single space if (c == '\n') { c = ' '; } if (c == match) { contentEnd = slice.Start; closeSticks = slice.CountAndSkipChar(match); if (openSticks == closeSticks) { break; } allSpace = false; builder.Append(match, closeSticks); c = slice.CurrentChar; } else { builder.Append(c); if (c != ' ') { allSpace = false; } c = slice.NextChar(); } } bool isMatching = false; if (closeSticks == openSticks) { string content; // Remove one space from front and back if the string is not all spaces if (!allSpace && builder.Length > 2 && builder[0] == ' ' && builder[builder.Length - 1] == ' ') { content = builder.ToString(1, builder.Length - 2); } else { content = builder.ToString(); } int delimiterCount = Math.Min(openSticks, closeSticks); var spanStart = processor.GetSourcePosition(startPosition, out int line, out int column); var spanEnd = processor.GetSourcePosition(slice.Start - 1); processor.Inline = new CodeInline() { Delimiter = match, Content = content, ContentWithTrivia = new StringSlice(slice.Text, contentStart, contentEnd - 1), Span = new SourceSpan(spanStart, spanEnd), Line = line, Column = column, DelimiterCount = delimiterCount, }; isMatching = true; } return(isMatching); }
public override bool Match(InlineProcessor processor, ref StringSlice slice) { var match = slice.CurrentChar; var pc = slice.PeekCharExtra(-1); if (pc == match) { return(false); } var startPosition = slice.Start; // Match the opened $ or $$ int openDollars = 1; // we have at least a $ var c = slice.NextChar(); if (c == match) { openDollars++; c = slice.NextChar(); } pc.CheckUnicodeCategory(out bool openPrevIsWhiteSpace, out bool openPrevIsPunctuation); c.CheckUnicodeCategory(out bool openNextIsWhiteSpace, out _); // Check that opening $/$$ is correct, using the different heuristics than for emphasis delimiters // If a $/$$ is not preceded by a whitespace or punctuation, this is a not a math block if ((!openPrevIsWhiteSpace && !openPrevIsPunctuation)) { return(false); } bool isMatching = false; int closeDollars = 0; // Eat any leading spaces while (c.IsSpaceOrTab()) { c = slice.NextChar(); } var start = slice.Start; var end = 0; pc = match; var lastWhiteSpace = -1; while (c != '\0') { // Don't allow newline in an inline math expression if (c == '\r' || c == '\n') { return(false); } // Don't process sticks if we have a '\' as a previous char if (pc != '\\') { // Record continuous whitespaces at the end if (c.IsSpaceOrTab()) { if (lastWhiteSpace < 0) { lastWhiteSpace = slice.Start; } } else { bool hasClosingDollars = c == match; if (hasClosingDollars) { closeDollars += slice.CountAndSkipChar(match); c = slice.CurrentChar; } if (closeDollars >= openDollars) { break; } lastWhiteSpace = -1; if (hasClosingDollars) { pc = match; continue; } } } if (closeDollars > 0) { closeDollars = 0; } else { pc = c; c = slice.NextChar(); } } if (closeDollars >= openDollars) { pc.CheckUnicodeCategory(out bool closePrevIsWhiteSpace, out _); c.CheckUnicodeCategory(out bool closeNextIsWhiteSpace, out bool closeNextIsPunctuation); // A closing $/$$ should be followed by at least a punctuation or a whitespace // and if the character after an opening $/$$ was a whitespace, it should be // a whitespace as well for the character preceding the closing of $/$$ if ((!closeNextIsPunctuation && !closeNextIsWhiteSpace) || (openNextIsWhiteSpace != closePrevIsWhiteSpace)) { return(false); } if (closePrevIsWhiteSpace && lastWhiteSpace > 0) { end = lastWhiteSpace + openDollars - 1; } else { end = slice.Start - 1; } // Create a new MathInline var inline = new MathInline() { Span = new SourceSpan(processor.GetSourcePosition(startPosition, out int line, out int column), processor.GetSourcePosition(slice.End)), Line = line, Column = column, Delimiter = match, DelimiterCount = openDollars, Content = slice }; inline.Content.Start = start; // We substract the end to the number of opening $ to keep inside the block the additionals $ inline.Content.End = end - openDollars; // Add the default class if necessary if (DefaultClass != null) { inline.GetAttributes().AddClass(DefaultClass); } processor.Inline = inline; isMatching = true; } return(isMatching); } }