# `htl` String Literal
This package additionally provides the `@htl_str` non-standard string
literal.
using HypertextLiteral
name = "World"
htl"Hello $name"
#-> Hello World
@htl_str "Hello \$name"
#-> Hello World
## Notable Differences
Unlike `@htl`, the `htl` string literal uses `@raw_str` escaping rules.
So long as a double-quote character does not come before a slash, the
slash itself need not be escaped.
htl"\some\path"
#-> \some\path
In this notation, `\"` can be used to escape a double quote. However,
other escape sequences are not expanded.
htl"Hello\"\nWorld\""
#-> Hello"\nWorld"
As a special case, the dollar-sign (`$`) can be escaped by doubling.
amount = 42
htl"They paid $$$amount"
#-> They paid $42
Alternatively, one can use the HTML character entity `#&36;`.
htl"They paid $$amount"
#-> They paid $42
Unlike the `@htl` macro, nesting doesn't work.
htl"Hello $(htl"World")"
#-> ERROR: syntax: cannot juxtapose string literal
Triple double-quoted syntax can be used in this case.
htl"""Hello $(htl"World")"""
#-> Hello World
However, this trick works only one level deep. Hence, there are some
significant downsides to using this format, which are explored in detail
at Julia #38948.
## Dynamic Templates
The `@htl_str` macro can be used to dynamically construct templates.
Suppose you have a schema that is provided dynamically. Let's make a
test database with exactly one row.
T = NamedTuple{(:idx, :value), Tuple{Int64, String}};
database = [T((1, "A&B"))];
display(database)
#=>
1-element Vector{NamedTuple{(:idx, :value), …}:
(idx = 1, value = "A&B")
=#
We could construct a table header from this schema.
fields = T.parameters[1]
#-> (:idx, :value)
head = @htl "
$([@htl("$x") for x in fields])"
#-> |
idx | value
Then, we need to compute a template for each row.
row_template = " |
---|
$(join(["\$(row[$(repr(x))])" for x in fields]))"
print(row_template)
#-> |
$(row[:idx]) | $(row[:value])
Using `eval` with `@htl_str` we could construct our template function.
eval(:(tablerow(row) = @htl_str $row_template))
tablerow(database[1])
#-> |
1 | A&B
A template for the entire table could be constructed.
table_template = "$head\$([tablerow(row) for row in data]) "
print(table_template)
#-> idx…$([tablerow(row) for row in data]) |
---|
eval(:(print_table(data) = @htl_str $table_template))
Then, finally, this could be used.
print_table(database)
#->
## Regression Tests & Notes
Due to `@raw_str` escaping, string literal forms are a bit quirky. Use
the triple double-quoted form if your content has a double quote. Avoid
slashes preceding a double quote, instead use the `/` HTML entity.
htl"\"\t\\"
#-> "\t\
htl"(\\\")"
#-> (\")
Even though we could permit interpretation of arrays notation, we stick
with keeping this an error for consistency with the macro form.
htl"$[1,2,3]"
#=>
ERROR: LoadError: DomainError with [1, 2, 3]:
interpolations must be symbols or parenthesized⋮
=#
Let's also not permit top-level assignments.
htl"$(k=value)"
#=>
ERROR: LoadError: DomainError with k = value:
assignments are not permitted in an interpolation⋮
=#
Since the implementers of the notation have some control over the
parsing, we can reliably detect string literals (Julia #38501).
htl"""$("A&B")"""
#-> A&B
There is one less round of parenthesis needed for tuples, named tuples
and generators (Julia #38734).
name = "Hello"
htl""
#->
print(htl"$(n for n in 1:3)")
#-> 123
Due to escaping rules, we interpret a dollar sign as beginning an
expression, even if it might otherwise be preceded by a slash.
htl"Hello\$#"
#=>
ERROR: LoadError: "missing expression at 7: #"⋮
=#
|