Script Interpolation
Within a <script>
tag, Julia values are serialized to their equivalent Javascript. String literal values are rendered as double-quoted values.
using HypertextLiteral
v = """Brown "M&M's"!""";
@htl "<script>var x = $v</script>"
#-> <script>var x = "Brown \"M&M's\"!"</script>
Julia tuples and vectors are serialized as Javascript array. Integers, boolean, and floating point values are handled. As special cases, nothing
is represented using undefined
and missing
using null
.
v = Any[true, 1, 1.0, nothing, missing]
@htl "<script>var x = $v</script>"
#-> <script>var x = [true, 1, 1.0, undefined, null]</script>
Julia named tuples and dictionaries are serialized as a Javascript object. Symbols are converted to string values.
v = Dict(:min=>1, :max=>8)
@htl "<script>var x = $v</script>"
#-> <script>var x = {"max": 8, "min": 1}</script>
JavaScript
Sometimes you already have content that is valid Javascript. This can be printed directly, without escaping using a wrapper similar to HTML
:
using HypertextLiteral: JavaScript
expr = JavaScript("""console.log("Hello World")""")
@htl "<script>$expr</script>"
#-> <script>console.log("Hello World")</script>
The JavaScript
wrapper indicates the content should be printed within a "text/javascript"
context. Even so, it does help catch content which is not properly escaped for use within a <script>
tag.
expr = """<script>console.log("Hello World")</script>"""
@htl "<script>$(JavaScript(expr))</script>"
#-> …ERROR: "Content within a script tag must not contain `</script>`"⋮
Similarly, a comment sequence is also forbidden.
expr = "<!-- invalid comment -->"
@htl "<script>$(JavaScript(expr))</script>"
#-> …ERROR: "Content within a script tag must not contain `<!--`"⋮
Script Attributes
If a quoted attribute starts with "on"
, then its interpolation is done as if it were in a script
tag, only that the result is additionally ampersand escaped.
v = """Brown "M&M's"!""";
@htl "<div onclick='alert($v)'>"
#-> <div onclick='alert("Brown \"M&M's\"!")'>
Although strictly unnecessary, slash escaping to prevent <\script>
content is still provided.
v = "<script>nested</script>"
@htl "<div onclick='alert($v)'>"
#-> <div onclick='alert("<\script>nested<\/script>")'>
The JavaScript
wrapper can be used to suppress this conversion.
expr = JavaScript("""console.log("Hello World")""")
@htl "<div onclick='$expr'>"
#-> <div onclick='console.log("Hello World")'>
This interpolation rule does not apply to unquoted attribute values. Moreover, special treatment of booleans still applies in this case.
expr = """console.log("Hello World")"""
@htl "<div onclick=$expr>"
#-> <div onclick='console.log("Hello World")'>
@htl "<div onclick=$(nothing)>...</div>"
#-> <div>...</div>
Extensions
Within the script
tag, content is not "text/html"
, instead, it is treated as "text/javascript"
. Custom objects which are showable
as "text/javascript"
can be printed without any escaping in this context.
struct Log
data
end
function Base.show(io::IO, mime::MIME"text/javascript", c::Log)
print(io, "console.log(", c.data, ")")
end
print(@htl """<script>$(Log("undefined"))</script>""")
#-> <script>console.log(undefined)</script>
Alternatively, one could implement print_script
to provide a representation for this context.
import HypertextLiteral: print_script
function print_script(io::IO, c::Log)
print(io, "console.log(")
print_script(io, c.data)
print(io, ")")
end
print(@htl """<script>$(Log(nothing))</script>""")
#-> <script>console.log(undefined)</script>
This content must not only be valid Javascript, but also escaped so that <script>
, </script>
, and <!--
literal values do not appear. When using print_script
this work is performed automatically.
content = """<script>alert("hello")</script>"""
print(@htl "<script>$(Log(content))</script>")
#-> <script>console.log("<\script>alert(\"hello\")<\/script>")</script>
Edge Cases
Within a <script>
tag, the script open and close tags are escaped.
v = "<script>nested</script>"
@htl "<script>var x = $v</script>"
#-> <script>var x = "<\script>nested<\/script>"</script>
Within a <script>
tag, comment start (<!--
) must also be escaped. Moreover, capital <Script>
and permutations are included. We only scan the first character after the left-than character.
v = "<!-- <Script> <! 3<4 </ <s !>"
@htl "<script>var x = $v</script>"
#-> <script>var x = "<\!-- <\Script> <\! 3<4 <\/ <\s !>"</script>
Within a <script>
tag, we want to ensure that numbers are properly converted.
v = (-Inf, Inf, NaN, 6.02214e23)
@htl "<script>var x = $v</script>"
#-> <script>var x = [-Infinity, Infinity, NaN, 6.02214e23]</script>
Besides dictionary objects, we support named tuples.
v = (min=1, max=8)
@htl "<script>var x = $v</script>"
#-> <script>var x = {"min": 1, "max": 8}</script>
Comments should not exist within a script tag.
@htl("<script><!-- comment --></script>")
#-> ERROR: LoadError: "script escape or comment is not implemented"⋮