mirror of
https://github.com/moparisthebest/xeps
synced 2024-11-21 16:55:07 -05:00
Add tooling for conversion to/from Markdown
Long past time I recorded these somewhere.
This commit is contained in:
parent
64b62599a8
commit
10da72c298
411
tools/2xep.lua
Normal file
411
tools/2xep.lua
Normal file
@ -0,0 +1,411 @@
|
||||
-- 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.
|
||||
|
||||
-- luacheck: globals Blocksep Doc Space SoftBreak Str LineBreak Emph Strong Subscript Superscript SmallCaps Strikeout
|
||||
-- luacheck: globals Link Image Code InlineMath DisplayMath SingleQuoted DoubleQuoted Note Span RawInline Cite Plain
|
||||
-- luacheck: globals Para Header BlockQuote HorizontalRule LineBlock CodeBlock BulletList OrderedList DefinitionList
|
||||
-- luacheck: globals CaptionedImage Table RawBlock Div
|
||||
|
||||
local escape_table = { ["'"] = "'", ["\""] = """, ["<"] = "<", [">"] = ">", ["&"] = "&" };
|
||||
local function escape(s, in_attribute)
|
||||
return (string.gsub(s, in_attribute and "['&<>\"]" or "[&<>]", escape_table));
|
||||
end
|
||||
|
||||
local sectionstack = {};
|
||||
|
||||
-- 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
|
||||
|
||||
-- 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 = { [[
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!DOCTYPE xep SYSTEM 'xep.dtd' [
|
||||
<!ENTITY % ents SYSTEM 'xep.ent'>
|
||||
%ents;
|
||||
]>
|
||||
<?xml-stylesheet type='text/xsl' href='xep.xsl'?>
|
||||
<xep><header>]]
|
||||
}
|
||||
local function add(s)
|
||||
table.insert(buffer, s)
|
||||
end
|
||||
local header_schema = [[
|
||||
(title , abstract , legal , number , status , lastcall* ,
|
||||
interim* , type , sig , approver* , dependencies , supersedes ,
|
||||
supersededby , shortname , schemaloc* , registry? , discuss? ,
|
||||
expires? , author+ , revision+ , councilnote?)
|
||||
]];
|
||||
for field, r in string.gmatch(header_schema, "(%w+)(%p?)") do
|
||||
local v = metadata[field] or variables[field];
|
||||
if not v then
|
||||
if field == "legal" then
|
||||
add("&LEGALNOTICE;");
|
||||
goto next;
|
||||
elseif field == "supersedes" or field == "supersededby" then
|
||||
add(("<%s/>"):format(field));
|
||||
goto next;
|
||||
elseif r ~= "*" and r ~= "?" then
|
||||
error(string.format("Missing required metadata field '%s'", field));
|
||||
else
|
||||
io.stderr:write(string.format("Missing optional metadata field '%s'\n", field));
|
||||
goto next;
|
||||
end
|
||||
end
|
||||
if field == "number" then
|
||||
assert(tonumber(v) or v == "xxxx", "Invalid XEP number");
|
||||
if v ~= "xxxx" then
|
||||
v = string.format("%04d", tonumber(v));
|
||||
end
|
||||
end
|
||||
if type(v) == "table" then
|
||||
for sk, sv in pairs(v) do
|
||||
add(string.format("<%s>", field));
|
||||
if type(sk) == "string" then
|
||||
add(("<%s>%s</%s>"):format(sk, tostring(sv), sk));
|
||||
elseif field == "author" then
|
||||
local first, last = sv:match("(%S+)%s+(%S+)"); -- Names are hard
|
||||
add(("<firstname>%s</firstname>"):format(first));
|
||||
add(("<surname>%s</surname>"):format(last));
|
||||
-- Why is there HTML in the thing?
|
||||
for typ, addr in sv:gmatch("%shref='(%a+):([^']+)") do
|
||||
if typ == "mailto" then
|
||||
add(("<email>%s</email>"):format(addr));
|
||||
elseif typ == "xmpp" then
|
||||
add(("<jid>%s</jid>"):format(addr));
|
||||
end
|
||||
end
|
||||
elseif field == "dependencies" then
|
||||
add(("<spec>%s</spec>"):format(tostring(sv)));
|
||||
elseif field == "revision" then
|
||||
for rev_field in string.gmatch("( version, date, initials )", "%w+") do
|
||||
add(("<%s>%s</%s>"):format(rev_field, tostring(sv[rev_field]), rev_field));
|
||||
end
|
||||
add("<remark>");
|
||||
for _, remark in ipairs(sv.remark) do
|
||||
add(("<p>%s</p>"):format(remark));
|
||||
end
|
||||
add("</remark>");
|
||||
else
|
||||
add(("<%s>%s</%s>"):format(field, tostring(sv), field));
|
||||
end
|
||||
add(string.format("</%s>", field));
|
||||
end
|
||||
else
|
||||
add(("<%s>%s</%s>"):format(field, tostring(v), field));
|
||||
end
|
||||
::next::
|
||||
end
|
||||
add("</header>");
|
||||
add(body)
|
||||
for i = 1, #sectionstack do
|
||||
add("</section"..sectionstack[i]..">");
|
||||
end
|
||||
add("</xep>\n");
|
||||
return table.concat(buffer,'\n') .. '\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 SoftBreak()
|
||||
return "\n"
|
||||
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, attr)
|
||||
return "<a href='" .. escape(src,true) .. "' title='" ..
|
||||
escape(tit,true) .. "'>" .. s .. "</a>"
|
||||
end
|
||||
|
||||
function Image(s, src, tit, attr)
|
||||
return "<img src='" .. escape(src,true) .. "' title='" ..
|
||||
escape(tit,true) .. "'/>"
|
||||
end
|
||||
|
||||
function Code(s, attr)
|
||||
return "<tt" .. attributes(attr) .. ">" .. escape(s) .. "</tt>"
|
||||
end
|
||||
|
||||
function InlineMath(s)
|
||||
return "\\(" .. escape(s) .. "\\)"
|
||||
end
|
||||
|
||||
function DisplayMath(s)
|
||||
return "\\[" .. escape(s) .. "\\]"
|
||||
end
|
||||
|
||||
function SingleQuoted(s)
|
||||
return "'" .. s .. "'"
|
||||
end
|
||||
|
||||
function DoubleQuoted(str)
|
||||
return escape('"' .. str .. '"');
|
||||
end
|
||||
|
||||
function Note(s)
|
||||
return "<note>" .. s .. "</note>";
|
||||
end
|
||||
|
||||
function Span(s, attr)
|
||||
return "<span" .. attributes(attr) .. ">" .. s .. "</span>"
|
||||
end
|
||||
|
||||
function RawInline(format, str)
|
||||
if format == "html" then
|
||||
return str
|
||||
else
|
||||
return ''
|
||||
end
|
||||
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)
|
||||
local ret = ""
|
||||
if sectionstack[1] and sectionstack[#sectionstack] >= lev then
|
||||
repeat
|
||||
ret = ret .. "</section"..table.remove(sectionstack)..">"
|
||||
until sectionstack[1] == nil or sectionstack[#sectionstack] == lev -1;
|
||||
end
|
||||
table.insert(sectionstack, lev);
|
||||
attr.topic = s;
|
||||
attr.anchor, attr.id = attr.id, nil;
|
||||
ret = ret .. "<section" .. lev .. attributes(attr) .. ">"
|
||||
return ret;
|
||||
end
|
||||
|
||||
function BlockQuote(s)
|
||||
return "<blockquote>\n" .. s .. "\n</blockquote>"
|
||||
end
|
||||
|
||||
function HorizontalRule()
|
||||
return "<hr/>"
|
||||
end
|
||||
|
||||
function LineBlock(ls)
|
||||
return '<div style="white-space: pre-line;">' .. table.concat(ls, '\n') ..
|
||||
'</div>'
|
||||
end
|
||||
|
||||
local function has(haystack, needle) --> boolean
|
||||
if type(haystack) == "table" then
|
||||
for _, v in ipairs(haystack) do
|
||||
if v == needle then return true; end
|
||||
end
|
||||
elseif type(haystack) == "string" then
|
||||
for v in haystack:gmatch("%S+") do
|
||||
if v == needle then return true; end
|
||||
end
|
||||
else
|
||||
error("unhandled haystack type "..type(haystack))
|
||||
end
|
||||
return false;
|
||||
end
|
||||
|
||||
function CodeBlock(s, attr)
|
||||
if attr and attr.class and has(attr.class, "example") then
|
||||
return "<example><![CDATA[".. s .. "]]></example>"
|
||||
else
|
||||
return "<code"..attributes(attr).."><![CDATA[".. s .. "]]></code>"
|
||||
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
|
||||
|
||||
function DefinitionList(items)
|
||||
local buffer = {}
|
||||
for _,item in pairs(items) do
|
||||
local k, v = next(item)
|
||||
table.insert(buffer,"<di><dt>" .. k .. "</dt>\n<dd>" ..
|
||||
table.concat(v,"</dd>\n<dd>") .. "</dd></di>")
|
||||
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.
|
||||
local 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
|
||||
|
||||
function CaptionedImage(src, tit, caption, attr)
|
||||
return '<div class="figure">\n<img src="' .. escape(src,true) ..
|
||||
'" title="' .. escape(tit,true) .. '"/>\n' ..
|
||||
'<p class="caption">' .. caption .. '</p>\n</div>'
|
||||
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("%.0f%%", 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 not empty_header then
|
||||
add('<tr class="header">')
|
||||
for _,h in pairs(header_row) do
|
||||
add(h)
|
||||
end
|
||||
add('</tr>')
|
||||
else
|
||||
-- head = "" -- XXX What is this?
|
||||
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 RawBlock(format, str)
|
||||
if format == "html" then
|
||||
return str
|
||||
else
|
||||
return ''
|
||||
end
|
||||
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)
|
||||
|
611
tools/xep2md.lua
Normal file
611
tools/xep2md.lua
Normal file
@ -0,0 +1,611 @@
|
||||
#!/usr/bin/env lua5.3
|
||||
-- XEP to Markdown converter
|
||||
--
|
||||
|
||||
-- Inlined util.events from Prosody, you may wanna skip ahead ~160 lines
|
||||
-- or so to the main script.
|
||||
package.preload["util.events"] = (function()
|
||||
-- Prosody IM
|
||||
-- Copyright (C) 2008-2010 Matthew Wild
|
||||
-- Copyright (C) 2008-2010 Waqas Hussain
|
||||
--
|
||||
-- This project is MIT/X11 licensed. Please see the
|
||||
-- COPYING file in the source package for more information.
|
||||
--
|
||||
|
||||
|
||||
local pairs = pairs;
|
||||
local t_insert = table.insert;
|
||||
local t_remove = table.remove;
|
||||
local t_sort = table.sort;
|
||||
local setmetatable = setmetatable;
|
||||
local next = next;
|
||||
|
||||
local _ENV = nil;
|
||||
|
||||
local function new()
|
||||
local handlers = {};
|
||||
local global_wrappers;
|
||||
local wrappers = {};
|
||||
local event_map = {};
|
||||
local function _rebuild_index(handlers, event)
|
||||
local _handlers = event_map[event];
|
||||
if not _handlers or next(_handlers) == nil then return; end
|
||||
local index = {};
|
||||
for handler in pairs(_handlers) do
|
||||
t_insert(index, handler);
|
||||
end
|
||||
t_sort(index, function(a, b) return _handlers[a] > _handlers[b]; end);
|
||||
handlers[event] = index;
|
||||
return index;
|
||||
end;
|
||||
setmetatable(handlers, { __index = _rebuild_index });
|
||||
local function add_handler(event, handler, priority)
|
||||
local map = event_map[event];
|
||||
if map then
|
||||
map[handler] = priority or 0;
|
||||
else
|
||||
map = {[handler] = priority or 0};
|
||||
event_map[event] = map;
|
||||
end
|
||||
handlers[event] = nil;
|
||||
end;
|
||||
local function remove_handler(event, handler)
|
||||
local map = event_map[event];
|
||||
if map then
|
||||
map[handler] = nil;
|
||||
handlers[event] = nil;
|
||||
if next(map) == nil then
|
||||
event_map[event] = nil;
|
||||
end
|
||||
end
|
||||
end;
|
||||
local function get_handlers(event)
|
||||
return handlers[event];
|
||||
end;
|
||||
local function add_handlers(handlers)
|
||||
for event, handler in pairs(handlers) do
|
||||
add_handler(event, handler);
|
||||
end
|
||||
end;
|
||||
local function remove_handlers(handlers)
|
||||
for event, handler in pairs(handlers) do
|
||||
remove_handler(event, handler);
|
||||
end
|
||||
end;
|
||||
local function _fire_event(event_name, event_data)
|
||||
local h = handlers[event_name];
|
||||
if h then
|
||||
for i=1,#h do
|
||||
local ret = h[i](event_data);
|
||||
if ret ~= nil then return ret; end
|
||||
end
|
||||
end
|
||||
end;
|
||||
local function fire_event(event_name, event_data)
|
||||
local w = wrappers[event_name] or global_wrappers;
|
||||
if w then
|
||||
local curr_wrapper = #w;
|
||||
local function c(event_name, event_data)
|
||||
curr_wrapper = curr_wrapper - 1;
|
||||
if curr_wrapper == 0 then
|
||||
if global_wrappers == nil or w == global_wrappers then
|
||||
return _fire_event(event_name, event_data);
|
||||
end
|
||||
w, curr_wrapper = global_wrappers, #global_wrappers;
|
||||
return w[curr_wrapper](c, event_name, event_data);
|
||||
else
|
||||
return w[curr_wrapper](c, event_name, event_data);
|
||||
end
|
||||
end
|
||||
return w[curr_wrapper](c, event_name, event_data);
|
||||
end
|
||||
return _fire_event(event_name, event_data);
|
||||
end
|
||||
local function add_wrapper(event_name, wrapper)
|
||||
local w;
|
||||
if event_name == false then
|
||||
w = global_wrappers;
|
||||
if not w then
|
||||
w = {};
|
||||
global_wrappers = w;
|
||||
end
|
||||
else
|
||||
w = wrappers[event_name];
|
||||
if not w then
|
||||
w = {};
|
||||
wrappers[event_name] = w;
|
||||
end
|
||||
end
|
||||
w[#w+1] = wrapper;
|
||||
end
|
||||
local function remove_wrapper(event_name, wrapper)
|
||||
local w;
|
||||
if event_name == false then
|
||||
w = global_wrappers;
|
||||
else
|
||||
w = wrappers[event_name];
|
||||
end
|
||||
if not w then return; end
|
||||
for i = #w, 1 do
|
||||
if w[i] == wrapper then
|
||||
t_remove(w, i);
|
||||
end
|
||||
end
|
||||
if #w == 0 then
|
||||
if event_name == nil then
|
||||
global_wrappers = nil;
|
||||
else
|
||||
wrappers[event_name] = nil;
|
||||
end
|
||||
end
|
||||
end
|
||||
return {
|
||||
add_handler = add_handler;
|
||||
remove_handler = remove_handler;
|
||||
add_handlers = add_handlers;
|
||||
remove_handlers = remove_handlers;
|
||||
get_handlers = get_handlers;
|
||||
wrappers = {
|
||||
add_handler = add_wrapper;
|
||||
remove_handler = remove_wrapper;
|
||||
};
|
||||
add_wrapper = add_wrapper;
|
||||
remove_wrapper = remove_wrapper;
|
||||
fire_event = fire_event;
|
||||
_handlers = handlers;
|
||||
_event_map = event_map;
|
||||
};
|
||||
end
|
||||
|
||||
return {
|
||||
new = new;
|
||||
};
|
||||
end);
|
||||
|
||||
local lxp = require "lxp";
|
||||
local lom = require "lxp.lom";
|
||||
local events = require"util.events".new();
|
||||
|
||||
local handler = {};
|
||||
local stack = {};
|
||||
|
||||
local meta = {};
|
||||
|
||||
local no_write = true;
|
||||
local function output(...)
|
||||
if no_write then return end
|
||||
io.write(...);
|
||||
end
|
||||
|
||||
local function print_empty_line()
|
||||
output("\n\n");
|
||||
return true;
|
||||
end
|
||||
|
||||
local text_buf;
|
||||
|
||||
-- FIXME LuaExpat claims to not require this hack
|
||||
local function CharacterDataDone()
|
||||
if text_buf then
|
||||
local text = table.concat(text_buf);
|
||||
if text ~= "" then
|
||||
events.fire_event("#text", { stack = stack, text = text });
|
||||
end
|
||||
text_buf = nil;
|
||||
end
|
||||
end
|
||||
|
||||
function handler:StartElement(tagname, attr)
|
||||
CharacterDataDone();
|
||||
tagname = tagname:gsub("^([^\1]+)\1", "{%1}");
|
||||
table.insert(stack, tagname)
|
||||
events.fire_event(tagname, { stack = stack, attr = attr });
|
||||
end
|
||||
|
||||
function handler:CharacterData(data)
|
||||
if text_buf then
|
||||
table.insert(text_buf, data)
|
||||
else
|
||||
text_buf = { data };
|
||||
end
|
||||
end
|
||||
|
||||
function handler:EndElement()
|
||||
CharacterDataDone();
|
||||
events.fire_event(table.remove(stack) .. "/", { stack = stack });
|
||||
end
|
||||
|
||||
-- Oh god oh god we're all gonna die!
|
||||
local function escape_text(event)
|
||||
event.text = event.text:gsub("['&<>\"]", "\\%1"):gsub("^%s+", ""):gsub("%s+$", ""):gsub("%s+", " ");
|
||||
end
|
||||
events.add_handler("#text", escape_text, 1000);
|
||||
|
||||
events.add_handler("#text", function (event)
|
||||
local stack = event.stack;
|
||||
return events.fire_event(stack[#stack].."#text", event);
|
||||
end, 10);
|
||||
|
||||
events.add_handler("#text", function (event)
|
||||
if event.text:find"%S" then
|
||||
output(event.text);
|
||||
end
|
||||
return true;
|
||||
end);
|
||||
|
||||
local metafields = "title abstract number status lastcall type sig shortname"
|
||||
for field in metafields:gmatch("%S+") do
|
||||
events.add_handler(field.."#text", function (event)
|
||||
meta[field] = event.text:match("%S.*%S");
|
||||
return true;
|
||||
end);
|
||||
end
|
||||
|
||||
do
|
||||
local author;
|
||||
events.add_handler("author", function (event)
|
||||
author = { };
|
||||
return true;
|
||||
end);
|
||||
|
||||
for _, field in pairs{"firstname", "surname", "email", "jid"} do
|
||||
events.add_handler(field.."#text", function (event) author[field] = event.text; return true; end);
|
||||
end
|
||||
|
||||
events.add_handler("author/", function (event)
|
||||
if author.email then
|
||||
author = string.format("%s %s <%s>", author.firstname, author.surname, author.email);
|
||||
else
|
||||
author = string.format("%s %s", author.firstname, author.surname);
|
||||
end
|
||||
|
||||
local authors = meta.author;
|
||||
if not authors then
|
||||
meta.author = { author; };
|
||||
else
|
||||
table.insert(authors, author);
|
||||
end
|
||||
|
||||
author = nil;
|
||||
return true;
|
||||
end);
|
||||
end
|
||||
|
||||
do
|
||||
local revision;
|
||||
for _, field in pairs{"version", "date", "initials"} do
|
||||
events.add_handler(field.."#text", function (event)
|
||||
if revision then
|
||||
revision[field] = event.text;
|
||||
return true;
|
||||
end
|
||||
end);
|
||||
end
|
||||
|
||||
local function handle_remark(event)
|
||||
if revision and event.text and event.text:match("%S") then
|
||||
table.insert(revision.remark, event.text);
|
||||
end
|
||||
end
|
||||
|
||||
events.add_handler("remark#text", handle_remark, 100);
|
||||
|
||||
events.add_handler("remark", function (event)
|
||||
events.add_handler("p#text", handle_remark, 100);
|
||||
end);
|
||||
|
||||
events.add_handler("remark/", function (event)
|
||||
events.remove_handler("p#text", handle_remark);
|
||||
end);
|
||||
|
||||
events.add_handler("revision", function (event)
|
||||
revision = {remark={}};
|
||||
return true;
|
||||
end);
|
||||
|
||||
local revisions = {};
|
||||
events.add_handler("revision/", function (event)
|
||||
table.insert(revisions, revision);
|
||||
meta.revision = revisions;
|
||||
revision = nil;
|
||||
return true;
|
||||
end);
|
||||
|
||||
end
|
||||
|
||||
events.add_handler("date#text", function (event)
|
||||
if meta and not meta.date then
|
||||
meta.date = event.text;
|
||||
end
|
||||
end);
|
||||
|
||||
events.add_handler("spec#text", function (event)
|
||||
if not meta then return end
|
||||
|
||||
local kind = stack[#stack-1];
|
||||
if meta[kind] then
|
||||
table.insert(meta[kind], event.text);
|
||||
else
|
||||
meta[kind] = { event.text };
|
||||
end
|
||||
end);
|
||||
|
||||
events.add_handler("header/", function (event)
|
||||
no_write = false;
|
||||
if next(meta) ~= nil then
|
||||
if meta.title and meta.number then
|
||||
meta.title = "XEP-"..meta.number..": "..meta.title;
|
||||
end
|
||||
if meta.author and #meta.author == 1 then
|
||||
meta.author = meta.author[1];
|
||||
end
|
||||
local have_yaml, yaml = pcall(require, "lyaml");
|
||||
if have_yaml and yaml.dump then
|
||||
output(yaml.dump({meta}));
|
||||
else
|
||||
print("% "..meta.title);
|
||||
if type(meta.author) == "table" then
|
||||
print("% "..table.concat(meta.author, "; "));
|
||||
elseif meta.author then
|
||||
print("% "..meta.author);
|
||||
else
|
||||
print("% ");
|
||||
end
|
||||
print("% "..meta.date);
|
||||
end
|
||||
end
|
||||
return true;
|
||||
end);
|
||||
|
||||
for i = 1, 6 do
|
||||
events.add_handler("section"..i, function ()
|
||||
output("\n");
|
||||
end, 10);
|
||||
events.add_handler("section"..i.."/", function ()
|
||||
output("\n");
|
||||
return true;
|
||||
end, 10);
|
||||
|
||||
events.add_handler("section"..i, function (event)
|
||||
assert(event.attr.topic, "no @topic");
|
||||
output(string.rep("#", i), " ", event.attr.topic);
|
||||
if event.attr.anchor and event.attr.anchor ~= "" then
|
||||
output(" {#", event.attr.anchor, "}\n")
|
||||
else
|
||||
output("\n");
|
||||
end
|
||||
return true;
|
||||
end);
|
||||
end
|
||||
|
||||
events.add_handler("section1", function (event)
|
||||
output(event.attr.topic);
|
||||
if event.attr.anchor and event.attr.anchor ~= "" then
|
||||
output(" {#", event.attr.anchor, "}");
|
||||
end
|
||||
output("\n", string.rep("=", #event.attr.topic), "\n\n");
|
||||
return true;
|
||||
end, 1);
|
||||
|
||||
events.add_handler("section2", function (event)
|
||||
output(event.attr.topic);
|
||||
if event.attr.anchor and event.attr.anchor ~= "" then
|
||||
output(" {#", event.attr.anchor, "}");
|
||||
end
|
||||
output("\n", string.rep("-", #event.attr.topic), "\n\n");
|
||||
return true;
|
||||
end, 1);
|
||||
|
||||
local function normalize_whitespace(event)
|
||||
event.text = event.text:gsub("%s+", " ")
|
||||
-- event.text = event.text:match("^%s*(.-)%s*$")
|
||||
end
|
||||
events.add_handler("p#text", normalize_whitespace, 10);
|
||||
events.add_handler("li#text", normalize_whitespace, 10);
|
||||
events.add_handler("dt#text", normalize_whitespace, 10);
|
||||
events.add_handler("dd#text", normalize_whitespace, 10);
|
||||
|
||||
local example_count = 1;
|
||||
|
||||
events.add_handler("example", function (event)
|
||||
output("\n#### Example ", example_count, ". ");
|
||||
if event.attr.caption then
|
||||
output(event.attr.caption, " ")
|
||||
end
|
||||
output("{#example-", example_count, " .unnumbered}\n\n")
|
||||
example_count = example_count + 1;
|
||||
output("``` {.xml .example}\n");
|
||||
events.remove_handler("#text", escape_text);
|
||||
end);
|
||||
|
||||
events.add_handler("example#text", function (event)
|
||||
local example_text = event.text:match("^%s*(.-)%s*$");
|
||||
output(example_text, "\n");
|
||||
return true;
|
||||
end);
|
||||
|
||||
events.add_handler("example/", function ()
|
||||
events.add_handler("#text", escape_text, 1000);
|
||||
output("```\n\n");
|
||||
return true;
|
||||
end);
|
||||
|
||||
events.add_handler("note", function (event)
|
||||
output(" ^[");
|
||||
return true;
|
||||
end);
|
||||
|
||||
events.add_handler("note/", function (event)
|
||||
output("]");
|
||||
return true;
|
||||
end);
|
||||
|
||||
-- TODO magically import citation data
|
||||
events.add_handler("cite#text", function (event)
|
||||
output("**", event.text, "**");
|
||||
if meta.references then
|
||||
local refid = event.text:gsub("%W", ""):lower();
|
||||
if meta.references[refid] then
|
||||
output("[@", refid, "]");
|
||||
end
|
||||
end
|
||||
return true;
|
||||
end);
|
||||
|
||||
local url;
|
||||
events.add_handler("link", function (event)
|
||||
url = event.attr.url;
|
||||
if url then
|
||||
output("[");
|
||||
end
|
||||
return true;
|
||||
end);
|
||||
|
||||
events.add_handler("link/", function (event)
|
||||
if url then
|
||||
output("](", url, ")");
|
||||
url = nil;
|
||||
end
|
||||
return true;
|
||||
end);
|
||||
|
||||
|
||||
local list_depth, list_type = 0, "ul";
|
||||
events.add_handler("ul", function ()
|
||||
list_depth = list_depth + 1;
|
||||
list_type = "ul";
|
||||
end);
|
||||
|
||||
events.add_handler("ul/", function (event)
|
||||
local stack = event.stack;
|
||||
list_depth = list_depth - 1;
|
||||
for i = #stack, 1, -1 do
|
||||
local element = stack[i]
|
||||
if element == "ul" or element == "ol" then
|
||||
list_type = element;
|
||||
break;
|
||||
end
|
||||
end
|
||||
return true;
|
||||
end);
|
||||
|
||||
events.add_handler("li", function (event)
|
||||
for i = 2, list_depth do
|
||||
output(" ");
|
||||
end
|
||||
if list_type == "ul" then
|
||||
output("- ");
|
||||
elseif list_type == "ul" then
|
||||
output("#. ");
|
||||
end
|
||||
return true;
|
||||
end);
|
||||
|
||||
events.add_handler("li#text", function (event)
|
||||
local text = event.text:gsub("%s+", " ");
|
||||
output(text);
|
||||
return true;
|
||||
end);
|
||||
|
||||
events.add_handler("dd#text", function (event)
|
||||
output("\n: ");
|
||||
end);
|
||||
|
||||
events.add_handler("li/", print_empty_line, 1);
|
||||
events.add_handler("ul", print_empty_line, 1);
|
||||
events.add_handler("ul/", print_empty_line, 1);
|
||||
events.add_handler("ol", print_empty_line, 1);
|
||||
events.add_handler("ol/", print_empty_line, 1);
|
||||
events.add_handler("p/", print_empty_line, 1);
|
||||
events.add_handler("dd/", print_empty_line, 1);
|
||||
|
||||
local function printcell(event)
|
||||
output("|");
|
||||
end
|
||||
events.add_handler("th", printcell, 1);
|
||||
events.add_handler("td", printcell, 1);
|
||||
events.add_handler("tr/", printcell, 3);
|
||||
events.add_handler("tr", function () output(" ") end, 1);
|
||||
events.add_handler("tr/", function () output("\n") end, 1);
|
||||
|
||||
local th;
|
||||
events.add_handler("table", function () th = 0; end);
|
||||
events.add_handler("table/", function () th = 0; end);
|
||||
events.add_handler("th", function () if th then th = th + 1; end end);
|
||||
events.add_handler("tr/", function () if th then output("\n");output(" |"); output(string.rep("---|", th)); th = nil end end, 2);
|
||||
|
||||
-- Non-example code blocks, like schemas
|
||||
events.add_handler("code", function (event)
|
||||
output("```xml\n");
|
||||
events.remove_handler("#text", escape_text);
|
||||
return true;
|
||||
end);
|
||||
|
||||
events.add_handler("code#text", function (event)
|
||||
local example_text = event.text:match("^%s*(.-)%s*$");
|
||||
output(example_text, "\n");
|
||||
return true;
|
||||
end);
|
||||
|
||||
events.add_handler("code/", function ()
|
||||
events.add_handler("#text", escape_text, 1000);
|
||||
output("```\n\n");
|
||||
return true;
|
||||
end);
|
||||
|
||||
if meta.references then
|
||||
events.add_handler("xep/", function ()
|
||||
output("\n\n# References {#references}\n\n");
|
||||
end);
|
||||
end
|
||||
|
||||
if arg[1] == "--debug" then
|
||||
events.add_wrapper(false, function (fire_event, event_name, event_data)
|
||||
io.stderr:write("D: "..event_name.."\n");
|
||||
io.stderr:write("D: /"..table.concat(event_data.stack, "/").."\n");
|
||||
return fire_event(event_name, event_data);
|
||||
end);
|
||||
setmetatable(handler, {
|
||||
__index = function (_, missinghandler)
|
||||
io.stderr:write("D: Missing handler: "..missinghandler.."\n");
|
||||
return function (parser, ...)
|
||||
io.stderr:write("D: ", missinghandler, "(");
|
||||
local count = select('#', ...);
|
||||
for i = 1, count do
|
||||
local arg = select(i, ...);
|
||||
local arg_t = type(arg);
|
||||
io.stderr:write(arg_t, ":");
|
||||
if arg_t == "string" then
|
||||
io.stderr:write(string.format("%q", arg));
|
||||
else
|
||||
io.stderr:write(tostring(arg));
|
||||
end
|
||||
if i ~= count then
|
||||
io.stderr:write(", ");
|
||||
end
|
||||
end
|
||||
io.stderr:write(")\n");
|
||||
return "";
|
||||
end;
|
||||
end;
|
||||
});
|
||||
end
|
||||
|
||||
local parser = lxp.new(handler, "\1");
|
||||
parser:setbase(".");
|
||||
local function chunks(file, size)
|
||||
return function ()
|
||||
return file:read(size);
|
||||
end
|
||||
end
|
||||
|
||||
for chunk in chunks(io.stdin, 4096) do
|
||||
local ok, err, line, col = parser:parse(chunk);
|
||||
if not ok then
|
||||
io.stderr:write("E: "..err.." on line "..line..", col "..col.."\n");
|
||||
os.exit(1);
|
||||
end
|
||||
end
|
||||
|
||||
parser:close();
|
3
tools/xep2md.sh
Executable file
3
tools/xep2md.sh
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
xmllint --nonet --noent --loaddtd "$@" | lua5.3 -lluarocks.loader xep2md.lua
|
Loading…
Reference in New Issue
Block a user