-
Notifications
You must be signed in to change notification settings - Fork 15
How to Write a Tag or Block
A tag can be either closed or unclosed:
{% mytag [args]* %}
{% myblock [args]* %}
Inside block
{% endmyblock %}
If a tag has an "end" markup then it's a block.
To create a block you need to create a class which overrides ICustomBlockTagRenderer. This has one method:
StringValue Render(RenderingVisitor renderingVisitor,
ITemplateContext templateContext,
TreeNode<IASTNode> liquidBlock,
IList<Option<IExpressionConstant>> args);
The arguments give you access to something that allows you to render the arguments, the current state of the rendering, the pre-rendered content, and the the tag's arguments:
-
renderingVisitor: can render your content. Most of the time you will want to 1) evaluate the AST with the renderingVisitor and capture the result, then 2) manipulate that result and give it back to the renderer or the templateContext. The first part is accomplished like this:
String result = "";
// override the place where the text goes so that we can capture it
renderingVisitor.PushTextAccumulator(str => result += str);
new LiquidASTRenderer().StartVisiting(renderingVisitor, liquidBlock);
// reset the capturing to whatever happened before.
renderingVisitor.PopTextAccumulator();
DoSomethingWithTheResult(result);Note: You could create your own RenderingVisitor and capture text with that instead of using the current RenderingVisitor, but you will run into subtle bugs when your tag's content contains side-effecting tags that modify global state such as cycle. It's recommended that you use the RenderingVisitor that's passed in.
Normally you will either put the result directly back into the output stream, or else you'll save the result as a value somewhere. The return value of Render(...) is what gets written out to the final result:
return new StringValue(myresult);Or you can modify the state of the template context. For example, you could write your parsed content into the global variable uglyglobal like this:
templateContext.SymbolTableStack.DefineGlobal("uglyglobal", myresult);-
The
templateContext: -
The
liquidBlock: is content that appears between the tags comes in theliquidBlock. It's in the form of an AST fragment, and you can pass it to a LiquidASTRenderer to get the final rendered content:
new LiquidASTRenderer().StartVisiting(renderingVisitor, liquidBlock);- The args is a list of
IExpressionConstants
public class WordReverserBlockTag : ICustomBlockTagRenderer
{
public StringValue Render(
RenderingVisitor renderingVisitor,
ITemplateContext templatecontext,
TreeNode<IASTNode> liquidBlock,
IList<Option<IExpressionConstant>> args)
{
var result = EvalLiquidBlock(renderingVisitor, liquidBlock);
var words = result.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
return new StringValue(String.Join(" ",
words.Select(x => new String(x.Reverse().ToArray()))));
}
private static String EvalLiquidBlock(RenderingVisitor renderingVisitor, TreeNode<IASTNode> liquidBlock)
{
// Create an accumulator to temporarily shunt the output to our
String result = "";
Action<String> accumulator = str => result += str;
renderingVisitor.PushTextAccumulator(accumulator); // temporarily shunt the output to our accumulator
new LiquidASTRenderer().StartVisiting(renderingVisitor, liquidBlock);
renderingVisitor.PopTextAccumulator();
return result;
}
private static StringValue Reverse(string result)
{
var words = result.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries);
return new StringValue(String.Join(" ", words.Select(x => new String(x.Reverse().ToArray()))));
}
}