public static InlineStack FindMatchingOpener(InlineStack seachBackwardsFrom, InlineStackPriority priority, char delimeter, out bool canClose) { canClose = true; var istack = seachBackwardsFrom; while (true) { if (istack == null) { // this cannot be a closer since there is no opener available. canClose = false; return null; } if (istack.Priority > priority || (istack.Delimeter == delimeter && 0 != (istack.Flags & InlineStackFlags.Closer))) { // there might be a closer further back but we cannot go there yet because a higher priority element is blocking // the other option is that the stack entry could be a closer for the same char - this means // that any opener we might find would first have to be matched against this closer. return null; } if (istack.Delimeter == delimeter) return istack; istack = istack.Previous; } }
private static Inline HandleLeftSquareBracket(Subject subj, bool isImage) { Inline inlText; if (isImage) { inlText = new Inline("![", subj.Position - 1, subj.Position + 1); } else { inlText = new Inline("[", subj.Position, subj.Position + 1); } // move past the '[' subj.Position++; var istack = new InlineStack(); istack.Delimeter = '['; istack.StartingInline = inlText; istack.StartPosition = subj.Position; istack.Priority = InlineStack.InlineStackPriority.Links; istack.Flags = InlineStack.InlineStackFlags.Opener | (isImage ? InlineStack.InlineStackFlags.ImageLink : InlineStack.InlineStackFlags.None); InlineStack.AppendStackEntry(istack, subj); return(inlText); }
public static Inline parse_inlines(Subject subj, Dictionary <string, Reference> refmap, Func <Subject, Inline>[] parsers, char[] specialCharacters) { var len = subj.Length; if (len == 0) { return(null); } var first = ParseInline(subj, parsers, specialCharacters); subj.LastInline = first.LastSibling; Inline cur; while (subj.Position < len) { cur = ParseInline(subj, parsers, specialCharacters); if (cur != null) { subj.LastInline.NextSibling = cur; subj.LastInline = cur.LastSibling; } } InlineStack.PostProcessInlineStack(subj, subj.FirstPendingInline, subj.LastPendingInline, InlineStack.InlineStackPriority.Maximum); return(first); }
public static InlineStack FindMatchingOpener(InlineStack seachBackwardsFrom, InlineStackPriority priority, char delimiter, out bool canClose) { canClose = true; var istack = seachBackwardsFrom; while (true) { if (istack == null) { // this cannot be a closer since there is no opener available. canClose = false; return(null); } if (istack.Priority > priority || (istack.Delimiter == delimiter && 0 != (istack.Flags & InlineStackFlags.Closer))) { // there might be a closer further back but we cannot go there yet because a higher priority element is blocking // the other option is that the stack entry could be a closer for the same char - this means // that any opener we might find would first have to be matched against this closer. return(null); } if (istack.Delimiter == delimiter) { return(istack); } istack = istack.Previous; } }
internal static void MatchSquareBracketStack(InlineStack opener, Subject subj, Reference details) { if (details != null) { var inl = opener.StartingInline; var isImage = 0 != (opener.Flags & InlineStack.InlineStackFlags.ImageLink); inl.Tag = isImage ? InlineTag.Image : InlineTag.Link; inl.FirstChild = inl.NextSibling; inl.NextSibling = null; inl.SourceLastPosition = subj.Position; inl.TargetUrl = details.Url; inl.LiteralContent = details.Title; if (!isImage) { // since there cannot be nested links, remove any other link openers before this var temp = opener.Previous; while (temp != null && temp.Priority <= InlineStack.InlineStackPriority.Links) { if (temp.Delimeter == '[' && temp.Flags == opener.Flags) { // mark the previous entries as "inactive" if (temp.DelimeterCount == -1) { break; } temp.DelimeterCount = -1; } temp = temp.Previous; } } InlineStack.RemoveStackEntry(opener, subj, null); if (subj != null) { subj.LastInline = inl; } } else { // this looked like a link, but was not. // remove the opener stack entry but leave the inbetween intact InlineStack.RemoveStackEntry(opener, subj, opener); var inl = new Inline("]", subj.Position - 1, subj.Position); subj.LastInline.LastSibling.NextSibling = inl; subj.LastInline = inl; } }
private static Inline HandleTilde(Subject subj) { bool canOpen, canClose; var numdelims = ScanEmphasisDelimeters(subj, '~', out canOpen, out canClose); if (numdelims == 1) { return(new Inline("~", subj.Position - 1, subj.Position)); } if (canClose) { // walk the stack and find a matching opener, if there is one var istack = InlineStack.FindMatchingOpener(subj.LastPendingInline, InlineStack.InlineStackPriority.Emphasis, '~', out canClose); if (istack != null) { MatchInlineStack(istack, subj, numdelims, null, null, InlineTag.Strikethrough); // if the closer was not fully used, move back a char or two and try again. if (numdelims > 2) { subj.Position = subj.Position - numdelims + 2; // use recursion only if it will not be very deep. if (numdelims < 10) { return(HandleTilde(subj)); } } return(null); } } var inlText = new Inline(subj.Buffer, subj.Position - numdelims, numdelims, subj.Position - numdelims, subj.Position); if (canOpen || canClose) { var istack = new InlineStack(); istack.DelimeterCount = numdelims; istack.Delimeter = '~'; istack.StartingInline = inlText; istack.Priority = InlineStack.InlineStackPriority.Emphasis; istack.Flags = (canOpen ? InlineStack.InlineStackFlags.Opener : 0) | (canClose ? InlineStack.InlineStackFlags.Closer : 0); InlineStack.AppendStackEntry(istack, subj); } return(inlText); }
private static Inline HandleOpenerCloser(Subject subj, InlineTag singleCharTag, InlineTag doubleCharTag) { bool canOpen, canClose; var c = subj.Buffer[subj.Position]; var numdelims = ScanEmphasisDelimiters(subj, c, out canOpen, out canClose); if (canClose) { // walk the stack and find a matching opener, if there is one var istack = InlineStack.FindMatchingOpener(subj.LastPendingInline, InlineStack.InlineStackPriority.Emphasis, c, numdelims, canOpen, out canClose); if (istack != null) { var useDelims = MatchInlineStack(istack, subj, numdelims, null, singleCharTag, doubleCharTag); if (useDelims > 0) { // if the closer was not fully used, move back a char or two and try again. if (useDelims < numdelims) { subj.Position = subj.Position - numdelims + useDelims; // use recursion only if it will not be very deep. // however it cannot be used if the single char will not be parsed. if (numdelims < 10) { return(HandleOpenerCloser(subj, singleCharTag, doubleCharTag)); } } return(null); } } } var inlText = new Inline(subj.Buffer, subj.Position - numdelims, numdelims, subj.Position - numdelims, subj.Position, c); if (canOpen || canClose) { var istack = new InlineStack(); istack.DelimiterCount = numdelims; istack.Delimiter = c; istack.StartingInline = inlText; istack.Priority = InlineStack.InlineStackPriority.Emphasis; istack.Flags = (canOpen ? InlineStack.InlineStackFlags.Opener : 0) | (canClose ? (InlineStack.InlineStackFlags.Closer | InlineStack.InlineStackFlags.CloserOriginally) : 0); InlineStack.AppendStackEntry(istack, subj); } return(inlText); }
public static void AppendStackEntry(InlineStack entry, Subject subj) { if (subj.LastPendingInline != null) { entry.Previous = subj.LastPendingInline; subj.LastPendingInline.Next = entry; } if (subj.FirstPendingInline == null) { subj.FirstPendingInline = entry; } subj.LastPendingInline = entry; }
public static InlineStack FindMatchingOpener(InlineStack searchBackwardsFrom, InlineStackPriority priority, char delimiter, int closerDelimiterCount, bool closerCanOpen, out bool canClose) { canClose = true; var istack = searchBackwardsFrom; while (true) { if (istack == null) { // this cannot be a closer since there is no opener available. canClose = false; return(null); } if (istack.Priority > priority || (istack.Delimiter == delimiter && 0 != (istack.Flags & InlineStackFlags.Closer))) { // there might be a closer further back but we cannot go there yet because a higher priority element is blocking // the other option is that the stack entry could be a closer for the same char - this means // that any opener we might find would first have to be matched against this closer. return(null); } if (istack.Delimiter == delimiter) { // interior closer of size 2 does not match opener of size 1 and vice versa. // for more details, see https://github.com/jgm/cmark/commit/c50197bab81d7105c9c790548821b61bcb97a62a var oddMatch = (closerCanOpen || (istack.Flags & InlineStackFlags.CloserOriginally) > 0) && istack.DelimiterCount != closerDelimiterCount && ((istack.DelimiterCount + closerDelimiterCount) % 3 == 0); if (!oddMatch) { return(istack); } } istack = istack.Previous; } }
public static InlineStack FindMatchingOpener(InlineStack searchBackwardsFrom, InlineStackPriority priority, char delimiter, int closerDelimiterCount, bool closerCanOpen, out bool canClose) { canClose = true; var istack = searchBackwardsFrom; while (true) { if (istack == null) { // this cannot be a closer since there is no opener available. canClose = false; return null; } if (istack.Priority > priority || (istack.Delimiter == delimiter && 0 != (istack.Flags & InlineStackFlags.Closer))) { // there might be a closer further back but we cannot go there yet because a higher priority element is blocking // the other option is that the stack entry could be a closer for the same char - this means // that any opener we might find would first have to be matched against this closer. return null; } if (istack.Delimiter == delimiter) { // interior closer of size 2 does not match opener of size 1 and vice versa. // for more details, see https://github.com/jgm/cmark/commit/c50197bab81d7105c9c790548821b61bcb97a62a var oddMatch = (closerCanOpen || (istack.Flags & InlineStackFlags.CloserOriginally) > 0) && istack.DelimiterCount != closerDelimiterCount && ((istack.DelimiterCount + closerDelimiterCount) % 3 == 0); if (!oddMatch) return istack; } istack = istack.Previous; } }
private static Inline HandleOpenerCloser(Subject subj, InlineTag singleCharTag, InlineTag doubleCharTag, InlineParserParameters parameters) { bool canOpen, canClose; var c = subj.Buffer[subj.Position]; var numdelims = ScanEmphasisDelimeters(subj, c, out canOpen, out canClose); if (canClose) { // walk the stack and find a matching opener, if there is one var istack = InlineStack.FindMatchingOpener(subj.LastPendingInline, InlineStack.InlineStackPriority.Emphasis, c, out canClose); if (istack != null) { var useDelims = MatchInlineStack(istack, subj, numdelims, null, singleCharTag, doubleCharTag, parameters); // if the closer was not fully used, move back a char or two and try again. if (useDelims < numdelims) { subj.Position = subj.Position - numdelims + useDelims; // use recursion only if it will not be very deep. if (numdelims < 10) return HandleOpenerCloser(subj, singleCharTag, doubleCharTag, parameters); } return null; } } var inlText = new Inline(subj.Buffer, subj.Position - numdelims, numdelims, subj.Position - numdelims, subj.Position, c); if (canOpen || canClose) { var istack = new InlineStack(); istack.DelimeterCount = numdelims; istack.Delimeter = c; istack.StartingInline = inlText; istack.Priority = InlineStack.InlineStackPriority.Emphasis; istack.Flags = (canOpen ? InlineStack.InlineStackFlags.Opener : 0) | (canClose ? InlineStack.InlineStackFlags.Closer : 0); InlineStack.AppendStackEntry(istack, subj); } return inlText; }
/// <summary> /// Removes a subset of the stack. /// </summary> /// <param name="first">The first entry to be removed.</param> /// <param name="subj">The subject associated with this stack. Can be <see langword="null"/> if the pointers in the subject should not be updated.</param> /// <param name="last">The last entry to be removed. Can be <see langword="null"/> if everything starting from <paramref name="first"/> has to be removed.</param> public static void RemoveStackEntry(InlineStack first, Subject subj, InlineStack last) { var curPriority = first.Priority; if (last == null) { if (first.Previous != null) { first.Previous.Next = null; } else if (subj != null) { subj.FirstPendingInline = null; } if (subj != null) { last = subj.LastPendingInline; subj.LastPendingInline = first.Previous; } first = first.Next; } else { if (first.Previous != null) { first.Previous.Next = last.Next; } else if (subj != null) { subj.FirstPendingInline = last.Next; } if (last.Next != null) { last.Next.Previous = first.Previous; } else if (subj != null) { subj.LastPendingInline = first.Previous; } if (first == last) { return; } first = first.Next; last = last.Previous; } if (last == null || first == null) { return; } first.Previous = null; last.Next = null; // handle case like [*b*] (the whole [..] is being removed but the inner *..* must still be matched). // this is not done automatically because the initial * is recognized as a potential closer (assuming // potential scenario '*[*' ). if (curPriority > 0) { PostProcessInlineStack(null, first, last, curPriority); } }
public static void PostProcessInlineStack(Subject subj, InlineStack first, InlineStack last, InlineStackPriority ignorePriority) { while (ignorePriority > 0) { var istack = first; while (istack != null) { if (istack.Priority >= ignorePriority) { RemoveStackEntry(istack, subj, istack); } else if (0 != (istack.Flags & InlineStackFlags.Closer)) { bool canClose; var iopener = FindMatchingOpener(istack.Previous, istack.Priority, istack.Delimiter, istack.DelimiterCount, (istack.Flags & InlineStackFlags.Opener) > 0, out canClose); if (iopener != null) { bool retry = false; if (iopener.Delimiter == '~') { InlineMethods.MatchInlineStack(iopener, subj, istack.DelimiterCount, istack, (InlineTag)0, InlineTag.Strikethrough); if (istack.DelimiterCount > 1) { retry = true; } } else { var useDelims = InlineMethods.MatchInlineStack(iopener, subj, istack.DelimiterCount, istack, InlineTag.Emphasis, InlineTag.Strong); if (istack.DelimiterCount > 0) { retry = true; } } if (retry) { // remove everything between opened and closer (not inclusive). if (istack.Previous != null && iopener.Next != istack.Previous) { RemoveStackEntry(iopener.Next, subj, istack.Previous); } continue; } else { // remove opener, everything in between, and the closer RemoveStackEntry(iopener, subj, istack); } } else if (!canClose) { // this case means that a matching opener does not exist // remove the Closer flag so that a future Opener can be matched against it. istack.Flags &= ~InlineStackFlags.Closer; } } if (istack == last) { break; } istack = istack.Next; } ignorePriority--; } }
private static Inline HandleRightSquareBracket(Subject subj) { // move past ']' subj.Position++; bool canClose; var istack = InlineStack.FindMatchingOpener(subj.LastPendingInline, InlineStack.InlineStackPriority.Links, '[', out canClose); if (istack != null) { // if the opener is "inactive" then it means that there was a nested link if (istack.DelimeterCount == -1) { InlineStack.RemoveStackEntry(istack, subj, istack); return(new Inline("]", subj.Position - 1, subj.Position)); } var endpos = subj.Position; // try parsing details for '[foo](/url "title")' or '[foo][bar]' var details = ParseLinkDetails(subj); // try lookup of the brackets themselves if (details == null || details == Reference.SelfReference) { var startpos = istack.StartPosition; var label = new StringPart(subj.Buffer, startpos, endpos - startpos - 1); details = LookupReference(subj.ReferenceMap, label); } if (details == Reference.InvalidReference) { details = null; } MatchSquareBracketStack(istack, subj, details); return(null); } var inlText = new Inline("]", subj.Position - 1, subj.Position); if (canClose) { // note that the current implementation will not work if there are other inlines with priority // higher than Links. // to fix this the parsed link details should be added to the closer element in the stack. throw new NotSupportedException("It is not supported to have inline stack priority higher than Links."); ////istack = new InlineStack(); ////istack.Delimeter = '['; ////istack.StartingInline = inlText; ////istack.StartPosition = subj.Position; ////istack.Priority = InlineStack.InlineStackPriority.Links; ////istack.Flags = InlineStack.InlineStackFlags.Closer; ////InlineStack.AppendStackEntry(istack, subj); } return(inlText); }
public static void PostProcessInlineStack(Subject subj, InlineStack first, InlineStack last, InlineStackPriority ignorePriority) { while (ignorePriority > 0) { var istack = first; while (istack != null) { if (istack.Priority >= ignorePriority) { RemoveStackEntry(istack, subj, istack); } else if (0 != (istack.Flags & InlineStackFlags.Closer)) { bool canClose; var iopener = FindMatchingOpener(istack.Previous, istack.Priority, istack.Delimeter, out canClose); if (iopener != null) { bool retry = false; if (iopener.Delimeter == '~') { InlineMethods.MatchInlineStack(iopener, subj, istack.DelimeterCount, istack, null, InlineTag.Strikethrough); if (istack.DelimeterCount > 1) retry = true; } else { var useDelims = InlineMethods.MatchInlineStack(iopener, subj, istack.DelimeterCount, istack, InlineTag.Emphasis, InlineTag.Strong); if (istack.DelimeterCount > 0) retry = true; } if (retry) { // remove everything between opened and closer (not inclusive). if (istack.Previous != null && iopener.Next != istack.Previous) RemoveStackEntry(iopener.Next, subj, istack.Previous); continue; } else { // remove opener, everything in between, and the closer RemoveStackEntry(iopener, subj, istack); } } else if (!canClose) { // this case means that a matching opener does not exist // remove the Closer flag so that a future Opener can be matched against it. istack.Flags &= ~InlineStackFlags.Closer; } } if (istack == last) break; istack = istack.Next; } ignorePriority--; } }
/// <summary> /// Removes a subset of the stack. /// </summary> /// <param name="first">The first entry to be removed.</param> /// <param name="subj">The subject associated with this stack. Can be <c>null</c> if the pointers in the subject should not be updated.</param> /// <param name="last">The last entry to be removed. Can be <c>null</c> if everything starting from <paramref name="first"/> has to be removed.</param> /// <param name="parameters">Inline parser parameters.</param> public static void RemoveStackEntry(InlineStack first, Subject subj, InlineStack last, InlineParserParameters parameters) { var curPriority = first.Priority; if (last == null) { if (first.Previous != null) first.Previous.Next = null; else if (subj != null) subj.FirstPendingInline = null; if (subj != null) { last = subj.LastPendingInline; subj.LastPendingInline = first.Previous; } first = first.Next; } else { if (first.Previous != null) first.Previous.Next = last.Next; else if (subj != null) subj.FirstPendingInline = last.Next; if (last.Next != null) last.Next.Previous = first.Previous; else if (subj != null) subj.LastPendingInline = first.Previous; if (first == last) return; first = first.Next; last = last.Previous; } if (last == null || first == null) return; first.Previous = null; last.Next = null; // handle case like [*b*] (the whole [..] is being removed but the inner *..* must still be matched). // this is not done automatically because the initial * is recognized as a potential closer (assuming // potential scenario '*[*' ). if (curPriority > 0) PostProcessInlineStack(null, first, last, curPriority, parameters); }
private static Inline HandleTilde(Subject subj) { bool canOpen, canClose; var numdelims = ScanEmphasisDelimeters(subj, '~', out canOpen, out canClose); if (numdelims == 1) return new Inline("~", subj.Position - 1, subj.Position); if (canClose) { // walk the stack and find a matching opener, if there is one var istack = InlineStack.FindMatchingOpener(subj.LastPendingInline, InlineStack.InlineStackPriority.Emphasis, '~', out canClose); if (istack != null) { MatchInlineStack(istack, subj, numdelims, null, null, InlineTag.Strikethrough); // if the closer was not fully used, move back a char or two and try again. if (numdelims > 2) { subj.Position = subj.Position - numdelims + 2; // use recursion only if it will not be very deep. if (numdelims < 10) return HandleTilde(subj); } return null; } } var inlText = new Inline(subj.Buffer, subj.Position - numdelims, numdelims, subj.Position - numdelims, subj.Position); if (canOpen || canClose) { var istack = new InlineStack(); istack.DelimeterCount = numdelims; istack.Delimeter = '~'; istack.StartingInline = inlText; istack.Priority = InlineStack.InlineStackPriority.Emphasis; istack.Flags = (canOpen ? InlineStack.InlineStackFlags.Opener : 0) | (canClose ? InlineStack.InlineStackFlags.Closer : 0); InlineStack.AppendStackEntry(istack, subj); } return inlText; }
public static void PostProcessInlineStack(Subject subj, InlineStack first, InlineStack last, InlineStackPriority ignorePriority, InlineParserParameters parameters) { var singleCharTags = parameters.SingleCharTags; var doubleCharTags = parameters.DoubleCharTags; while (ignorePriority > 0) { var istack = first; while (istack != null) { if (istack.Priority >= ignorePriority) { RemoveStackEntry(istack, subj, istack, parameters); } else if (0 != (istack.Flags & InlineStackFlags.Closer)) { bool canClose; var iopener = FindMatchingOpener(istack.Previous, istack.Priority, istack.Delimeter, out canClose); if (iopener != null) { bool retry = false; var singleCharTag = iopener.Delimeter < singleCharTags.Length ? singleCharTags[iopener.Delimeter] : (InlineTag)0; var doubleCharTag = iopener.Delimeter < doubleCharTags.Length ? doubleCharTags[iopener.Delimeter] : (InlineTag)0; if (singleCharTag != 0 || doubleCharTag != 0) { var useDelims = InlineMethods.MatchInlineStack(iopener, subj, istack.DelimeterCount, istack, singleCharTag, doubleCharTag, parameters); if (istack.DelimeterCount > 0) retry = true; } if (retry) { // remove everything between opened and closer (not inclusive). if (istack.Previous != null && iopener.Next != istack.Previous) RemoveStackEntry(iopener.Next, subj, istack.Previous, parameters); continue; } else { // remove opener, everything in between, and the closer RemoveStackEntry(iopener, subj, istack, parameters); } } else if (!canClose) { // this case means that a matching opener does not exist // remove the Closer flag so that a future Opener can be matched against it. istack.Flags &= ~InlineStackFlags.Closer; } } if (istack == last) break; istack = istack.Next; } ignorePriority--; } }
internal static int MatchInlineStack(InlineStack opener, Subject subj, int closingDelimeterCount, InlineStack closer, InlineTag?singleCharTag, InlineTag?doubleCharTag) { // calculate the actual number of delimeters used from this closer int useDelims; var openerDelims = opener.DelimeterCount; if (closingDelimeterCount < 3 || openerDelims < 3) { useDelims = closingDelimeterCount <= openerDelims ? closingDelimeterCount : openerDelims; if (useDelims == 1 && singleCharTag == null) { return(0); } } else if (singleCharTag == null) { useDelims = 2; } else if (doubleCharTag == null) { useDelims = 1; } else { useDelims = closingDelimeterCount % 2 == 0 ? 2 : 1; } Inline inl = opener.StartingInline; if (openerDelims == useDelims) { // the opener is completely used up - remove the stack entry and reuse the inline element inl.Tag = useDelims == 1 ? singleCharTag.Value : doubleCharTag.Value; inl.LiteralContent = null; inl.FirstChild = inl.NextSibling; inl.NextSibling = null; InlineStack.RemoveStackEntry(opener, subj, closer.Previous); } else { // the opener will only partially be used - stack entry remains (truncated) and a new inline is added. opener.DelimeterCount -= useDelims; inl.LiteralContent = inl.LiteralContent.Substring(0, opener.DelimeterCount); inl.SourceLastPosition -= useDelims; inl.NextSibling = new Inline(useDelims == 1 ? singleCharTag.Value : doubleCharTag.Value, inl.NextSibling); inl = inl.NextSibling; inl.SourcePosition = opener.StartingInline.SourcePosition + opener.DelimeterCount; } // there are two callers for this method, distinguished by the `closer` argument. // if closer == null it means the method is called during the initial subject parsing and the closer // characters are at the current position in the subject. The main benefit is that there is nothing // parsed that is located after the matched inline element. // if closer != null it means the method is called when the second pass for previously unmatched // stack elements is done. The drawback is that there can be other elements after the closer. if (closer != null) { var clInl = closer.StartingInline; if ((closer.DelimeterCount -= useDelims) > 0) { // a new inline element must be created because the old one has to be the one that // finalizes the children of the emphasis var newCloserInline = new Inline(clInl.LiteralContent.Substring(useDelims)); newCloserInline.SourcePosition = inl.SourceLastPosition = clInl.SourcePosition + useDelims; newCloserInline.SourceLength = closer.DelimeterCount; newCloserInline.NextSibling = clInl.NextSibling; clInl.LiteralContent = null; clInl.NextSibling = null; inl.NextSibling = closer.StartingInline = newCloserInline; } else { inl.SourceLastPosition = clInl.SourceLastPosition; clInl.LiteralContent = null; inl.NextSibling = clInl.NextSibling; clInl.NextSibling = null; } } else if (subj != null) { inl.SourceLastPosition = subj.Position - closingDelimeterCount + useDelims; subj.LastInline = inl; } return(useDelims); }
internal static void MatchSquareBracketStack(InlineStack opener, Subject subj, Reference details, InlineParserParameters parameters) { if (details != null) { var inl = opener.StartingInline; var isImage = 0 != (opener.Flags & InlineStack.InlineStackFlags.ImageLink); inl.Tag = isImage ? InlineTag.Image : InlineTag.Link; inl.FirstChild = inl.NextSibling; inl.NextSibling = null; inl.SourceLastPosition = subj.Position; inl.TargetUrl = details.Url; inl.LiteralContent = details.Title; if (!isImage) { // since there cannot be nested links, remove any other link openers before this var temp = opener.Previous; while (temp != null && temp.Priority <= InlineStack.InlineStackPriority.Links) { if (temp.Delimeter == '[' && temp.Flags == opener.Flags) { // mark the previous entries as "inactive" if (temp.DelimeterCount == -1) break; temp.DelimeterCount = -1; } temp = temp.Previous; } } InlineStack.RemoveStackEntry(opener, subj, null, parameters); subj.LastInline = inl; } else { // this looked like a link, but was not. // remove the opener stack entry but leave the inbetween intact InlineStack.RemoveStackEntry(opener, subj, opener, parameters); var inl = new Inline("]", subj.Position - 1, subj.Position); subj.LastInline.LastSibling.NextSibling = inl; subj.LastInline = inl; } }
private static Inline HandleLeftSquareBracket(Subject subj, bool isImage, CommonMarkSettings settings) { Inline inlText; if (isImage) { inlText = new Inline("![", subj.Position - 1, subj.Position + 1); } else { inlText = new Inline("[", subj.Position, subj.Position + 1); } // move past the '[' subj.Position++; var istack = new InlineStack(); istack.Delimeter = '['; istack.StartingInline = inlText; istack.StartPosition = subj.Position; istack.Priority = InlineStack.InlineStackPriority.Links; istack.Flags = InlineStack.InlineStackFlags.Opener | (isImage ? InlineStack.InlineStackFlags.ImageLink : InlineStack.InlineStackFlags.None); InlineStack.AppendStackEntry(istack, subj); return inlText; }
public static void AppendStackEntry(InlineStack entry, Subject subj) { if (subj.LastPendingInline != null) { entry.Previous = subj.LastPendingInline; subj.LastPendingInline.Next = entry; } if (subj.FirstPendingInline == null) subj.FirstPendingInline = entry; subj.LastPendingInline = entry; }
internal static int MatchInlineStack(InlineStack opener, Subject subj, int closingDelimeterCount, InlineStack closer, InlineTag singleCharTag, InlineTag doubleCharTag, InlineParserParameters parameters) { // calculate the actual number of delimeters used from this closer int useDelims; var openerDelims = opener.DelimeterCount; if (closingDelimeterCount < 3 || openerDelims < 3) { useDelims = closingDelimeterCount <= openerDelims ? closingDelimeterCount : openerDelims; if (useDelims == 2 && doubleCharTag == 0) useDelims = 1; if (useDelims == 1 && singleCharTag == 0) return 0; } else if (singleCharTag == 0) useDelims = 2; else if (doubleCharTag == 0) useDelims = 1; else useDelims = closingDelimeterCount % 2 == 0 ? 2 : 1; Inline inl = opener.StartingInline; InlineTag tag = useDelims == 1 ? singleCharTag : doubleCharTag; if (openerDelims == useDelims) { // the opener is completely used up - remove the stack entry and reuse the inline element inl.Tag = tag; inl.LiteralContent = null; inl.FirstChild = inl.NextSibling; inl.NextSibling = null; InlineStack.RemoveStackEntry(opener, subj, closer?.Previous, parameters); } else { // the opener will only partially be used - stack entry remains (truncated) and a new inline is added. opener.DelimeterCount -= useDelims; inl.LiteralContent = inl.LiteralContent.Substring(0, opener.DelimeterCount); inl.SourceLastPosition -= useDelims; inl.NextSibling = new Inline(tag, inl.NextSibling); inl = inl.NextSibling; inl.SourcePosition = opener.StartingInline.SourcePosition + opener.DelimeterCount; } // there are two callers for this method, distinguished by the `closer` argument. // if closer == null it means the method is called during the initial subject parsing and the closer // characters are at the current position in the subject. The main benefit is that there is nothing // parsed that is located after the matched inline element. // if closer != null it means the method is called when the second pass for previously unmatched // stack elements is done. The drawback is that there can be other elements after the closer. if (closer != null) { var clInl = closer.StartingInline; if ((closer.DelimeterCount -= useDelims) > 0) { // a new inline element must be created because the old one has to be the one that // finalizes the children of the emphasis var newCloserInline = new Inline(clInl.LiteralContent.Substring(useDelims)); newCloserInline.SourcePosition = inl.SourceLastPosition = clInl.SourcePosition + useDelims; newCloserInline.SourceLength = closer.DelimeterCount; newCloserInline.NextSibling = clInl.NextSibling; clInl.LiteralContent = null; clInl.NextSibling = null; inl.NextSibling = closer.StartingInline = newCloserInline; } else { inl.SourceLastPosition = clInl.SourceLastPosition; clInl.LiteralContent = null; inl.NextSibling = clInl.NextSibling; clInl.NextSibling = null; } } else if (subj != null) { inl.SourceLastPosition = subj.Position - closingDelimeterCount + useDelims; subj.LastInline = inl; } return useDelims; }
private static Syntax.Inline HandleEmphasis(Subject subj, char c) { bool can_open = false, can_close = false; var numdelims = scan_delims(subj, c, out can_open, out can_close); if (can_close) { // walk the stack and find a matching opener, if there is one var istack = subj.EmphasisStack; while (true) { if (istack == null) goto cannotClose; // the only combination that is not processed is **foo* if ((istack.DelimeterCount != 2 || numdelims != 1) && istack.Delimeter == c) break; istack = istack.Previous; } // calculate the actual number of delimeters used from this closer var useDelims = istack.DelimeterCount; if (useDelims == 3) useDelims = numdelims == 3 ? 1 : numdelims; else if (useDelims > numdelims) useDelims = 1; if (istack.DelimeterCount == useDelims) { // the opener is completely used up - remove the stack entry and reuse the inline element var inl = istack.StartingInline; inl.Tag = useDelims == 1 ? InlineTag.Emphasis : InlineTag.Strong; inl.Content.Literal = null; inl.Content.Inlines = inl.Next; inl.Next = null; subj.EmphasisStack = istack.Previous; istack.Previous = null; subj.LastInline = inl; } else { // the opener will only partially be used - stack entry remains (truncated) and a new inline is added. var inl = istack.StartingInline; istack.DelimeterCount -= useDelims; inl.Content.Literal = istack.StartingInline.Content.Literal.Substring(0, istack.DelimeterCount); var emph = useDelims == 1 ? make_emph(inl.Next) : make_strong(inl.Next); inl.Next = emph; subj.LastInline = emph; } // if the closer was not fully used, move back a char or two and try again. if (useDelims < numdelims) { subj.Position = subj.Position - numdelims + useDelims; return HandleEmphasis(subj, c); } return make_str(string.Empty); } cannotClose: var inlText = make_str(BString.bmidstr(subj.Buffer, subj.Position - numdelims, numdelims)); if (can_open) { var istack = new InlineStack(); istack.DelimeterCount = numdelims; istack.Delimeter = c; istack.StartingInline = inlText; istack.Previous = subj.EmphasisStack; subj.EmphasisStack = istack; } return inlText; }