private static void SetRowSpanState(List <GridTableState.ColumnSlice> columns, StringSlice line, out bool isHeaderRow, out bool hasRowSpan) { var lineStart = line.Start; isHeaderRow = line.PeekChar(1) == '=' || line.PeekChar(2) == '='; hasRowSpan = false; foreach (var columnSlice in columns) { if (columnSlice.CurrentCell != null) { line.Start = lineStart + columnSlice.Start + 1; line.End = lineStart + columnSlice.End - 1; line.Trim(); if (line.IsEmptyOrWhitespace() || !IsRowSeperator(line)) { hasRowSpan = true; columnSlice.CurrentCell.RowSpan++; columnSlice.CurrentCell.AllowClose = false; } else { columnSlice.CurrentCell.AllowClose = true; } } } }
public override bool Match(InlineProcessor processor, ref StringSlice slice) { if (slice.CurrentChar == '$' && slice.PeekChar(1) == '(') { slice.NextChar(); slice.NextChar(); int start = slice.Start; int end = start; while (slice.CurrentChar != ')') { end = slice.Start; slice.NextChar(); } processor.GetSourcePosition(slice.Start, out int line, out int column); processor.Inline = new VariableInline { Line = line, Column = column, VariableName = new StringSlice(slice.Text, start, end) }; slice.NextChar(); return(true); } return(false); }
/// <summary> /// Splits a slice into an IEnumerable of slices split on newlines /// </summary> /// <param name="slice">The slice to split</param> /// <returns>The line split into lines</returns> public static IEnumerable <StringSlice> SplitSliceToLines(StringSlice slice) { var sliceStart = slice.Start; while (!slice.IsEmpty) { if (slice.CurrentChar == '\n') { slice.NextChar(); yield return(new StringSlice(slice.Text, sliceStart, slice.Start - 1)); sliceStart = slice.Start; continue; } if (slice.CurrentChar == '\r' && slice.PeekChar(1) == '\n') { slice.Start += 2; yield return(new StringSlice(slice.Text, sliceStart, slice.Start - 1)); sliceStart = slice.Start; continue; } slice.NextChar(); } if (sliceStart != slice.Start) { yield return(new StringSlice(slice.Text, sliceStart, slice.Start - 1)); } }
private static void SetColumnSpanState(List <GridTableState.ColumnSlice> columns, StringSlice line) { foreach (var columnSlice in columns) { columnSlice.PreviousColumnSpan = columnSlice.CurrentColumnSpan; columnSlice.CurrentColumnSpan = 0; } // | ------------- | ------------ | ---------------------------------------- | // Calculate the colspan for the new row int columnIndex = -1; for (int i = 0; i < columns.Count; i++) { var columnSlice = columns[i]; var peek = line.PeekChar(columnSlice.Start); if (peek == '|' || peek == '+') { columnIndex = i; } if (columnIndex >= 0) { columns[columnIndex].CurrentColumnSpan++; } } }
public override bool Match(InlineProcessor processor, ref StringSlice slice) { var startPosition = slice.Start; // Go to escape character var c = slice.NextChar(); int line; int column; if (c.IsAsciiPunctuation()) { processor.Inline = new LiteralInline() { Content = new StringSlice(slice.Text, slice.Start, slice.Start), Span = { Start = processor.GetSourcePosition(startPosition, out line, out column) }, Line = line, Column = column, IsFirstCharacterEscaped = true, }; processor.Inline.Span.End = processor.Inline.Span.Start + 1; slice.SkipChar(); return(true); } // A backslash at the end of the line is a [hard line break]: if (c == '\n' || c == '\r') { var newLine = c == '\n' ? NewLine.LineFeed : NewLine.CarriageReturn; if (c == '\r' && slice.PeekChar() == '\n') { newLine = NewLine.CarriageReturnLineFeed; } var inline = new LineBreakInline() { IsHard = true, IsBackslash = true, Span = { Start = processor.GetSourcePosition(startPosition, out line, out column) }, Line = line, Column = column, }; processor.Inline = inline; if (processor.TrackTrivia) { inline.NewLine = newLine; } inline.Span.End = inline.Span.Start + 1; slice.SkipChar(); // Skip \n or \r alone if (newLine == NewLine.CarriageReturnLineFeed) { slice.SkipChar(); // Skip \r\n } return(true); } return(false); }
public override bool Match(InlineProcessor processor, ref StringSlice slice) { var nextChar = slice.PeekChar(); if (nextChar != '{') { return(false); } var start = slice.Start; slice.NextChar(); nextChar = slice.NextChar(); var contentBuilder = processor.StringBuilders.Get(); while (nextChar != '}') { contentBuilder.Append(nextChar); nextChar = slice.NextChar(); } if (slice.PeekChar() != ']') { processor.StringBuilders.Release(contentBuilder); return(false); } slice.NextChar(); slice.NextChar(); processor.Inline = new CodeInclude { DisplayName = contentBuilder.ToString(), Span = new SourceSpan(processor.GetSourcePosition( start, out var line, out var column), processor.GetSourcePosition(slice.End)), Line = line, Column = column }; return(true); } }
public override bool Match(InlineProcessor processor, ref StringSlice slice) { // Hard line breaks are for separating inline content within a block. Neither syntax for hard line breaks works at the end of a paragraph or other block element: if (!processor.Block !.IsParagraphBlock) { return(false); } var startPosition = slice.Start; var hasDoubleSpacesBefore = slice.PeekCharExtra(-1).IsSpace() && slice.PeekCharExtra(-2).IsSpace(); var newLine = NewLine.LineFeed; if (processor.TrackTrivia) { if (slice.CurrentChar == '\r') { if (slice.PeekChar() == '\n') { newLine = NewLine.CarriageReturnLineFeed; slice.SkipChar(); // Skip \n } else { newLine = NewLine.CarriageReturn; } } else { newLine = NewLine.LineFeed; } } else { if (slice.CurrentChar == '\r' && slice.PeekChar() == '\n') { slice.SkipChar(); // Skip \n } } slice.SkipChar(); // Skip \r or \n processor.Inline = new LineBreakInline { Span = { Start = processor.GetSourcePosition(startPosition, out int line, out int column) },
/// <summary> /// Tries to match the specified slice. /// </summary> /// <param name="processor">The parser processor.</param> /// <param name="slice">The text slice.</param> /// <returns><c>true</c> if this parser found a match; <c>false</c> otherwise</returns> public override bool Match(InlineProcessor processor, ref StringSlice slice) { var c = slice.CurrentChar; var startPosition = processor.GetSourcePosition(slice.Start, out var line, out var column); var isImage = false; if (c == '!') { isImage = true; c = slice.NextChar(); if (c != LinkOpenChar) { return(false); } } switch (c) { case LinkOpenChar: if (slice.PeekChar() != LinkOpenChar) { return(false); } var saved = slice; var currentPosition = slice.Start; if (TryParseLink(ref slice, out var title, out var display, out var hasDisplay, out var autoDisplay, out var endPosition)) { processor.Inline = new WikiLinkDelimiterInline(this) { Type = DelimiterType.Open, AutoDisplay = autoDisplay, Display = display, HasDisplay = hasDisplay, Title = title, IsImage = isImage, Span = new SourceSpan(startPosition, processor.GetSourcePosition(slice.Start - 1)), Line = line, Column = column, }; slice = saved; if (endPosition == currentPosition) { slice.NextChar(); slice.NextChar(); } else { slice.Start = endPosition; slice.NextChar(); } }
/// <inheritdoc /> public virtual void Collapse(ref StringSlice line, float collapseRatio) { if (collapseRatio < 0 || collapseRatio > 1) { throw new ArgumentOutOfRangeException(nameof(collapseRatio), string.Format(Strings.ArgumentOutOfRangeException_Shared_ValueMustBeWithinRange, "[0, 1]", collapseRatio)); } if (line.IsEmpty || collapseRatio == 1) { return; } if (collapseRatio == 0) { line.TrimStart(); // Remove all leading whitespace } else { int leadingWhitespaceCount = 0; while (line.PeekChar(leadingWhitespaceCount).IsWhitespace()) { leadingWhitespaceCount++; } if (leadingWhitespaceCount == 0) { return; } // collapseRatio is defined as finalLeadingWhitespaceCount/initialLeadingWhitespaceCount, // so collapseLength = initialLeadingWhitespaceCount - finalLeadingWhitespaceCount = initialLeadingWhitespaceCount - initialLeadingWhitespaceCount*collapseRatio int collapseLength = leadingWhitespaceCount - (int)Math.Round(leadingWhitespaceCount * collapseRatio); for (int start = 0; start < collapseLength; start++) { line.NextChar(); } } }
/// <inheritdoc /> public virtual void Dedent(ref StringSlice line, int dedentLength) { if (dedentLength < 0) { throw new ArgumentOutOfRangeException(nameof(dedentLength), string.Format(Strings.ArgumentOutOfRangeException_Shared_ValueCannotBeNegative, dedentLength)); } if (line.IsEmpty || dedentLength == 0) { return; } for (int offset = 0; offset < dedentLength; offset++) { if (!line.PeekChar(offset).IsWhitespace()) { line.Start += offset; return; // No more whitespace to dedent or collapse } } line.Start += dedentLength; }
private BlockState TryParseTagType16(BlockProcessor state, StringSlice line, int startColumn, int startPosition) { char c; c = line.CurrentChar; if (c == '!') { c = line.NextChar(); if (c == '-' && line.PeekChar() == '-') { return(CreateHtmlBlock(state, HtmlBlockType.Comment, startColumn, startPosition)); // group 2 } if (c.IsAlphaUpper()) { return(CreateHtmlBlock(state, HtmlBlockType.DocumentType, startColumn, startPosition)); // group 4 } if (c == '[' && line.Match("CDATA[", 1)) { return(CreateHtmlBlock(state, HtmlBlockType.CData, startColumn, startPosition)); // group 5 } return(BlockState.None); } if (c == '?') { return(CreateHtmlBlock(state, HtmlBlockType.ProcessingInstruction, startColumn, startPosition)); // group 3 } var hasLeadingClose = c == '/'; if (hasLeadingClose) { c = line.NextChar(); } var tag = new char[10]; var count = 0; for (; count < tag.Length; count++) { if (!c.IsAlphaNumeric()) { break; } tag[count] = char.ToLowerInvariant(c); c = line.NextChar(); } if ( !(c == '>' || (!hasLeadingClose && c == '/' && line.PeekChar() == '>') || c.IsWhitespace() || c == '\0')) { return(BlockState.None); } if (count == 0) { return(BlockState.None); } var tagName = new string(tag, 0, count); var tagIndex = Array.BinarySearch(HtmlTags, tagName, StringComparer.Ordinal); if (tagIndex < 0) { return(BlockState.None); } // Cannot start with </script </pre or </style if ((tagIndex == 49 || tagIndex == 50 || tagIndex == 53)) { if (c == '/' || hasLeadingClose) { return(BlockState.None); } return(CreateHtmlBlock(state, HtmlBlockType.ScriptPreOrStyle, startColumn, startPosition)); } return(CreateHtmlBlock(state, HtmlBlockType.InterruptingBlock, startColumn, startPosition)); }
private bool TryProcessLinkOrImage(InlineProcessor inlineState, ref StringSlice text) { LinkDelimiterInline openParent = null; foreach (var parent in inlineState.Inline.FindParentOfType <LinkDelimiterInline>()) { openParent = parent; break; } if (openParent != null) { // If we do find one, but it’s not active, // we remove the inactive delimiter from the stack, // and return a literal text node ]. if (!openParent.IsActive) { inlineState.Inline = new LiteralInline() { Content = new StringSlice("["), Span = openParent.Span, Line = openParent.Line, Column = openParent.Column, }; openParent.ReplaceBy(inlineState.Inline); return(false); } // If we find one and it’s active, // then we parse ahead to see if we have // an inline link/image, reference link/image, // compact reference link/image, // or shortcut reference link/image var parentDelimiter = openParent.Parent; switch (text.CurrentChar) { case '(': string url; string title; SourceSpan linkSpan; SourceSpan titleSpan; if (LinkHelper.TryParseInlineLink(ref text, out url, out title, out linkSpan, out titleSpan)) { // Inline Link var link = new LinkInline() { Url = HtmlHelper.Unescape(url), Title = HtmlHelper.Unescape(title), IsImage = openParent.IsImage, LabelSpan = openParent.LabelSpan, UrlSpan = inlineState.GetSourcePositionFromLocalSpan(linkSpan), TitleSpan = inlineState.GetSourcePositionFromLocalSpan(titleSpan), Span = new SourceSpan(openParent.Span.Start, inlineState.GetSourcePosition(text.Start - 1)), Line = openParent.Line, Column = openParent.Column, }; openParent.ReplaceBy(link); // Notifies processor as we are creating an inline locally inlineState.Inline = link; // Process emphasis delimiters inlineState.PostProcessInlines(0, link, null, false); // If we have a link (and not an image), // we also set all [ delimiters before the opening delimiter to inactive. // (This will prevent us from getting links within links.) if (!openParent.IsImage) { MarkParentAsInactive(parentDelimiter); } link.IsClosed = true; return(true); } break; default: var labelSpan = SourceSpan.Empty; string label = null; bool isLabelSpanLocal = true; // Handle Collapsed links if (text.CurrentChar == '[') { if (text.PeekChar(1) == ']') { label = openParent.Label; labelSpan = openParent.LabelSpan; isLabelSpanLocal = false; text.NextChar(); // Skip [ text.NextChar(); // Skip ] } } else { label = openParent.Label; } if (label != null || LinkHelper.TryParseLabel(ref text, true, out label, out labelSpan)) { if (isLabelSpanLocal) { labelSpan = inlineState.GetSourcePositionFromLocalSpan(labelSpan); } if (ProcessLinkReference(inlineState, label, labelSpan, openParent, inlineState.GetSourcePosition(text.Start - 1))) { // Remove the open parent openParent.Remove(); if (!openParent.IsImage) { MarkParentAsInactive(parentDelimiter); } } else { return(false); } return(true); } break; } // We have a nested [ ] // firstParent.Remove(); // The opening [ will be transformed to a literal followed by all the childrens of the [ var literal = new LiteralInline() { Span = openParent.Span, Content = new StringSlice(openParent.IsImage ? "![" : "[") }; inlineState.Inline = openParent.ReplaceBy(literal); return(false); } return(false); }
public override bool Match(InlineProcessor processor, ref StringSlice slice) { // We are matching the following characters: // // ' ‘ ’ ‘ ’ 'left-single-quote', 'right-single-quote' // '' “ ” “ ” 'left-double-quote', 'right-double-quote' // " “ ” “ ” 'left-double-quote', 'right-double-quote' // << >> « » « » 'left-angle-quote', 'right-angle-quote' // ... … … 'ellipsis' // Special case: – and — are handle as a PostProcess step to avoid conflicts with pipetables header separator row // -- – – 'ndash' // --- — — 'mdash' var pc = slice.PeekCharExtra(-1); var c = slice.CurrentChar; var openingChar = c; var startingPosition = slice.Start; // undefined first var type = (SmartyPantType)0; switch (c) { case '\'': type = SmartyPantType.Quote; // We will resolve them at the end of parsing all inlines if (slice.PeekChar(1) == '\'') { slice.NextChar(); type = SmartyPantType.DoubleQuote; // We will resolve them at the end of parsing all inlines } break; case '"': type = SmartyPantType.DoubleQuote; break; case '<': if (slice.NextChar() == '<') { type = SmartyPantType.LeftAngleQuote; } break; case '>': if (slice.NextChar() == '>') { type = SmartyPantType.RightAngleQuote; } break; case '.': if (slice.NextChar() == '.' && slice.NextChar() == '.') { type = SmartyPantType.Ellipsis; } break; case '-': if (slice.NextChar() == '-') { var quotePants = GetOrCreateState(processor); quotePants.HasDash = true; return(false); } break; } // If it is not matched, early exit if (type == 0) { return(false); } // Skip char c = slice.NextChar(); bool canOpen; bool canClose; CharHelper.CheckOpenCloseDelimiter(pc, c, false, out canOpen, out canClose); bool postProcess = false; switch (type) { case SmartyPantType.Quote: postProcess = true; if (canOpen && !canClose) { type = SmartyPantType.LeftQuote; } else if (!canOpen && canClose) { type = SmartyPantType.RightQuote; } else { return(false); } break; case SmartyPantType.DoubleQuote: postProcess = true; if (canOpen && !canClose) { type = SmartyPantType.LeftDoubleQuote; } else if (!canOpen && canClose) { type = SmartyPantType.RightDoubleQuote; } else { return(false); } break; case SmartyPantType.LeftAngleQuote: postProcess = true; if (!canOpen || canClose) { return(false); } break; case SmartyPantType.RightAngleQuote: postProcess = true; if (canOpen || !canClose) { return(false); } break; case SmartyPantType.Ellipsis: if (canOpen || !canClose) { return(false); } break; } // Create the SmartyPant inline int line; int column; var pant = new SmartyPant() { Span = { Start = processor.GetSourcePosition(startingPosition, out line, out column) }, Line = line, Column = column, OpeningCharacter = openingChar, Type = type }; pant.Span.End = pant.Span.Start + slice.Start - startingPosition - 1; // We will check in a post-process step for balanced open/close quotes if (postProcess) { var quotePants = GetOrCreateState(processor); // Register only if we don't have yet any quotes if (quotePants.Count == 0) { processor.Block.ProcessInlinesEnd += BlockOnProcessInlinesEnd; } quotePants.Add(pant); } processor.Inline = pant; return(true); }
/// <summary> /// Tries to match a block opening. /// </summary> /// <param name="processor">The parser processor.</param> /// <returns>The result of the match</returns> public override BlockState TryOpen(BlockProcessor processor) { // We expect no indentation for a fenced code block. if (processor.IsCodeIndent) { return(BlockState.None); } // Only accept a frontmatter at the beginning of the file if (processor.Start != 0) { return(BlockState.None); } int count = 0; var line = processor.Line; char c = line.CurrentChar; // Must consist of exactly three dashes while (c == '-' && count < 4) { count++; c = line.NextChar(); } // If three dashes (optionally followed by whitespace) // this is a YAML front matter block if (count == 3 && (c == '\0' || c.IsWhitespace()) && line.TrimEnd()) { bool hasFullYamlFrontMatter = false; // We make sure that there is a closing frontmatter somewhere in the document // so here we work on the full document instead of just the line var fullLine = new StringSlice(line.Text, line.Start, line.Text.Length - 1); c = fullLine.CurrentChar; while (c != '\0') { c = fullLine.NextChar(); if (c == '\n' || c == '\r') { var nc = fullLine.PeekChar(); if (c == '\r' && nc == '\n') { c = fullLine.NextChar(); } nc = fullLine.PeekChar(); if (nc == '-') { if (fullLine.NextChar() == '-' && fullLine.NextChar() == '-' && fullLine.NextChar() == '-' && (fullLine.NextChar() == '\0' || fullLine.SkipSpacesToEndOfLineOrEndOfDocument())) { hasFullYamlFrontMatter = true; break; } } else if (nc == '.') { if (fullLine.NextChar() == '.' && fullLine.NextChar() == '.' && fullLine.NextChar() == '.' && (fullLine.NextChar() == '\0' || fullLine.SkipSpacesToEndOfLineOrEndOfDocument())) { hasFullYamlFrontMatter = true; break; } } } } if (hasFullYamlFrontMatter) { // Create a front matter block var block = this.CreateFrontMatterBlock(processor); block.Column = processor.Column; block.Span.Start = 0; block.Span.End = line.Start; // Store the number of matched string into the context processor.NewBlocks.Push(block); // Discard the current line as it is already parsed return(BlockState.ContinueDiscard); } } return(BlockState.None); }
/// <summary> /// Peeks a character at the specified offset from the current position in the line. /// </summary> /// <param name="offset">The offset.</param> /// <returns>A character peeked at the specified offset</returns> public char PeekChar(int offset) { return(Line.PeekChar(offset)); }
private BlockState TryParseTagType16(BlockProcessor state, StringSlice line, int startColumn, int startPosition) { char c; c = line.CurrentChar; if (c == '!') { c = line.NextChar(); if (c == '-' && line.PeekChar() == '-') { return(CreateHtmlBlock(state, HtmlBlockType.Comment, startColumn, startPosition)); // group 2 } if (c.IsAlphaUpper()) { return(CreateHtmlBlock(state, HtmlBlockType.DocumentType, startColumn, startPosition)); // group 4 } if (c == '[' && line.Match("CDATA[", 1)) { return(CreateHtmlBlock(state, HtmlBlockType.CData, startColumn, startPosition)); // group 5 } return(BlockState.None); } if (c == '?') { return(CreateHtmlBlock(state, HtmlBlockType.ProcessingInstruction, startColumn, startPosition)); // group 3 } var hasLeadingClose = c == '/'; if (hasLeadingClose) { c = line.NextChar(); } Span <char> tag = stackalloc char[10]; var count = 0; for (; count < tag.Length; count++) { if (!c.IsAlphaNumeric()) { break; } tag[count] = char.ToLowerInvariant(c); c = line.NextChar(); } if ( !(c == '>' || (!hasLeadingClose && c == '/' && line.PeekChar() == '>') || c.IsWhitespace() || c == '\0')) { return(BlockState.None); } if (count == 0) { return(BlockState.None); } if (!HtmlTags.TryMatchExact(tag.Slice(0, count), out var match)) { return(BlockState.None); } int tagIndex = match.Value; // Cannot start with </script </pre or </style or </textArea if ((tagIndex == 49 || tagIndex == 50 || tagIndex == 53 || tagIndex == 56)) { if (c == '/' || hasLeadingClose) { return(BlockState.None); } return(CreateHtmlBlock(state, HtmlBlockType.ScriptPreOrStyle, startColumn, startPosition)); } return(CreateHtmlBlock(state, HtmlBlockType.InterruptingBlock, startColumn, startPosition)); }
public override bool Match(InlineProcessor processor, ref StringSlice slice) { var c = slice.CurrentChar; var p = processor.GetSourcePosition(slice.Start, out var line, out var column); // consume first '[' var c2 = slice.NextChar(); if (c2 != '[') { return(false); } var start = slice.Start + 1; var labelStart = start; var hasEscape = false; SourceSpan?labelSpan = null; string label = null; SourceSpan?urlSpan = null; string url = null; var success = false; var hasSeparator = false; while (true) { c = slice.NextChar(); if (c == '\0' || c == '\r' || c == '\n') { break; } if (hasEscape) { // ignore escaped characters hasEscape = false; } else { if (c == '\\') { hasEscape = true; } else if (c == '|') { var span = new SourceSpan(start, slice.Start - 1); if (span.Length != 0) { urlSpan = span; url = slice.Text.Substring(span.Start, span.Length); } hasSeparator = true; labelStart = slice.Start + 1; } else if (c == ']') { c2 = slice.PeekChar(1); if (c2 == ']') { // end of link success = true; c = slice.NextChar(); var span = new SourceSpan(labelStart, slice.Start - 2); var trailingCharsLen = 0; var linkEnd = -1; if (!hasSeparator && IncludeTrailingCharacters) { c2 = slice.PeekChar(1); if (char.IsLetter(c2) || c2 == '\'') { linkEnd = slice.Start - 1; c = slice.NextChar(); while (char.IsLetter(c) || c == '\'') { trailingCharsLen++; c = slice.NextChar(); } } } slice.NextChar(); if (linkEnd != -1) { url = slice.Text.Substring(labelStart, linkEnd - 2); urlSpan = span; label = slice.Text.Substring(labelStart, linkEnd - 2) + slice.Text.Substring(linkEnd + 2, trailingCharsLen); } if (span.Length != 0) { labelSpan = span; if (label == null) { label = slice.Text.Substring(span.Start, span.Length); } } break; } } } } if (success) { if (url == null) { // occurs when no separator were used // copy label as url url = label; urlSpan = labelSpan; // keep only the page name var lastSegment = label.LastIndexOf('/'); if (lastSegment != -1) { label = label.Substring(lastSegment + 1); } // remove any hash var hash = label.LastIndexOf('#'); if (hash != -1) { label = label.Substring(0, hash); } } if (label == null) { label = url; // keep only the page name var lastSegment = label.LastIndexOf('/'); if (lastSegment != -1) { label = label.Substring(lastSegment + 1); } // remove any hash var hash = label.LastIndexOf('#'); if (hash != -1) { label = label.Substring(0, hash); } labelSpan = urlSpan; } if (!url.StartsWith("http://", StringComparison.OrdinalIgnoreCase) && !url.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) { // adapt relative url url = Regex.Replace(url, "[ ]", $"{WhiteSpaceUrlChar}"); if (!string.IsNullOrEmpty(Extension)) { url += Extension; } } var link = new LinkInline() { Column = column, Line = line, LabelSpan = labelSpan, Label = label, Url = url, UrlSpan = urlSpan, IsClosed = true, //IsShortcut = false, IsImage = false, }; link.AppendChild(new LiteralInline(label)); processor.Inline = link; } return(success); }
private bool TryProcessLinkOrImage(InlineProcessor inlineState, ref StringSlice text) { LinkDelimiterInline?openParent = inlineState.Inline !.FirstParentOfType <LinkDelimiterInline>(); if (openParent is null) { return(false); } // If we do find one, but it’s not active, // we remove the inactive delimiter from the stack, // and return a literal text node ]. if (!openParent.IsActive) { inlineState.Inline = new LiteralInline() { Content = new StringSlice("["), Span = openParent.Span, Line = openParent.Line, Column = openParent.Column, }; openParent.ReplaceBy(inlineState.Inline); return(false); } // If we find one and it’s active, // then we parse ahead to see if we have // an inline link/image, reference link/image, // compact reference link/image, // or shortcut reference link/image var parentDelimiter = openParent.Parent; var savedText = text; if (text.CurrentChar == '(') { if (inlineState.TrackTrivia) { if (LinkHelper.TryParseInlineLinkTrivia( ref text, out string?url, out SourceSpan unescapedUrlSpan, out string?title, out SourceSpan unescapedTitleSpan, out char titleEnclosingCharacter, out SourceSpan linkSpan, out SourceSpan titleSpan, out SourceSpan triviaBeforeLink, out SourceSpan triviaAfterLink, out SourceSpan triviaAfterTitle, out bool urlHasPointyBrackets)) { var wsBeforeLink = new StringSlice(text.Text, triviaBeforeLink.Start, triviaBeforeLink.End); var wsAfterLink = new StringSlice(text.Text, triviaAfterLink.Start, triviaAfterLink.End); var wsAfterTitle = new StringSlice(text.Text, triviaAfterTitle.Start, triviaAfterTitle.End); var unescapedUrl = new StringSlice(text.Text, unescapedUrlSpan.Start, unescapedUrlSpan.End); var unescapedTitle = new StringSlice(text.Text, unescapedTitleSpan.Start, unescapedTitleSpan.End); // Inline Link var link = new LinkInline() { TriviaBeforeUrl = wsBeforeLink, Url = HtmlHelper.Unescape(url), UnescapedUrl = unescapedUrl, UrlHasPointyBrackets = urlHasPointyBrackets, TriviaAfterUrl = wsAfterLink, Title = HtmlHelper.Unescape(title), UnescapedTitle = unescapedTitle, TitleEnclosingCharacter = titleEnclosingCharacter, TriviaAfterTitle = wsAfterTitle, IsImage = openParent.IsImage, LabelSpan = openParent.LabelSpan, UrlSpan = inlineState.GetSourcePositionFromLocalSpan(linkSpan), TitleSpan = inlineState.GetSourcePositionFromLocalSpan(titleSpan), Span = new SourceSpan(openParent.Span.Start, inlineState.GetSourcePosition(text.Start - 1)), Line = openParent.Line, Column = openParent.Column, }; openParent.ReplaceBy(link); // Notifies processor as we are creating an inline locally inlineState.Inline = link; // Process emphasis delimiters inlineState.PostProcessInlines(0, link, null, false); // If we have a link (and not an image), // we also set all [ delimiters before the opening delimiter to inactive. // (This will prevent us from getting links within links.) if (!openParent.IsImage) { MarkParentAsInactive(parentDelimiter); } link.IsClosed = true; return(true); } } else { if (LinkHelper.TryParseInlineLink(ref text, out string?url, out string?title, out SourceSpan linkSpan, out SourceSpan titleSpan)) { // Inline Link var link = new LinkInline() { Url = HtmlHelper.Unescape(url), Title = HtmlHelper.Unescape(title), IsImage = openParent.IsImage, LabelSpan = openParent.LabelSpan, UrlSpan = inlineState.GetSourcePositionFromLocalSpan(linkSpan), TitleSpan = inlineState.GetSourcePositionFromLocalSpan(titleSpan), Span = new SourceSpan(openParent.Span.Start, inlineState.GetSourcePosition(text.Start - 1)), Line = openParent.Line, Column = openParent.Column, }; openParent.ReplaceBy(link); // Notifies processor as we are creating an inline locally inlineState.Inline = link; // Process emphasis delimiters inlineState.PostProcessInlines(0, link, null, false); // If we have a link (and not an image), // we also set all [ delimiters before the opening delimiter to inactive. // (This will prevent us from getting links within links.) if (!openParent.IsImage) { MarkParentAsInactive(parentDelimiter); } link.IsClosed = true; return(true); } } text = savedText; } var labelSpan = SourceSpan.Empty; string? label = null; SourceSpan labelWithTrivia = SourceSpan.Empty; bool isLabelSpanLocal = true; bool isShortcut = false; LocalLabel localLabel = LocalLabel.Local; // Handle Collapsed links if (text.CurrentChar == '[') { if (text.PeekChar() == ']') { label = openParent.Label; labelSpan = openParent.LabelSpan; isLabelSpanLocal = false; localLabel = LocalLabel.Empty; text.SkipChar(); // Skip [ text.SkipChar(); // Skip ] } } else { localLabel = LocalLabel.None; label = openParent.Label; isShortcut = true; } if (label != null || LinkHelper.TryParseLabelTrivia(ref text, true, out label, out labelSpan)) { labelWithTrivia = new SourceSpan(labelSpan.Start, labelSpan.End); if (isLabelSpanLocal) { labelSpan = inlineState.GetSourcePositionFromLocalSpan(labelSpan); } if (ProcessLinkReference(inlineState, text, label !, labelWithTrivia, isShortcut, labelSpan, openParent, inlineState.GetSourcePosition(text.Start - 1), localLabel)) { // Remove the open parent openParent.Remove(); if (!openParent.IsImage) { MarkParentAsInactive(parentDelimiter); } return(true); } else if (text.CurrentChar != ']' && text.CurrentChar != '[') { return(false); } }