parse method

  1. @override
Node parse(
  1. BlockParser parser
)
override

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