util/stanza.lua

2009-06-25

author
Waqas Hussain <waqas20@gmail.com>
date
Thu Jun 25 17:22:53 2009 +0500
changeset 1416
f916f0ff90e5
parent 1415
957a81b72cb2
child 1420
1576a5aa52f8
permissions
-rw-r--r--

util.stanza: Rewrote stanza_mt.__tostring. 20-30% faster stanza serialization. - #optimization

     1 -- Prosody IM v0.4
     2 -- Copyright (C) 2008-2009 Matthew Wild
     3 -- Copyright (C) 2008-2009 Waqas Hussain
     4 -- 
     5 -- This project is MIT/X11 licensed. Please see the
     6 -- COPYING file in the source package for more information.
     7 --
    10 local t_insert      =  table.insert;
    11 local t_concat      =  table.concat;
    12 local t_remove      =  table.remove;
    13 local t_concat      =  table.concat;
    14 local s_format      = string.format;
    15 local s_match       =  string.match;
    16 local tostring      =      tostring;
    17 local setmetatable  =  setmetatable;
    18 local getmetatable  =  getmetatable;
    19 local pairs         =         pairs;
    20 local ipairs        =        ipairs;
    21 local type          =          type;
    22 local next          =          next;
    23 local print         =         print;
    24 local unpack        =        unpack;
    25 local s_gsub        =   string.gsub;
    26 local s_char        =   string.char;
    27 local os            =            os;
    29 local do_pretty_printing = not os.getenv("WINDIR");
    30 local getstyle, getstring = require "util.termcolours".getstyle, require "util.termcolours".getstring;
    32 local log = require "util.logger".init("stanza");
    34 module "stanza"
    36 stanza_mt = {};
    37 stanza_mt.__index = stanza_mt;
    39 function stanza(name, attr)
    40 	local stanza = { name = name, attr = attr or {}, tags = {}, last_add = {}};
    41 	return setmetatable(stanza, stanza_mt);
    42 end
    44 function stanza_mt:query(xmlns)
    45 	return self:tag("query", { xmlns = xmlns });
    46 end
    48 function stanza_mt:body(text, attr)
    49 	return self:tag("body", attr):text(text);
    50 end
    52 function stanza_mt:tag(name, attrs)
    53 	local s = stanza(name, attrs);
    54 	(self.last_add[#self.last_add] or self):add_direct_child(s);
    55 	t_insert(self.last_add, s);
    56 	return self;
    57 end
    59 function stanza_mt:text(text)
    60 	(self.last_add[#self.last_add] or self):add_direct_child(text);
    61 	return self; 
    62 end
    64 function stanza_mt:up()
    65 	t_remove(self.last_add);
    66 	return self;
    67 end
    69 function stanza_mt:reset()
    70 	local last_add = self.last_add;
    71 	for i = 1,#last_add do
    72 		last_add[i] = nil;
    73 	end
    74 	return self;
    75 end
    77 function stanza_mt:add_direct_child(child)
    78 	if type(child) == "table" then
    79 		t_insert(self.tags, child);
    80 	end
    81 	t_insert(self, child);
    82 end
    84 function stanza_mt:add_child(child)
    85 	(self.last_add[#self.last_add] or self):add_direct_child(child);
    86 	return self;
    87 end
    89 function stanza_mt:child_with_name(name)
    90 	for _, child in ipairs(self.tags) do	
    91 		if child.name == name then return child; end
    92 	end
    93 end
    95 function stanza_mt:child_with_ns(ns)
    96 	for _, child in ipairs(self.tags) do	
    97 		if child.attr.xmlns == ns then return child; end
    98 	end
    99 end
   101 function stanza_mt:children()
   102 	local i = 0;
   103 	return function (a)
   104 			i = i + 1
   105 			local v = a[i]
   106 			if v then return v; end
   107 		end, self, i;
   109 end
   110 function stanza_mt:childtags()
   111 	local i = 0;
   112 	return function (a)
   113 			i = i + 1
   114 			local v = self.tags[i]
   115 			if v then return v; end
   116 		end, self.tags[1], i;
   118 end
   120 local xml_escape = (function()
   121 	local escape_table = { ["'"] = "&apos;", ["\""] = "&quot;", ["<"] = "&lt;", [">"] = "&gt;", ["&"] = "&amp;" };
   122 	return function(str) return (s_gsub(str, "['&<>\"]", escape_table)); end
   123 end)();
   124 local function _dostring(t, buf, self, xml_escape)
   125 	local nsid, ns, attrk = 0;
   126 	t_insert(buf, "<"..t.name);
   127 	for k, v in pairs(t.attr) do
   128 		ns, attrk = s_match(k, "^([^|]+)|(.+)$");
   129 		if ns then
   130 			nsid = nsid + 1;
   131 			t_insert(buf, " xmlns:ns"..nsid.."='"..xml_escape(ns).."' ".."ns"..nsid..":"..attrk.."='"..xml_escape(v).."'");
   132 		else
   133 			t_insert(buf, " "..k.."='"..xml_escape(v).."'");
   134 		end
   135 	end
   136 	t_insert(buf, ">");
   137 	for n=1,#t do
   138 		local child = t[n];
   139 		if child.name then
   140 			self(child, buf, self, xml_escape);
   141 		else
   142 			t_insert(buf, xml_escape(child));
   143 		end
   144 	end
   145 	t_insert(buf, "</"..t.name..">");
   146 end
   147 function stanza_mt.__tostring(t)
   148 	local buf = {};
   149 	_dostring(t, buf, _dostring, xml_escape);
   150 	return t_concat(buf);
   151 end
   153 function stanza_mt.top_tag(t)
   154 	local attr_string = "";
   155 	if t.attr then
   156 		for k, v in pairs(t.attr) do if type(k) == "string" then attr_string = attr_string .. s_format(" %s='%s'", k, xml_escape(tostring(v))); end end
   157 	end
   158 	return s_format("<%s%s>", t.name, attr_string);
   159 end
   161 function stanza_mt.__add(s1, s2)
   162 	return s1:add_direct_child(s2);
   163 end
   166 do
   167         local id = 0;
   168         function new_id()
   169                 id = id + 1;
   170                 return "lx"..id;
   171         end
   172 end
   174 function preserialize(stanza)
   175 	local s = { name = stanza.name, attr = stanza.attr };
   176 	for _, child in ipairs(stanza) do
   177 		if type(child) == "table" then
   178 			t_insert(s, preserialize(child));
   179 		else
   180 			t_insert(s, child);
   181 		end
   182 	end
   183 	return s;
   184 end
   186 function deserialize(stanza)
   187 	-- Set metatable
   188 	if stanza then
   189 		local attr = stanza.attr;
   190 		for i=1,#attr do attr[i] = nil; end
   191 		setmetatable(stanza, stanza_mt);
   192 		for _, child in ipairs(stanza) do
   193 			if type(child) == "table" then
   194 				deserialize(child);
   195 			end
   196 		end
   197 		if not stanza.tags then
   198 			-- Rebuild tags
   199 			local tags = {};
   200 			for _, child in ipairs(stanza) do
   201 				if type(child) == "table" then
   202 					t_insert(tags, child);
   203 				end
   204 			end
   205 			stanza.tags = tags;
   206 			if not stanza.last_add then
   207 				stanza.last_add = {};
   208 			end
   209 		end
   210 	end
   212 	return stanza;
   213 end
   215 function clone(stanza)
   216     local lookup_table = {};
   217     local function _copy(object)
   218         if type(object) ~= "table" then
   219             return object;
   220         elseif lookup_table[object] then
   221             return lookup_table[object];
   222         end
   223         local new_table = {};
   224         lookup_table[object] = new_table;
   225         for index, value in pairs(object) do
   226             new_table[_copy(index)] = _copy(value);
   227         end
   228         return setmetatable(new_table, getmetatable(object));
   229     end
   230     return _copy(stanza)
   231 end
   233 function message(attr, body)
   234 	if not body then
   235 		return stanza("message", attr);
   236 	else
   237 		return stanza("message", attr):tag("body"):text(body);
   238 	end
   239 end
   240 function iq(attr)
   241 	if attr and not attr.id then attr.id = new_id(); end
   242 	return stanza("iq", attr or { id = new_id() });
   243 end
   245 function reply(orig)
   246 	return stanza(orig.name, orig.attr and { to = orig.attr.from, from = orig.attr.to, id = orig.attr.id, type = ((orig.name == "iq" and "result") or orig.attr.type) });
   247 end
   249 function error_reply(orig, type, condition, message)
   250 	local t = reply(orig);
   251 	t.attr.type = "error";
   252 	t:tag("error", {type = type})
   253 		:tag(condition, {xmlns = "urn:ietf:params:xml:ns:xmpp-stanzas"}):up();
   254 	if (message) then t:tag("text"):text(message):up(); end
   255 	return t; -- stanza ready for adding app-specific errors
   256 end
   258 function presence(attr)
   259 	return stanza("presence", attr);
   260 end
   262 if do_pretty_printing then
   263 	local style_attrk = getstyle("yellow");
   264 	local style_attrv = getstyle("red");
   265 	local style_tagname = getstyle("red");
   266 	local style_punc = getstyle("magenta");
   268 	local attr_format = " "..getstring(style_attrk, "%s")..getstring(style_punc, "=")..getstring(style_attrv, "'%s'");
   269 	local top_tag_format = getstring(style_punc, "<")..getstring(style_tagname, "%s").."%s"..getstring(style_punc, ">");
   270 	--local tag_format = getstring(style_punc, "<")..getstring(style_tagname, "%s").."%s"..getstring(style_punc, ">").."%s"..getstring(style_punc, "</")..getstring(style_tagname, "%s")..getstring(style_punc, ">");
   271 	local tag_format = top_tag_format.."%s"..getstring(style_punc, "</")..getstring(style_tagname, "%s")..getstring(style_punc, ">");
   272 	function stanza_mt.pretty_print(t)
   273 		local children_text = "";
   274 		for n, child in ipairs(t) do
   275 			if type(child) == "string" then	
   276 				children_text = children_text .. xml_escape(child);
   277 			else
   278 				children_text = children_text .. child:pretty_print();
   279 			end
   280 		end
   282 		local attr_string = "";
   283 		if t.attr then
   284 			for k, v in pairs(t.attr) do if type(k) == "string" then attr_string = attr_string .. s_format(attr_format, k, tostring(v)); end end
   285 		end
   286 		return s_format(tag_format, t.name, attr_string, children_text, t.name);
   287 	end
   289 	function stanza_mt.pretty_top_tag(t)
   290 		local attr_string = "";
   291 		if t.attr then
   292 			for k, v in pairs(t.attr) do if type(k) == "string" then attr_string = attr_string .. s_format(attr_format, k, tostring(v)); end end
   293 		end
   294 		return s_format(top_tag_format, t.name, attr_string);
   295 	end
   296 else
   297 	-- Sorry, fresh out of colours for you guys ;)
   298 	stanza_mt.pretty_print = stanza_mt.__tostring;
   299 	stanza_mt.pretty_top_tag = stanza_mt.top_tag;
   300 end
   302 return _M;

mercurial