/// <summary> /// Parsing helper method. /// </summary> /// <param name="listItem"></param> /// <param name="markdown"></param> /// <param name="start"></param> /// <param name="end"></param> private static void AppendTextToListItem(ListItemBlock listItem, string markdown, int start, int end) { ListItemBuilder listItemBuilder = null; if (listItem.Blocks.Count > 0) { listItemBuilder = listItem.Blocks[listItem.Blocks.Count - 1] as ListItemBuilder; } if (listItemBuilder == null) { // Add a new block. listItemBuilder = new ListItemBuilder(); listItem.Blocks.Add(listItemBuilder); } var builder = listItemBuilder.Builder; if (builder.Length >= 2 && Common.IsWhiteSpace(builder[builder.Length - 2]) && Common.IsWhiteSpace(builder[builder.Length - 1])) { builder.Length -= 2; builder.AppendLine(); } else if (builder.Length > 0) { builder.Append(' '); } builder.Append(markdown.Substring(start, end - start)); }
/// <summary> /// Parsing helper method. /// </summary> /// <param name="listItem"></param> /// <param name="markdown"></param> /// <param name="start"></param> /// <param name="end"></param> private static bool AppendTextToListItem(ListItemBlock listItem, string markdown, int start, int end) { ListItemBuilder listItemBuilder = null; if (listItem.Blocks.Count > 0) { listItemBuilder = listItem.Blocks[listItem.Blocks.Count - 1] as ListItemBuilder; } if (listItemBuilder == null) { // Add a new block. listItemBuilder = new ListItemBuilder(); listItem.Blocks.Add(listItemBuilder); } var builder = listItemBuilder.Builder; if (builder.Length >= 2 && Common.IsWhiteSpace(builder[builder.Length - 2]) && Common.IsWhiteSpace(builder[builder.Length - 1])) { builder.Length -= 2; builder.AppendLine(); } else if (builder.Length > 0) { builder.Append(' '); } var str = markdown.Substring(start, end - start); bool codeTag = false; if (str.StartsWith("```")) { builder.AppendLine(); codeTag = true; } builder.AppendLine(str); return(codeTag); }
/// <summary> /// Parsing helper method. /// </summary> /// <param name="listItem"></param> /// <param name="markdown"></param> /// <param name="start"></param> /// <param name="end"></param> private static void AppendTextToListItem(ListItemBlock listItem, string markdown, int start, int end) { ListItemBuilder listItemBuilder = null; if (listItem.Blocks.Count > 0) listItemBuilder = listItem.Blocks[listItem.Blocks.Count - 1] as ListItemBuilder; if (listItemBuilder == null) { // Add a new block. listItemBuilder = new ListItemBuilder(); listItem.Blocks.Add(listItemBuilder); } var builder = listItemBuilder.Builder; if (builder.Length >= 2 && Common.IsWhiteSpace(builder[builder.Length - 2]) && Common.IsWhiteSpace(builder[builder.Length - 1])) { builder.Length -= 2; builder.AppendLine(); } else if (builder.Length > 0) builder.Append(' '); builder.Append(markdown.Substring(start, end - start)); }
/// <summary> /// Parses a list block. /// </summary> /// <param name="markdown"> The markdown text. </param> /// <param name="start"> The location of the first character in the block. </param> /// <param name="maxEnd"> The location to stop parsing. </param> /// <param name="quoteDepth"> The current nesting level for block quoting. </param> /// <param name="actualEnd"> Set to the end of the block when the return value is non-null. </param> /// <returns> A parsed list block, or <c>null</c> if this is not a list block. </returns> internal static ListBlock Parse(string markdown, int start, int maxEnd, int quoteDepth, out int actualEnd) { var russianDolls = new List<NestedListInfo>(); int russianDollIndex = -1; bool previousLineWasBlank = false; ListItemBlock currentListItem = null; actualEnd = start; foreach (var lineInfo in Common.ParseLines(markdown, start, maxEnd, quoteDepth)) { // Is this line blank? if (lineInfo.IsLineBlank) { // The line is blank, which means the next line which contains text may end the list (or it may not...). previousLineWasBlank = true; } else { // Does the line contain a list item? ListItemPreamble listItemPreamble = null; if (lineInfo.FirstNonWhitespaceChar - lineInfo.StartOfLine < (russianDollIndex + 2) * 4) listItemPreamble = ParseItemPreamble(markdown, lineInfo.FirstNonWhitespaceChar, lineInfo.EndOfLine); if (listItemPreamble != null) { // Yes, this line contains a list item. // Determining the nesting level is done as follows: // 1. If this is the first line, then the list is not nested. // 2. If the number of spaces at the start of the line is equal to that of // an existing list, then the nesting level is the same as that list. // 3. Otherwise, if the number of spaces is 0-4, then the nesting level // is one level deep. // 4. Otherwise, if the number of spaces is 5-8, then the nesting level // is two levels deep (but no deeper than one level more than the // previous list item). // 5. Etcetera. ListBlock listToAddTo = null; int spaceCount = lineInfo.FirstNonWhitespaceChar - lineInfo.StartOfLine; russianDollIndex = russianDolls.FindIndex(rd => rd.SpaceCount == spaceCount); if (russianDollIndex >= 0) { // Add the new list item to an existing list. listToAddTo = russianDolls[russianDollIndex].List; // Don't add new list items to items higher up in the list. russianDolls.RemoveRange(russianDollIndex + 1, russianDolls.Count - (russianDollIndex + 1)); } else { russianDollIndex = Math.Max(1, 1 + (spaceCount - 1) / 4); if (russianDollIndex < russianDolls.Count) { // Add the new list item to an existing list. listToAddTo = russianDolls[russianDollIndex].List; // Don't add new list items to items higher up in the list. russianDolls.RemoveRange(russianDollIndex + 1, russianDolls.Count - (russianDollIndex + 1)); } else { // Create a new list. listToAddTo = new ListBlock { Style = listItemPreamble.Style, Items = new List<ListItemBlock>() }; if (russianDolls.Count > 0) currentListItem.Blocks.Add(listToAddTo); russianDollIndex = russianDolls.Count; russianDolls.Add(new NestedListInfo { List = listToAddTo, SpaceCount = spaceCount }); } } // Add a new list item. currentListItem = new ListItemBlock() { Blocks = new List<MarkdownBlock>() }; listToAddTo.Items.Add(currentListItem); // Add the rest of the line to the builder. AppendTextToListItem(currentListItem, markdown, listItemPreamble.ContentStartPos, lineInfo.EndOfLine); } else { // No, this line contains text. // Is there even a list in progress? if (currentListItem == null) { actualEnd = start; return null; } // 0 spaces = end of the list. // 1-4 spaces = first level. // 5-8 spaces = second level, etc. if (previousLineWasBlank) { // This is the start of a new paragraph. int spaceCount = lineInfo.FirstNonWhitespaceChar - lineInfo.StartOfLine; if (spaceCount == 0) break; russianDollIndex = Math.Min(russianDollIndex, (spaceCount - 1) / 4); ListBlock listToAddTo = russianDolls[russianDollIndex].List; currentListItem = listToAddTo.Items[listToAddTo.Items.Count - 1]; currentListItem.Blocks.Add(new ListItemBuilder()); AppendTextToListItem(currentListItem, markdown, Math.Min(lineInfo.FirstNonWhitespaceChar, lineInfo.StartOfLine + (russianDollIndex + 1) * 4), lineInfo.EndOfLine); } else { // Inline text. AppendTextToListItem(currentListItem, markdown, lineInfo.FirstNonWhitespaceChar, lineInfo.EndOfLine); } } // The line was not blank. previousLineWasBlank = false; } // Go to the next line. actualEnd = lineInfo.EndOfLine; } var result = russianDolls[0].List; ReplaceStringBuilders(result); return result; }
/// <summary> /// Parses a list block. /// </summary> /// <param name="markdown"> The markdown text. </param> /// <param name="start"> The location of the first character in the block. </param> /// <param name="maxEnd"> The location to stop parsing. </param> /// <param name="quoteDepth"> The current nesting level for block quoting. </param> /// <param name="actualEnd"> Set to the end of the block when the return value is non-null. </param> /// <returns> A parsed list block, or <c>null</c> if this is not a list block. </returns> internal static ListBlock Parse(string markdown, int start, int maxEnd, int quoteDepth, out int actualEnd) { actualEnd = start; ListItemBlock currentListItem = null; ListBlock listToAddTo = null; var listStack = new Stack <ListBlock>(); var level = 0; var lastLevel = -1; var previousLineWasBlank = false; var previousLineWasListHeader = false; var isCodeBlock = false; var lastStype = ListStyle.Bulleted; foreach (var lineInfo in Common.ParseLines(markdown, start, maxEnd, quoteDepth)) { // Is this line blank? if (lineInfo.IsLineBlank) { // The line is blank, which means the next line // which contains text may end the list (or it may not...). previousLineWasBlank = true; } else { // Does the line contain a list item? ListItemPreamble listItemPreamble = null; if (lineInfo.FirstNonWhitespaceChar - lineInfo.StartOfLine < (level + 2) * 4) { listItemPreamble = ParseItemPreamble(markdown, lineInfo.FirstNonWhitespaceChar, lineInfo.EndOfLine); } if (listItemPreamble != null) { previousLineWasListHeader = true; // Yes, this line contains a list item. // Determining the nesting level is done as follows: // 1. If this is the first line, then the list is not nested. // 2. If the number of spaces at the start of the line is equal to that of // an existing list, then the nesting level is the same as that list. // 3. Otherwise, if the number of spaces is 0-4, then the nesting level // is one level deep. // 4. Otherwise, if the number of spaces is 5-8, then the nesting level // is two levels deep (but no deeper than one level more than the // previous list item). // 5. Etcetera. var info = markdown.Substring(lineInfo.StartOfLine, lineInfo.EndOfLine - lineInfo.StartOfLine); //Console.WriteLine(info); // Stack if (markdown.Substring(lineInfo.StartOfLine).TrimStart().StartsWith("- [gdb with lua")) { int jj = 0; } int spaceCount = lineInfo.FirstNonWhitespaceChar - lineInfo.StartOfLine; level = GetLevel(markdown.Substring(lineInfo.StartOfLine, spaceCount)); if (level > lastLevel) { // Create a new list. var newlistToAddTo = new ListBlock { Style = listItemPreamble.Style, Items = new List <ListItemBlock>() }; lastStype = newlistToAddTo.Style; listStack.Push(newlistToAddTo); if (currentListItem != null) { currentListItem.Blocks.Add(newlistToAddTo); } listToAddTo = newlistToAddTo; } else if (level < lastLevel) { int popCount = lastLevel - level; while (popCount > 0) { popCount--; listStack.Pop(); } listToAddTo = listStack.Peek(); } // Add a new list item. currentListItem = new ListItemBlock { Blocks = new List <MarkdownBlock>() }; listToAddTo.Items.Add(currentListItem); // Add the rest of the line to the builder. AppendTextToListItem(currentListItem, markdown, listItemPreamble.ContentStartPos, lineInfo.EndOfLine); lastLevel = level; } else { // No, this line contains text. // Is there even a list in progress? if (currentListItem == null) { actualEnd = start; return(null); } // -------------------------------- // // 0 spaces = end of the list. // // 1-4 spaces = first level. // // 5-8 spaces = second level, etc. // // -------------------------------- // bool codeTag = false; if ((previousLineWasBlank) && (!isCodeBlock)) { // This is the start of a new paragraph. int spaceCount = lineInfo.FirstNonWhitespaceChar - lineInfo.StartOfLine; if (spaceCount == 0) { break; } level = GetLevel(markdown.Substring(lineInfo.StartOfLine, spaceCount)); int popCount = lastLevel - level; while (popCount > 0) { popCount--; listStack.Pop(); } listToAddTo = listStack.Peek(); currentListItem = listToAddTo.Items[listToAddTo.Items.Count - 1]; currentListItem.Blocks.Add(new ListItemBuilder()); codeTag = AppendTextToListItem(currentListItem, markdown, Math.Min(lineInfo.FirstNonWhitespaceChar, lineInfo.StartOfLine + (level + 1) * 4), lineInfo.EndOfLine); if (codeTag) { isCodeBlock = !isCodeBlock; } } else { // Inline text. if (previousLineWasListHeader) { //currentListItem = new ListItemBlock { // Blocks = new List<MarkdownBlock>() //}; //listToAddTo.Items.Add(currentListItem); var newlistToAddTo = new ListBlock { Style = lastStype, Items = new List <ListItemBlock>() }; listStack.Push(newlistToAddTo); if (currentListItem != null) { currentListItem.Blocks.Add(newlistToAddTo); } listToAddTo = newlistToAddTo; // Add a new list item. currentListItem = new ListItemBlock { Blocks = new List <MarkdownBlock>() }; listToAddTo.Items.Add(currentListItem); lastLevel = lastLevel + 1; } codeTag = AppendTextToListItem(currentListItem, markdown, lineInfo.FirstNonWhitespaceChar, lineInfo.EndOfLine); } previousLineWasListHeader = false; if (codeTag) { isCodeBlock = !isCodeBlock; } } // The line was not blank. previousLineWasBlank = false; } // Go to the next line. actualEnd = lineInfo.EndOfLine; } var result = listStack.Last(); ReplaceStringBuilders(result); return(result); }
/// <summary> /// Parses a list block. /// </summary> /// <param name="markdown"> The markdown text. </param> /// <param name="start"> The location of the first character in the block. </param> /// <param name="maxEnd"> The location to stop parsing. </param> /// <param name="quoteDepth"> The current nesting level for block quoting. </param> /// <param name="actualEnd"> Set to the end of the block when the return value is non-null. </param> /// <returns> A parsed list block, or <c>null</c> if this is not a list block. </returns> internal static ListBlock Parse(string markdown, int start, int maxEnd, int quoteDepth, out int actualEnd) { var russianDolls = new List <NestedListInfo>(); int russianDollIndex = -1; bool previousLineWasBlank = false; ListItemBlock currentListItem = null; actualEnd = start; foreach (var lineInfo in Common.ParseLines(markdown, start, maxEnd, quoteDepth)) { // Is this line blank? if (lineInfo.IsLineBlank) { // The line is blank, which means the next line which contains text may end the list (or it may not...). previousLineWasBlank = true; } else { // Does the line contain a list item? ListItemPreamble listItemPreamble = null; if (lineInfo.FirstNonWhitespaceChar - lineInfo.StartOfLine < (russianDollIndex + 2) * 4) { listItemPreamble = ParseItemPreamble(markdown, lineInfo.FirstNonWhitespaceChar, lineInfo.EndOfLine); } if (listItemPreamble != null) { // Yes, this line contains a list item. // Determining the nesting level is done as follows: // 1. If this is the first line, then the list is not nested. // 2. If the number of spaces at the start of the line is equal to that of // an existing list, then the nesting level is the same as that list. // 3. Otherwise, if the number of spaces is 0-4, then the nesting level // is one level deep. // 4. Otherwise, if the number of spaces is 5-8, then the nesting level // is two levels deep (but no deeper than one level more than the // previous list item). // 5. Etcetera. ListBlock listToAddTo = null; int spaceCount = lineInfo.FirstNonWhitespaceChar - lineInfo.StartOfLine; russianDollIndex = russianDolls.FindIndex(rd => rd.SpaceCount == spaceCount); if (russianDollIndex >= 0) { // Add the new list item to an existing list. listToAddTo = russianDolls[russianDollIndex].List; } else { russianDollIndex = Math.Max(1, 1 + (spaceCount - 1) / 4); if (russianDollIndex < russianDolls.Count) { // Add the new list item to an existing list. listToAddTo = russianDolls[russianDollIndex].List; } else { // Create a new list. listToAddTo = new ListBlock { Style = listItemPreamble.Style, Items = new List <ListItemBlock>() }; if (russianDolls.Count > 0) { currentListItem.Blocks.Add(listToAddTo); } russianDollIndex = russianDolls.Count; russianDolls.Add(new NestedListInfo { List = listToAddTo, SpaceCount = spaceCount }); } } // Add a new list item. currentListItem = new ListItemBlock() { Blocks = new List <MarkdownBlock>() }; listToAddTo.Items.Add(currentListItem); // Add the rest of the line to the builder. AppendTextToListItem(currentListItem, markdown, listItemPreamble.ContentStartPos, lineInfo.EndOfLine); } else { // No, this line contains text. // Is there even a list in progress? if (currentListItem == null) { actualEnd = start; return(null); } // 0 spaces = end of the list. // 1-4 spaces = first level. // 5-8 spaces = second level, etc. if (previousLineWasBlank) { // This is the start of a new paragraph. int spaceCount = lineInfo.FirstNonWhitespaceChar - lineInfo.StartOfLine; if (spaceCount == 0) { break; } russianDollIndex = Math.Min(russianDollIndex, (spaceCount - 1) / 4); ListBlock listToAddTo = russianDolls[russianDollIndex].List; currentListItem = listToAddTo.Items[listToAddTo.Items.Count - 1]; currentListItem.Blocks.Add(new ListItemBuilder()); AppendTextToListItem(currentListItem, markdown, Math.Min(lineInfo.FirstNonWhitespaceChar, lineInfo.StartOfLine + (russianDollIndex + 1) * 4), lineInfo.EndOfLine); } else { // Inline text. AppendTextToListItem(currentListItem, markdown, lineInfo.FirstNonWhitespaceChar, lineInfo.EndOfLine); } } // The line was not blank. previousLineWasBlank = false; } // Go to the next line. actualEnd = lineInfo.EndOfLine; } var result = russianDolls[0].List; ReplaceStringBuilders(result); return(result); }