@@ -0,0 +1,312 @@
1+-- This is a sample custom writer for pandoc. It produces output
2+-- that is very similar to that of pandoc's HTML writer.
3+-- There is one new feature: code blocks marked with class 'dot'
4+-- are piped through graphviz and images are included in the HTML
5+-- output using 'data:' URLs.
6+--
7+-- Invoke with: pandoc -t sample.lua
8+--
9+-- Note: you need not have lua installed on your system to use this
10+-- custom writer. However, if you do have lua installed, you can
11+-- use it to test changes to the script. 'lua sample.lua' will
12+-- produce informative error messages if your code contains
13+-- syntax errors.
14+
15+-- Character escaping
16+local function escape(s, in_attribute)
17+ return s:gsub("[<>&\"']",
18+ function(x)
19+ if x == '<' then
20+ return '<'
21+ elseif x == '>' then
22+ return '>'
23+ elseif x == '&' then
24+ return '&'
25+ elseif x == '"' then
26+ return '"'
27+ elseif x == "'" then
28+ return '''
29+ else
30+ return x
31+ end
32+ end)
33+end
34+
35+-- Helper function to convert an attributes table into
36+-- a string that can be put into HTML tags.
37+local function attributes(attr)
38+ local attr_table = {}
39+ for x,y in pairs(attr) do
40+ if y and y ~= "" then
41+ table.insert(attr_table, ' ' .. x .. '="' .. escape(y,true) .. '"')
42+ end
43+ end
44+ return table.concat(attr_table)
45+end
46+
47+-- Run cmd on a temporary file containing inp and return result.
48+local function pipe(cmd, inp)
49+ local tmp = os.tmpname()
50+ local tmph = io.open(tmp, "w")
51+ tmph:write(inp)
52+ tmph:close()
53+ local outh = io.popen(cmd .. " " .. tmp,"r")
54+ local result = outh:read("*all")
55+ outh:close()
56+ os.remove(tmp)
57+ return result
58+end
59+
60+-- Table to store footnotes, so they can be included at the end.
61+local notes = {}
62+
63+-- Blocksep is used to separate block elements.
64+function Blocksep()
65+ return "\n\n"
66+end
67+
68+-- This function is called once for the whole document. Parameters:
69+-- body, title, date are strings; authors is an array of strings;
70+-- variables is a table. One could use some kind of templating
71+-- system here; this just gives you a simple standalone HTML file.
72+function Doc(body, title, authors, date, variables)
73+ local buffer = {}
74+ local function add(s)
75+ table.insert(buffer, s)
76+ end
77+ add('<!DOCTYPE html>')
78+ add('<html>')
79+ add('<head>')
80+ add('<title>' .. title .. '</title>')
81+ add('</head>')
82+ add('<body>')
83+ if title ~= "" then
84+ add('<h1 class="title">' .. title .. '</h1>')
85+ end
86+ for _, author in pairs(authors) do
87+ add('<h2 class="author">' .. author .. '</h2>')
88+ end
89+ if date ~= "" then
90+ add('<h3 class="date">' .. date .. '</h3>')
91+ end
92+ add(body)
93+ if #notes > 0 then
94+ add('<ol class="footnotes">')
95+ for _,note in pairs(notes) do
96+ add(note)
97+ end
98+ add('</ol>')
99+ end
100+ add('</body>')
101+ add('</html>')
102+ return table.concat(buffer,'\n')
103+end
104+
105+-- The functions that follow render corresponding pandoc elements.
106+-- s is always a string, attr is always a table of attributes, and
107+-- items is always an array of strings (the items in a list).
108+-- Comments indicate the types of other variables.
109+
110+function Str(s)
111+ return escape(s)
112+end
113+
114+function Space()
115+ return " "
116+end
117+
118+function LineBreak()
119+ return "<br/>"
120+end
121+
122+function Emph(s)
123+ return "<em>" .. s .. "</em>"
124+end
125+
126+function Strong(s)
127+ return "<strong>" .. s .. "</strong>"
128+end
129+
130+function Subscript(s)
131+ return "<sub>" .. s .. "</sub>"
132+end
133+
134+function Superscript(s)
135+ return "<sup>" .. s .. "</sup>"
136+end
137+
138+function SmallCaps(s)
139+ return '<span style="font-variant: small-caps;">' .. s .. '</span>'
140+end
141+
142+function Strikeout(s)
143+ return '<del>' .. s .. '</del>'
144+end
145+
146+function Link(s, src, tit)
147+ return "<a href='" .. escape(src,true) .. "' title='" ..
148+ escape(tit,true) .. "'>" .. s .. "</a>"
149+end
150+
151+function Image(s, src, tit)
152+ return "<img src='" .. escape(src,true) .. "' title='" ..
153+ escape(tit,true) .. "'/>"
154+end
155+
156+function Code(s, attr)
157+ return "<code" .. attributes(attr) .. ">" .. escape(s) .. "</code>"
158+end
159+
160+function InlineMath(s)
161+ return "\\(" .. escape(s) .. "\\)"
162+end
163+
164+function DisplayMath(s)
165+ return "\\[" .. escape(s) .. "\\]"
166+end
167+
168+function Note(s)
169+ local num = #notes + 1
170+ -- insert the back reference right before the final closing tag.
171+ s = string.gsub(s,
172+ '(.*)</', '%1 <a href="#fnref' .. num .. '">↩</a></')
173+ -- add a list item with the note to the note table.
174+ table.insert(notes, '<li id="fn' .. num .. '">' .. s .. '</li>')
175+ -- return the footnote reference, linked to the note.
176+ return '<a id="fnref' .. num .. '" href="#fn' .. num ..
177+ '"><sup>' .. num .. '</sup></a>'
178+end
179+
180+function Plain(s)
181+ return s
182+end
183+
184+function Para(s)
185+ return "<p>" .. s .. "</p>"
186+end
187+
188+-- lev is an integer, the header level.
189+function Header(lev, s, attr)
190+ return "<h" .. lev .. attributes(attr) .. ">" .. s .. "</h" .. lev .. ">"
191+end
192+
193+function BlockQuote(s)
194+ return "<blockquote>\n" .. s .. "\n</blockquote>"
195+end
196+
197+function HorizontalRule()
198+ return "<hr/>"
199+end
200+
201+function CodeBlock(s, attr)
202+ -- If code block has class 'dot', pipe the contents through dot
203+ -- and base64, and include the base64-encoded png as a data: URL.
204+ if attr.class and string.match(' ' .. attr.class .. ' ',' dot ') then
205+ local png = pipe("base64", pipe("dot -Tpng", s))
206+ return '<img src="data:image/png;base64,' .. png .. '"/>'
207+ -- otherwise treat as code (one could pipe through a highlighter)
208+ else
209+ return "<pre><code" .. attributes(attr) .. ">" .. escape(s) ..
210+ "</code></pre>"
211+ end
212+end
213+
214+function BulletList(items)
215+ local buffer = {}
216+ for _, item in pairs(items) do
217+ table.insert(buffer, "<li>" .. item .. "</li>")
218+ end
219+ return "<ul>\n" .. table.concat(buffer, "\n") .. "\n</ul>"
220+end
221+
222+function OrderedList(items)
223+ local buffer = {}
224+ for _, item in pairs(items) do
225+ table.insert(buffer, "<li>" .. item .. "</li>")
226+ end
227+ return "<ol>\n" .. table.concat(buffer, "\n") .. "\n</ol>"
228+end
229+
230+-- Revisit association list STackValue instance.
231+function DefinitionList(items)
232+ local buffer = {}
233+ for _,item in pairs(items) do
234+ for k, v in pairs(item) do
235+ table.insert(buffer,"<dt>" .. k .. "</dt>\n<dd>" ..
236+ table.concat(v,"</dd>\n<dd>") .. "</dd>")
237+ end
238+ end
239+ return "<dl>\n" .. table.concat(buffer, "\n") .. "\n</dl>"
240+end
241+
242+-- Convert pandoc alignment to something HTML can use.
243+-- align is AlignLeft, AlignRight, AlignCenter, or AlignDefault.
244+function html_align(align)
245+ if align == 'AlignLeft' then
246+ return 'left'
247+ elseif align == 'AlignRight' then
248+ return 'right'
249+ elseif align == 'AlignCenter' then
250+ return 'center'
251+ else
252+ return 'left'
253+ end
254+end
255+
256+-- Caption is a string, aligns is an array of strings,
257+-- widths is an array of floats, headers is an array of
258+-- strings, rows is an array of arrays of strings.
259+function Table(caption, aligns, widths, headers, rows)
260+ local buffer = {}
261+ local function add(s)
262+ table.insert(buffer, s)
263+ end
264+ add("<table>")
265+ if caption ~= "" then
266+ add("<caption>" .. caption .. "</caption>")
267+ end
268+ if widths and widths[1] ~= 0 then
269+ for _, w in pairs(widths) do
270+ add('<col width="' .. string.format("%d%%", w * 100) .. '" />')
271+ end
272+ end
273+ local header_row = {}
274+ local empty_header = true
275+ for i, h in pairs(headers) do
276+ local align = html_align(aligns[i])
277+ table.insert(header_row,'<th align="' .. align .. '">' .. h .. '</th>')
278+ empty_header = empty_header and h == ""
279+ end
280+ if empty_header then
281+ head = ""
282+ else
283+ add('<tr class="header">')
284+ for _,h in pairs(header_row) do
285+ add(h)
286+ end
287+ add('</tr>')
288+ end
289+ local class = "even"
290+ for _, row in pairs(rows) do
291+ class = (class == "even" and "odd") or "even"
292+ add('<tr class="' .. class .. '">')
293+ for i,c in pairs(row) do
294+ add('<td align="' .. html_align(aligns[i]) .. '">' .. c .. '</td>')
295+ end
296+ add('</tr>')
297+ end
298+ add('</table')
299+ return table.concat(buffer,'\n')
300+end
301+
302+-- The following code will produce runtime warnings when you haven't defined
303+-- all of the functions you need for the custom writer, so it's useful
304+-- to include when you're working on a writer.
305+local meta = {}
306+meta.__index =
307+ function(_, key)
308+ io.stderr:write(string.format("WARNING: Undefined function '%s'\n",key))
309+ return function() return "" end
310+ end
311+setmetatable(_G, meta)
312+