# `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])" #-> idxvalue 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]) #-> 1A&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) #->
idxvalue
1A&B
## 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: #"⋮ =#