1 # -*- coding: UTF-8 -*-
5 # - better error handling
6 # - use namespace parser
8 from Tools.Import import my_import
10 from Components.Sources.Source import ObsoleteSource
11 from Components.Converter.Converter import Converter
12 from Components.Element import Element
14 from xml.sax import make_parser
15 from xml.sax.handler import ContentHandler, feature_namespaces
16 from xml.sax.saxutils import escape as escape_xml
17 from twisted.python import util
18 from urllib2 import quote
20 #DO NOT REMOVE THIS IMPORT
21 #It IS used (dynamically)
22 from WebScreens import *
23 #DO NOT REMOVE THIS IMPORT
29 # The classes and Function in File handle all ScreenPage-based requests
30 # ScreenPages use enigma2 standard functionality to bring contents to a webfrontend
32 # Like Skins a ScreenPage can consist of several Elements and Converters
34 #===============================================================================
37 # This is the Standard Element for Rendering a "standard" WebElement
38 #===============================================================================
39 class OneTimeElement(Element):
40 def __init__(self, id):
41 Element.__init__(self)
44 def handleCommand(self, args):
45 if ',' in self.source_id:
46 paramlist = self.source_id.split(",")
49 arg = args.get(key, ())
54 list[key] = "".join(arg)
57 self.source.handleCommand(list)
59 for c in args.get(self.source_id, ()):
60 self.source.handleCommand(c)
62 def render(self, request):
63 t = self.source.getHTML(self.source_id)
67 self.suspended = False
81 #===============================================================================
84 # A MacroElement helps using OneTimeElements inside a (Simple)ListFiller Loop
85 #===============================================================================
86 class MacroElement(OneTimeElement):
87 def __init__(self, id, macro_dict, macro_name):
88 OneTimeElement.__init__(self, id)
89 self.macro_dict = macro_dict
90 self.macro_name = macro_name
92 def render(self, request):
93 self.macro_dict[self.macro_name] = self.source.getHTML(self.source_id)
95 #===============================================================================
98 # In difference to an OneTimeElement a StreamingElement sends an ongoing Stream
99 # of Data. The end of the Streaming is usually when the client disconnects
100 #===============================================================================
101 class StreamingElement(OneTimeElement):
102 def __init__(self, id):
103 OneTimeElement.__init__(self, id)
106 def changed(self, what):
108 self.render(self.request)
110 def setRequest(self, request):
111 self.request = request
113 #===============================================================================
116 # a to-be-filled list item
117 #===============================================================================
119 def __init__(self, name, filternum):
121 self.filternum = filternum
123 #===============================================================================
126 # MacroItem inside a (Simple)ListFiller
127 #===============================================================================
129 def __init__(self, macrodict, macroname):
130 self.macrodict = macrodict
131 self.macroname = macroname
134 #===============================================================================
137 # Returns the String as is
138 #===============================================================================
139 class TextToHTML(Converter):
140 def __init__(self, arg):
141 Converter.__init__(self, arg)
143 def getHTML(self, id):
144 return self.source.text.replace('\xc2\x86', '').replace('\xc2\x87', '').decode("utf-8", "ignore").encode("utf-8") # encode & etc. here!
146 #===============================================================================
149 # Escapes the given Text to be XML conform
150 #===============================================================================
151 class TextToXML(Converter):
152 def __init__(self, arg):
153 Converter.__init__(self, arg)
155 def getHTML(self, id):
156 return escape_xml(self.source.text).replace('\xc2\x86', '').replace('\xc2\x87', '').replace("\x19", "").replace("\x1c", "").replace("\x1e", "").decode("utf-8", "ignore").encode("utf-8")
158 #===============================================================================
161 # Escapes the given Text so it can be used inside a URL
162 #===============================================================================
163 class TextToURL(Converter):
164 def __init__(self, arg):
165 Converter.__init__(self, arg)
167 def getHTML(self, id):
168 return self.source.text.replace(" ", "%20").replace("+", "%2b").replace("&", "%26").replace('\xc2\x86', '').replace('\xc2\x87', '').decode("utf-8", "ignore").encode("utf-8")
170 #===============================================================================
173 # Returns a XML only consisting of <rootElement />
174 #===============================================================================
175 class ReturnEmptyXML(Converter):
176 def __init__(self, arg):
177 Converter.__init__(self, arg)
179 def getHTML(self, id):
180 return "<rootElement />"
182 #===============================================================================
184 # Return simply NOTHING
185 # Useful if you only want to issue a command.
186 #===============================================================================
187 class Null(Converter):
188 def __init__(self, arg):
189 Converter.__init__(self, arg)
191 def getHTML(self, id):
195 #===============================================================================
198 # Transforms a string into a javascript update pattern
199 #===============================================================================
200 class JavascriptUpdate(Converter):
201 def __init__(self, arg):
202 Converter.__init__(self, arg)
204 def getHTML(self, id):
205 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
206 # all other will replace this in JS
207 return '<script>parent.set("%s", "%s");</script>\n' % (id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"').replace('\xb0', '°'))
209 #===============================================================================
212 # The performant 'one-dimensonial listfiller' engine (podlfe)
213 #===============================================================================
214 class SimpleListFiller(Converter):
215 def __init__(self, arg):
216 Converter.__init__(self, arg)
219 l = self.source.simplelist
220 conv_args = self.converter_arguments
223 for element in conv_args:
224 if isinstance(element, basestring):
225 list.append((element, None))
226 elif isinstance(element, ListItem):
227 list.append((element, element.filternum))
228 elif isinstance(element, ListMacroItem):
229 list.append(element.macrodict[element.macroname], None)
231 raise Exception("neither string, ListItem nor ListMacroItem")
234 append = strlist.append
239 for (element, filternum) in list:
240 #filter out "non-displayable" Characters - at the very end, do it the hard way...
241 item = str(item).replace('\xc2\x86', '').replace('\xc2\x87', '').replace("\x19", "").replace("\x1c", "").replace("\x1e", "").decode("utf-8", "ignore").encode("utf-8")
246 append(item.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
248 append(escape_xml(item))
250 append(item.replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
254 time = parseint(item) or 0
256 append("%02d:%02d" % (t.tm_hour, t.tm_min))
258 time = parseint(item) or 0
260 append("%d min" % (time / 60))
263 # (this will be done in c++ later!)
265 return ''.join(strlist)
267 text = property(getText)
269 #===============================================================================
270 # the performant 'listfiller'-engine (plfe)
271 #===============================================================================
272 class ListFiller(Converter):
273 def __init__(self, arg):
274 Converter.__init__(self, arg)
275 # print "ListFiller-arg: ",arg
279 lut = self.source.lut
280 conv_args = self.converter_arguments
282 # now build a ["string", 1, "string", 2]-styled list, with indices into the
283 # list to avoid lookup of item name for each entry
285 for element in conv_args:
286 if isinstance(element, basestring):
287 lutlist.append((element, None))
288 elif isinstance(element, ListItem):
289 lutlist.append((lut[element.name], element.filternum))
290 elif isinstance(element, ListMacroItem):
291 lutlist.append((element.macrodict[element.macroname], None))
293 raise Exception("neither string, ListItem nor ListMacroItem")
295 # now, for the huge list, do:
297 append = strlist.append
299 for (element, filternum) in lutlist:
303 #filter out "non-displayable" Characters - at the very end, do it the hard way...
304 curitem = str(item[element]).replace('\xc2\x86', '').replace('\xc2\x87', '').replace("\x19", "").replace("\x1c", "").replace("\x1e", "").decode("utf-8", "ignore").encode("utf-8")
314 append(curitem.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
316 append( escape_xml( curitem ))
318 append(curitem.replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
320 append(quote(curitem))
322 from time import localtime
323 time = int(float(curitem)) or 0
325 append("%02d:%02d" % (t.tm_hour, t.tm_min))
327 from time import localtime
328 time = int(float(curitem)) or 0
330 append("%d min" % (time / 60))
333 # (this will be done in c++ later!)
335 return ''.join(strlist)
337 text = property(getText)
339 #===============================================================================
342 # Handles the Content of a Web-Request
343 # It looks up the source, instantiates the Element and Calls the Converter
344 #===============================================================================
345 class webifHandler(ContentHandler):
346 def __init__(self, session, request):
350 self.session = session
352 self.request = request
355 def start_element(self, attrs):
358 wsource = attrs["source"]
360 path = wsource.split('.')
362 scr = self.screen.getRelatedScreen(path[0])
364 print "[webif.py] Parent Screen not found!"
368 source = scr.get(path[0])
370 if isinstance(source, ObsoleteSource):
371 # however, if we found an "obsolete source", issue warning, and resolve the real source.
372 print "WARNING: WEBIF '%s' USES OBSOLETE SOURCE '%s', USE '%s' INSTEAD!" % (name, wsource, source.new_source)
373 print "OBSOLETE SOURCE WILL BE REMOVED %s, PLEASE UPDATE!" % (source.removal_date)
374 if source.description:
375 print source.description
377 wsource = source.new_source
380 # otherwise, use that source.
383 self.source_id = str(attrs.get("id", wsource))
384 self.is_streaming = "streaming" in attrs
385 self.macro_name = attrs.get("macro") or None
387 def end_element(self):
388 # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
389 if not self.is_streaming:
390 if self.macro_name is None:
391 c = OneTimeElement(self.source_id)
393 c = MacroElement(self.source_id, self.macros, self.macro_name)
395 assert self.macro_name is None
396 c = StreamingElement(self.source_id)
398 c.connect(self.source)
400 self.screen.renderer.append(c)
403 def start_convert(self, attrs):
404 ctype = attrs["type"]
406 # TODO: we need something better here
407 if ctype[:4] == "web:": # for now
408 self.converter = eval(ctype[4:])
411 self.converter = my_import('.'.join(("Components", "Converter", ctype))).__dict__.get(ctype)
413 self.converter = my_import('.'.join(("Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype))).__dict__.get(ctype)
416 def end_convert(self):
417 if len(self.sub) == 1:
418 self.sub = self.sub[0]
419 c = self.converter(self.sub)
420 c.connect(self.source)
424 def parse_item(self, attrs):
426 filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4, "urlencode": 5, "time": 6, "minutes": 7}[attrs.get("filter", "")]
427 self.sub.append(ListItem(attrs["name"], filter))
429 assert "macro" in attrs, "e2:item must have a name= or macro= attribute!"
430 self.sub.append(ListMacroItem(self.macros, attrs["macro"]))
432 def startElement(self, name, attrs):
433 if name == "e2:screen":
434 self.screen = eval(attrs["name"])(self.session, self.request) # fixme
435 self.screens.append(self.screen)
438 if name[:3] == "e2:":
441 tag = '<' + name + ''.join([' %s="%s"' % x for x in attrs.items()]) + '>'
442 #tag = tag.encode('utf-8')
446 elif self.mode == 1: # expect "<e2:element>"
447 assert name == "e2:element", "found %s instead of e2:element" % name
448 self.start_element(attrs)
449 elif self.mode == 2: # expect "<e2:convert>"
450 if name[:3] == "e2:":
451 assert name == "e2:convert"
452 self.start_convert(attrs)
456 assert name == "e2:item", "found %s instead of e2:item!" % name
458 self.parse_item(attrs)
460 def endElement(self, name):
461 if name == "e2:screen":
465 tag = "</" + name + ">"
468 elif self.mode == 2 and name[:3] != "e2:":
470 elif self.mode == 2: # closed 'convert' -> sub
472 elif self.mode == 1: # closed 'element'
474 if name[:3] == "e2:":
477 def processingInstruction(self, target, data):
478 self.res.append('<?' + target + ' ' + data + '>')
480 def characters(self, ch):
481 ch = ch.encode('utf-8')
487 def startEntity(self, name):
488 self.res.append('&' + name + ';');
491 for screen in self.screens:
495 print "screen cleanup!"
496 for screen in self.screens:
501 #===============================================================================
504 # Creates the Handler for a Request and calls it
505 # Also ensures that the Handler is finished after the Request is done
506 #===============================================================================
507 def renderPage(request, path, session):
508 # read in the template, create required screens
509 # we don't have persistense yet.
510 # if we had, this first part would only be done once.
511 handler = webifHandler(session, request)
512 parser = make_parser()
513 parser.setFeature(feature_namespaces, 0)
514 parser.setContentHandler(handler)
515 parser.parse(open(util.sibpath(__file__, path)))
517 # by default, we have non-streaming pages
520 # first, apply "commands" (aka. URL argument)
521 for x in handler.res:
522 if isinstance(x, Element):
523 x.handleCommand(request.args)
527 # now, we have a list with static texts mixed
528 # with non-static Elements.
529 # flatten this list, write into the request.
530 for x in handler.res:
531 if isinstance(x, Element):
532 if isinstance(x, StreamingElement):
534 x.setRequest(request)
537 request.write(str(x))
539 # if we met a "StreamingElement", there is at least one
540 # element which wants to output data more than once,
541 # i.e. on host-originated changes.
542 # in this case, don't finish yet, don't cleanup yet,
543 # but instead do that when the client disconnects.
545 requestFinish(handler, request)
548 def requestFinishDeferred(nothing, handler, request):
549 from twisted.internet import reactor
550 reactor.callLater(0, requestFinish, handler, request)
552 d = request.notifyFinish()
554 d.addBoth( requestFinishDeferred, handler, request )
556 #===============================================================================
559 # This has to be/is called at the end of every ScreenPage-based Request
560 #===============================================================================
561 def requestFinish(handler, request):