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.ProcessDelimiters(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); }
private bool ProcessLinkReference(InlineProcessor state, string label, SourceSpan labelSpan, LinkDelimiterInline parent, int endPosition) { bool isValidLink = false; LinkReferenceDefinition linkRef; if (state.Document.TryGetLinkReferenceDefinition(label, out linkRef)) { Inline link = null; // Try to use a callback directly defined on the LinkReferenceDefinition if (linkRef.CreateLinkInline != null) { link = linkRef.CreateLinkInline(state, linkRef, parent.FirstChild); } // Create a default link if the callback was not found if (link == null) { // Inline Link link = new LinkInline() { Url = HtmlHelper.Unescape(linkRef.Url), Title = HtmlHelper.Unescape(linkRef.Title), Label = label, LabelSpan = labelSpan, IsImage = parent.IsImage, Reference = linkRef, Span = new SourceSpan(parent.Span.Start, endPosition), Line = parent.Line, Column = parent.Column, }; } var containerLink = link as ContainerInline; if (containerLink != null) { var child = parent.FirstChild; if (child == null) { child = new LiteralInline() { Content = new StringSlice(label), IsClosed = true, // Not exact but we leave it like this Span = parent.Span, Line = parent.Line, Column = parent.Column, }; containerLink.AppendChild(child); } else { // Insert all child into the link while (child != null) { var next = child.NextSibling; child.Remove(); containerLink.AppendChild(child); child = next; } } } link.IsClosed = true; // Process emphasis delimiters state.ProcessDelimiters(0, link, null, false); state.Inline = link; isValidLink = true; } //else //{ // // Else output a literal, leave it opened as we may have literals after // // that could be append to this one // var literal = new LiteralInline() // { // ContentBuilder = processor.StringBuilders.Get().Append('[').Append(label).Append(']') // }; // processor.Inline = literal; //} return(isValidLink); }
public bool ProcessDelimiters(InlineProcessor state, Inline root, Inline lastChild, int delimiterProcessorIndex, bool isFinalProcessing) { var container = root as ContainerInline; var tableState = state.ParserStates[Index] as TableState; // If the delimiters are being processed by an image link, we need to transform them back to literals if (!isFinalProcessing) { if (container == null || tableState == null) { return(true); } var child = container.LastChild; List <PiprTableDelimiterInline> delimitersToRemove = null; while (child != null) { var pipeDelimiter = child as PiprTableDelimiterInline; if (pipeDelimiter != null) { if (delimitersToRemove == null) { delimitersToRemove = new List <PiprTableDelimiterInline>(); } delimitersToRemove.Add(pipeDelimiter); } if (child == lastChild) { break; } var subContainer = child as ContainerInline; child = subContainer?.LastChild; } // If we have found any delimiters, transform them to literals if (delimitersToRemove != null) { bool leftIsDelimiter = false; bool rightIsDelimiter = false; for (int i = 0; i < delimitersToRemove.Count; i++) { var pipeDelimiter = delimitersToRemove[i]; var literalInline = new LiteralInline() { Content = new StringSlice("|"), IsClosed = true }; pipeDelimiter.ReplaceBy(literalInline); // Check that the pipe that is being removed is not going to make a line without pipe delimiters var tableDelimiters = tableState.ColumnAndLineDelimiters; var delimiterIndex = tableDelimiters.IndexOf(pipeDelimiter); if (i == 0) { leftIsDelimiter = delimiterIndex > 0 && tableDelimiters[delimiterIndex - 1] is PiprTableDelimiterInline; } else if (i + 1 == delimitersToRemove.Count) { rightIsDelimiter = delimiterIndex + 1 < tableDelimiters.Count && tableDelimiters[delimiterIndex + 1] is PiprTableDelimiterInline; } // Remove this delimiter from the table processor tableState.ColumnAndLineDelimiters.Remove(pipeDelimiter); } // If we didn't have any delimiter before and after the delimiters we jsut removed, we mark the processor of the current line as no pipe if (!leftIsDelimiter && !rightIsDelimiter) { tableState.LineHasPipe = false; } } return(true); } // Continue if (tableState == null || container == null || tableState.IsInvalidTable || !tableState.LineHasPipe) //|| tableState.LineIndex != state.LocalLineIndex) { return(true); } var delimiters = tableState.ColumnAndLineDelimiters; delimiters.Add(null); var aligns = FindHeaderRow(delimiters); if (Options.RequireHeaderSeparator && aligns == null) { return(true); } var table = new Table(); // If the current paragraph block has any attributes attached, we can copy them var attributes = state.Block.TryGetAttributes(); if (attributes != null) { attributes.CopyTo(table.GetAttributes()); } state.BlockNew = table; TableRow firstRow = null; int maxColumn = 0; var cells = tableState.Cells; cells.Clear(); Inline column = container.FirstChild; if (column is PiprTableDelimiterInline) { column = ((PiprTableDelimiterInline)column).FirstChild; } // TODO: This is not accurate for the table table.Span.Start = column.Span.Start; table.Span.End = column.Span.End; table.Line = column.Line; table.Column = column.Column; int lastIndex = 0; for (int i = 0; i < delimiters.Count; i++) { var delimiter = delimiters[i]; if (delimiter == null || IsLine(delimiter)) { var beforeDelimiter = delimiter?.PreviousSibling; var nextLineColumn = delimiter?.NextSibling; TableRow row = null; for (int j = lastIndex; j <= i; j++) { var columnSeparator = delimiters[j]; var pipeSeparator = columnSeparator as PiprTableDelimiterInline; var endOfColumn = columnSeparator?.PreviousSibling; // This is the first column empty if (j == lastIndex && pipeSeparator != null && endOfColumn == null) { columnSeparator.Remove(); column = pipeSeparator.FirstChild; continue; } if (pipeSeparator != null && IsTrailingColumnDelimiter(pipeSeparator)) { TrimEnd(endOfColumn); columnSeparator.Remove(); continue; } var cellContainer = new ContainerInline(); var item = column; var isFirstItem = true; TrimStart(item); while (item != null && !IsLine(item) && !(item is PiprTableDelimiterInline)) { var nextSibling = item.NextSibling; item.Remove(); cellContainer.AppendChild(item); if (isFirstItem) { cellContainer.Line = item.Line; cellContainer.Column = item.Column; cellContainer.Span.Start = item.Span.Start; isFirstItem = false; } cellContainer.Span.End = item.Span.End; item = nextSibling; } var tableParagraph = new ParagraphBlock() { Span = cellContainer.Span, Line = cellContainer.Line, Column = cellContainer.Column, Inline = cellContainer }; var tableCell = new TableCell() { Span = cellContainer.Span, Line = cellContainer.Line, Column = cellContainer.Column, }; tableCell.Add(tableParagraph); if (row == null) { row = new TableRow() { Span = cellContainer.Span, Line = cellContainer.Line, Column = cellContainer.Column, }; } row.Add(tableCell); cells.Add(tableCell); // If we have reached the end, we can add remaining delimiters as pure child of the current cell if (row.Count == maxColumn && columnSeparator is PiprTableDelimiterInline) { columnSeparator.Remove(); tableParagraph.Inline.AppendChild(columnSeparator); break; } TrimEnd(endOfColumn); //TrimEnd(previousSibling); if (columnSeparator != null) { if (pipeSeparator != null) { column = pipeSeparator.FirstChild; } columnSeparator.Remove(); } } if (row != null) { table.Add(row); } TrimEnd(beforeDelimiter); if (delimiter != null) { delimiter.Remove(); } if (nextLineColumn != null) { column = nextLineColumn; } if (firstRow == null) { firstRow = row; maxColumn = firstRow.Count; } lastIndex = i + 1; } } // If we have a header row, we can remove it if (aligns != null) { table.RemoveAt(1); var tableRow = (TableRow)table[0]; table.ColumnDefinitions.AddRange(aligns); tableRow.IsHeader = true; } // Perform delimiter processor that are coming after this processor var delimiterProcessors = state.Parsers.DelimiterProcessors; for (int i = 0; i < delimiterProcessors.Length; i++) { if (delimiterProcessors[i] == this) { foreach (var cell in cells) { var paragraph = (ParagraphBlock)cell[0]; state.ProcessDelimiters(i + 1, paragraph.Inline, null, true); } break; } } // Clear cells when we are done cells.Clear(); // We don't want to continue procesing delimiters, as we are already processing them here return(false); }