parse method

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

Implementation

@override
Node parse(BlockParser parser) {
  var items = <ListItem>[];
  var childLines = <String>[];
  var offset = 0; //offset of the first line of [childLines]

  void endItem() {
    if (childLines.isNotEmpty) {
      items.add(ListItem(childLines, offset + parser.offset));
      childLines = <String>[];
    }
  }
  void addChildLine(String line) {
    if (childLines.isEmpty) offset = parser._pos;
    childLines.add(line);
  }

  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.
      addChildLine('');
    } else if (indent != null && indent.length <= leadingExpandedTabLength) {
      // Strip off indent and add to current item.
      var line = parser.current
          .replaceFirst(leadingSpace, ' ' * leadingExpandedTabLength)
          .replaceFirst(indent, '');
      addChildLine(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();
      addChildLine(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.
      addChildLine(parser.current);
    }
    parser.advance();
  }

  endItem();
  var itemNodes = <Element>[];

  items.forEach(_removeLeadingEmptyLine);
  var anyEmptyLines = _removeTrailingEmptyLines(items);
  var anyEmptyLinesBetweenBlocks = false;

  for (var item in items) {
    var hasCheck = false,
      checked = false;
    if (item.lines.isNotEmpty) {
      final m = _reCheckbox.firstMatch(item.lines[0]);
      if (m != null) {
        hasCheck = true;
        checked = m[1] == 'x';
        item.lines[0] = m[2]!;
      }
    }

    var itemParser = parser.document.getBlockParser(item.lines)
      ..offset = item.offset
      .._inList = true;
    var children = itemParser.parseLines();
    if (hasCheck) {
      final check = Element('input', null);
      if (checked) check.attributes['checked'] = 'checked';
      if (parser.document.checkable) {
        check.attributes['data-line'] = '${item.offset}';
      } else {
        check.attributes['disabled'] = 'disabled';
      }
      check
        ..attributes['class'] = 'todo'
        ..attributes['type'] = 'checkbox';

      var child = children.first;
      (child is Element && child.tag == 'p' ? child.children: children)
      ?.insert(0, check);
    }

    final li = Element('li', children);
    if (hasCheck) {
      li.attributes['class'] = 'todo';
    }
    itemNodes.add(li);
    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!);
          }
        }
      }
    }
  }

  final ul = Element(listTag, itemNodes);
  if (listTag == 'ol' && startNumber != 1) {
    ul.attributes['start'] = '$startNumber';
  }
  return ul;
}