Parsing Tag Attributes
From Textpattern CMS User Documentation
Conventions for attributes
Most Textpattern tags allow you to specify attributes as key/value pairs to override default behaviour.
<txp:tag key="value" />
<txp:category type="image" title="1" />
Usage of attributes in Textpattern tags is similar to attributes in HTML.
A few important rules to remember:
- attribute values must always be delimited by double (or single) quotes. Single quotes have special meaning, so use double quotes by default.
- attribute keys must be lower case.
- for plugin developers: attribute keys can consist of the letters 'a' through 'z' and underscore (not as the first or last character).
Attribute value escaping
Almost any character can be used in an attribute value. Unlike in HTML, there is no need for using escape codes for characters like
&. In fact, attribute values should contain no HTML escape codes at all, because the tag itself should take care of proper escaping.
However, there is one exception: the delimiter character.
The attribute value is delimited by a pair of double (or single) quotes and this poses a restriction on using the delimiter character inside the attribute value.
Suppose you wanted to use this as an attribute value: reward for "good" behaviour:
<txp:tag key="reward for "good" behaviour" />
For a human, it's easy to understand how this was intended, but for Textpattern the attribute value ends at the double quote before 'good'. The remainder of the attribute value makes it an incorrectly formatted tag (causing to show up as-is in the resulting HTML code).
If single quotes didn't have special meaning, one would solve this by using single quotes to delimit the attribute value:
<txp:tag key='reward for "good" behaviour' />
While this works as intended, it doesn't account for attribute values containing both single and double quotes.
Since Textpattern 4.0.7, duplicate delimiter characters used as part of an attribute value are interpreted as a literal character instead of delimiters. A few examples:
<txp:tag key="reward for ""good"" behaviour" />
<txp:tag key='reward for ''good'' behaviour' />
If you look closely at the second example, you'll see that in the attribute value, there are two single quotes on each side of the word 'good', not a double quote.
One last example that shows what's possible:
<txp:tag key="let's use ""double"" quotes & <html> here" />
The only character that needs escaping is the chosen attribute delimiter itself where it occurs inside the attribute value (double quote in this instance).
NOTE: In Textpattern 4.0.6 and older, delimiter escaping did not exist and the tag parser also failed to handle attribute values containing a
Parsed attribute values
In most cases, you want attribute values to be treated as just a string of text, but there are situations where it can be useful to let Textpattern parse the attribute value itself. Given the popularity of the asy_wondertag plugin (up until Textpattern 4.0.6 at least) for enabling tags-in-attributes, attribute parsing is now possible natively for single quoted values.
Double quoted attribute values are not parsed, so if your attribute value contains a value that looks like a tag, but should be treated as literal text, you must always use double quotes. In fact, you should use double quotes to delimit attribute values at all times, unless you want the attribute value parsed. The reason for this is simple: speed. Parsing an attribute value is slower than treating it as plain text.
What does all this mean? Here are some examples, starting with attribute values that are not parsed:
<txp:tag key="plain text" />
<txp:tag key="literal <txp:tag />" />
In the above examples, the attribute are treated as plain text; the literal tag is not parsed. If you wanted the tag in the attribute value to be parsed and return the actual result of the tag, you should write it like this:
<txp:tag key='parsed <txp:tag />' />
Using an article that has a custom field named 'email' containing an email address firstname.lastname@example.org and a custom field 'name' containing 'Dean Allen':
<txp:email email='<txp:custom_field name="email" />' linktext="Send email" title='Send email to <txp:custom_field name="name" />' />
Because the single quoted attribute values are parsed, after parsing the attribute values, it looks like this:
<txp:email email="email@example.com" linktext="Send email" title="Send email to Dean Allen" />
If it were just one article, you wouldn't need attribute parsing, but if you have many articles with different email addresses in such a custom field, this can be very useful.
Attribute value parsing has no real limitations. Within a parsed attribute value, you can:
- have an unlimited number of tags
- mix plain text with tags
- use container tags (yes, even <txp:php>), self-closing tags and if/else constructs
- (only for die-hard users) even parse the attributes of tags inside an attribute to unlimited depth, provided you use proper attribute value quoting and escaping
Nesting quotes in quotes in...
Referring to the last point above, things can become a little hairy if you want to insert a tag into an attribute of a tag that's already an attribute! But keep your wits about you and you can do things like this:
<txp:variable name="file_count" value='<txp:file_download_list form="file_cat" category=''<txp:l10n_get_lang type="short" />'' />' /> <txp:if_variable name="file_count"> <h3>Some Header</h3> <txp:file_download_list category='<txp:l10n_get_lang type="short" />' wraptag="ul" break="li" /> <txp:else /> <p>no files for this language</p> </txp:if_variable>
In the first statement, notice the pairs of single apostrophes inside the category attribute? They are necessary to escape the single quotes so the parser does not see them as the end of the value attribute in the
<txp:variable />. At each nested level, the number of single quotes surrounding a tag is usually doubled to maintain the integrity of the statements; this can get mighty interesting unless you keep your wits about you!
That example is on a real-world multi-lingual site that uses the MLP system. It reads the current language code in use by the visitor (
<txp:l10n_get_lang type="short" />) and grabs all the files from a category of the same name, assuming there are any at all.
The file_cat form returns some markup (in this case, just a series of links to the relevant files) which is all assigned to the value of the variable named file_count.
The upshot is that this variable can be tested with if_variable to see if it contains anything at all and -- if it does -- the file list can be displayed. Conversely, if there are no files for that language then the else part of the conditional statement is displayed.