The developer can supply a custom Tag Library that can supplement the "tag" functionality that JETT supports. The developer supplies a class that implements the TagLibrary interface, which defines one method:
public Map<String, Class<? extends Tag>> getTagMap();
This method returns a Map that maps the name of the tag (e.g. "if") to the tag class object that defines the tag's functionality (e.g. IfTag.class).
The developer creates a custom tag by implementing the Tag interface, although it is easier to subclass the abstract class BaseTag, which implements Tag and provides a lot of boiler-plate Tag code. See "AggTag", "IfTag", "NullTag", "SpanTag", and "TotalTag" for examples about how to implement tags by subclassing "BaseTag".
To subclass "BaseTag", implement the following abstract methods:
For a repeating tag, the developer may want to subclass the abstract class BaseLoopTag, which subclasses BaseTag. This abstract class defines the "process" method, but in doing so, it uses the Template Method Pattern to provide additional abstract methods as "hooks" to assist the "process" method in processing the loop. See the code for "ForEachTag", "MultiForEachTag", and "ForTag" for examples about how to implement repeating tags by subclassing "BaseLoopTag". Here are the abstract methods to implement:
In JETT's JUnit tests, there is code for the custom "AddOneTag", a custom tag that subclasses BaseTag.
package net.sf.jett.test.model; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.RichTextString; import org.apache.poi.ss.usermodel.Sheet; import net.sf.jett.expression.Expression; import net.sf.jett.exception.TagParseException; import net.sf.jett.model.Block; import net.sf.jett.tag.BaseTag; import net.sf.jett.tag.TagContext; import net.sf.jett.util.AttributeUtil; import net.sf.jett.util.SheetUtil; /** * An <code>AddOneTag</code> is a custom <code>Tag</code> that adds 1 to the * numeric "value" attribute. The main purpose of this <code>Tag</code> is to * demonstrate custom tags and custom tag libraries. * * <br>Attributes: * <ul> * <li><em>Inherits all attributes from {@link BaseTag}.</em> * <li>value (required): <code>Number</code> * </ul> */ public class AddOneTag extends BaseTag { /** * Attribute for specifying the value. */ public static final String ATTR_VALUE = "value"; private static final List<String> REQ_ATTRS = new ArrayList<String>(Arrays.asList(ATTR_VALUE)); private double myValue; /** * Returns this <code>Tag's</code> name. * @return This <code>Tag's</code> name. */ public String getName() { return "addOne"; } /** * Returns a <code>List</code> of required attribute names. * @return A <code>List</code> of required attribute names. */ protected List<String> getRequiredAttributes() { List<String> reqAttrs = super.getRequiredAttributes(); reqAttrs.addAll(REQ_ATTRS); return reqAttrs; } /** * Returns a <code>List</code> of optional attribute names. * @return A <code>List</code> of optional attribute names. */ protected List<String> getOptionalAttributes() { return super.getOptionalAttributes(); } /** * Validates the attributes for this <code>Tag</code>. Some optional * attributes are only valid for bodiless tags, and others are only valid * for tags without bodies. */ public void validateAttributes() { super.validateAttributes(); TagContext context = getContext(); Map<String, Object> beans = context.getBeans(); Map<String, RichTextString> attributes = getAttributes(); if (!isBodiless()) throw new TagParseException("AddOne tags must not have a body. AddOne tag with body found" + getLocation()); myValue = AttributeUtil.evaluateDouble(context, attributes.get(ATTR_VALUE), beans, ATTR_VALUE, 0); } /** * Replace the cell's content with the value plus one. * @return <code>true</code>, this cell's content was processed. */ public boolean process() { TagContext context = getContext(); Sheet sheet = context.getSheet(); Block block = context.getBlock(); // Replace the bodiless tag text with the proper result. Cell cell = sheet.getRow(block.getTopRowNum()).getCell(block.getLeftColNum()); SheetUtil.setCellValue(cell, myValue + 1, getAttributes().get(ATTR_VALUE)); return true; } }
In JETT's JUnit tests, there is code for the "CustomTagLibrary", a custom tag library that implements TagLibrary.
package net.sf.jett.test.model; import java.util.HashMap; import java.util.Map; import net.sf.jett.tag.Tag; import net.sf.jett.tag.TagLibrary; /** * The <code>CustomTagLibrary</code> is for testing the custom tag libraries * feature. */ public class CustomTagLibrary implements TagLibrary { private static CustomTagLibrary theLibrary = new CustomTagLibrary(); private Map<String, Class<? extends Tag>> myTagMap; /** * Singleton constructor. */ private CustomTagLibrary() { myTagMap = new HashMap<String, Class<? extends Tag>>(); myTagMap.put("addOne", AddOneTag.class); } /** * Returns the singleton instance of a <code>CustomTagLibrary</code>. * @return The <code>CustomTagLibrary</code>. */ public static CustomTagLibrary getCustomTagLibrary() { return theLibrary; } /** * Returns the <code>Map</code> of tag names to tag <code>Class</code> * objects. * @return A <code>Map</code> of tag names to tag <code>Class</code> * objects. */ public Map<String, Class<? extends Tag>> getTagMap() { return myTagMap; } }
Prior to transformation, call the proper ExcelTransformer method to register a custom Tag Library:
ExcelTransformer transformer = new ExcelTransformer(); CustomTagLibrary library = CustomTagLibrary.getCustomTagLibrary(); transformer.registerTagLibrary("custom", library);
Here is the example template. During transformation, the "num" bean contains 3.14.
Demo addOne tag, custom namespace: | <custom:addOne value="5"/> |
Pass variable: | <custom:addOne value="${num}"/> |
... gets transformed into...
Demo addOne tag, custom namespace: | 6 |
Pass variable: | 4.14 |