|
-- This is a sample custom writer for pandoc. It produces output |
|
-- that is very similar to that of pandoc's HTML writer. |
|
-- There is one new feature: code blocks marked with class 'dot' |
|
-- are piped through graphviz and images are included in the HTML |
|
-- output using 'data:' URLs. |
|
-- |
|
-- Invoke with: pandoc -t sample.lua |
|
-- |
|
-- Note: you need not have lua installed on your system to use this |
|
-- custom writer. However, if you do have lua installed, you can |
|
-- use it to test changes to the script. 'lua sample.lua' will |
|
-- produce informative error messages if your code contains |
|
-- syntax errors. |
|
|
|
-- Character escaping |
|
local function escape(s, in_attribute) |
|
return s:gsub("[<>&\"']", |
|
function(x) |
|
if x == '<' then |
|
return '<' |
|
elseif x == '>' then |
|
return '>' |
|
elseif x == '&' then |
|
return '&' |
|
elseif x == '"' then |
|
return '"' |
|
elseif x == "'" then |
|
return ''' |
|
else |
|
return x |
|
end |
|
end) |
|
end |
|
|
|
-- Helper function to convert an attributes table into |
|
-- a string that can be put into HTML tags. |
|
local function attributes(attr) |
|
local attr_table = {} |
|
for x,y in pairs(attr) do |
|
if y and y ~= "" then |
|
table.insert(attr_table, ' ' .. x .. '="' .. escape(y,true) .. '"') |
|
end |
|
end |
|
return table.concat(attr_table) |
|
end |
|
|
|
-- Run cmd on a temporary file containing inp and return result. |
|
local function pipe(cmd, inp) |
|
local tmp = os.tmpname() |
|
local tmph = io.open(tmp, "w") |
|
tmph:write(inp) |
|
tmph:close() |
|
local outh = io.popen(cmd .. " " .. tmp,"r") |
|
local result = outh:read("*all") |
|
outh:close() |
|
os.remove(tmp) |
|
return result |
|
end |
|
|
|
-- Table to store footnotes, so they can be included at the end. |
|
local notes = {} |
|
|
|
-- Blocksep is used to separate block elements. |
|
function Blocksep() |
|
return "\n\n" |
|
end |
|
|
|
-- This function is called once for the whole document. Parameters: |
|
-- body is a string, metadata is a table, variables is a table. |
|
-- This gives you a fragment. You could use the metadata table to |
|
-- fill variables in a custom lua template. Or, pass `--template=...` |
|
-- to pandoc, and pandoc will add do the template processing as |
|
-- usual. |
|
function Doc(body, metadata, variables) |
|
local buffer = {} |
|
local function add(s) |
|
table.insert(buffer, s) |
|
end |
|
add('<article>' .. body .. '</article>') |
|
if #notes > 0 then |
|
add('<ol class="footnotes">') |
|
for _,note in pairs(notes) do |
|
add(note) |
|
end |
|
add('</ol>') |
|
end |
|
return table.concat(buffer,'\n') |
|
end |
|
|
|
-- The functions that follow render corresponding pandoc elements. |
|
-- s is always a string, attr is always a table of attributes, and |
|
-- items is always an array of strings (the items in a list). |
|
-- Comments indicate the types of other variables. |
|
|
|
function Str(s) |
|
return escape(s) |
|
end |
|
|
|
function Space() |
|
return " " |
|
end |
|
|
|
function LineBreak() |
|
return "<br/>" |
|
end |
|
|
|
function Emph(s) |
|
return "<em>" .. s .. "</em>" |
|
end |
|
|
|
function Strong(s) |
|
return "<strong>" .. s .. "</strong>" |
|
end |
|
|
|
function Subscript(s) |
|
return "<sub>" .. s .. "</sub>" |
|
end |
|
|
|
function Superscript(s) |
|
return "<sup>" .. s .. "</sup>" |
|
end |
|
|
|
function SmallCaps(s) |
|
return '<span style="font-variant: small-caps;">' .. s .. '</span>' |
|
end |
|
|
|
function Strikeout(s) |
|
return '<del>' .. s .. '</del>' |
|
end |
|
|
|
function Link(s, src, tit) |
|
return "<a href='" .. escape(src,true) .. "' title='" .. |
|
escape(tit,true) .. "'>" .. s .. "</a>" |
|
end |
|
|
|
function Image(s, src, tit) |
|
return "<img src='" .. escape(src,true) .. "' title='" .. |
|
escape(tit,true) .. "'/>" |
|
end |
|
|
|
function Code(s, attr) |
|
return "<code" .. attributes(attr) .. ">" .. escape(s) .. "</code>" |
|
end |
|
|
|
function InlineMath(s) |
|
return "\\(" .. escape(s) .. "\\)" |
|
end |
|
|
|
function DisplayMath(s) |
|
return "\\[" .. escape(s) .. "\\]" |
|
end |
|
|
|
function Note(s) |
|
-- return the footnote reference, linked to the note. |
|
plains = string.gsub(s, '^<p>(.*)</p>$', '%1') |
|
return '<span class="sidenote-number"></span><span class="sidenote">' .. plains .. '</span>' |
|
end |
|
|
|
function Span(s, attr) |
|
return "<span" .. attributes(attr) .. ">" .. s .. "</span>" |
|
end |
|
|
|
function Cite(s, cs) |
|
local ids = {} |
|
for _,cit in ipairs(cs) do |
|
table.insert(ids, cit.citationId) |
|
end |
|
return "<span class=\"cite\" data-citation-ids=\"" .. table.concat(ids, ",") .. |
|
"\">" .. s .. "</span>" |
|
end |
|
|
|
function Plain(s) |
|
return s |
|
end |
|
|
|
function Para(s) |
|
return "<p>" .. s .. "</p>" |
|
end |
|
|
|
-- lev is an integer, the header level. |
|
function Header(lev, s, attr) |
|
return "<h" .. lev .. attributes(attr) .. ">" .. s .. "</h" .. lev .. ">" |
|
end |
|
|
|
function BlockQuote(s) |
|
return "<blockquote>\n" .. s .. "\n</blockquote>" |
|
end |
|
|
|
function HorizontalRule() |
|
return "<hr/>" |
|
end |
|
|
|
function CodeBlock(s, attr) |
|
-- If code block has class 'dot', pipe the contents through dot |
|
-- and base64, and include the base64-encoded png as a data: URL. |
|
if attr.class and string.match(' ' .. attr.class .. ' ',' dot ') then |
|
local png = pipe("base64", pipe("dot -Tpng", s)) |
|
return '<img src="data:image/png;base64,' .. png .. '"/>' |
|
-- otherwise treat as code (one could pipe through a highlighter) |
|
else |
|
return "<pre><code" .. attributes(attr) .. ">" .. escape(s) .. |
|
"</code></pre>" |
|
end |
|
end |
|
|
|
function BulletList(items) |
|
local buffer = {} |
|
for _, item in pairs(items) do |
|
table.insert(buffer, "<li>" .. item .. "</li>") |
|
end |
|
return "<ul>\n" .. table.concat(buffer, "\n") .. "\n</ul>" |
|
end |
|
|
|
function OrderedList(items) |
|
local buffer = {} |
|
for _, item in pairs(items) do |
|
table.insert(buffer, "<li>" .. item .. "</li>") |
|
end |
|
return "<ol>\n" .. table.concat(buffer, "\n") .. "\n</ol>" |
|
end |
|
|
|
-- Revisit association list STackValue instance. |
|
function DefinitionList(items) |
|
local buffer = {} |
|
for _,item in pairs(items) do |
|
for k, v in pairs(item) do |
|
table.insert(buffer,"<dt>" .. k .. "</dt>\n<dd>" .. |
|
table.concat(v,"</dd>\n<dd>") .. "</dd>") |
|
end |
|
end |
|
return "<dl>\n" .. table.concat(buffer, "\n") .. "\n</dl>" |
|
end |
|
|
|
-- Convert pandoc alignment to something HTML can use. |
|
-- align is AlignLeft, AlignRight, AlignCenter, or AlignDefault. |
|
function html_align(align) |
|
if align == 'AlignLeft' then |
|
return 'left' |
|
elseif align == 'AlignRight' then |
|
return 'right' |
|
elseif align == 'AlignCenter' then |
|
return 'center' |
|
else |
|
return 'left' |
|
end |
|
end |
|
|
|
-- Caption is a string, aligns is an array of strings, |
|
-- widths is an array of floats, headers is an array of |
|
-- strings, rows is an array of arrays of strings. |
|
function Table(caption, aligns, widths, headers, rows) |
|
local buffer = {} |
|
local function add(s) |
|
table.insert(buffer, s) |
|
end |
|
add("<table>") |
|
if caption ~= "" then |
|
add("<caption>" .. caption .. "</caption>") |
|
end |
|
if widths and widths[1] ~= 0 then |
|
for _, w in pairs(widths) do |
|
add('<col width="' .. string.format("%d%%", w * 100) .. '" />') |
|
end |
|
end |
|
local header_row = {} |
|
local empty_header = true |
|
for i, h in pairs(headers) do |
|
local align = html_align(aligns[i]) |
|
table.insert(header_row,'<th align="' .. align .. '">' .. h .. '</th>') |
|
empty_header = empty_header and h == "" |
|
end |
|
if empty_header then |
|
head = "" |
|
else |
|
add('<tr class="header">') |
|
for _,h in pairs(header_row) do |
|
add(h) |
|
end |
|
add('</tr>') |
|
end |
|
local class = "even" |
|
for _, row in pairs(rows) do |
|
class = (class == "even" and "odd") or "even" |
|
add('<tr class="' .. class .. '">') |
|
for i,c in pairs(row) do |
|
add('<td align="' .. html_align(aligns[i]) .. '">' .. c .. '</td>') |
|
end |
|
add('</tr>') |
|
end |
|
add('</table') |
|
return table.concat(buffer,'\n') |
|
end |
|
|
|
function Div(s, attr) |
|
return "<div" .. attributes(attr) .. ">\n" .. s .. "</div>" |
|
end |
|
|
|
-- The following code will produce runtime warnings when you haven't defined |
|
-- all of the functions you need for the custom writer, so it's useful |
|
-- to include when you're working on a writer. |
|
local meta = {} |
|
meta.__index = |
|
function(_, key) |
|
io.stderr:write(string.format("WARNING: Undefined function '%s'\n",key)) |
|
return function() return "" end |
|
end |
|
setmetatable(_G, meta) |
|
|