-
Notifications
You must be signed in to change notification settings - Fork 10
/
convert.jl
214 lines (178 loc) · 5.84 KB
/
convert.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
"""
print_value(io, value)
This is the default translation of interpolated values within rawtext
tags, such as `<style>` and attribute values.
* The elements of a `Tuple` or `AbstractArray` object are printed,
with a space between each item.
* The `Pair`, `NamedTuple`, and `Dict` objects are treated as if
they are CSS style elements, with a colon between key and value,
each pair delimited by a semi-colon.
* The `Nothing` object is treated as an empty string.
Otherwise, this method simply uses the standard `print` representation
for the given object.
"""
print_value(io::IO, @nospecialize value) =
print(io, value)
print_value(io::IO, ::Nothing) =
nothing
function print_value(io::IO, xs::Union{Tuple, AbstractArray, Base.Generator})
prior = false
for x in xs
if prior
print(io, " ")
end
print_value(io, x)
prior = true
end
end
function print_pairs(io, xs)
prior = false
for (key, value) in xs
name = normalize_attribute_name(key)
if prior
print(io, "; ")
end
print(io, name)
print(io, ": ")
print_value(io, value)
prior = true
end
print(io, ";")
end
print_value(io::IO, pair::Pair) = print_pairs(io, (pair,))
print_value(io::IO, items::Dict) = print_pairs(io, items)
print_value(io::IO, items::NamedTuple) = print_pairs(io, pairs(items))
print_value(io::IO, items::Tuple{Pair, Vararg{Pair}}) = print_pairs(io, items)
"""
attribute_value(x)
This method may be implemented to specify a printed representation
suitable for use within a quoted attribute value.
"""
attribute_value(x::String) = x
attribute_value(x::Number) = x
attribute_value(x::Symbol) = x
mutable struct AttributeValue
content::Any
end
Base.print(ep::EscapeProxy, x::AttributeValue) =
print_value(ep, x.content)
attribute_value(@nospecialize x) = AttributeValue(x)
"""
content(x)
This method may be implemented to specify a printed representation
suitable for `text/html` output. `AbstractString`, `Symbol` and `Number`
(including `Bool`) types are printed, with proper escaping.
A default implementation first looks to see if `typeof(x)` has
implemented a way to show themselves as `text/html`, if so, this is
used. Otherwise, the result is printed within a `<span>` tag, using a
`class` that includes the module and type name. Hence, `missing` is
serialized as: `<span class="Base-Missing">missing</span>`.
"""
@generated function content(x)
if hasmethod(show, Tuple{IO, MIME{Symbol("text/html")}, x})
return :(Render(x))
else
mod = parentmodule(x)
cls = string(nameof(x))
if mod == Core || mod == Base || pathof(mod) !== nothing
cls = join(fullname(mod), "-") * "-" * cls
end
span = """<span class="$cls">"""
return :(reprint(Bypass($span), x, Bypass("</span>")))
end
end
function reprint(xs...)
# generated functions cannot contain a closure
Reprint() do io::IO
for x in xs
print(io, x)
end
end
end
content(x::Union{AbstractString, Symbol}) = x
content(x::Nothing) = ""
content(x::Union{AbstractFloat, Bool, Integer}) = x
content(xs...) = content(xs)
function content(xs::Union{Tuple, AbstractArray, Base.Generator})
Reprint() do io::IO
for x in xs
print(io, content(x))
end
end
end
#-------------------------------------------------------------------------
"""
attribute_pair(name, value)
Wrap and escape attribute name and pair within a single-quoted context
so that it is `showable("text/html")`. It's assumed that the attribute
name has already been normalized.
If an attribute value is `Bool` or `Nothing`, then special treatment is
provided. If the value is `false` or `nothing` then the entire pair is
not printed. If the value is `true` than an empty string is produced.
"""
no_content = Reprint(io::IO -> nothing)
function attribute_pair(name, value)
Reprint() do io::IO
print(io, " ")
print(io, name)
print(io, Bypass("='"))
print(io, attribute_value(value))
print(io, Bypass("'"))
end
end
function attribute_pair(name, value::Bool)
if value == false
return no_content
end
Reprint() do io::IO
print(io, " ")
print(io, name)
print(io, Bypass("=''"))
end
end
attribute_pair(name, value::Nothing) = no_content
"""
inside_tag(value)
Convert Julian object into a serialization of attribute pairs,
`showable` via `MIME"text/html"`. The default implementation of this
delegates value construction of each pair to `attribute_pair()`.
"""
function inside_tag(value::Pair)
name = normalize_attribute_name(value.first)
return attribute_pair(name, value.second)
end
function inside_tag(value::Union{AbstractString, Symbol})
name = normalize_attribute_name(value)
return attribute_pair(name, "")
end
function inside_tag(xs::AbstractDict)
Reprint() do io::IO
for (key, value) in xs
name = normalize_attribute_name(key)
print(io, attribute_pair(name, value))
end
end
end
inside_tag(values::NamedTuple) =
inside_tag(pairs(values))
inside_tag(::Nothing) = no_content
"""
tag_name(x)
Tag names need to start with `/[a-z]/i`,
and can't contain any spaces, `>` or `/`.
Although technically all other characters would be valid,
we only allow letters, numbers and hyphens for now.
"""
function tag_name(x::String)
if isempty(x)
throw("A tag name can not be empty")
elseif !occursin(r"^[a-z]"i, x)
throw("A tag name can only start with letters, not `$(x[1])`")
elseif occursin(r"[^a-z0-9-]", x)
throw("Content within a tag name can only contain latin letters, numbers or hyphens (`-`)")
else
x
end
end
tag_name(x::Symbol) = tag_name(string(x))
tag_name(x::Any) = throw("Can't use complex objects as tag name")