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>This translation attempts to convert numbers properly.
v = (-Inf, Inf, NaN, 6.02214e23)
@htl "<script>var x = $v</script>"
#-> <script>var x = [-Infinity, Infinity, NaN, 6.02214e23]</script>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>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>String values are escaped to avoid <script>, </script>, and <!--.
content = """<script>alert("no injection!")</script>"""
@htl "<script>v = $content</script>"
#-> <script>v = "<\script>alert(\"no injection!\")<\/script>"</script>
content = """--><!-- no injection!"""
@htl "<script>v = $content</script>"
#-> <script>v = "--><\!-- no injection!"</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 directly displayed within a "text/javascript" context. We try to 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
Conversion of Julia values to JavaScript can be performed explicitly within attributes using js(), which is not exported by default.
using HypertextLiteral: js
v = """Brown "M&M's"!""";
@htl "<div onclick='alert($(js(v)))'>"
#-> <div onclick='alert("Brown \"M&M's\"!")'>The js() function can be used independently.
msg = "alert($(js(v)))"
@htl "<div onclick=$msg>"
#-> <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($(js(v)))'>"
#-> <div onclick='alert("<\script>nested<\/script>")'>Extensions
If an object is not showable as "text/javascript" then you will get the following exception.
@htl("<script>$(π)</script>")
#-> …ERROR: "Irrational{:π} is not showable as text/javascript"⋮This can be overcome with a show() method for "text/javascript",
struct Log
data
end
function Base.show(io::IO, mime::MIME"text/javascript", c::Log)
print(io, "console.log(", c.data, ")")
endLike the HTML wrapper, you take full control of ensuring this content is relevant to the context.
print(@htl """<script>$(Log(missing))</script>""")
#-> <script>console.log(missing)</script>Alternatively, one could implement print_script, recursively calling this function on datatypes which require further translation.
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(missing))</script>""")
#-> <script>console.log(null)</script>This method is how we provide support for datatypes in Base without committing type piracy by implementing show for "text/javascript".
Edge Cases
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 (<) symbol, so there may be strictly unnecessary escaping.
v = "<!-- <Script> <! 3<4 </ <s !>"
@htl "<script>var x = $v</script>"
#-> <script>var x = "<\!-- <\Script> <\! 3<4 <\/ <\s !>"</script>It's important to handle unicode content properly.
s = "α\n"
@htl("<script>alert($(s))</script>")
#-> <script>alert("α\n")</script>