{"action":"created","issue":{"url":"https://api.github.com/repos/thymeleaf/thymeleaf/issues/399","repository_url":"https://api.github.com/repos/thymeleaf/thymeleaf","labels_url":"https://api.github.com/repos/thymeleaf/thymeleaf/issues/399/labels{/name}","comments_url":"https://api.github.com/repos/thymeleaf/thymeleaf/issues/399/comments","events_url":"https://api.github.com/repos/thymeleaf/thymeleaf/issues/399/events","html_url":"https://github.com/thymeleaf/thymeleaf/issues/399","id":108014785,"number":399,"title":"[MAJOR FEAT] New Processor API","user":{"login":"danielfernandez","id":1492299,"avatar_url":"https://avatars.githubusercontent.com/u/1492299?v=3","gravatar_id":"","url":"https://api.github.com/users/danielfernandez","html_url":"https://github.com/danielfernandez","followers_url":"https://api.github.com/users/danielfernandez/followers","following_url":"https://api.github.com/users/danielfernandez/following{/other_user}","gists_url":"https://api.github.com/users/danielfernandez/gists{/gist_id}","starred_url":"https://api.github.com/users/danielfernandez/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/danielfernandez/subscriptions","organizations_url":"https://api.github.com/users/danielfernandez/orgs","repos_url":"https://api.github.com/users/danielfernandez/repos","events_url":"https://api.github.com/users/danielfernandez/events{/privacy}","received_events_url":"https://api.github.com/users/danielfernandez/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/thymeleaf/thymeleaf/labels/status:insnapshot","name":"status:insnapshot","color":"bfe5bf"},{"url":"https://api.github.com/repos/thymeleaf/thymeleaf/labels/tracker:feature","name":"tracker:feature","color":"005dc5"}],"state":"closed","locked":false,"assignee":{"login":"danielfernandez","id":1492299,"avatar_url":"https://avatars.githubusercontent.com/u/1492299?v=3","gravatar_id":"","url":"https://api.github.com/users/danielfernandez","html_url":"https://github.com/danielfernandez","followers_url":"https://api.github.com/users/danielfernandez/followers","following_url":"https://api.github.com/users/danielfernandez/following{/other_user}","gists_url":"https://api.github.com/users/danielfernandez/gists{/gist_id}","starred_url":"https://api.github.com/users/danielfernandez/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/danielfernandez/subscriptions","organizations_url":"https://api.github.com/users/danielfernandez/orgs","repos_url":"https://api.github.com/users/danielfernandez/repos","events_url":"https://api.github.com/users/danielfernandez/events{/privacy}","received_events_url":"https://api.github.com/users/danielfernandez/received_events","type":"User","site_admin":false},"milestone":{"url":"https://api.github.com/repos/thymeleaf/thymeleaf/milestones/11","html_url":"https://github.com/thymeleaf/thymeleaf/milestones/Thymeleaf%203.0","labels_url":"https://api.github.com/repos/thymeleaf/thymeleaf/milestones/11/labels","id":213281,"number":11,"title":"Thymeleaf 3.0","description":"","creator":{"login":"danielfernandez","id":1492299,"avatar_url":"https://avatars.githubusercontent.com/u/1492299?v=3","gravatar_id":"","url":"https://api.github.com/users/danielfernandez","html_url":"https://github.com/danielfernandez","followers_url":"https://api.github.com/users/danielfernandez/followers","following_url":"https://api.github.com/users/danielfernandez/following{/other_user}","gists_url":"https://api.github.com/users/danielfernandez/gists{/gist_id}","starred_url":"https://api.github.com/users/danielfernandez/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/danielfernandez/subscriptions","organizations_url":"https://api.github.com/users/danielfernandez/orgs","repos_url":"https://api.github.com/users/danielfernandez/repos","events_url":"https://api.github.com/users/danielfernandez/events{/privacy}","received_events_url":"https://api.github.com/users/danielfernandez/received_events","type":"User","site_admin":false},"open_issues":0,"closed_issues":149,"state":"open","created_at":"2012-11-14T23:08:17Z","updated_at":"2016-04-17T16:50:09Z","due_on":null,"closed_at":null},"comments":2,"created_at":"2015-09-23T21:54:31Z","updated_at":"2016-04-22T07:45:07Z","closed_at":"2015-09-23T21:54:52Z","body":"The new engine introduced in Thymeleaf 3.0 (see #389) offers a refactored processor API that allows users to create custom processors, i.e. to apply custom behaviours to their templates.\r\n\r\n**Processors can be created for each type of event** fired by the template engine, whichever the *template mode*:\r\n\r\n  * **Template start/end**\r\n  * **Element Tags**\r\n  * **Texts**\r\n  * **Comments**\r\n  * **CDATA Sections**\r\n  * **DOCTYPE Clauses**\r\n  * **XML Declarations**\r\n  * **Processing Instructions**\r\n\r\nAnd also an additional type of processor, not linked with a specific event, but rather a *model* (see #389):\r\n\r\n  * **Element Models**: sequences of events representing an *entire element*, i.e. an element with its entire body, including any nested elements or any other kind of artifacts that might appear inside. If the modelled element is a *standalone element*, the model will only contain its corresponding event; but if the modelled element has a body, the model will contain every event from its *open tag* to its *close tag*, both included.\r\n\r\nAll these types of processors are created by implementing a specific interface, or by extending one of the available *abstract implementations*. All these artifacts conforming the Thymeleaf 3.0 Processor API live at the `org.thymeleaf.processor` package.\r\n\r\n\r\n### The main interface, `IProcessor`\r\n\r\nThe `org.thymeleaf.processor.IProcessor` is the main interface implemented by all processors in Thymeleaf 3.0. It is quite simple:\r\n```java\r\npublic interface IProcessor {\r\n\r\n    public TemplateMode getTemplateMode();\r\n    public int getPrecedence();\r\n\r\n}\r\n```\r\nSo every processor instance is associated with a specific *template mode*, which means that we will need to create different processor objects of the same class if one or several of our processor classes can be applied in more than one template mode.\r\n\r\nAlso, every processor has a *precedence*, which will mark the order in which processors will be executed when more than one are found to be applicable to the same event. There is a difference with Thymeleaf 2.1 here, though, as processor precedence in Thymeleaf 3.0 is **relative to the owner dialect's precedence** –declared at the dialect interfaces– so that it is possible to declare *all the processors* in a specific dialect to be executed before or after another the processors of other specific dialect.\r\n\r\n\r\n\r\n### Element Processors\r\n\r\nElement Tag processors are those that are executed on the *open element* (`IOpenElementTag`) or *standalone element* (`IStandaloneElementTag`) events, normally by means of matching the name of the element (and/or one of its attributes) with a matching configuration specified by the processor. This is what the `IElementProcessor` interface looks like:\r\n```java\r\npublic interface IElementProcessor extends IProcessor {\r\n\r\n    public MatchingElementName getMatchingElementName();\r\n    public MatchingAttributeName getMatchingAttributeName();\r\n\r\n}\r\n```\r\nNote however that element processor implementations are not meant to directly implement this interface. Instead, element processors should fall into one of two categories, already mentioned above:\r\n\r\n  * **Element Tag Processors**, implementing the `IElementTagProcessor` interface. These processors execute on *open/standalone tag events only* (no processors can be applied to *close tags*), and have no (direct) access to the element body.\r\n  * **Element Model Processors**, implementing the `IElementModelProcessor` interface. These processors execute on *complete elements*, including their bodies, in the form of `IModel` objects.\r\n\r\nWe should have a look at each of these interfaces separately:\r\n\r\n\r\n#### Element Tag Processors: `IElementTagProcessor`\r\n\r\nElement Tag Processors, as explained, execute on the single *open element* or *standalone element* tag that matches its matching configuration (seen in `IElementProcessor`). The interface to be implemented is `IElementTagProcessor`, which looks like this:\r\n```java\r\npublic interface IElementTagProcessor extends IElementProcessor {\r\n\r\n    public void process(\r\n            final ITemplateContext context, \r\n            final IProcessableElementTag tag,\r\n            final IElementTagStructureHandler structureHandler);\r\n\r\n}\r\n```\r\nAs we can see, besides extending `IElementProcessor` it only specifies a `process(...)` method that will be executed when the *matching configuration* matches (and in the order established by its *precedence*, established at the `IProcessor` superinterface). The `process(...)` signature is quite compact, and follows a pattern found in every Thymeleaf processor interface:\r\n\r\n   * The `process(...)` method returns `void`. Any actions will be performed via the `structureHandler`.\r\n   * The `context` argument contains the context with which the template is being executed: variables, template data, etc.\r\n   * The `tag` argument is the event on which the processor is being fired. It contains both the name of the element and its attributes.\r\n   * The `structureHandler` is a special object that allows the processor to give instructions to the engine about actions that it should perform as a consequence of the execution of the processor.\r\n\r\nSo we have two ways of performing *actions* during the execution of these processors:\r\n\r\n**Using the `structureHandler`**\r\n\r\nThe `tag` argument passed to `process(...)` is an **immutable** object. So there is no way to, for example, directly modify the attributes of a tag on the `tag` object itself. Instead, the `structureHandler` should be used.\r\n\r\nFor example, let's see how we would read the value of a specific `tag` attribute, unescape it and keep it in a variable, and then remove the attribute from the tag:\r\n\r\n```java\r\n// Obtain the attribute value\r\nString attributeValue = tag.getAttributeValue(attributeName);\r\n\r\n// Unescape the attribute value\r\nattributeValue = \r\n    EscapedAttributeUtils.unescapeAttribute(context.getTemplateMode(), attributeValue);\r\n\r\n// Instruct the structureHandler to remove the attribute from the tag\r\nstructureHandler.removeAttribute(attributeName);\r\n\r\n... // do something with that attributeValue\r\n```\r\n\r\n*Note that the code above is only meant to showcase some attribute management concepts -- in most processors we won't need to do this \"get value + unescape + remove\" operation manually as it will all be handled by an extended superclass such as `AbstractAttributeTagProcessor`*.\r\n\r\nAbove we've seen only one of the *operations* offered by the `structureHandler`. Let's know more.\r\n\r\nThere is a *structure handler* for each type of processor in Thymeleaf, and the one for *element tag* processors implements the `IElementTagStructureHandler` interface, which looks like this:\r\n```java\r\npublic interface IElementTagStructureHandler {\r\n\r\n    public void reset();\r\n\r\n    public void setLocalVariable(final String name, final Object value);\r\n    public void removeLocalVariable(final String name);\r\n\r\n    public void setAttribute(final String attributeName, final String attributeValue);\r\n    public void setAttribute(final String attributeName, final String attributeValue, \r\n                             final AttributeValueQuotes attributeValueQuotes);\r\n\r\n    public void replaceAttribute(final AttributeName oldAttributeName, \r\n                                 final String attributeName, final String attributeValue);\r\n    public void replaceAttribute(final AttributeName oldAttributeName, \r\n                                 final String attributeName, final String attributeValue, \r\n                                 final AttributeValueQuotes attributeValueQuotes);\r\n\r\n    public void removeAttribute(final String attributeName);\r\n    public void removeAttribute(final String prefix, final String name);\r\n    public void removeAttribute(final AttributeName attributeName);\r\n\r\n    public void setSelectionTarget(final Object selectionTarget);\r\n\r\n    public void setInliner(final IInliner inliner);\r\n\r\n    public void setTemplateData(final TemplateData templateData);\r\n\r\n    public void setBody(final String text, final boolean processable);\r\n    public void setBody(final IModel model, final boolean processable);\r\n\r\n    public void insertBefore(final IModel model); // cannot be processable\r\n    public void insertImmediatelyAfter(final IModel model, final boolean processable);\r\n\r\n    public void replaceWith(final String text, final boolean processable);\r\n    public void replaceWith(final IModel model, final boolean processable);\r\n\r\n\r\n    public void removeElement();\r\n    public void removeTags();\r\n    public void removeBody();\r\n    public void removeAllButFirstChild();\r\n\r\n    public void iterateElement(final String iterVariableName, \r\n                               final String iterStatusVariableName, \r\n                               final Object iteratedObject);\r\n\r\n}\r\n```\r\nThere we can see all the actions that a processor can ask the template engine to do as a result of its execution. The method names are quite self-explanatory (and there is javadoc for them), but very briefly:\r\n\r\n  * `setLocalVariable(...)`/`removeLocalVariable(...)` will add a local variable to the template execution. This *local variable* will be accessible during the rest of the execution of the current event, and also during all its *body* (i.e. until its corresponding *close tag*)\r\n  * `setAttribute(...)` adds a new attribute to the tag with a specified value (and maybe also type of surrounding quotes). If the attribute already exists, its value will be replaced.\r\n  * `replaceAttribute(...)` replaces an existing attribute with a new one, taking its place in the attribute (including its surrounding white space, for example).\r\n  * `removeAttribute(...)` removes an attribute from the tag.\r\n  * `setSelectionTarget(...)` modifies the object that is to be considered the *selection target*, i.e. the object on which *selection expressions* (`*{...}`) will be executed. In the Standard Dialect, this *selection target* is usually modified by means of the `th:object` attribute, but custom processors can do it too. Note the *selection target* has the same scope as a local variable, and will therefore be accessible only inside the body of the element being processed.\r\n  * `setInliner(...)` modifies the *inliner* to be used for processing all text nodes (`IText` events) appearing in the body of the element being processed. This is the mechanism used by the `th:inline` attribute to enable *inlining* in any of the specified modes (`text`, `javascript`, etc).\r\n  * `setTemplateData(...)` modifies the metadata about the template that is actually being processed. When inserting fragments, this allows the engine to know data about the specific fragment being processed, and also the complete stack of fragments being nested.\r\n  * `setBody(...)` replaces all the body of the element being processed with the passed text or model (sequence of events = fragment of markup). This is the way e.g. that `th:text`/`th:utext` work. Note that the specified replacement text or model can be set as *processable* or not, depending on whether we want to execute any processors that might be associated with them. In the case of `th:utext=\"${var}\"`, for example, the replacement is set as *non-processable* in order to avoid executing any markup that might be returned by `${var}` as a part of the template.\r\n  * `insertBefore(...)`/`insertImmediatelyAfter(...)` allow the specification of a model (fragment of markup) that should appear before or *immediately* after the tag being processed. Note that `insertImmediatelyAfter` means *after the tag being processed* (and therefore as the first part of the element's body) and not *after the entire element that opens here, and closes in a close tag somewhere*.\r\n  * `replaceWith(...)` allows the current *element* (entire element) to be replaced with the text or model specified as argument.\r\n  * `removeElement()`/`removeTags()`/`removeBody()`/`removeAllButFirstChild()` allow the processor to remove, respectively, the entire element including its body, only the executed tags (open + close) but not the body, only the body but not the wrapping tags, and lastly all the tag's children except the first child element. Note all these options basically mirror the different values that can be used at the `th:remove` attribute.\r\n  * `iterateElement(...)` allows the current element (body included) to be iterated as many times as elements exist in the `iteratedObject` (which will usually be a `Collection`, `Map`, `Iterator` or an array). The other two arguments will be used for specifying the names of the variables used for the iterated elements and the status variable.\r\n\r\n\r\n**Abstract implementations for `IElementTagProcessor`**\r\n\r\nThymeleaf offers two basic implementations of `IElementTagProcessor` that processors might implement for convenience:\r\n\r\n  * `org.thymeleaf.processor.element.AbstractElementTagProcessor`, meant for processors that match element events by their element name (i.e. without looking at attributes). Similar to *element processors* in Thymeleaf 2.1.\r\n  * `org.thymeleaf.processor.element.AbstractAttributeTagProcessor`, meant for processors that match element events by one of their attributes (and optionally also the element name). Similar to *attribute processors* in Thymeleaf 2.1.\r\n\r\n\r\n#### Element Model Processors: `IElementModelProcessor`\r\n\r\nElement Model Processors execute on the entire elements they match –including their bodies–, in the form of an `IModel` object that contains the complete sequence of events that models such element and its contents. The `IElementModelProcessor` is very similar to the one seen above for *tag processors*:\r\n```java\r\npublic interface IElementModelProcessor extends IElementProcessor {\r\n\r\n    public void process(\r\n            final ITemplateContext context, \r\n            final IModel model,\r\n            final IElementModelStructureHandler structureHandler);\r\n\r\n}\r\n```\r\nNote how this interface also extends `IElementProcessor`, and how the `process(...)` method it contains follows the same structure as the one in tag processors, replacing `tag` with `model` of course:\r\n\r\n   * `process(...)` returns `void`. Actions will be performed on `model` or `structureHandler`, not by returning anything.\r\n   * `context` contains the execution context: variables, etc.\r\n   * `model` is the sequence of events modelling the entire element on which the processor is being executed. This model can be directly modified from the processor.\r\n   * `structureHandler` allows instructing the engine to perform actions beyond model modification (e.g. setting local variables).\r\n\r\n\r\n**Reading and modifying the model**\r\n\r\nThe `IModel` object passed as a parameter to the `process()` method is a **mutable** model, so it allows any modifications to be done on it (*models* are mutable, *events* such as *tags* are immutable). For example, we might want to modify it so that we replace every text node from its body with a comment with the same contents:\r\n```java\r\nfinal IModelFactory modelFactory =  context.getModelFactory();\r\n\r\nint n = model.size();\r\nwhile (n-- != 0) {\r\n    final ITemplateEvent event = model.get(n);\r\n    if (event instanceof IText) {\r\n        final IComment comment =\r\n                modelFactory.createComment(((IText)event).getText());\r\n        model.insert(n, comment);\r\n        model.remove(n + 1);\r\n    }\r\n}\r\n```\r\nNote also that the `IModel` interface includes an `accept(IModelVisitor visitor)` method, useful for traversing an entire model looking for specific nodes or relevant data the *Visitor* pattern.\r\n\r\n\r\n**Using the `structureHandler`**\r\n\r\nSimilarly to *tag processors*, model processors are passed a *structure handler* object that allows them to instruct the engine to take any actions that cannot be done by directly acting on the `IModel model` object itself. The interface these structure handlers implement, much smaller than the one for tag processors, is `IElementModelStructureHandler`:\r\n```java\r\npublic interface IElementModelStructureHandler {\r\n\r\n    public void reset();\r\n\r\n    public void setLocalVariable(final String name, final Object value);\r\n    public void removeLocalVariable(final String name);\r\n\r\n    public void setSelectionTarget(final Object selectionTarget);\r\n\r\n    public void setInliner(final IInliner inliner);\r\n\r\n    public void setTemplateData(final TemplateData templateData);\r\n\r\n}\r\n```\r\nIt's easy to see this is a subset of the one for tag processors. The few methods there work the same way:\r\n\r\n  * `setLocalVariable(...)`/`removeLocalVariable(...)` for adding/removing local variables that will be available during the model's execution (after the current processor's execution).\r\n  * `setSelectionTarget(...)` for modifying the *selection target* applied during the model's execution.\r\n  * `setInliner(...)` for setting an inliner.\r\n  * `setTemplateData(...)` for setting metadata about the template being processed.\r\n\r\n\r\n**Abstract implementations for `IElementModelProcessor`**\r\n\r\nThymeleaf offers two basic implementations of `IElementModelProcessor` that processors might implement for convenience:\r\n\r\n  * `org.thymeleaf.processor.element.AbstractElementModelProcessor`, meant for processors that match element events by their element name (i.e. without looking at attributes). Similar to *element processors* in Thymeleaf 2.1.\r\n  * `org.thymeleaf.processor.element.AbstractAttributeModelProcessor`, meant for processors that match element events by one of their attributes (and optionally also the element name). Similar to *attribute processors* in Thymeleaf 2.1.\r\n\r\n\r\n### Template start/end Processors: `ITemplateBoundariesProcessor`\r\n\r\nTemplate Boundaries Processors are a kind of processors that execute on the *template start* and *template end* events fired during template processing. They allow to perform any kind of initialization or disposal of resources at beginning or end of the template processing operation. Note that these events are **only fired for the first-level template**, and not for each of the fragments that might be parsed and/or included into the template being processed.\r\n\r\nThe `ITemplateBoundariesProcessor` interface looks like this:\r\n```java\r\npublic interface ITemplateBoundariesProcessor extends IProcessor {\r\n\r\n    public void processTemplateStart(\r\n            final ITemplateContext context,\r\n            final ITemplateStart templateStart,\r\n            final ITemplateBoundariesStructureHandler structureHandler);\r\n\r\n    public void processTemplateEnd(\r\n            final ITemplateContext context,\r\n            final ITemplateEnd templateEnd,\r\n            final ITemplateBoundariesStructureHandler structureHandler);\r\n\r\n}\r\n```\r\nThis time the interface offers two `process*(...)` methods, one for the *template start* and another one for the *template end* events. Their signature follows the same patter as the other `process(...)` methods we saw before, receiving the context, the event object, and the structure handler. Structure handler that, in this case, implements a quite simple `ITemplateBoundariesStructureHandler` interface:\r\n```java\r\npublic interface ITemplateBoundariesStructureHandler {\r\n\r\n    public void reset();\r\n\r\n    public void setLocalVariable(final String name, final Object value);\r\n    public void removeLocalVariable(final String name);\r\n\r\n    public void setSelectionTarget(final Object selectionTarget);\r\n\r\n    public void setInliner(final IInliner inliner);\r\n\r\n    public void insert(final String text, final boolean processable);\r\n    public void insert(final IModel model, final boolean processable);\r\n\r\n}\r\n```\r\nWe can see how, besides the usual methods for managing local variables, selection target and inliner, we can also use the structure handler for inserting text or a model, which in this case will appear at the very beginning or the very end of the result (depending on the event being processed).\r\n\r\n\r\n### Other processors\r\n\r\nThere are other events for which Thymeleaf 3.0 allows processors to be declared, each of them implementing their corresponding interface:\r\n\r\n   * **Text** events: interface `ITextProcessor`\r\n   * **Comment** events: interface `ICommentProcessor`\r\n   * **CDATA Section** events: interface `ICDATASectionProcessor`\r\n   * **DOCTYPE Clause** events: interface `IDocTypeProcessor`\r\n   * **XML Declaration** events: interface `IXMLDeclarationProcessor`\r\n   * **Processing Instruction** events: interface `IProcessingInstructionProcessor`\r\n\r\nAll of them look pretty much like this (which is the one for text events):\r\n```java\r\npublic interface ITextProcessor extends IProcessor {\r\n\r\n    public void process(\r\n            final ITemplateContext context, \r\n            final IText text,\r\n            final ITextStructureHandler structureHandler);\r\n\r\n}\r\n```\r\nSame pattern as all other `process(...)` methods: context, event, structure handler. And these structure handlers are very simple, just like this (again, the one for text events):\r\n```java\r\npublic interface ITextStructureHandler {\r\n\r\n    public void reset();\r\n\r\n    public void setText(final CharSequence text);\r\n\r\n    public void replaceWith(final IModel model, final boolean processable);\r\n\r\n    public void removeText();\r\n\r\n}\r\n```\r\n"},"comment":{"url":"https://api.github.com/repos/thymeleaf/thymeleaf/issues/comments/213308248","html_url":"https://github.com/thymeleaf/thymeleaf/issues/399#issuecomment-213308248","issue_url":"https://api.github.com/repos/thymeleaf/thymeleaf/issues/399","id":213308248,"user":{"login":"danielfernandez","id":1492299,"avatar_url":"https://avatars.githubusercontent.com/u/1492299?v=3","gravatar_id":"","url":"https://api.github.com/users/danielfernandez","html_url":"https://github.com/danielfernandez","followers_url":"https://api.github.com/users/danielfernandez/followers","following_url":"https://api.github.com/users/danielfernandez/following{/other_user}","gists_url":"https://api.github.com/users/danielfernandez/gists{/gist_id}","starred_url":"https://api.github.com/users/danielfernandez/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/danielfernandez/subscriptions","organizations_url":"https://api.github.com/users/danielfernandez/orgs","repos_url":"https://api.github.com/users/danielfernandez/repos","events_url":"https://api.github.com/users/danielfernandez/events{/privacy}","received_events_url":"https://api.github.com/users/danielfernandez/received_events","type":"User","site_admin":false},"created_at":"2016-04-22T07:45:07Z","updated_at":"2016-04-22T07:45:07Z","body":"The `IModelFactory.parse(...)` method seems to be what you need. It will turn a `String` into an `IModel`. There is no way to directly replace the IModel you receive as an argument because it is mutable on purpose: you can just reset it and then execute `addModel()` with the newly created model."}}