From Masashi Honma:
[obnox/wireshark/wip.git] / epan / wslua / dtd_gen.lua
1 -- dtd_gen.lua
2 --
3 -- a DTD generator for wireshark
4 --
5 -- (c) 2006 Luis E. Garcia Ontanon <luis@ontanon.org>
6 --
7 -- $Id$
8 -- 
9 -- Wireshark - Network traffic analyzer
10 -- By Gerald Combs <gerald@wireshark.org>
11 -- Copyright 1998 Gerald Combs
12 --
13 -- This program is free software; you can redistribute it and/or
14 -- modify it under the terms of the GNU General Public License
15 -- as published by the Free Software Foundation; either version 2
16 -- of the License, or (at your option) any later version.
17 --
18 -- This program is distributed in the hope that it will be useful,
19 -- but WITHOUT ANY WARRANTY; without even the implied warranty of
20 -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 -- GNU General Public License for more details.
22 --
23 -- You should have received a copy of the GNU General Public License
24 -- along with this program; if not, write to the Free Software
25 -- Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
26
27 if gui_enabled() then
28         local xml_fld = Field.new("xml")
29
30         local function dtd_generator()
31                 local displayed = {} -- whether or not a dtd is already displayed
32                 local dtds = {} -- the dtds
33                 local changed = {} -- whether or not a dtd has been modified
34                 local dtd -- the dtd being dealt with
35                 local dtd_name -- its name
36
37                 -- we'll tap onto every frame that has xml
38
39                 local ws = {} -- the windows for each dtd
40                 local w = TextWindow.new("DTD Generator")
41
42                 local function help()
43                         local wh = TextWindow.new("DTD Generator Help")
44                         -- XXX write help
45                         wh:set('DTD Generator Help\n')
46                 end
47
48                 local function get_dtd_from_xml(text,d,parent)
49                 -- obtains dtd information from xml
50                 --   text: xml to be parsed
51                 --   d: the current dtd (if any)
52                 --   parent: parent entity (if any)
53
54                         -- cleanup the text from useless chars
55                         text = string.gsub(text ,"%s*<%s*","<");
56                         text = string.gsub(text ,"%s*>%s*",">");
57                         text = string.gsub(text ,"<%-%-(.-)%-%->"," ");
58                         text = string.gsub(text ,"%s+"," ");
59
60                         while true do
61                                 -- find the first tag
62                                 local open_tag = string.match(text,"%b<>")
63
64                                 if open_tag == nil then 
65                                         -- no more tags, we're done
66                                         return true
67                                 end
68
69                                 local name = string.match(open_tag,"[%w%d_-]+")
70                                 local this_ent = nil
71
72                                 if d == nil then
73                                         -- there's no current dtd, this is entity is it
74                                         d = dtds[name]
75
76                                         if d == nil then
77                                                 d = {ents = {}, attrs = {}}
78                                                 dtds[name] = d
79                                         end
80
81                                         dtd = d
82                                         dtd_name = name
83                                 end
84
85                                 this_ent = d[name]
86
87                                 if this_ent == nil then
88                                         -- no entity by this name in this dtd, create it
89                                         this_ent = {ents = {}, attrs = {}}
90                                         d.ents[name] = this_ent
91                                         changed[dtd_name] = true
92                                 end
93
94                                 if parent ~= nil then
95                                         -- add this entity to its parent
96                                         parent.ents[name] = 1
97                                         changed[dtd_name] = true
98                                 end
99                                 
100                                 -- add the attrs to the entity
101                                 for att in string.gmatch(open_tag, "([%w%d_-]+)%s*=") do
102                                         if not this_ent.attrs[att] then
103                                                 changed[dtd_name] = true
104                                                 this_ent.attrs[att] = true
105                                         end
106                                 end
107
108                                 if string.match(open_tag,"/>") then
109                                         -- this tag is "self closed" just remove it and continue
110                                         text = string.gsub(text,"%b<>","",1)
111                                 else
112                                         local close_tag_pat = "</%s*" .. name .. "%s*>"
113                                         if not string.match(text,close_tag_pat) then return false end
114                                         local span,left = string.match(text,"%b<>(.-)" .. close_tag_pat .. "(.*)")
115
116                                         if span ~= nil then
117                                                 -- recurse to find child entities
118                                                 if not get_dtd_from_xml(span,d,this_ent) then
119                                                         return false
120                                                 end
121                                         end
122
123                                         -- continue with what's left
124                                         text = left
125                                 end
126                         end
127
128                         return true
129                 end
130
131                 local function entity_tostring(name,entity_data)
132                 -- name: the name of the entity
133                 -- entity_data: a table containg the entity data
134                 -- returns the dtd text for that entity
135                         local text = ''
136                         text = text .. '\t<!ELEMENT ' .. name .. '  (' --)
137                         for e,j in pairs(entity_data.ents) do
138                                 text = text .. " " .. e .. ' |'
139                         end
140                         text = text .. " #PCDATA ) >\n"
141                         
142                         text = text .. "\t<!ATTLIST " .. name
143                         for a,j in pairs(entity_data.attrs) do
144                                 text = text .. "\n\t\t" .. a .. ' CDTATA #IMPLIED'
145                         end
146                         text = text .. " >\n\n"
147                         
148                         text = string.gsub(text,"<!ATTLIST " .. name .. " >\n","")
149                         
150                         return text
151                 end
152
153                 local function dtd_tostring(name,doctype) 
154                         local text = '<? wireshark:protocol proto_name="' .. name ..'" hierarchy="yes" ?>\n\n'
155                         local root = doctype.ents[name]
156                         doctype.ents[name] = nil
157
158                         text = text .. entity_tostring(name,root)
159                         
160                         for n,es in pairs(doctype.ents) do
161                                 text = text .. entity_tostring(n,es)
162                         end
163
164                         doctype.ents[name] = root
165
166                         return text
167                 end
168
169
170                 local function element_body(name,text)
171                 -- get the entity's children from dtd text
172                 --    name: the name of the element
173                 --    text: the list of children
174                         text = string.gsub(text,"[%s%?%*%#%+%(%)]","")
175                         text = string.gsub(text,"$","|")
176                         text = string.gsub(text,
177                                                            "^(.-)|",
178                                                            function(s)
179                                                                         if dtd.ents[name] == nil then
180                                                                            dtd.ents[name] = {ents={},attrs={}}
181                                                                         end
182                                                            
183                                                                         dtd.ents[name].ents[s] = true
184                                                                         return ""
185                                                            end
186                                                            )
187                         return ''
188                 end
189
190                 local function attlist_body(name,text)
191                 -- get the entity's attributes from dtd text
192                 --    name: the name of the element
193                 --    text: the list of attributes
194                 text = string.gsub(text,"([%w%d_-]+) [A-Z]+ #[A-Z]+",
195                                                                 function(s)
196                                                                         dtd.atts[s] = true
197                                                                         return ""
198                                                                 end
199                                                                 )
200                         return ''
201                 end
202
203                 local function dtd_body(buff)
204                 -- get the dtd's entities from dtd text
205                 --    buff: the dtd text
206
207                         local old_buff = buff
208
209                         buff = string.gsub(buff,"<!ELEMENT ([%w%d_-]+) (%b())>%s*",element_body)
210                         buff = string.gsub(buff,"<!ATTLIST ([%w%d_-]+) (.-)>%s*",attlist_body)
211                 end
212
213                 local function load_dtd(filename)
214                         local dtd_filename = USER_DIR ..  "/dtds/" .. filename
215                         local buff = ''
216                         local wireshark_info
217
218                         dtd_name = nil
219                         dtd = nil
220
221                         for line in io.lines(dtd_filename) do
222                                 buff = buff .. line
223                         end
224
225                         buff = string.gsub(buff ,"%s*<%!%s*","<!");
226                         buff = string.gsub(buff ,"%s*>%s*",">");
227                         buff = string.gsub(buff ,"<!%-%-(.-)%-%->"," ");
228                         buff = string.gsub(buff ,"%s+"," ");
229                         buff = string.gsub(buff ,"^%s+","");
230
231
232                         buff = string.gsub(buff,'(<%?%s*wireshark:protocol%s+.-%s*%?>)',
233                                                            function(s)
234                                                                         wireshark_info = s
235                                                            end
236                                                            )
237
238                         buff = string.gsub(buff,"^<!DOCTYPE ([%w%d_-]+) (%b[])%s*>",
239                                                            function(name,body) 
240                                                                    dtd = { ents = {}, attrs = {}}
241                                                                    dtd_name = name
242                                                                    
243                                                                    dtds[name] = dtd
244                                                                    
245                                                                    dtd_body(body)
246                                                                    
247                                                                    return ""
248                                                            end
249                                                            )
250
251                         if not dtd then
252                                 dtd_body(buff)
253                         end
254                         
255                         if wireshark_info then
256                                 dtd.wstag = wireshark_info
257                         end
258                 end
259
260                 local function load_dtds()
261                 -- loads all existing dtds in the user directory
262                         local dirname = persconffile_path("dtds")
263                         local status, dir = pcall(Dir.open,dirname,".dtd")
264
265                          w:set('Loading DTDs from ' .. dirname .. ' \n')
266
267                         if not status then
268                                 w:append("Error: could not open the directory" .. dirname .. " , make sure it exists.\n")
269                                 return
270                         end
271                                                  
272                         for dtd_filename in dir do
273                                 w:append("File:" .. dtd_filename .. "\n")
274                                 load_dtd(dtd_filename)
275                         end
276
277                 end
278
279                 local function dtd_window(name)
280                         return function()
281                                 local wd = TextWindow.new(name .. '.dtd')
282                                 wd:set(dtd_tostring(name,dtds[name]))
283                                 wd:set_editable()
284
285                                 local function save()
286                                         local file = io.open(persconffile_path("dtds/") .. name .. ".dtd" ,"w")
287                                         file:write(wd:get_text())
288                                         file:close()
289                                 end
290                                 
291                                 wd:add_button("Save",save)
292                         end
293                 end
294
295                 local function close()
296                         if li ~= nil then
297                                 li:remove()
298                                 li = nil
299                         end
300                 end
301
302                 w:set_atclose(close)
303
304                 -- w:add_button("Help",help)
305
306                 load_dtds()
307
308                 local li = Listener.new("frame","xml")
309
310                 w:append('Running')
311
312                 function li.packet() 
313                         w:append('.')
314                         local txt = xml_fld().range:string();
315                         get_dtd_from_xml(txt)
316                 end
317
318                 function li.draw()
319
320                         for name,j in pairs(changed) do
321                                 w:append("\n" .. name .. " has changed\n")
322                                 if not displayed[name] then
323                                         w:add_button(name,dtd_window(name))
324                                         displayed[name] = true
325                                 end
326                         end
327                 end
328
329                 retap_packets()
330                  w:append(t2s(dtds))
331                 w:append('\n')
332
333         end
334
335         register_menu("DTD Generator",dtd_generator,MENU_TOOLS_UNSORTED)
336 end