// Copyright (c) Alexandre Mutel. All rights reserved. // This file is licensed under the BSD-Clause 2 license. // See the license.txt file in the project root for more information. using Markdig.Parsers; using Markdig.Syntax; namespace Markdig.Extensions.DefinitionLists; /// /// The block parser for a . /// /// public class DefinitionListParser : BlockParser { /// /// Initializes a new instance of the class. /// public DefinitionListParser() { OpeningCharacters = [':', '~']; } /// /// Attempts to open a block at the current parser position. /// public override BlockState TryOpen(BlockProcessor processor) { var paragraphBlock = processor.LastBlock as ParagraphBlock; if (processor.IsCodeIndent || paragraphBlock is null || paragraphBlock.LastLine - processor.LineIndex > 1) { return BlockState.None; } var startPosition = processor.Start; var column = processor.ColumnBeforeIndent; processor.NextChar(); processor.ParseIndent(); var delta = processor.Column - column; // We expect to have a least if (delta < 4) { // Return back to original position processor.GoToColumn(column); return BlockState.None; } if (delta > 4) { processor.GoToColumn(column + 4); } var previousParent = paragraphBlock.Parent!; var currentDefinitionList = GetCurrentDefinitionList(paragraphBlock, previousParent); processor.Discard(paragraphBlock); // If the paragraph block was not part of the opened blocks, we need to remove it manually from its parent container if (paragraphBlock.Parent != null) { paragraphBlock.Parent.Remove(paragraphBlock); } if (currentDefinitionList is null) { currentDefinitionList = new DefinitionList(this) { Span = new SourceSpan(paragraphBlock.Span.Start, processor.Line.End), Column = paragraphBlock.Column, Line = paragraphBlock.Line, }; previousParent.Add(currentDefinitionList); } var definitionItem = new DefinitionItem(this) { Line = processor.LineIndex, Column = column, Span = new SourceSpan(paragraphBlock.Span.Start, processor.Line.End), OpeningCharacter = processor.CurrentChar, }; for (int i = 0; i < paragraphBlock.Lines.Count; i++) { var line = paragraphBlock.Lines.Lines[i]; var term = new DefinitionTerm(this) { Column = paragraphBlock.Column, Line = line.Line, Span = new SourceSpan(paragraphBlock.Span.Start, paragraphBlock.Span.End), IsOpen = false }; term.AppendLine(ref line.Slice, line.Column, line.Line, line.Position, processor.TrackTrivia); definitionItem.Add(term); } currentDefinitionList.Add(definitionItem); processor.Open(definitionItem); // Update the end position currentDefinitionList.UpdateSpanEnd(processor.Line.End); return BlockState.Continue; } private static DefinitionList? GetCurrentDefinitionList(ParagraphBlock paragraphBlock, ContainerBlock previousParent) { var index = previousParent.IndexOf(paragraphBlock) - 1; if (index < 0) return null; switch (previousParent[index]) { case DefinitionList definitionList: return definitionList; case BlankLineBlock: if (index > 0 && previousParent[index - 1] is DefinitionList definitionList2) { previousParent.RemoveAt(index); return definitionList2; } break; } return null; } /// /// Attempts to continue parsing the specified block. /// public override BlockState TryContinue(BlockProcessor processor, Block block) { var definitionItem = (DefinitionItem)block; if (processor.IsCodeIndent) { processor.GoToCodeIndent(); return BlockState.Continue; } var list = (DefinitionList)definitionItem.Parent!; var lastBlankLine = definitionItem.LastChild as BlankLineBlock; // Check if we have another definition list if (Array.IndexOf(OpeningCharacters!, processor.CurrentChar) >= 0) { var startPosition = processor.Start; var column = processor.ColumnBeforeIndent; processor.NextChar(); processor.ParseIndent(); var delta = processor.Column - column; // We expect to have a least if (delta < 4) { // Remove the blankline before breaking this definition item if (lastBlankLine != null) { definitionItem.RemoveAt(definitionItem.Count - 1); } list.Span.End = list.LastChild!.Span.End; return BlockState.None; } if (delta > 4) { processor.GoToColumn(column + 4); } processor.Close(definitionItem); var nextDefinitionItem = new DefinitionItem(this) { Span = new SourceSpan(startPosition, processor.Line.End), Line = processor.LineIndex, Column = processor.Column, OpeningCharacter = processor.CurrentChar, }; list.Add(nextDefinitionItem); processor.Open(nextDefinitionItem); return BlockState.Continue; } var isBreakable = definitionItem.LastChild?.IsBreakable ?? true; if (processor.IsBlankLine) { if (lastBlankLine is null && isBreakable) { definitionItem.Add(new BlankLineBlock()); } return isBreakable ? BlockState.ContinueDiscard : BlockState.Continue; } var paragraphBlock = definitionItem.LastChild as ParagraphBlock; if (lastBlankLine is null && paragraphBlock != null) { return BlockState.Continue; } // Remove the blankline before breaking this definition item if (lastBlankLine != null) { definitionItem.RemoveAt(definitionItem.Count - 1); } list.Span.End = list.LastChild!.Span.End; return BlockState.Break; } }