| // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| import '../ast.dart'; | |
| import '../block_parser.dart'; | |
| import '../charcode.dart'; | |
| import '../line.dart'; | |
| import '../patterns.dart'; | |
| import '../util.dart'; | |
| import 'block_syntax.dart'; | |
| import 'code_block_syntax.dart'; | |
| import 'paragraph_syntax.dart'; | |
| /// Parses email-style blockquotes: `> quote`. | |
| class BlockquoteSyntax extends BlockSyntax { | |
| @override | |
| RegExp get pattern => blockquotePattern; | |
| const BlockquoteSyntax(); | |
| /// Whether this blockquote ends with a lazy continuation line. | |
| // The definition of lazy continuation lines: | |
| // https://spec.commonmark.org/0.30/#lazy-continuation-line | |
| static var _lazyContinuation = false; | |
| @override | |
| List<Line> parseChildLines(BlockParser parser) { | |
| // Grab all of the lines that form the blockquote, stripping off the ">". | |
| final childLines = <Line>[]; | |
| _lazyContinuation = false; | |
| while (!parser.isDone) { | |
| final currentLine = parser.current; | |
| final match = pattern.firstMatch(parser.current.content); | |
| if (match != null) { | |
| // A block quote marker consists of a `>` together with an optional | |
| // following space of indentation, see | |
| // https://spec.commonmark.org/0.30/#block-quote-marker. | |
| final markerStart = match.match.indexOf('>'); | |
| int markerEnd; | |
| if (currentLine.content.length > 1) { | |
| var hasSpace = false; | |
| // Check if there is a following space if the marker is not at the end | |
| // of this line. | |
| if (markerStart < currentLine.content.length - 1) { | |
| final nextChar = currentLine.content.codeUnitAt(markerStart + 1); | |
| hasSpace = nextChar == $tab || nextChar == $space; | |
| } | |
| markerEnd = markerStart + (hasSpace ? 2 : 1); | |
| } else { | |
| markerEnd = markerStart + 1; | |
| } | |
| childLines.add(Line(currentLine.content.substring(markerEnd))); | |
| parser.advance(); | |
| _lazyContinuation = false; | |
| continue; | |
| } | |
| final lastLine = childLines.last; | |
| // A paragraph continuation is OK. This is content that cannot be parsed | |
| // as any other syntax except Paragraph, and it doesn't match the bar in | |
| // a Setext header. | |
| // Because indented code blocks cannot interrupt paragraphs, a line | |
| // matched CodeBlockSyntax is also paragraph continuation text. | |
| final otherMatched = parser.blockSyntaxes.firstWhere( | |
| (s) => s.canParse(parser), | |
| ); | |
| if ((otherMatched is ParagraphSyntax && | |
| !lastLine.isBlankLine && | |
| !codeFencePattern.hasMatch(lastLine.content)) || | |
| (otherMatched is CodeBlockSyntax && | |
| !indentPattern.hasMatch(lastLine.content))) { | |
| childLines.add(parser.current); | |
| _lazyContinuation = true; | |
| parser.advance(); | |
| } else { | |
| break; | |
| } | |
| } | |
| return childLines; | |
| } | |
| @override | |
| Node parse(BlockParser parser) { | |
| final childLines = parseChildLines(parser); | |
| // Recursively parse the contents of the blockquote. | |
| final children = BlockParser(childLines, parser.document).parseLines( | |
| // The setext heading underline cannot be a lazy continuation line in a | |
| // block quote. | |
| // https://spec.commonmark.org/0.30/#example-93 | |
| disabledSetextHeading: _lazyContinuation, | |
| parentSyntax: this, | |
| ); | |
| return Element('blockquote', children); | |
| } | |
| } |