// "+++ <name>" with optional trailing whitespace internal virtual bool IsOpeningLine(BlockProcessor blockProcessor) { StringSlice line = blockProcessor.Line; if (line.NextChar() != '+' || line.NextChar() != '+' || line.NextChar() != ' ') { return(false); } for (int i = 0; i < _nameLength; i++) { if (line.NextChar() != _name[i]) { return(false); } } // Nothing other than whitespace after the name. A multipart block's name may begin with the name of another // multipart block, this ensures we don't confuse such blocks. line.NextChar(); line.TrimEnd(); return(line.CurrentChar == '\0'); }
/// <summary> /// Extracts content for a <see cref="Row"/>. /// </summary> /// <param name="line">The line to extract content from.</param> /// <param name="targetRow">The <see cref="Row"/> to extract content for.</param> /// <exception cref="ArgumentNullException">Thrown if <paramref name="targetRow"/> is <c>null</c>.</exception> protected virtual void ExtractContent(StringSlice line, Row targetRow) { if (targetRow == null) { throw new ArgumentNullException(nameof(targetRow)); } int numColumns = targetRow.Count; int lineStart = line.Start; for (int columnIndex = 0; columnIndex < numColumns;) { Cell targetCell = targetRow[columnIndex]; if (targetCell.IsOpen) { // Generate slice for cell StringSlice cellLine = line; cellLine.Start = lineStart + targetCell.StartOffset + 1; // Skip '|' or '+', don't trim leading spaces in case we have a block that depends on them cellLine.End = lineStart + targetCell.EndOffset - 1; // Don't include ending '|' or '+' cellLine.TrimEnd(); // Add slice to cell targetCell.Lines.Add(cellLine); } columnIndex = targetCell.EndColumnIndex + 1; } }
static public void CanTrimEnd() { StringSlice firstString = "Trim Me \r"; StringSlice trimmed = firstString.TrimEnd(); Assert.AreEqual("Trim Me", trimmed.ToString()); }
/// <summary> /// Tries to match partial tag from the current slice /// </summary> /// <param name="processor">The processor</param> /// <param name="slice">The slice</param> /// <returns>If a match was found</returns> public override bool Match(Processor processor, ref StringSlice slice) { var tagStart = slice.Start - processor.CurrentTags.StartTag.Length; var index = slice.Start; while (slice[index].IsWhitespace()) { index++; } var match = slice[index]; if (match == TagId) { index++; while (slice[index].IsWhitespace()) { index++; } slice.Start = index; var startIndex = slice.Start; var partialTag = new PartialToken { LineIndent = processor.HasSeenNonSpaceOnLine ? 0 : processor.LineIndent, TagStartPosition = tagStart, ContentStartPosition = startIndex, IsClosed = false }; processor.CurrentToken = partialTag; while (!slice.IsEmpty && !slice.Match(processor.CurrentTags.EndTag)) { slice.NextChar(); } if (slice.IsEmpty) { return(false); } var content = new StringSlice(slice.Text, startIndex, slice.Start - 1); content.TrimEnd(); var contentEnd = content.End + 1; partialTag.ContentEndPosition = contentEnd; partialTag.TagEndPosition = slice.Start + processor.CurrentTags.EndTag.Length; partialTag.IsClosed = true; slice.Start += processor.CurrentTags.EndTag.Length; return(true); } return(false); }
internal virtual bool LineContainsClosingFence(StringSlice line, int openingFenceCharCount) { int numFenceChars = 0; while (line.CurrentChar == _fenceChar) { numFenceChars++; line.NextChar(); } line.TrimEnd(); return((_matchingFencesRequired && numFenceChars == openingFenceCharCount || !_matchingFencesRequired && numFenceChars >= openingFenceCharCount) && // By default (commonmark specs) closing fence must have as many or more fence chars line.CurrentChar == '\0'); // No trailing characters other than whitespace }
// "+++" with optional trailing whitespace internal virtual bool IsPartDividerLine(BlockProcessor blockProcessor) { StringSlice line = blockProcessor.Line; if (line.CurrentChar != '+' || line.NextChar() != '+' || line.NextChar() != '+') { return(false); } // Nothing other than whitespace after "+++" line.NextChar(); line.TrimEnd(); return(line.CurrentChar == '\0'); }
internal virtual bool LineContainsOpeningFence(StringSlice line, out int numFenceChars) { numFenceChars = 0; char currentChar = line.CurrentChar; while (currentChar == _fenceChar) { numFenceChars++; currentChar = line.NextChar(); } if (numFenceChars < 3) { return(false); // Line must have at least 3 chars. Indices, so (end - start + 1) < 3 } if (_fenceTrailingCharacters == FenceTrailingCharacters.All || currentChar == '\0') { return(true); } if (_fenceTrailingCharacters == FenceTrailingCharacters.Whitespace) { line.TrimEnd(); return(line.CurrentChar == '\0'); } if (_fenceTrailingCharacters == FenceTrailingCharacters.AllButFenceCharacter) { currentChar = line.NextChar(); while (currentChar != '\0') { if (currentChar == _fenceChar) { return(false); } currentChar = line.NextChar(); } } return(true); }
public override bool Match(Processor processor, ref StringSlice slice) { if (processor is null) { throw new System.ArgumentNullException(nameof(processor)); } var tagStart = slice.Start - processor.CurrentTags.StartTag.Length; var index = slice.Start; while (slice[index].IsWhitespace()) { index++; } var nameStart = index; // Skip whitespace or until end tag while (!slice[index].IsWhitespace() && !slice.Match(processor.CurrentTags.EndTag, index - slice.Start)) { index++; } var name = slice.ToString(nameStart, index); // Skip whitespace or until end tag while (slice[index].IsWhitespace() && !slice.Match(processor.CurrentTags.EndTag, index - slice.Start)) { index++; } if (!_helperMap.TryGetValue(name, out var helperRef)) { return(false); } int contentEnd; var argsList = ImmutableArray <HelperArgument> .Empty; if (helperRef.ArgumentTypes.Length > 1) { var argsStart = index; slice.Start = index; while (!slice.IsEmpty && !slice.Match(processor.CurrentTags.EndTag)) { slice.NextChar(); } var args = new StringSlice(slice.Text, argsStart, slice.Start - 1); args.TrimEnd(); contentEnd = args.End + 1; argsList = ParseArguments(new StringSlice(args.Text, args.Start, args.End)); } else { while (!slice.IsEmpty && !slice.Match(processor.CurrentTags.EndTag)) { slice.NextChar(); } contentEnd = slice.Start; } if (!slice.Match(processor.CurrentTags.EndTag)) { throw new StubbleException($"Unclosed Tag at {slice.Start.ToString(CultureInfo.InvariantCulture)}"); } var tag = new HelperToken { TagStartPosition = tagStart, ContentStartPosition = nameStart, Name = name, Args = argsList, ContentEndPosition = contentEnd, TagEndPosition = slice.Start + processor.CurrentTags.EndTag.Length, IsClosed = true }; slice.Start += processor.CurrentTags.EndTag.Length; processor.CurrentToken = tag; processor.HasSeenNonSpaceOnLine = true; return(true); }
/// <summary> /// Tries to match interpolation tags from the current slice /// </summary> /// <param name="processor">The processor</param> /// <param name="slice">The slice</param> /// <returns>If the match was successful</returns> public override bool Match(Processor processor, ref StringSlice slice) { var tagStart = slice.Start - processor.CurrentTags.StartTag.Length; var index = slice.Start; var escapeResult = true; var isTripleMustache = false; while (slice[index].IsWhitespace()) { index++; } var match = slice[index]; if (match == '&') { escapeResult = false; index++; } else if (match == '{') { escapeResult = false; isTripleMustache = true; index++; } while (slice[index].IsWhitespace()) { index++; } slice.Start = index; var startIndex = index; var endTag = isTripleMustache ? '}' + processor.CurrentTags.EndTag : processor.CurrentTags.EndTag; while (!slice.IsEmpty && !slice.Match(endTag)) { slice.NextChar(); } var content = new StringSlice(slice.Text, startIndex, slice.Start - 1); content.TrimEnd(); var contentEnd = content.End + 1; var tag = new InterpolationToken { EscapeResult = escapeResult, TagStartPosition = tagStart, ContentStartPosition = startIndex, IsClosed = true }; if (!slice.Match(endTag)) { throw new StubbleException($"Unclosed Tag at {slice.Start.ToString()}"); } tag.ContentEndPosition = contentEnd; tag.TagEndPosition = slice.Start + endTag.Length; slice.Start += endTag.Length; processor.CurrentToken = tag; processor.HasSeenNonSpaceOnLine = true; return(true); }
/// <summary> /// Tries to match delimiter tags from the current slice /// </summary> /// <param name="processor">The processor</param> /// <param name="slice">The slice</param> /// <returns>If the match was successful</returns> public override bool Match(Processor processor, ref StringSlice slice) { var tagStart = slice.Start - processor.CurrentTags.StartTag.Length; var index = slice.Start; while (slice[index].IsWhitespace()) { index++; } var match = slice[index]; if (match == openingTagDelimiter[0]) { index++; while (slice[index].IsWhitespace()) { index++; } slice.Start = index; var startIndex = slice.Start; // Take Characters that aren't whitespace while (!slice.CurrentChar.IsWhitespace()) { slice.NextChar(); } var startTag = slice.ToString(startIndex, slice.Start); // Skip Whitespace any while (slice.CurrentChar.IsWhitespace()) { slice.NextChar(); } var endTagStartIndex = slice.Start; // Take characters until end delimiter; var closingTag = closingTagDelimiter[0] + processor.CurrentTags.EndTag; while (!slice.IsEmpty && !slice.Match(closingTag)) { slice.NextChar(); } var endTag = new StringSlice(slice.Text, endTagStartIndex, slice.Start - 1); endTag.TrimEnd(); var contentEnd = endTag.End + 1; var tag = new DelimiterToken { TagStartPosition = tagStart, ContentStartPosition = startIndex, ContentEndPosition = contentEnd, TagEndPosition = slice.Start + closingTag.Length, StartTag = startTag, EndTag = endTag.ToString(), IsClosed = true }; processor.CurrentToken = tag; processor.CurrentTags = new Classes.Tags(tag.StartTag, tag.EndTag); slice.Start += closingTag.Length; return(true); } return(false); }