comparison cagou/core/simple_xhtml.py @ 312:772c170b47a9

Python3 port: /!\ Cagou now runs with Python 3.6+ Port has been done in the same way as for backend (check backend commit b2d067339de3 message for details).
author Goffi <goffi@goffi.org>
date Tue, 13 Aug 2019 19:14:22 +0200
parents 86b1cd8121dd
children 5868a5575e01
comparison
equal deleted inserted replaced
311:a0d978d3ce84 312:772c170b47a9
29 from sat_frontends.tools import css_color, strings as sat_strings 29 from sat_frontends.tools import css_color, strings as sat_strings
30 from cagou.core.image import AsyncImage 30 from cagou.core.image import AsyncImage
31 import webbrowser 31 import webbrowser
32 32
33 33
34 class Escape(unicode): 34 class Escape(str):
35 """Class used to mark that a message need to be escaped""" 35 """Class used to mark that a message need to be escaped"""
36
37 def __init__(self, text):
38 super(Escape, self).__init__(text)
39 36
40 37
41 class SimpleXHTMLWidgetEscapedText(Label): 38 class SimpleXHTMLWidgetEscapedText(Label):
42 39
43 def on_parent(self, instance, parent): 40 def on_parent(self, instance, parent):
50 links = 0 47 links = 0
51 while True: 48 while True:
52 m = sat_strings.RE_URL.search(text[idx:]) 49 m = sat_strings.RE_URL.search(text[idx:])
53 if m is not None: 50 if m is not None:
54 text_elts.append(escape_markup(m.string[0:m.start()])) 51 text_elts.append(escape_markup(m.string[0:m.start()]))
55 link_key = u'link_' + unicode(links) 52 link_key = 'link_' + str(links)
56 url = m.group() 53 url = m.group()
57 text_elts.append(u'[color=5500ff][ref={link}]{url}[/ref][/color]'.format( 54 text_elts.append('[color=5500ff][ref={link}]{url}[/ref][/color]'.format(
58 link = link_key, 55 link = link_key,
59 url = url 56 url = url
60 )) 57 ))
61 if not links: 58 if not links:
62 self.ref_urls = {link_key: url} 59 self.ref_urls = {link_key: url}
66 idx += m.end() 63 idx += m.end()
67 else: 64 else:
68 if links: 65 if links:
69 text_elts.append(escape_markup(text[idx:])) 66 text_elts.append(escape_markup(text[idx:]))
70 self.markup = True 67 self.markup = True
71 self.text = u''.join(text_elts) 68 self.text = ''.join(text_elts)
72 break 69 break
73 70
74 def on_text(self, instance, text): 71 def on_text(self, instance, text):
75 # do NOT call the method if self.markup is set 72 # do NOT call the method if self.markup is set
76 # this would result in infinite loop (because self.text 73 # this would result in infinite loop (because self.text
105 """ 102 """
106 parent = self.parent 103 parent = self.parent
107 while parent and not isinstance(parent, SimpleXHTMLWidget): 104 while parent and not isinstance(parent, SimpleXHTMLWidget):
108 parent = parent.parent 105 parent = parent.parent
109 if parent is None: 106 if parent is None:
110 log.error(u"no SimpleXHTMLWidget parent found") 107 log.error("no SimpleXHTMLWidget parent found")
111 return parent 108 return parent
112 109
113 def _on_source_load(self, value): 110 def _on_source_load(self, value):
114 # this method is called when image is loaded 111 # this method is called when image is loaded
115 super(SimpleXHTMLWidgetImage, self)._on_source_load(value) 112 super(SimpleXHTMLWidgetImage, self)._on_source_load(value)
206 def _do_complexe_sizing(self, width): 203 def _do_complexe_sizing(self, width):
207 try: 204 try:
208 self.splitted 205 self.splitted
209 except AttributeError: 206 except AttributeError:
210 # XXX: to make things easier, we split labels in words 207 # XXX: to make things easier, we split labels in words
211 log.debug(u"split start") 208 log.debug("split start")
212 children = self.children[::-1] 209 children = self.children[::-1]
213 self.clear_widgets() 210 self.clear_widgets()
214 for child in children: 211 for child in children:
215 if isinstance(child, Label): 212 if isinstance(child, Label):
216 log.debug(u"label before split: {}".format(child.text)) 213 log.debug("label before split: {}".format(child.text))
217 styles = [] 214 styles = []
218 tag = False 215 tag = False
219 new_text = [] 216 new_text = []
220 current_tag = [] 217 current_tag = []
221 current_value = [] 218 current_value = []
226 # on each new word (actually each space) 223 # on each new word (actually each space)
227 # FIXME: handle '\n' and other white chars 224 # FIXME: handle '\n' and other white chars
228 for c in child.text: 225 for c in child.text:
229 if tag: 226 if tag:
230 # we are parsing a markup tag 227 # we are parsing a markup tag
231 if c == u']': 228 if c == ']':
232 current_tag_s = u''.join(current_tag) 229 current_tag_s = ''.join(current_tag)
233 current_style = (current_tag_s, u''.join(current_value)) 230 current_style = (current_tag_s, ''.join(current_value))
234 if close: 231 if close:
235 for idx, s in enumerate(reversed(styles)): 232 for idx, s in enumerate(reversed(styles)):
236 if s[0] == current_tag_s: 233 if s[0] == current_tag_s:
237 del styles[len(styles) - idx - 1] 234 del styles[len(styles) - idx - 1]
238 break 235 break
241 current_tag = [] 238 current_tag = []
242 current_value = [] 239 current_value = []
243 tag = False 240 tag = False
244 value = False 241 value = False
245 close = False 242 close = False
246 elif c == u'/': 243 elif c == '/':
247 close = True 244 close = True
248 elif c == u'=': 245 elif c == '=':
249 value = True 246 value = True
250 elif value: 247 elif value:
251 current_value.append(c) 248 current_value.append(c)
252 else: 249 else:
253 current_tag.append(c) 250 current_tag.append(c)
254 new_text.append(c) 251 new_text.append(c)
255 else: 252 else:
256 # we are parsing regular text 253 # we are parsing regular text
257 if c == u'[': 254 if c == '[':
258 new_text.append(c) 255 new_text.append(c)
259 tag = True 256 tag = True
260 elif c == u' ': 257 elif c == ' ':
261 # new word, we do a new widget 258 # new word, we do a new widget
262 new_text.append(u' ') 259 new_text.append(' ')
263 for t, v in reversed(styles): 260 for t, v in reversed(styles):
264 new_text.append(u'[/{}]'.format(t)) 261 new_text.append('[/{}]'.format(t))
265 current_wid.text = u''.join(new_text) 262 current_wid.text = ''.join(new_text)
266 new_text = [] 263 new_text = []
267 self.add_widget(current_wid) 264 self.add_widget(current_wid)
268 log.debug(u"new widget: {}".format(current_wid.text)) 265 log.debug("new widget: {}".format(current_wid.text))
269 current_wid = self._createText() 266 current_wid = self._createText()
270 for t, v in styles: 267 for t, v in styles:
271 new_text.append(u'[{tag}{value}]'.format( 268 new_text.append('[{tag}{value}]'.format(
272 tag = t, 269 tag = t,
273 value = u'={}'.format(v) if v else u'')) 270 value = '={}'.format(v) if v else ''))
274 else: 271 else:
275 new_text.append(c) 272 new_text.append(c)
276 if current_wid.text: 273 if current_wid.text:
277 # we may have a remaining widget after the parsing 274 # we may have a remaining widget after the parsing
278 close_styles = [] 275 close_styles = []
279 for t, v in reversed(styles): 276 for t, v in reversed(styles):
280 close_styles.append(u'[/{}]'.format(t)) 277 close_styles.append('[/{}]'.format(t))
281 current_wid.text = u''.join(close_styles) 278 current_wid.text = ''.join(close_styles)
282 self.add_widget(current_wid) 279 self.add_widget(current_wid)
283 log.debug(u"new widget: {}".format(current_wid.text)) 280 log.debug("new widget: {}".format(current_wid.text))
284 else: 281 else:
285 # non Label widgets, we just add them 282 # non Label widgets, we just add them
286 self.add_widget(child) 283 self.add_widget(child)
287 self.splitted = True 284 self.splitted = True
288 log.debug(u"split OK") 285 log.debug("split OK")
289 286
290 # we now set the content width 287 # we now set the content width
291 # FIXME: for now we just use the full width 288 # FIXME: for now we just use the full width
292 self.content_width = width 289 self.content_width = width
293 290
301 @param e(ET.Element): element to parse 298 @param e(ET.Element): element to parse
302 """ 299 """
303 try: 300 try:
304 method = getattr(self, "xhtml_{}".format(e.tag)) 301 method = getattr(self, "xhtml_{}".format(e.tag))
305 except AttributeError: 302 except AttributeError:
306 log.warning(u"Unhandled XHTML tag: {}".format(e.tag)) 303 log.warning("Unhandled XHTML tag: {}".format(e.tag))
307 method = self.xhtml_generic 304 method = self.xhtml_generic
308 method(e) 305 method(e)
309 306
310 def _addStyle(self, tag, value=None, append_to_list=True): 307 def _addStyle(self, tag, value=None, append_to_list=True):
311 """add a markup style to label 308 """add a markup style to label
315 @param append_to_list(bool): if True style we be added to self.styles 312 @param append_to_list(bool): if True style we be added to self.styles
316 self.styles is needed to keep track of styles to remove 313 self.styles is needed to keep track of styles to remove
317 should most probably be set to True 314 should most probably be set to True
318 """ 315 """
319 label = self._getLabel() 316 label = self._getLabel()
320 label.text += u'[{tag}{value}]'.format( 317 label.text += '[{tag}{value}]'.format(
321 tag = tag, 318 tag = tag,
322 value = u'={}'.format(value) if value else '' 319 value = '={}'.format(value) if value else ''
323 ) 320 )
324 if append_to_list: 321 if append_to_list:
325 self.styles.append((tag, value)) 322 self.styles.append((tag, value))
326 323
327 def _removeStyle(self, tag, remove_from_list=True): 324 def _removeStyle(self, tag, remove_from_list=True):
330 @param tag(unicode): markup tag to remove 327 @param tag(unicode): markup tag to remove
331 @param remove_from_list(bool): if True, remove from self.styles too 328 @param remove_from_list(bool): if True, remove from self.styles too
332 should most probably be set to True 329 should most probably be set to True
333 """ 330 """
334 label = self._getLabel() 331 label = self._getLabel()
335 label.text += u'[/{tag}]'.format( 332 label.text += '[/{tag}]'.format(
336 tag = tag 333 tag = tag
337 ) 334 )
338 if remove_from_list: 335 if remove_from_list:
339 for rev_idx, style in enumerate(reversed(self.styles)): 336 for rev_idx, style in enumerate(reversed(self.styles)):
340 if style[0] == tag: 337 if style[0] == tag:
380 377
381 self._css_styles will be created and contained markup styles added by this method 378 self._css_styles will be created and contained markup styles added by this method
382 @param e(ET.Element): element which may have a "style" attribute 379 @param e(ET.Element): element which may have a "style" attribute
383 """ 380 """
384 styles_limit = len(self.styles) 381 styles_limit = len(self.styles)
385 styles = e.attrib['style'].split(u';') 382 styles = e.attrib['style'].split(';')
386 for style in styles: 383 for style in styles:
387 try: 384 try:
388 prop, value = style.split(u':') 385 prop, value = style.split(':')
389 except ValueError: 386 except ValueError:
390 log.warning(u"can't parse style: {}".format(style)) 387 log.warning("can't parse style: {}".format(style))
391 continue 388 continue
392 prop = prop.strip().replace(u'-', '_') 389 prop = prop.strip().replace('-', '_')
393 value = value.strip() 390 value = value.strip()
394 try: 391 try:
395 method = getattr(self, "css_{}".format(prop)) 392 method = getattr(self, "css_{}".format(prop))
396 except AttributeError: 393 except AttributeError:
397 log.warning(u"Unhandled CSS: {}".format(prop)) 394 log.warning("Unhandled CSS: {}".format(prop))
398 else: 395 else:
399 method(e, value) 396 method(e, value)
400 self._css_styles = self.styles[styles_limit:] 397 self._css_styles = self.styles[styles_limit:]
401 398
402 def _closeCSS(self): 399 def _closeCSS(self):
417 @param style(bool): if True handle style attribute (CSS) 414 @param style(bool): if True handle style attribute (CSS)
418 @param markup(tuple[unicode, (unicode, None)], None): kivy markup to use 415 @param markup(tuple[unicode, (unicode, None)], None): kivy markup to use
419 """ 416 """
420 # we first add markup and CSS style 417 # we first add markup and CSS style
421 if markup is not None: 418 if markup is not None:
422 if isinstance(markup, basestring): 419 if isinstance(markup, str):
423 tag, value = markup, None 420 tag, value = markup, None
424 else: 421 else:
425 tag, value = markup 422 tag, value = markup
426 self._addStyle(tag, value) 423 self._addStyle(tag, value)
427 style_ = 'style' in elem.attrib and style 424 style_ = 'style' in elem.attrib and style
458 455
459 def xhtml_img(self, elem): 456 def xhtml_img(self, elem):
460 try: 457 try:
461 src = elem.attrib['src'] 458 src = elem.attrib['src']
462 except KeyError: 459 except KeyError:
463 log.warning(u"<img> element without src: {}".format(ET.tostring(elem))) 460 log.warning("<img> element without src: {}".format(ET.tostring(elem)))
464 return 461 return
465 try: 462 try:
466 target_height = int(elem.get(u'height', 0)) 463 target_height = int(elem.get('height', 0))
467 except ValueError: 464 except ValueError:
468 log.warning(u"Can't parse image height: {}".format(elem.get(u'height'))) 465 log.warning("Can't parse image height: {}".format(elem.get('height')))
469 target_height = 0 466 target_height = 0
470 try: 467 try:
471 target_width = int(elem.get(u'width', 0)) 468 target_width = int(elem.get('width', 0))
472 except ValueError: 469 except ValueError:
473 log.warning(u"Can't parse image width: {}".format(elem.get(u'width'))) 470 log.warning("Can't parse image width: {}".format(elem.get('width')))
474 target_width = 0 471 target_width = 0
475 472
476 img = SimpleXHTMLWidgetImage(source=src, target_height=target_height, target_width=target_width) 473 img = SimpleXHTMLWidgetImage(source=src, target_height=target_height, target_width=target_width)
477 self.current_wid = img 474 self.current_wid = img
478 self.add_widget(img) 475 self.add_widget(img)
489 self.xhtml_generic(elem, markup='b') 486 self.xhtml_generic(elem, markup='b')
490 487
491 # methods handling CSS properties 488 # methods handling CSS properties
492 489
493 def css_color(self, elem, value): 490 def css_color(self, elem, value):
494 self._addStyle(u"color", css_color.parse(value)) 491 self._addStyle("color", css_color.parse(value))
495 492
496 def css_text_decoration(self, elem, value): 493 def css_text_decoration(self, elem, value):
497 if value == u'underline': 494 if value == 'underline':
498 self._addStyle('u') 495 self._addStyle('u')
499 elif value == u'line-through': 496 elif value == 'line-through':
500 self._addStyle('s') 497 self._addStyle('s')
501 else: 498 else:
502 log.warning(u"unhandled text decoration: {}".format(value)) 499 log.warning("unhandled text decoration: {}".format(value))