""" Reprint(fn) - apply the lambda function when printed """ mutable struct Reprint content::Function end Base.print(io::IO, r::Reprint) = r.content(io) """ Render(data) - printed object shows its text/html """ struct Render{T} content::T end Base.print(io::IO, r::Render) = show(io, MIME"text/html"(), r.content) """ Bypass(data) - printed object passes though EscapeProxy unescaped """ mutable struct Bypass{T} content::T end Base.print(io::IO, x::Bypass) = print(io, x.content) abstract type IOProxy <: IO end """ EscapeProxy(io) - wrap an `io` to perform HTML escaping This is a transparent proxy that performs HTML escaping so that objects that are printed are properly converted into valid HTML values. As a special case, objects wrapped with `Bypass` are not escaped, and bypass the proxy. # Examples ```julia-repl julia> ep = EscapeProxy(stdout); julia> print(ep, "A&B") A&B julia> print(ep, Bypass("")) ``` """ struct EscapeProxy{T<:IO} <: IOProxy io::T end Base.print(ep::EscapeProxy, h::Reprint) = h.content(ep) Base.print(ep::EscapeProxy, w::Render) = show(ep.io, MIME"text/html"(), w.content) Base.print(ep::EscapeProxy, x::Bypass) = print(ep.io, x) function Base.write(ep::EscapeProxy, octet::UInt8) if octet == Int('&') write(ep.io, "&") elseif octet == Int('<') write(ep.io, "<") elseif octet == Int('"') write(ep.io, """) elseif octet == Int('\'') write(ep.io, "'") else write(ep.io, octet) end end function Base.unsafe_write(ep::EscapeProxy, input::Ptr{UInt8}, nbytes::UInt) written = 0 last = cursor = input final = input + nbytes while cursor < final ch = unsafe_load(cursor) if ch == Int('&') written += unsafe_write(ep.io, last, cursor - last) written += unsafe_write(ep.io, pointer("&"), 5) cursor += 1 last = cursor continue end if ch == Int('<') written += unsafe_write(ep.io, last, cursor - last) written += unsafe_write(ep.io, pointer("<"), 4) cursor += 1 last = cursor continue end if ch == Int('\'') written += unsafe_write(ep.io, last, cursor - last) written += unsafe_write(ep.io, pointer("'"), 6) cursor += 1 last = cursor continue end if ch == Int('"') written += unsafe_write(ep.io, last, cursor - last) written += unsafe_write(ep.io, pointer("""), 6) cursor += 1 last = cursor continue end cursor += 1 end if last < final written += unsafe_write(ep.io, last, final - last) end return written end # IO passthrough methods: Base.in(key_value::Pair, io::IOProxy) = in(key_value, io.io) Base.haskey(io::IOProxy, key) = haskey(io.io, key) Base.getindex(io::IOProxy, key) = getindex(io.io, key) Base.get(io::IOProxy, key, default) = get(io.io, key, default) Base.keys(io::IOProxy) = keys(io.io) Base.displaysize(io::IOProxy) = displaysize(io.io)