-
Notifications
You must be signed in to change notification settings - Fork 3
/
fenced-blocks.lua
308 lines (256 loc) · 8.2 KB
/
fenced-blocks.lua
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
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
--[[
A Pandoc 2 lua filter converting Pandoc native divs to amsthm style numbered environments.
Author: Bryan Clair, expanding latex-div.lua by Romain Lesur, Christophe Dervieux, and Yihui Xie
License: Public domain
For LaTeX output, things are fairly simple since the amsthm package handles
all the numbering and cross referencing.
For HTML output, the numbering and cross referencing are done in this filter,
and those numbers are placed into the HTML output as static values.
Currently this interferes with Bookdown's ```{theorem}``` style blocks, because
each of those shows up as two blocks in the output.
--]]
--[[
USAGE
::: example
This is a fenced example div that will be numbered.
:::
::: {.example #refname data="Important Example"}
This is an important example that can be referenced.
:::
::: {.theorem data="R is complete" data-latex="$\mathbb{R} is complete"}
You can customize the theorem name for latex with data-latex or for html with data-html.
:::
::: {.theorem data-html="2π Theorem" data-latex="$2\pi$ Theorem"}
This theorem has different names depending on output.
:::
::: theorem*
All classes have unnumbered * variants built-in.
:::
--]]
--[[
SETTINGS
Only fenced divs with names appearing in the divstyle definition
below will be affected by this filter.
Settings include style, sequence, name, numbered.
These settings only matter for HTML output.
In LaTeX, this setup is handled by amsthm (or ntheorem) definitions in the preamble.
Since those are currently hardcoded into bookdown, they cannot be changed by this filter.
Ideally, these settings should come from the YAML for the source document,
and generate the appropriate LaTeX \newtheorem commands as well.
--]]
divstyle = {
alert = {},
assumptions = {},
definition = { style = "definition"},
example = { sequence = "definition", style = "definition"},
exercise = { style = "definition", name = "" },
proof = { numbered = false, style = "remark", name = "Proof:" },
proposition = {},
remark = { numbered = false, style = "remark" },
theorem = {},
tryit = {}
}
--[[
HTML GLOBALS
--]]
-- counters: one counter for each class of fenced block
counters = {}
-- labels: each labeled block's reference number is stored here
labels = {}
-- chapter counter and current chapter string
chapter = 0
cur_chapter = "??"
--[[
HTML METHODS
--]]
-- handle_fenced_div
-- assign a number
-- associate that number with the div's identifier
-- insert heading text
handle_fenced_div = function (div)
local env = div.classes[1]
-- if the div has no class, we're not interested
if not env then return nil end
-- check for unnumbered * variants
local starred = false
if env:match("*$") then
starred = true
env = env:sub(1, #env - 1) -- change to unstarred version
end
-- setup style from divstyle or ignore this div
if not divstyle[env] then
return nil
-- divstyle[env] = {} -- use defaults
end
div.classes[1] = env
local name = divstyle[env].name or env:gsub("^%l", string.upper)
local style = divstyle[env].style or "plain"
local sequence = divstyle[env].sequence or env
local numbered = true -- the 'or' idiom fails for booleans
if divstyle[env].numbered ~= nil then
numbered = divstyle[env].numbered
end
if starred then numbered = false end
-- calculate numbering string for this env
local number = ""
if numbered then
-- increment counter for this numbering sequence
if counters[sequence] then
counters[sequence] = counters[sequence] + 1
else
counters[sequence] = 1
end
number = cur_chapter .. "." .. tostring(counters[sequence])
end
-- set label in dictionary if there is a label
local label = div.identifier
if label ~= '' then
if labels[label] then
io.stderr:write("Duplicate fenced block label: " .. label .. "\n")
end
labels[label] = number
end
-- insert begin text before content
local begintxt = string.format(
'<span class="divhead-%s %s-before">%s %s</span>',
style, -- css class divhead-plain, divhead-definition, divhead-remark, etc.
env, -- css class env-before for customization of this env's style
name, -- display name
number -- display number
)
table.insert(
div.content, 1,
pandoc.RawBlock('html', begintxt)
)
local parentxt = div.attributes["data-html"] or div.attributes["data"]
if parentxt then
-- handle references *within* the data text
-- this will only catch back-references, but has an important use case
-- where a proof environment wants to refer to the theorem it's proving
parentxt = refstring(parentxt)
table.insert(
div.content, 2,
pandoc.RawBlock('html', "(" .. parentxt .. ")")
)
-- remove data attribute from further processing
if not div.attributes["data-html"] then
div.attributes["data"] = nil
else
div.attributes["data-html"] = nil
end
end
return div
end
-- new_chapter
-- increment chapter counter (carefully)
-- reset fenced div counters
function new_chapter(el)
-- check if this is a numbered chapter
local numbered = true
for i,c in ipairs(el.classes) do
if c == "unnumbered" then numbered = false end
end
-- set the current chapter label prefix
if numbered then
chapter = chapter + 1
cur_chapter = tostring(chapter)
else
-- use first character of identifier as the chapter value
cur_chapter = el.identifier:sub(1,1):upper()
end
-- reset all env counters
counters = {}
return nil
end
-- Called on all Block elements, dispatch as appropriate
function block_dispatch(el)
if el.t == "Header" and el.level == 1 then
return new_chapter(el)
elseif el.t == "Div" then
return handle_fenced_div(el)
end
end
-- this function takes a string and replaces all @ref(label) inside
-- it with the appropriate div number and link
-- returns the string.
refstring = function (s)
local changed
repeat
changed = false
leading, reflabel, trailing = s:match "(.*)@ref%((.-)%)(.*)"
if reflabel and labels[reflabel] then
changed = true
s = leading
s = s ..'<a href="#' .. reflabel .. '">'
s = s .. labels[reflabel] .. '</a>'
s = s .. trailing
end
until not changed
return s
end
fixrefs = function (elem)
link = refstring(elem.text)
if link ~= elem.text then
return pandoc.RawInline("html", link)
end
return elem
end
--[[
LATEX METHODS
--]]
latex_div = function (div)
local env = div.classes[1]
-- if the div has no class, the object is left unchanged
if not env then return nil end
-- build begin text
local begintxt = '\\begin{' .. env .. '}'
local parentxt = div.attributes['data-latex'] or div.attributes['data']
if parentxt then
--[[
-- Switching to ntheorem from amsthm solved the idiosyncratic proof environment,
-- rendering this code unnecessary.
if env == "proof" then
-- latex proof environment optional text [parentxt] behaves differently
-- from the other theorem environments. Either we coerce things here
-- or we coerce things on the HTML side. I've chosen to deal with
-- it here, since it's latex's inconsistency. Unfortunately, this
-- is bad for i18n.
parentxt = "Proof (" .. parentxt .. "):"
end
--]]
begintxt = begintxt .. '[' .. parentxt .. ']'
if not div.attributes["data-latex"] then
div.attributes["data"] = nil
else
div.attributes["data-latex"] = nil
end
end
local label = div.identifier
if label ~= '' then
begintxt = begintxt .. '\\label{' .. label .. '}'
-- don't let the identifier escape to become a double label
div.identifier = ''
end
-- insert text before and after content
table.insert(
div.content, 1,
pandoc.RawBlock('tex', begintxt)
)
table.insert(
div.content,
pandoc.RawBlock('tex', '\\end{' .. env .. '}')
)
return div
end
--[[
FILTER
--]]
if FORMAT == "latex" or FORMAT == "beamer" then
return {{ Div = latex_div }}
elseif FORMAT == "gitbook" or FORMAT == "html" or FORMAT == "html4" then
-- returning in this order means that the labels get resolved
-- before the reference handler replaces them
return {{ Block = block_dispatch } , { Str = fixrefs }}
else
return nil
end