mirror of https://github.com/bjc/prosody
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
139 lines
3.3 KiB
139 lines
3.3 KiB
|
|
local t_insert = table.insert;
|
|
local st = require "util.stanza";
|
|
local lxp = require "lxp";
|
|
local setmetatable = setmetatable;
|
|
local pairs = pairs;
|
|
local error = error;
|
|
local s_gsub = string.gsub;
|
|
|
|
local print = print;
|
|
|
|
module("template")
|
|
|
|
local function process_stanza(stanza, ops)
|
|
-- process attrs
|
|
for key, val in pairs(stanza.attr) do
|
|
if val:match("{[^}]*}") then
|
|
t_insert(ops, {stanza.attr, key, val});
|
|
end
|
|
end
|
|
-- process children
|
|
local i = 1;
|
|
while i <= #stanza do
|
|
local child = stanza[i];
|
|
if child.name then
|
|
process_stanza(child, ops);
|
|
elseif child:match("^{[^}]*}$") then -- text
|
|
t_insert(ops, {stanza, i, child:match("^{([^}]*)}$"), true});
|
|
elseif child:match("{[^}]*}") then -- text
|
|
t_insert(ops, {stanza, i, child});
|
|
end
|
|
i = i + 1;
|
|
end
|
|
end
|
|
|
|
local parse_xml = (function()
|
|
local ns_prefixes = {
|
|
["http://www.w3.org/XML/1998/namespace"] = "xml";
|
|
};
|
|
local ns_separator = "\1";
|
|
local ns_pattern = "^([^"..ns_separator.."]*)"..ns_separator.."?(.*)$";
|
|
return function(xml)
|
|
local handler = {};
|
|
local stanza = st.stanza("root");
|
|
function handler:StartElement(tagname, attr)
|
|
local curr_ns,name = tagname:match(ns_pattern);
|
|
if name == "" then
|
|
curr_ns, name = "", curr_ns;
|
|
end
|
|
if curr_ns ~= "" then
|
|
attr.xmlns = curr_ns;
|
|
end
|
|
for i=1,#attr do
|
|
local k = attr[i];
|
|
attr[i] = nil;
|
|
local ns, nm = k:match(ns_pattern);
|
|
if nm ~= "" then
|
|
ns = ns_prefixes[ns];
|
|
if ns then
|
|
attr[ns..":"..nm] = attr[k];
|
|
attr[k] = nil;
|
|
end
|
|
end
|
|
end
|
|
stanza:tag(name, attr);
|
|
end
|
|
function handler:CharacterData(data)
|
|
data = data:gsub("^%s*", ""):gsub("%s*$", "");
|
|
stanza:text(data);
|
|
end
|
|
function handler:EndElement(tagname)
|
|
stanza:up();
|
|
end
|
|
local parser = lxp.new(handler, "\1");
|
|
local ok, err, line, col = parser:parse(xml);
|
|
if ok then ok, err, line, col = parser:parse(); end
|
|
--parser:close();
|
|
if ok then
|
|
return stanza.tags[1];
|
|
else
|
|
return ok, err.." (line "..line..", col "..col..")";
|
|
end
|
|
end;
|
|
end)();
|
|
|
|
local stanza_mt = st.stanza_mt;
|
|
local function fast_st_clone(stanza, lookup)
|
|
local stanza_attr = stanza.attr;
|
|
local stanza_tags = stanza.tags;
|
|
local tags, attr = {}, {};
|
|
local clone = { name = stanza.name, attr = attr, tags = tags, last_add = {} };
|
|
for k,v in pairs(stanza_attr) do attr[k] = v; end
|
|
lookup[stanza_attr] = attr;
|
|
for i=1,#stanza_tags do
|
|
local child = stanza_tags[i];
|
|
local new = fast_st_clone(child, lookup);
|
|
tags[i] = new;
|
|
lookup[child] = new;
|
|
end
|
|
for i=1,#stanza do
|
|
local child = stanza[i];
|
|
clone[i] = lookup[child] or child;
|
|
end
|
|
return setmetatable(clone, stanza_mt);
|
|
end
|
|
|
|
local function create_template(text)
|
|
local stanza, err = parse_xml(text);
|
|
if not stanza then error(err); end
|
|
local ops = {};
|
|
process_stanza(stanza, ops);
|
|
|
|
local template = {};
|
|
local lookup = {};
|
|
function template.apply(data)
|
|
local newstanza = fast_st_clone(stanza, lookup);
|
|
for i=1,#ops do
|
|
local op = ops[i];
|
|
local t, k, v, g = op[1], op[2], op[3], op[4];
|
|
if g then
|
|
lookup[t][k] = data[v];
|
|
else
|
|
lookup[t][k] = s_gsub(v, "{([^}]*)}", data);
|
|
end
|
|
end
|
|
return newstanza;
|
|
end
|
|
return template;
|
|
end
|
|
|
|
local templates = setmetatable({}, { __mode = 'k' });
|
|
return function(text)
|
|
local template = templates[text];
|
|
if not template then
|
|
template = create_template(text);
|
|
templates[text] = template;
|
|
end
|
|
return template;
|
|
end;
|
|
|