/// <summary> /// Register a Lava Tag with the Fluid Parser. /// </summary> /// <param name="tagName"></param> public void RegisterLavaTag(string tagName, LavaTagFormatSpecifier format = LavaTagFormatSpecifier.LiquidTag) { // Create a parser for the Lava tag that does the following: // 1. Processes all of the content from the end of the open tag until the end of the closing tag. // 2. Captures any optional attributes that are contained in the open tag. // 3. Create a new Statement that will execute when the tag is rendered. // 4. Throw an exception if the Tag is malformed. Parser <LavaTagResult> tokenEndParser; var registerTagName = tagName; string errorMessage; if (format == LavaTagFormatSpecifier.LavaShortcode) { tokenEndParser = LavaTagParsers.LavaShortcodeEnd(); tagName = tagName.Substring(0, tagName.Length - "_".Length); errorMessage = $"Invalid '{{[ {tagName} ]}}' shortcode tag"; } else { tokenEndParser = LavaTokenEnd; errorMessage = $"Invalid '{{% {tagName} %}}' tag"; } var lavaTag = AnyCharBefore(tokenEndParser, canBeEmpty: true) .AndSkip(tokenEndParser) .Then <Statement>(x => new FluidLavaTagStatement(tagName, format, x)) .ElseError(errorMessage); this.RegisteredTags[registerTagName] = lavaTag; }
/// <summary> /// Register a Lava Block with the Fluid Parser. /// </summary> /// <param name="tagName"></param> public void RegisterLavaBlock(string tagName, LavaTagFormatSpecifier format = LavaTagFormatSpecifier.LiquidTag) { // Create a parser for the Lava block that does the following: // 1. Captures any optional attributes that are contained in the open tag. // 2. Creates a new Statement that will execute when the block is rendered. The Statement captures the block content // as literal text, so that it can be scanned and tokenized by the Lava library before being passed back to Fluid // for final rendering. // 3. Throw an exception if the Block is malformed. Parser <LavaTagResult> tokenEndParser; var registerTagName = tagName; string errorMessage; if (format == LavaTagFormatSpecifier.LavaShortcode) { tokenEndParser = LavaTagParsers.LavaShortcodeEnd(); tagName = tagName.Substring(0, tagName.Length - "_".Length); errorMessage = $"Invalid '{{[ {tagName} ]}}' shortcode block"; } else { tokenEndParser = LavaTokenEnd; errorMessage = $"Invalid '{{% {tagName} %}}' block"; } var lavaBlock = AnyCharBefore(tokenEndParser, canBeEmpty: true) .AndSkip(tokenEndParser) .And(new LavaTagParsers.LavaBlockContentParser(tagName, format)) .Then <Statement>(x => new FluidLavaBlockStatement(this, tagName, format, x.Item1, x.Item2)) .ElseError(errorMessage); RegisteredTags[registerTagName] = lavaBlock; }
/// <summary> /// Replace the default Fluid comment block to allow empty content. /// </summary> private void RegisterLavaCommentTag() { var commentTag = LavaTagParsers.LavaTagEnd() .SkipAnd(AnyCharBefore(CreateTag("endcomment"), canBeEmpty: true)) .AndSkip(CreateTag("endcomment").ElseError($"'{{% endcomment %}}' was expected")) .Then <Statement>(x => new CommentStatement(x)) .ElseError("Invalid 'comment' tag"); RegisteredTags["comment"] = commentTag; }
private void CreateLavaDocumentParsers() { // Define the top-level parsers. var anyTags = CreateKnownTagsParser(throwOnUnknownTag: false); var knownTags = CreateKnownTagsParser(throwOnUnknownTag: true); var outputElement = OutputStart.SkipAnd(FilterExpression.And(OutputEnd.ElseError(ErrorMessages.ExpectedOutputEnd)) .Then <Statement>(x => new OutputStatement(x.Item1))); var textElement = AnyCharBefore(OutputStart.Or(LavaTagParsers.LavaTagStart().AsFluidTagResultParser()).Or(LavaTagParsers.LavaShortcodeStart().AsFluidTagResultParser())) .Then <Statement>((ctx, x) => { // Keep track of each text span such that whitespace trimming can be applied var p = ( FluidParseContext )ctx; var result = new TextSpanStatement(x); p.PreviousTextSpanStatement = result; if (p.StripNextTextSpanStatement) { result.StripLeft = true; p.StripNextTextSpanStatement = false; } result.PreviousIsTag = p.PreviousIsTag; result.PreviousIsOutput = p.PreviousIsOutput; return(result); }); // Define parsers for Lava block/inline comments. var blockCommentElement = Terms.Text("/-").SkipAnd(AnyCharBefore(Terms.Text("-/"))); var lineCommentElement = Terms.Text("/-").SkipAnd(AnyCharBefore(Terms.Char('\r').SkipAnd(Terms.Char('\n')))); var commentElement = blockCommentElement.Or(lineCommentElement); // Set the parser to be used for a block element. // This parser returns an empty result when an unknown tag is found. _anyTagsListParser = ZeroOrMany(outputElement.Or(anyTags).Or(textElement)); // Set the parser to be used for the entire template. // This parser raises an exception when an unknown tag is found. _knownTagsListParser = ZeroOrMany(outputElement.Or(knownTags).Or(textElement)); }
public override bool Parse(ParseContext context, ref ParseResult <LavaTagResult> result) { var start = context.Scanner.Cursor.Position; if (_skipWhiteSpace) { context.SkipWhiteSpace(); } Parser <(LavaTagResult, string, TextSpan, LavaTagResult)> tagParser; if (_tagFormat == LavaTagFormatSpecifier.LavaShortcode) { tagParser = LavaTagParsers.LavaShortcodeStart() .And(Terms.Text(_tagName)) .And(AnyCharBefore(LavaTagParsers.LavaShortcodeEnd(), canBeEmpty: true)) .And(LavaTagParsers.LavaShortcodeEnd()); } else { tagParser = LavaTagParsers.LavaTagStart() .And(Terms.Text(_tagName)). And(AnyCharBefore(LavaFluidParser.LavaTokenEnd, canBeEmpty: true)) .And(LavaFluidParser.LavaTokenEnd); } var tagResult = new ParseResult <(LavaTagResult, string, TextSpan, LavaTagResult)>(); var parseSucceeded = tagParser.Parse(context, ref tagResult); if (!parseSucceeded) { context.Scanner.Cursor.ResetPosition(start); return(false); } var lavaResult = new LavaTagResult(tagResult.Value.Item1.TagResult, new TextSpan(context.Scanner.Buffer, start.Offset, context.Scanner.Cursor.Position.Offset - start.Offset), _tagFormat); result.Set(start.Offset, context.Scanner.Cursor.Offset, lavaResult); return(true); }
/// <summary> /// Create a parser for the set of tags and shortcodes that have been defined. /// </summary> /// <param name="throwOnUnknownTag">If true, undefined tags return null rather than throwing an exception.</param> /// <returns></returns> /// <remarks>The option to ignore unknown tags can be used to ignore output tags that are not defined until the render process.</remarks> private Parser <Statement> CreateKnownTagsParser(bool throwOnUnknownTag) { var parser = OneOf( LavaTagParsers.LavaTagStart() .SkipAnd(Identifier.ElseError(ErrorMessages.IdentifierAfterTagStart) .Switch((context, tagName) => { if (RegisteredTags.TryGetValue(tagName, out var tag)) { return(tag); } else if (throwOnUnknownTag) { throw new global::Fluid.ParseException($"Unknown tag '{tagName}' at {context.Scanner.Cursor.Position}"); } return(null); }) ), LavaTagParsers.LavaShortcodeStart() .SkipAnd(Identifier.ElseError(ErrorMessages.IdentifierAfterTagStart) .Switch((context, tagName) => { var shortcodeTagName = tagName + "_"; if (RegisteredTags.TryGetValue(shortcodeTagName, out var shortcode)) { return(shortcode); } throw new global::Fluid.ParseException($"Unknown shortcode '{tagName}' at {context.Scanner.Cursor.Position}"); }) ) ); return(parser); }