1 # -*- coding: UTF-8 -*-
6 # - screens need to be defined somehow else.
7 # I don't know how, yet. Probably each in an own file.
8 # - better error handling
9 # - use namespace parser
11 from Tools.Import import my_import
13 from Components.Sources.Source import ObsoleteSource
14 from Components.Converter.Converter import Converter
15 from Components.Element import Element
17 from xml.sax import make_parser
18 from xml.sax.handler import ContentHandler, feature_namespaces
19 from xml.sax.saxutils import escape as escape_xml
20 from twisted.python import util
21 from urllib2 import quote
23 #DO NOT REMOVE THIS IMPORT
24 #It IS used (dynamically)
25 from WebScreens import *
26 #DO NOT REMOVE THIS IMPORT
28 # implements the 'render'-call.
29 # this will act as a downstream_element, like a renderer.
30 class OneTimeElement(Element):
31 def __init__(self, id):
32 Element.__init__(self)
35 # CHECKME: is this ok performance-wise?
36 def handleCommand(self, args):
37 if self.source_id.find(",") >= 0:
38 paramlist = self.source_id.split(",")
41 arg = args.get(key, [])
45 list[key] = "".join(arg)
48 self.source.handleCommand(list)
50 for c in args.get(self.source_id, []):
51 self.source.handleCommand(c)
53 def render(self, stream):
54 t = self.source.getHTML(self.source_id)
58 self.suspended = False
72 class MacroElement(OneTimeElement):
73 def __init__(self, id, macro_dict, macro_name):
74 OneTimeElement.__init__(self, id)
75 self.macro_dict = macro_dict
76 self.macro_name = macro_name
78 def render(self, stream):
79 self.macro_dict[self.macro_name] = self.source.getHTML(self.source_id)
81 class StreamingElement(OneTimeElement):
82 def __init__(self, id):
83 OneTimeElement.__init__(self, id)
86 def changed(self, what):
88 self.render(self.stream)
90 def setStream(self, stream):
93 # a to-be-filled list item
95 def __init__(self, name, filternum):
97 self.filternum = filternum
100 def __init__(self, macrodict, macroname):
101 self.macrodict = macrodict
102 self.macroname = macroname
104 class TextToHTML(Converter):
105 def __init__(self, arg):
106 Converter.__init__(self, arg)
108 def getHTML(self, id):
109 return self.source.text # encode & etc. here!
111 class TextToXML(Converter):
112 def __init__(self, arg):
113 Converter.__init__(self, arg)
115 def getHTML(self, id):
116 return escape_xml(self.source.text).replace("\x19", "").replace("\x1c", "").replace("\x1e", "")
118 class TextToURL(Converter):
119 def __init__(self, arg):
120 Converter.__init__(self, arg)
122 def getHTML(self, id):
123 return self.source.text.replace(" ", "%20")
125 class ReturnEmptyXML(Converter):
126 def __init__(self, arg):
127 Converter.__init__(self, arg)
129 def getHTML(self, id):
130 return "<rootElement></rootElement>"
132 # a null-output. Useful if you only want to issue a command.
133 class Null(Converter):
134 def __init__(self, arg):
135 Converter.__init__(self, arg)
137 def getHTML(self, id):
140 class JavascriptUpdate(Converter):
141 def __init__(self, arg):
142 Converter.__init__(self, arg)
144 def getHTML(self, id):
145 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
146 # all other will replace this in JS
147 return '<script>parent.set("%s", "%s");</script>\n' % (id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"').replace('\xb0', '°'))
149 # the performant 'one-dimensonial listfiller' engine (podlfe)
150 class SimpleListFiller(Converter):
151 def __init__(self, arg):
152 Converter.__init__(self, arg)
155 l = self.source.simplelist
156 conv_args = self.converter_arguments
159 for element in conv_args:
160 if isinstance(element, basestring):
161 list.append((element, None))
162 elif isinstance(element, ListItem):
163 list.append((element, element.filternum))
164 elif isinstance(element, ListMacroItem):
165 list.append(element.macrodict[element.macroname], None)
167 raise Exception("neither string, ListItem nor ListMacroItem")
170 append = strlist.append
175 for (element, filternum) in list:
179 append(str(item).replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
181 append(escape_xml(str(item)))
183 append(str(item).replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
185 append(quote(str(item)))
187 time = parseint(item) or 0
189 append("%02d:%02d" % (t.tm_hour, t.tm_min))
191 time = parseint(item) or 0
193 append("%d min" % (time / 60))
196 # (this will be done in c++ later!)
198 return ''.join(strlist)
200 text = property(getText)
204 # the performant 'listfiller'-engine (plfe)
205 class ListFiller(Converter):
206 def __init__(self, arg):
207 Converter.__init__(self, arg)
208 # print "ListFiller-arg: ",arg
212 lut = self.source.lut
213 conv_args = self.converter_arguments
215 # now build a ["string", 1, "string", 2]-styled list, with indices into the
216 # list to avoid lookup of item name for each entry
218 for element in conv_args:
219 if isinstance(element, basestring):
220 lutlist.append((element, None))
221 elif isinstance(element, ListItem):
222 lutlist.append((lut[element.name], element.filternum))
223 elif isinstance(element, ListMacroItem):
224 lutlist.append((element.macrodict[element.macroname], None))
226 raise Exception("neither string, ListItem nor ListMacroItem")
228 # now, for the huge list, do:
230 append = strlist.append
232 for (element, filternum) in lutlist:
236 curitem = item[element]
246 append(str(curitem).replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
248 append(escape_xml(str(curitem)))
250 append(str(curitem).replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
252 append(quote(str(curitem)))
254 from time import localtime
255 time = int(float(curitem)) or 0
257 append("%02d:%02d" % (t.tm_hour, t.tm_min))
259 from time import localtime
260 time = int(float(curitem)) or 0
262 append("%d min" % (time / 60))
265 # (this will be done in c++ later!)
267 return ''.join(strlist)
269 text = property(getText)
271 class webifHandler(ContentHandler):
272 def __init__(self, session, request):
276 self.session = session
278 self.request = request
281 def start_element(self, attrs):
284 wsource = attrs["source"]
286 path = wsource.split('.')
288 scr = self.screen.getRelatedScreen(path[0])
290 print "[webif.py] Parent Screen not found!"
294 source = scr.get(path[0])
296 if isinstance(source, ObsoleteSource):
297 # however, if we found an "obsolete source", issue warning, and resolve the real source.
298 print "WARNING: WEBIF '%s' USES OBSOLETE SOURCE '%s', USE '%s' INSTEAD!" % (name, wsource, source.new_source)
299 print "OBSOLETE SOURCE WILL BE REMOVED %s, PLEASE UPDATE!" % (source.removal_date)
300 if source.description:
301 print source.description
303 wsource = source.new_source
306 # otherwise, use that source.
309 self.source_id = str(attrs.get("id", wsource))
310 self.is_streaming = "streaming" in attrs
311 self.macro_name = attrs.get("macro") or None
313 def end_element(self):
314 # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
315 if not self.is_streaming:
316 if self.macro_name is None:
317 c = OneTimeElement(self.source_id)
319 c = MacroElement(self.source_id, self.macros, self.macro_name)
321 assert self.macro_name is None
322 c = StreamingElement(self.source_id)
324 c.connect(self.source)
326 self.screen.renderer.append(c)
329 def start_convert(self, attrs):
330 ctype = attrs["type"]
332 # TODO: we need something better here
333 if ctype[:4] == "web:": # for now
334 self.converter = eval(ctype[4:])
337 self.converter = my_import('.'.join(["Components", "Converter", ctype])).__dict__.get(ctype)
339 self.converter = my_import('.'.join(["Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype])).__dict__.get(ctype)
342 def end_convert(self):
343 if len(self.sub) == 1:
344 self.sub = self.sub[0]
345 c = self.converter(self.sub)
346 c.connect(self.source)
350 def parse_item(self, attrs):
352 filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4, "urlencode": 5, "time": 6, "minutes": 7}[attrs.get("filter", "")]
353 self.sub.append(ListItem(attrs["name"], filter))
355 assert "macro" in attrs, "e2:item must have a name= or macro= attribute!"
356 self.sub.append(ListMacroItem(self.macros, attrs["macro"]))
358 def startElement(self, name, attrs):
359 if name == "e2:screen":
360 self.screen = eval(attrs["name"])(self.session, self.request) # fixme
361 self.screens.append(self.screen)
364 if name[:3] == "e2:":
367 tag = [' %s="%s"' % (key, val) for (key, val) in attrs.items()]
371 tag = ''.join(tag)#.encode('utf-8')
375 elif self.mode == 1: # expect "<e2:element>"
376 assert name == "e2:element", "found %s instead of e2:element" % name
377 self.start_element(attrs)
378 elif self.mode == 2: # expect "<e2:convert>"
379 if name[:3] == "e2:":
380 assert name == "e2:convert"
381 self.start_convert(attrs)
385 assert name == "e2:item", "found %s instead of e2:item!" % name
387 self.parse_item(attrs)
389 def endElement(self, name):
390 if name == "e2:screen":
394 tag = "</" + name + ">"
397 elif self.mode == 2 and name[:3] != "e2:":
399 elif self.mode == 2: # closed 'convert' -> sub
401 elif self.mode == 1: # closed 'element'
403 if name[:3] == "e2:":
406 def processingInstruction(self, target, data):
407 self.res.append('<?' + target + ' ' + data + '>')
409 def characters(self, ch):
410 ch = ch.encode('utf-8')
416 def startEntity(self, name):
417 self.res.append('&' + name + ';');
420 for screen in self.screens:
424 print "screen cleanup!"
425 for screen in self.screens:
430 def renderPage(stream, path, req, session):
431 # read in the template, create required screens
432 # we don't have persistense yet.
433 # if we had, this first part would only be done once.
434 handler = webifHandler(session, req)
435 parser = make_parser()
436 parser.setFeature(feature_namespaces, 0)
437 parser.setContentHandler(handler)
438 parser.parse(open(util.sibpath(__file__, path)))
440 # by default, we have non-streaming pages
443 # first, apply "commands" (aka. URL argument)
444 for x in handler.res:
445 if isinstance(x, Element):
446 x.handleCommand(req.args)
450 # now, we have a list with static texts mixed
451 # with non-static Elements.
452 # flatten this list, write into the stream.
453 for x in handler.res:
454 if isinstance(x, Element):
455 if isinstance(x, StreamingElement):
463 from twisted.internet import reactor
465 reactor.callLater(3, ping, s)
467 # if we met a "StreamingElement", there is at least one
468 # element which wants to output data more than once,
469 # i.e. on host-originated changes.
470 # in this case, don't finish yet, don't cleanup yet,
471 # but instead do that when the client disconnects.
473 streamFinish(handler, stream)
476 # you *need* something which constantly sends something in a regular interval,
477 # in order to detect disconnected clients.
478 # i agree that this "ping" sucks terrible, so better be sure to have something
479 # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
481 stream.closed_callback = lambda : streamFinish(handler, stream)
483 def streamFinish(handler, stream):