Seldom used and expert built-ins - Apache FreeMarker Manual

Unsupported web browser - Use a modern browser to view this website!

- Home
- Manual
- Java API

- Contribute
- Report a Bug
- Download

Manual
Previous Next

# Seldom used and expert built-ins

These are the built-ins that normally you should not use, but in exceptional situations (debugging, advanced macros) they can be useful. If you need to use these in your normal page templates, you may revisit the data-model so you don't need to use these.

## absolute_template_name

Converts a template name to an absolute name, which can be safely passed to `<#include name>` or `<#import name as ns>` or `.get_optional_template(name)` and such in another template, as it won't be misinterpreted to be relative to the directory of the template that contains the `include`, `import`, etc. For example, if you are in template `"dir/here.ftl"`, then `"target.ftl"` is converted to `"/dir/target.ftl"` (note the initial `/`). If now you pass this value to a template in `"other-dir/there.ftl"`, where it's passed to the `include` directive, then it won't be misinterpreted as `"other-dir/target.ftl"`, like `"target.ftl"` would have been.

Optionally, you can specify a root based name (a name that's either relative to the template root directory, or is absolute) that will be used instead of the name of the current template, like `pathToConver?absolute_template_name(otherTemplateName)`.

Example of an application (also uses `.caller_template_name` and `.get_optional_template`):

Template

    <#--
      <@smileyInclude name /> behaves like <#include name>, but prints a "(:" before the
      template, or prints "):" instead if the template is missing.

      Note that just like with #include, if name is relative, it's resolved based on the
      directory of the caller template, not of the template that defines this macro. As
      .get_optional_template resolves relative names based on the current template, we
      had to convert the name to an absolute name based on the caller template before
      passing it to it.
    -->
    <#macro smileyInclude name>
      <#local t = .get_optional_template(
          name?absolute_template_name(.caller_template_name))>
      <#if t.exists>
        (:
        <@t.include />
      <#else>
        ):
      </#if>
    </#macro>

## api, has_api

Note:

These built-ins exists since FreeMarker 2.3.22

`value?api` provides access to the API (usually, the Java API) of `value`, like `value?api.someJavaMethod()` or `value?api.someBeanProperty`, if the value itself supports exposing its API. This meant to be used rarely, when you need to call a Java method of an object, but the by-design simplistic view of the value that FreeMarker exposes to the templates hides that, and there's no equivalent built-in either. For example, when you put a `Map` into the data-model (and you are using the default object wrapper), `myMap.myMethod()` in a template basically translates to `((Method) myMap.get("myMethod")).invoke(...)` in Java, thus you can't call `myMethod`. If, however, you write `myMap?api.myMethod()` instead, that means `myMap.myMethod()` in Java. Similarly, `myMap?api.myProperty` translates to `myMap.getMyProperty()` in Java, instead of to `myMap.get("myProperty")`.

You should avoid using `api`, and rely on the capabilities of the FTL types and the related built-ins as far as possible. For example, don't use `users?api.size()`, but `users?size`. The variation that uses `?api` is more verbose, slower, more easily breaks when FreeMarker configuration settings are changed, and most importantly, more prone to break as the technical details of the data-model change. For example, if `users` is changed from a `List` to an array, `users?size` will keep working, while `users?api.size()` will break.

Avoid calling methods that modify an object (especially `Map`-s and `Collection`-s) or that aren't thread safe from other reasons. Templates usually aren't expected to modify the objects exposed to them, just to display them. Thus the application may passes some objects to multiple (possibly concurrent) template processings.

The `api` built-in is not everywhere available, some requirements has to be met:

- The `api_builtin_enabled` configuration setting must be set to `true`. Its default is `false` (at least as of 2.3.22) for not lowering the security of existing applications.

- The value itself has to support it. We are talking about the value as the template sees it, which is created from the original object (that's coming from the data-model or from a Java method return value) value via object wrapping. Hence, this depends on the `object_wrapper` FreeMarker configuration setting, and on the class of the wrapped (the original) object:

  - When the object wrapper is a `DefaultObjectWrapper` with its `incompatibleImprovements` set to 2.3.22 or higher (see how to set it here), FTL values made from `Map`-s and `List`-s support `?api`. (Actually, what matters is that its `useAdaptersForContainer` property is set to `true`, but that's the default with said `incompatibleImprovements`.) Other `java.util.Collections` (such as `Set`-s) only support `?api` if `DefaultObjectWrapper`'s `forceLegacyNonListCollections` property is set to `false` (the default is `true` for better out-of-the-box backward compatibility).

  - When wrapped with pure `BeansWrapper`, all values support `?api`.

  - Custom `TemplateModel`-s can support `?api` by implementing the `freemarker.template.TemplateModelWithAPISupport` interface.

Using `?api` when it's not allowed in the configuration or when the value doesn't support it will abort template processing with error.

Whether a value supports `?api` can be checked like `value?has_api`, which returns a boolean value. Note that the result of `?has_api` isn't influenced by the `api_builtin_enabled` setting.

## byte, double, float, int, long, short

Returns a `SimpleNumber` which contains the same value as the original variable, but uses `java.lang.Type` for the internal representation of the value. This is useful if a method is overloaded, or if a `TemplateModel` unwrapper has problem with automatically choosing the suitable `java.lang.*` type. Note that since version 2.3.9 the unwrapper has been improved substantially, so you will hardly ever need to use these built-ins to convert between numerical types, except for resolving ambiguity in overloaded method invocation.

The `long` built-in can also be used with date, time and date-time values to get the value as `java.util.Date.getTime()` would return. This is useful if you have to call a Java methods that expect a timestamp as a `long`.

## eval

This built-in evaluates a string as an FTL expression. For example `"1+2"?eval` returns the number 3. (To render a template that's stored in a string, use the `interpret` built-in instead.)

Warning!

Do not use this to evaluate JSON! For that use the `eval_json` built-in instead. While FTL expression language looks similar to JSON, not all JSON is valid FTL expression. Also, FTL expressions can access variables, and call Java methods on them, so if you `?eval` strings coming from untrusted source, it can become an attack vector.

The evaluated expression sees the same variables (such as locals) that are visible at the place of the invocation of `eval`. That is, it behaves similarly as if in place of `s?eval` you had the value of `s` there. Except, it can't use loop variable built-ins that refer to a loop variable that was created outside `s`.

Regarding the configuration settings that affect the parsing (like syntax) and evaluation the rules are the same as with the `interpret` built-in.

## eval_json

Note:

This built-in is available since FreeMarker 2.3.31.

This built-in evaluates a string as a JSON expression, so that you can extract data from inside it. For example, if you receive data in the `dataJson` variable, but it's unfortunately just a flat string that contains `{"name": "foo", "ids": [11, 22]}`, then you can extract data from it like this:

Template

    <#assign data = dataJson?eval_json>
    <p>Name: ${data.name}
    <p>Ids:
    <ul>
      <#list data.ids as id>
        <li>${id}
      </#list>
    </ul>

Ideally, you shouldn't need `eval_json`, since the template should receive data already parsed (to `List`-s, `Map`-s, Java beans, etc.). This built-in is there as a workaround, if you can't improve the data-model.

The evaluated JSON expression doesn't have to be a JSON object (key-value pairs), it can be any kind of JSON value, like JSON array, JSON number, etc.

The syntax understood by this built-in is a superset of JSON:

- Java-style comments are supported (`/*...*/` and `//...`)

- BOM (byte order mark) and non-breaking space ("nbsp") are treated as whitespace (in a stricter JSON parser they are errors of occurring around tokens).

No other non-JSON extras are implemented, notably, it's impossible to refer to variables (unlike in the `eval` built-in). This is important for safety, when receiving JSON from untrusted sources.

## has_content

It is `true` if the variable exists (and isn't Java `null`) and is not "empty", otherwise it is `false`. The meaning of "empty" depends on the concrete case. This follows intuitive common-sense ideas. The following are empty: a string with 0 length, a markup output value with 0 length markup, a sequence or hash with no sub variables, a collection which has passed the last element. If the value is not of any of these types, then it counts as non-empty if it's a number or a date or a boolean (e.g. `0` and `false` are not empty), otherwise it counts as empty. Note that when your data-model implements multiple template model interfaces you may get unexpected results. However, when in doubt you can use always use `expr!?size > 0` or `expr!?length > 0` instead of `expr?has_content`.

This buit-in is exceptional in that you can use the parentheses trick like with the default value operator. That is, you can write both `product.color?has_content` and `(product.color)?has_content`. The first doesn't handle the case when `product` is missing, the last does.

## interpret

This built-in parses a string as an FTL template, and returns an user-defined directive that executes that template, just as if a template with that content were `include`-d at that point. Example:

Template

    <#assign x=["a", "b", "c"]>
    <#assign templateSource = r"<#list x as y>${y}</#list>">
    <#-- Note: That r was needed so that the ${y} is not interpreted above -->
    <#assign inlineTemplate = templateSource?interpret>
    <@inlineTemplate />

The output:

Output

    abc

As you can see, `inlineTemplate` is a user-defined directive that, when executed, runs the template whose content is the value of `templateSource`.

The name of the template created by `interpret` is the name of the template that calls `interpret`, plus `"->anonymous_interpreted"`. For example, if the template that calls the built-in is `"foo/bar.ftl"`, then the name of the resulting template is `"foo/bar.ftl->anonymous_interpreted"`. Thus, relative paths inside the interpreted template are relative to this path (i.e., the base directory will be `"foo"`), and errors inside the interpreted template will point to this generated template name.

For more helpful error messages, you can override the template name part after the `"->"`. For example, let's say `mailTemplateSource` comes from the `mail_template` database table, and in the case of error, you want the error log to contain the database ID of the failing template:

Template

    <#assign inlineTemplate = [mailTemplateSource, "mail_templates id=${mailTemplateId}"]?interpret>

As you can see, `interpret` can be applied on a sequence of two items, in which case the first item is the FTL string to interpret, and the second items is the template name used after the `"->"`.

The configuration settings that affect the interpreted template are the same as of the surrounding template, except that parser settings specified in the `ftl` directive or was established via tag syntax or naming convention auto-detection are instead coming from the `Configuration` object (or naturally, from the `TemplateConfiguration`, if there's any). Thus the tag syntax, naming convention, whitespace handling, etc. of the interpreted template is independent of that established inside the surrounding template. An important exception from this rule is that the output format and auto-escaping policy is inherited from the lexical context where `interpret` is called from. For example in a template that has `<#ftl output_format="XML">` header (or if you are inside a `<#output_format "XML">...</#output_format>` block), `interpret` calls in it will produce directives with XML output format.

## is_...

These built-ins check the type of a variable, and returns `true` or `false` depending on the type. The list of `is_...` built-ins:

Built-in | Returns `true` if the value is a ...
--- | ---
`is_string` | string
`is_number` | number
`is_boolean` | boolean
`is_date` | Don't use it! Same as `is_date_like`, use that instead. Later may changes meaning to `date_only`.
`is_date_like` | date-like, means either date, time or date-time, or date-like with unknown precise type (since FreeMarker 2.3.21)
`is_date_only` | date (no time of the day part) (since FreeMarker 2.3.21)
`is_time` | time (no year-month-day part) (since FreeMarker 2.3.21)
`is_datetime` | date-time (contains both year-month-day and time of the day)
`is_unknown_date_like` | date-like where we don't know if it's a date or a time or a date-time
`is_method` | method
`is_transform` | transform
`is_macro` | macro or function (yes, also for function; a historical glitch)
`is_hash` | hash (including extended hash)
`is_hash_ex` | extended hash (supports `?keys` and `?values`)
`is_sequence` | sequence (Historical quirk: Before `incompatible_improvements` 2.3.24 it returns `true` for Java methods as they implement the `[index]` operator, however, they fail on `?size`.)
`is_collection` | collection (including extended collection)
`is_collection_ex` | extended collection (supports `?size`)
`is_enumerable` | sequence or collection
`is_indexable` | sequence (Historical quirk: it returns `true` for Java methods as they implement the `[index]` operator.)
`is_directive` | Whatever kind of directive (for example a macro, or `TemplateDirectiveModel`, `TemplateTransformModel`, etc.), or function (a historical glitch)
`is_node` | node
`is_markup_output` | markup output (a value that won't be auto-escaped)

## markup_string

Note:

This built-in is available since FreeMarker 2.3.24.

Returns the markup stored inside a markup output value as string. This is useful if the value has to be passed to a Java method for a `String` parameter, or if we want to manipulate the markup directly in the template. Note that the resulting string can be converted back to markup output value with `?no_esc`.

## namespace

This built-in returns the namespace (i.e. the "gate" hash to the namespace) associated with a macro or function variable. You can use it with macros and functions only.

## new

This is to create a variable of a certain `TemplateModel` implementation.

On the left side of `?` you specify a string, the full-qualified class name of a `TemplateModel` implementation. The result is a method variable that calls the constructor, and returns the new variable.

Example:

Template

    <#-- Creates an user-defined directive be calling the parameterless constructor of the class -->
    <#assign word_wrapp = "com.acmee.freemarker.WordWrapperDirective"?new()>
    <#-- Creates an user-defined directive be calling the constructor with one numerical argument -->
    <#assign word_wrapp_narrow = "com.acmee.freemarker.WordWrapperDirective"?new(40)>

For more information about how the constructor parameters are unwrapped and how overloaded constructor is chosen, read: Programmer's Guide/Miscellaneous/Bean wrapper

This built-in can be a security concern because the template author can create arbitrary Java objects and then use them, as far as they implement `TemplateModel`. Also the template author can trigger static initialization for classes that don't even implement `TemplateModel`. You can (since 2.3.17) restrict the classes accessible with this built-in using `Configuration.setNewBuiltinClassResolver(TemplateClassResolver)` or the `new_builtin_class_resolver` setting. See the Java API docs for more information. If you are allowing not-so-much-trusted users to upload templates then you should definitely look into this topic.

## number_to_date, number_to_time, number_to_datetime

These are used to convert a number (usually a Java `long`) to a date, time or date-time, respectively. This does them same as `new java.util.Date(long)` in Java, that is, the number is interpreted as the milliseconds passed since the epoch. The number can be anything and of any type as far as its value fits into a `long`. If the number isn't a whole number, it will be rounded to whole with half-up rule.

Example:

Template

    ${1305575275540?number_to_datetime}
    ${1305575275540?number_to_date}
    ${1305575275540?number_to_time}

The output will be something like this (depending on the current locale and time zone):

Output

    May 16, 2011 3:47:55 PM
    May 16, 2011
    3:47:55 PM

## sequence

This built-in is used to convert a listable value (one that you can iterate through with the `list` directive) to a more capable sequence value. Sequences support operations like `xs[index]` and `xs?size`. Also, the resulting value is listable for multiple times, even if the original value was backed by a `java.util.Iterator` (which gives error when you try to list it for the 2nd time). This built-in is typically used to work around data-model problems, in case you can't fix the data-model itself. If you can, always fix the data-model instead (give a `java.util.List` or array to the template instead of a more restricted object, like a non-`List` `java.util.Collection`, or a `java.util.Iterator`).

If the value is already a sequence, then this built-in just returns that as is. If the value is not something that the `list` directive could list, then template processing will be aborted with error. Otherwise, it usually fetches all the values, and stores them into a sequence. Be careful if you can have a huge number of items, as all of them will be held in memory on the same time. However, in some special cases fetching and/or storing all elements is avoided; see about the optimizations later.

You should convert a value with `sequence` only once. If you need the resulting sequence at multiple places, always assign the result to a variable, because if the value you convert is only listable once, converting it for the second time will result in error or an empty sequence. Also the conversion is somewhat costly for big collections, so it's better to do it only once.

Example: Let's say you find that `users` is only listable once (because it's a `java.util.Iterator`), but you need to list it for multiple times in the template, and you can't fix the data-model. Then you could do this:

Template

    <#-- Collect all the users into a sequence: -->
    <#assign usersSeq = users?sequence>

    <#list usersSeq as user>...</#list>
    Again:
    <#list usersSeq as user>...</#list>

### Optimizations

Since version 2.3.29, if the result of the `sequence` built-in is directly the input of to the `[index]` or `[range]` operator, or of `?size`, or of `?first`, or a chain of these operations, then the elements will not be collected into the memory, and only as many elements as strictly necessary will be fetched. For example `anIterator?sequence[1]` will just fetch the first 2 items (instead of building a sequence that contains all the elements, and then getting the 2nd element from that). Or, if you write `anIterator?sequence?size`, it will just skip through all elements to count them, but won't store them in memory.

The optimizations will only work within the same chain of built-in calls, so for example in `<#assign seq = anIterator?sequence>${seq[1]}` the `?sequence` step will collect all the elements into the memory, as `anIterator?sequence` and `seq[1]` are separated. On the other hand, the optimizations will work in `anIterator?sequence[10..]?size`, as both `[range]` and `?size` supports it, and they are directly chained together.

## with_args

Note:

This built-in is available since 2.3.30

The goal of this built-in is to add parameters dynamically to the call of a directive (like a macro), function or method. Dynamically means that parameters are added based on a hash value (like `{'a': 1, 'b': 2, 'c': 3}` or a Java `Map`), or a sequence value (like `[1, 2, 3]` or a Java `List`), whose actual content is might only known at the moment when the call happens.

For example, we have this macro `m`:

Template

    <#macro m a b c>a=${a}, b=${b}, c=${c}</#macro>

Normally you call it like:

Template

    <@m a=1 b=2 c=3 />

Below call does the same, assuming `dynArgs` is the hash `{'a': 1, 'b': 2, 'c': 3}`:

Template

    <@m?with_args(dynArgs) />

Output

    a=1, b=2, c=3

Below call also does the same, but combines dynamic arguments from `dynArgsAB`, assumed to be `{'a': 1, 'b': 2}`, and argument `c` specified directly:

Template

    <@m?with_args(dynArgsAB) c=3 />

Output

    a=1, b=2, c=3

To understand why this works, you need to realize that macros, custom directives, functions, and methods in FreeMarker are just values, just like numbers, strings, etc. `<#macro m ...>` just creates a value that's a macro (as opposed to a number, or string, etc.), and then assigns it to variable `m`. Thus, `m` in itself is a valid expression, which evaluates to the macro (but it doesn't call the macro). `<@m ... />` evaluates the expression `m` (and you can use arbitrarily complex expressions there too, like `m?with_args(...)`), and then calls the resulting macro. `m?with_args(dynArgs)` returns a macro that's very similar to the original macro (that's stored in `m`), but its arguments default to the values specified in `dynArgs`. So the result of `m?with_args({'b': 22, 'c': 33})` is similar to a modified macro that was created as `<#macro unspefiedName a b=22 c=33>`. With an example:

Template

    <#assign mWithDefs = m?with_args({'b': 22, 'c': 33})>
    <@myWithDefs a=1 c='overridden'/>

Output

    a=1, b=22, c=overridden

Above we have created a new macro based on the value of `m`, stored it in variable `mWithDefs`, and then later we called it with `<@myWithDefs ... />`.

`with_args` can also be applied on functions (crated with `<#function ...>`) and Java methods (usually get from the data-model, like `myObject.myMethod`). But because functions and methods can only be called with positional arguments (like `f(1, 2, 3)`, and not as `f(a=1, b=2, c=3)`), the argument to `with_args` must be a sequence instead of a hash. Other than that, the same tricks work as with macros:

Template

    <#function f(a, b, c)><#return "a=${a}, b=${b}, c=${c}"></#function>
    <#assign dynArgs=[1, 2, 3]>

    ${f(1, 2, 3)}
    Same as:
    ${f?with_args(dynArgs)()}
    or as:
    ${f?with_args([1, 2])(3)}
    or as:
    ${f?with_args([1])(2, 3)}

    <#assign fWithOneAsFirstArg = f?with_args([1])>
    ${fWithOneAsFirstArg(2, 3)} <#-- same as f(1, 2, 3) -->

Note the double application of `(...)` above, like in `f?with_args(dynArgs)()`. That's because `f?with_args(dynArgs)` just returns a new function (which is just a value), but doesn't call it. So if you want to call that new function immediately (as opposed to assigning it to a variable for example), you need the second `()`.

Because macro calls support both named and positional arguments, the `with_args` argument can be a sequence for macros as well (though using a hash is usually a better practice):

Template

    <#macro m a b c>a=${a}, b=${b}, c=${c}</#macro>

    <#-- Called with named parameters: -->
    <@m a=1 b=2 c=3 />
    Same as:
    <#-- Called with positional parameters: -->
    <@m 1 2 3 />
    Same as:
    <@m?with_args([1, 2, 3]) />
    Same as:
    <#-- Sequence with_args with positional c parameter: -->
    <@m?with_args([1, 2]) 3 />
    Same as:
    <#-- Sequence with_args with named c parameter: -->
    <@m?with_args([1, 2]) c=3 />

To summarize, depending on the type of the value `with_args` is applied on, the type of argument to `with_args` can be:

- Function or method: sequence. Note that WRONG `f?with_args(1, 2)` is WRONG, the correct form is `f?with_args([1, 2])`.

- Macro: hash or sequence

- Directive (user defined): hash

The return type of `with_args` is the same as the type of value it was applied on, like if it's applied on a method (like `myObj.myMethod?with_args(dynArgs)`), then it returns a method.

Note that it's not possible to apply `with_args` on built-in directives, like `<#if ...>`, `<#list ...>`, etc., because they aren't available as values.

This built-in is often used together with the `.args` special variable. For example:

Template

    <#macro m1 a b c>
      m1 does things with ${a}, ${b}, ${c}
    </#macro>

    <#macro m2 a b c>
      m2 does things with ${a}, ${b}, ${c}
      Delegate to m1:
      <@m1?with_args(.args) />
    </#macro>

    <@m2 a=1 b=2 c=3 />

Output

      m2 does things with 1, 2, 3
      Delegate to m1:
      m1 does things with 1, 2, 3

FreeMarker syntax allows using the name before the `?with_args(...)` in the end-tag, just as if the `?with_args(...)` wasn't there:

Template

    <@myMacro?with_args({'a': 1})>...</@myMacro>

Note that as far as the order of arguments is concerned, arguments coming from `with_args(...)` are added before the arguments specified in the call to the returned directive/function/method. In some use cases it's more desirable to add them at the end instead, in which case use the `with_args_last` built-in.

## with_args_last

Note:

This built-in is available since 2.3.30

Same as `with_args`, but if the order of the arguments in resulting final argument list may differs (but not the values in it). This only matters if you pass parameters by position (typically, when calling functions or methods), or when there's catch-all argument.

A typical example with positional arguments is when you want to add the dynamic argument to the end of the parameter list:

Template

    <#function f a b c d>
      <#return "a=${a}, b=${b}, c=${c}, d=${d}">
    </#function>

    <#assign dynamicArgs=[3, 4]>

    with_args:
    ${f?with_args(dynamicArgs)(1, 2)}

    with_args_last:
    ${f?with_args_last(dynamicArgs)(1, 2)}

Output

    with_args:
    a=3, b=4, c=1, d=2

    with_args_last:
    a=1, b=2, c=3, d=4

In the case of name arguments, while the primary mean of identifying an argument is the its name, catch-all arguments (`others...` below) still have an order:

Template

    <#macro m a b others...>
      a=${a}
      b=${b}
      others:
      <#list others as k, v>
        ${k} = ${v}
      </#list>
    </#macro>

    <#assign dynamicArgs={'e': 5, 'f': 6}>

    with_args:
    <@m?with_args(dynamicArgs) a=1 b=2 c=3 d=4 />

    with_args_last:
    <@m?with_args_last(dynamicArgs) a=1 b=2 c=3 d=4 />

Output

    with_args:
      a=1
      b=2
      others:
        e = 5
        f = 6
        c = 3
        d = 4

    with_args_last:
      a=1
      b=2
      others:
        c = 3
        d = 4
        e = 5
        f = 6

If you specify a named parameter that are not catch-all, so they are declared in the `macro` tag (as `a` and `b` below), then `with_args` and `with_args_last` are no different, since the argument order is specified by the macro definition, not the macro call:

Template

    <#macro m a=0 b=0>
      <#-- We use .args to demonstrate the ordering of a and b: -->
      <#list .args as k, v>
        ${k} = ${v}
      </#list>
    </#macro>

    <#assign dynamicArgs={'b': 1}>

    with_args:
    <@m?with_args(dynamicArgs) a=1 />

    with_args_last:
    <@m?with_args_last(dynamicArgs) a=1 />

Output

    with_args:
        a = 1
        b = 1

    with_args_last:
        a = 1
        b = 1

If both the macro or directive call, and the `with_args_last` argument specifies named catch-all argument with the same name (like `b` below), then the placement of those parameters is decide by the macro/directive call:

Template

    <#macro m others...>
      <#list others as k, v>
        ${k} = ${v}
      </#list>
    </#macro>

    <#assign dynamicArgs={'b': 0, 'd': 4}>

    with_args:
    <@m?with_args(dynamicArgs) a=1 b=2 c=3 />

    with_args_last:
    <@m?with_args_last(dynamicArgs) a=1 b=2 c=3 />

Output

    with_args:
        b = 2
        d = 4
        a = 1
        c = 3

    with_args_last:
        a = 1
        b = 2
        c = 3
        d = 4

Previous Next