parse method
Implementation
@override
Node parse(BlockParser parser) {
var items = <ListItem>[];
var childLines = <String>[];
void endItem() {
if (childLines.isNotEmpty) {
items.add(ListItem(childLines));
childLines = <String>[];
}
}
late 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) {
var leadingSpace = _whitespaceRe.matchAsPrefix(parser.current)!.group(0)!;
var leadingExpandedTabLength = _expandedTabLength(leadingSpace);
if (tryMatch(_emptyPattern)) {
if (_emptyPattern.hasMatch(parser.next ?? '')) {
// 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.
var line = parser.current
.replaceFirst(leadingSpace, ' ' * leadingExpandedTabLength)
.replaceFirst(indent, '');
childLines.add(line);
} else if (tryMatch(_hrPattern)) {
// Horizontal rule takes precedence to a new list item.
break;
} else if (tryMatch(_ulPattern) || tryMatch(_olPattern)) {
var precedingWhitespace = match![1]!;
var digits = match![2] ?? '';
if (startNumber == null && digits.isNotEmpty) {
startNumber = int.parse(digits);
}
var marker = match![3]!;
var firstWhitespace = match![5] ?? '';
var restWhitespace = match![6] ?? '';
var content = match![7] ?? '';
var isBlank = content.isEmpty;
if (listMarker != null && listMarker != marker) {
// Changing the bullet or ordered list delimiter starts a new list.
break;
}
listMarker = marker;
var 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 new 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 new 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();
var itemNodes = <Element>[];
items.forEach(_removeLeadingEmptyLine);
var anyEmptyLines = _removeTrailingEmptyLines(items);
var anyEmptyLinesBetweenBlocks = false;
for (var item in items) {
var itemParser = BlockParser(item.lines, parser.document);
var 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
var listIsTight = !anyEmptyLines && !anyEmptyLinesBetweenBlocks;
if (listIsTight) {
// We must post-process the list items, converting any top-level paragraph
// elements to just text elements.
for (var item in itemNodes) {
var children = item.children;
if (children != null) {
for (var i = 0; i < children.length; i++) {
var child = children[i];
if (child is Element && child.tag == 'p') {
children.removeAt(i);
children.insertAll(i, child.children!);
}
}
}
}
}
if (listTag == 'ol' && startNumber != 1) {
return Element(listTag, itemNodes)..attributes['start'] = '$startNumber';
} else {
return Element(listTag, itemNodes);
}
}