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;
}