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
26 # The classes and Function in File handle all ScreenPage-based requests
27 # ScreenPages use enigma2 standard functionality to bring contents to a webfrontend
29 # Like Skins a ScreenPage can consist of several Elements and Converters
31 #===============================================================================
34 # This is the Standard Element for Rendering a "standard" WebElement
35 #===============================================================================
36 class OneTimeElement(Element):
37 def __init__(self, id):
38 Element.__init__(self)
41 def handleCommand(self, args):
42 if ',' in self.source_id:
43 paramlist = self.source_id.split(",")
46 arg = args.get(key, ())
51 list[key] = "".join(arg)
54 self.source.handleCommand(list)
56 for c in args.get(self.source_id, ()):
57 self.source.handleCommand(c)
59 def render(self, request):
60 t = self.source.getHTML(self.source_id)
64 self.suspended = False
78 #===============================================================================
81 # A MacroElement helps using OneTimeElements inside a (Simple)ListFiller Loop
82 #===============================================================================
83 class MacroElement(OneTimeElement):
84 def __init__(self, id, macro_dict, macro_name):
85 OneTimeElement.__init__(self, id)
86 self.macro_dict = macro_dict
87 self.macro_name = macro_name
89 def render(self, request):
90 self.macro_dict[self.macro_name] = self.source.getHTML(self.source_id)
92 #===============================================================================
95 # In difference to an OneTimeElement a StreamingElement sends an ongoing Stream
96 # of Data. The end of the Streaming is usually when the client disconnects
97 #===============================================================================
98 class StreamingElement(OneTimeElement):
99 def __init__(self, id):
100 OneTimeElement.__init__(self, id)
103 def changed(self, what):
105 self.render(self.request)
107 def setRequest(self, request):
108 self.request = request
110 #===============================================================================
113 # a to-be-filled list item
114 #===============================================================================
116 def __init__(self, name, filternum):
118 self.filternum = filternum
120 #===============================================================================
123 # MacroItem inside a (Simple)ListFiller
124 #===============================================================================
126 def __init__(self, macrodict, macroname):
127 self.macrodict = macrodict
128 self.macroname = macroname
131 #===============================================================================
134 # Returns the String as is
135 #===============================================================================
136 class TextToHTML(Converter):
137 def __init__(self, arg):
138 Converter.__init__(self, arg)
140 def getHTML(self, id):
141 return self.source.text.replace('\xc2\x86', '').replace('\xc2\x87', '') # encode & etc. here!
143 #===============================================================================
146 # Escapes the given Text to be XML conform
147 #===============================================================================
148 class TextToXML(Converter):
149 def __init__(self, arg):
150 Converter.__init__(self, arg)
152 def getHTML(self, id):
153 return escape_xml(self.source.text).replace("\x19", "").replace("\x1c", "").replace("\x1e", "").replace('\xc2\x86', '').replace('\xc2\x87', '')
155 #===============================================================================
158 # Escapes the given Text so it can be used inside a URL
159 #===============================================================================
160 class TextToURL(Converter):
161 def __init__(self, arg):
162 Converter.__init__(self, arg)
164 def getHTML(self, id):
165 return self.source.text.replace(" ", "%20").replace("+", "%2b").replace("&", "%26").replace('\xc2\x86', '').replace('\xc2\x87', '')
167 #===============================================================================
170 # Returns a XML only consisting of <rootElement />
171 #===============================================================================
172 class ReturnEmptyXML(Converter):
173 def __init__(self, arg):
174 Converter.__init__(self, arg)
176 def getHTML(self, id):
177 return "<rootElement />"
179 #===============================================================================
181 # Return simply NOTHING
182 # Useful if you only want to issue a command.
183 #===============================================================================
184 class Null(Converter):
185 def __init__(self, arg):
186 Converter.__init__(self, arg)
188 def getHTML(self, id):
192 #===============================================================================
195 # Transforms a string into a javascript update pattern
196 #===============================================================================
197 class JavascriptUpdate(Converter):
198 def __init__(self, arg):
199 Converter.__init__(self, arg)
201 def getHTML(self, id):
202 # 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
203 # all other will replace this in JS
204 return '<script>parent.set("%s", "%s");</script>\n' % (id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"').replace('\xb0', '°'))
206 #===============================================================================
209 # The performant 'one-dimensonial listfiller' engine (podlfe)
210 #===============================================================================
211 class SimpleListFiller(Converter):
212 def __init__(self, arg):
213 Converter.__init__(self, arg)
216 l = self.source.simplelist
217 conv_args = self.converter_arguments
220 for element in conv_args:
221 if isinstance(element, basestring):
222 list.append((element, None))
223 elif isinstance(element, ListItem):
224 list.append((element, element.filternum))
225 elif isinstance(element, ListMacroItem):
226 list.append(element.macrodict[element.macroname], None)
228 raise Exception("neither string, ListItem nor ListMacroItem")
231 append = strlist.append
236 for (element, filternum) in list:
237 item = str(item).replace('\xc2\x86', '').replace('\xc2\x87', '')
242 append(item.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
244 append(escape_xml(item))
246 append(item.replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
250 time = parseint(item) or 0
252 append("%02d:%02d" % (t.tm_hour, t.tm_min))
254 time = parseint(item) or 0
256 append("%d min" % (time / 60))
259 # (this will be done in c++ later!)
261 return ''.join(strlist)
263 text = property(getText)
265 #===============================================================================
266 # the performant 'listfiller'-engine (plfe)
267 #===============================================================================
268 class ListFiller(Converter):
269 def __init__(self, arg):
270 Converter.__init__(self, arg)
271 # print "ListFiller-arg: ",arg
275 lut = self.source.lut
276 conv_args = self.converter_arguments
278 # now build a ["string", 1, "string", 2]-styled list, with indices into the
279 # list to avoid lookup of item name for each entry
281 for element in conv_args:
282 if isinstance(element, basestring):
283 lutlist.append((element, None))
284 elif isinstance(element, ListItem):
285 lutlist.append((lut[element.name], element.filternum))
286 elif isinstance(element, ListMacroItem):
287 lutlist.append((element.macrodict[element.macroname], None))
289 raise Exception("neither string, ListItem nor ListMacroItem")
291 # now, for the huge list, do:
293 append = strlist.append
295 for (element, filternum) in lutlist:
299 curitem = str(item[element]).replace('\xc2\x86', '').replace('\xc2\x87', '')
309 append(curitem.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
311 append(escape_xml(curitem))
313 append(curitem.replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
315 append(quote(curitem))
317 from time import localtime
318 time = int(float(curitem)) or 0
320 append("%02d:%02d" % (t.tm_hour, t.tm_min))
322 from time import localtime
323 time = int(float(curitem)) or 0
325 append("%d min" % (time / 60))
328 # (this will be done in c++ later!)
330 return ''.join(strlist)
332 text = property(getText)
334 #===============================================================================
337 # Handles the Content of a Web-Request
338 # It looks up the source, instantiates the Element and Calls the Converter
339 #===============================================================================
340 class webifHandler(ContentHandler):
341 def __init__(self, session, request):
345 self.session = session
347 self.request = request
350 def start_element(self, attrs):
353 wsource = attrs["source"]
355 path = wsource.split('.')
357 scr = self.screen.getRelatedScreen(path[0])
359 print "[webif.py] Parent Screen not found!"
363 source = scr.get(path[0])
365 if isinstance(source, ObsoleteSource):
366 # however, if we found an "obsolete source", issue warning, and resolve the real source.
367 print "WARNING: WEBIF '%s' USES OBSOLETE SOURCE '%s', USE '%s' INSTEAD!" % (name, wsource, source.new_source)
368 print "OBSOLETE SOURCE WILL BE REMOVED %s, PLEASE UPDATE!" % (source.removal_date)
369 if source.description:
370 print source.description
372 wsource = source.new_source
375 # otherwise, use that source.
378 self.source_id = str(attrs.get("id", wsource))
379 self.is_streaming = "streaming" in attrs
380 self.macro_name = attrs.get("macro") or None
382 def end_element(self):
383 # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
384 if not self.is_streaming:
385 if self.macro_name is None:
386 c = OneTimeElement(self.source_id)
388 c = MacroElement(self.source_id, self.macros, self.macro_name)
390 assert self.macro_name is None
391 c = StreamingElement(self.source_id)
393 c.connect(self.source)
395 self.screen.renderer.append(c)
398 def start_convert(self, attrs):
399 ctype = attrs["type"]
401 # TODO: we need something better here
402 if ctype[:4] == "web:": # for now
403 self.converter = eval(ctype[4:])
406 self.converter = my_import('.'.join(("Components", "Converter", ctype))).__dict__.get(ctype)
408 self.converter = my_import('.'.join(("Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype))).__dict__.get(ctype)
411 def end_convert(self):
412 if len(self.sub) == 1:
413 self.sub = self.sub[0]
414 c = self.converter(self.sub)
415 c.connect(self.source)
419 def parse_item(self, attrs):
421 filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4, "urlencode": 5, "time": 6, "minutes": 7}[attrs.get("filter", "")]
422 self.sub.append(ListItem(attrs["name"], filter))
424 assert "macro" in attrs, "e2:item must have a name= or macro= attribute!"
425 self.sub.append(ListMacroItem(self.macros, attrs["macro"]))
427 def startElement(self, name, attrs):
428 if name == "e2:screen":
429 self.screen = eval(attrs["name"])(self.session, self.request) # fixme
430 self.screens.append(self.screen)
433 if name[:3] == "e2:":
436 tag = '<' + name + ''.join([' %s="%s"' % x for x in attrs.items()]) + '>'
437 #tag = tag.encode('utf-8')
441 elif self.mode == 1: # expect "<e2:element>"
442 assert name == "e2:element", "found %s instead of e2:element" % name
443 self.start_element(attrs)
444 elif self.mode == 2: # expect "<e2:convert>"
445 if name[:3] == "e2:":
446 assert name == "e2:convert"
447 self.start_convert(attrs)
451 assert name == "e2:item", "found %s instead of e2:item!" % name
453 self.parse_item(attrs)
455 def endElement(self, name):
456 if name == "e2:screen":
460 tag = "</" + name + ">"
463 elif self.mode == 2 and name[:3] != "e2:":
465 elif self.mode == 2: # closed 'convert' -> sub
467 elif self.mode == 1: # closed 'element'
469 if name[:3] == "e2:":
472 def processingInstruction(self, target, data):
473 self.res.append('<?' + target + ' ' + data + '>')
475 def characters(self, ch):
476 ch = ch.encode('utf-8')
482 def startEntity(self, name):
483 self.res.append('&' + name + ';');
486 for screen in self.screens:
490 print "screen cleanup!"
491 for screen in self.screens:
496 #===============================================================================
499 # Creates the Handler for a Request and calls it
500 # Also ensures that the Handler is finished after the Request is done
501 #===============================================================================
502 def renderPage(request, path, session):
503 # read in the template, create required screens
504 # we don't have persistense yet.
505 # if we had, this first part would only be done once.
506 handler = webifHandler(session, request)
507 parser = make_parser()
508 parser.setFeature(feature_namespaces, 0)
509 parser.setContentHandler(handler)
510 parser.parse(open(util.sibpath(__file__, path)))
512 # by default, we have non-streaming pages
515 # first, apply "commands" (aka. URL argument)
516 for x in handler.res:
517 if isinstance(x, Element):
518 x.handleCommand(request.args)
522 # now, we have a list with static texts mixed
523 # with non-static Elements.
524 # flatten this list, write into the request.
525 for x in handler.res:
526 if isinstance(x, Element):
527 if isinstance(x, StreamingElement):
529 x.setRequest(request)
532 request.write(str(x))
534 # if we met a "StreamingElement", there is at least one
535 # element which wants to output data more than once,
536 # i.e. on host-originated changes.
537 # in this case, don't finish yet, don't cleanup yet,
538 # but instead do that when the client disconnects.
540 requestFinish(handler, request)
543 def requestFinishDeferred(nothing, handler, request):
544 from twisted.internet import reactor
545 reactor.callLater(0, requestFinish, handler, request)
547 d = request.notifyFinish()
549 d.addBoth( requestFinishDeferred, handler, request )
551 #===============================================================================
554 # This has to be/is called at the end of every ScreenPage-based Request
555 #===============================================================================
556 def requestFinish(handler, request):