buildNodes static method

List<Node> buildNodes(
  1. Iterable<Node> markdownNodes
)

Converts the given markdownNodes to a list of Nodes.

This also handles html blocks inside the parsed markdown content.

Implementation

static List<Node> buildNodes(Iterable<md.Node> markdownNodes) {
  final root = ElementNode('_', {}, []);
  final List<ElementNode> stack = [root];
  List<Node> currentNodes = root.children!;

  for (final node in markdownNodes) {
    if (node is HtmlText) {
      final tokenizer = html.HtmlTokenizer(node.text, lowercaseElementName: false);

      while (tokenizer.moveNext()) {
        final token = tokenizer.current;

        if (token.kind == html.TokenKind.parseError) {
          final error = (token as html.ParseErrorToken).data;
          if (error == 'expected-tag-name-but-got-question-mark') {
            // This error happens with processing instructions like <?some-instruction ... ?>
            // We can safely ignore it, since the next token will be a comment containing the instruction.
            continue;
          } else {
            throw AssertionError('Unexpected parse error: ${token.data}');
          }
        }

        if (token.kind == html.TokenKind.startTag) {
          final tag = (token as html.StartTagToken).name ?? '';
          final attributes = token.data.map((k, v) => MapEntry(k.toString(), v));
          final element = ElementNode(tag, attributes, []);
          currentNodes.add(element);
          final selfClosing = token.selfClosing || const DomValidator().isSelfClosing(token.name ?? '');
          if (!selfClosing) {
            stack.add(element);
            currentNodes = element.children!;
          }
        } else if (token.kind == html.TokenKind.endTag) {
          if (stack.last.tag != (token as html.EndTagToken).name) {
            // If the end tag does not match the last opened tag, we ignore it.
            continue;
          }
          stack.removeLast();
          currentNodes = stack.last.children!;
        } else if (token.kind == html.TokenKind.characters || token.kind == html.TokenKind.spaceCharacters) {
          currentNodes.add(TextNode((token as html.StringToken).data));
        } else if (token.kind == html.TokenKind.comment) {
          var data = (token as html.CommentToken).data;
          if (data.startsWith('?') && data.endsWith('?')) {
            data = data.substring(1, data.length - 1);
          }
          currentNodes.add(TextNode('<!--$data-->', raw: true));
        } else if (token.kind == html.TokenKind.doctype) {
          // Ignore doctype tokens.
          continue;
        }
      }
    } else if (node is md.Text) {
      currentNodes.addAll(HtmlParser.buildNodes(html.parseFragment(node.text).nodes));
    } else if (node is md.Element) {
      final children = buildNodes(node.children ?? []);
      currentNodes.add(
        ElementNode(node.tag, {'id': ?node.generatedId, ...node.attributes}, children),
      );
    }
  }

  return root.children!;
}