parse method
Implementation
@override
Node parse(BlockParser parser) {
final items = <ListItem>[];
var childLines = <String>[];
void endItem() {
if (childLines.isNotEmpty) {
items.add(ListItem(childLines));
childLines = <String>[];
}
}
Match? match;
bool tryMatch(RegExp pattern) {
match = pattern.firstMatch(parser.current);
return match != null;
}
String? listMarker;
String? indent;
// In case the first number in an ordered list is not 1, use it as the
// "start".
int? startNumber;
while (!parser.isDone) {
final leadingSpace = _whitespaceRe.matchAsPrefix(parser.current)!.group(0)!;
final leadingExpandedTabLength = _expandedTabLength(leadingSpace);
if (tryMatch(_emptyPattern)) {
if (_emptyPattern.firstMatch(parser.next ?? '') != null) {
// Two blank lines ends a list.
break;
}
// Add a blank line to the current list item.
childLines.add('');
} else if (indent != null && indent.length <= leadingExpandedTabLength) {
// Strip off indent and add to current item.
final line = parser.current.replaceFirst(leadingSpace, ' ' * leadingExpandedTabLength).replaceFirst(indent, '');
childLines.add(line);
} else if (tryMatch(_hrPattern)) {
// Horizontal rule takes precedence to a list item.
break;
} else if (tryMatch(_ulPattern) || tryMatch(_olPattern)) {
final precedingWhitespace = match![1];
final digits = match![2] ?? '';
if (startNumber == null && digits.isNotEmpty) {
startNumber = int.parse(digits);
}
final marker = match![3];
final firstWhitespace = match![5] ?? '';
final restWhitespace = match![6] ?? '';
final content = match![7] ?? '';
final isBlank = content.isEmpty;
if (listMarker != null && listMarker != marker) {
// Changing the bullet or ordered list delimiter starts a list.
break;
}
listMarker = marker;
final markerAsSpaces = ' ' * (digits.length + marker!.length);
if (isBlank) {
// See http://spec.commonmark.org/0.28/#list-items under "3. Item
// starting with a blank line."
//
// If the list item starts with a blank line, the final piece of the
// indentation is just a single space.
indent = '$precedingWhitespace$markerAsSpaces ';
} else if (restWhitespace.length >= 4) {
// See http://spec.commonmark.org/0.28/#list-items under "2. Item
// starting with indented code."
//
// If the list item starts with indented code, we need to _not_ count
// any indentation past the required whitespace character.
indent = precedingWhitespace! + markerAsSpaces + firstWhitespace;
} else {
indent = precedingWhitespace! + markerAsSpaces + firstWhitespace + restWhitespace;
}
// End the current list item and start a one.
endItem();
childLines.add(restWhitespace + content);
} else if (BlockSyntax.isAtBlockEnd(parser)) {
// Done with the list.
break;
} else {
// If the previous item is a blank line, this means we're done with the
// list and are starting a top-level paragraph.
if ((childLines.isNotEmpty) && (childLines.last == '')) {
parser.encounteredBlankLine = true;
break;
}
// Anything else is paragraph continuation text.
childLines.add(parser.current);
}
parser.advance();
}
endItem();
final itemNodes = <Element>[];
items.forEach(removeLeadingEmptyLine);
final anyEmptyLines = removeTrailingEmptyLines(items);
var anyEmptyLinesBetweenBlocks = false;
for (final item in items) {
final itemParser = BlockParser(item.lines, parser.document);
final children = itemParser.parseLines();
itemNodes.add(Element('li', children));
anyEmptyLinesBetweenBlocks = anyEmptyLinesBetweenBlocks || itemParser.encounteredBlankLine;
}
// Must strip paragraph tags if the list is "tight".
// http://spec.commonmark.org/0.28/#lists
final listIsTight = !anyEmptyLines && !anyEmptyLinesBetweenBlocks;
if (listIsTight) {
// We must post-process the list items, converting any top-level paragraph
// elements to just text elements.
for (final item in itemNodes) {
for (var i = 0; i < item.children!.length; i++) {
final child = item.children![i];
if (child is Element && child.tag == 'p') {
item.children!.removeAt(i);
item.children!.insertAll(i, child.children!);
}
}
}
}
if (listTag == 'ol' && startNumber != 1) {
return Element(listTag, itemNodes)..attributes['start'] = '$startNumber';
} else {
return Element(listTag, itemNodes);
}
}