parse method

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

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