comparison cagou/core/simple_xhtml.py @ 491:203755bbe0fe

massive refactoring from camelCase -> snake_case. See backend commit log for more details
author Goffi <goffi@goffi.org>
date Sat, 08 Apr 2023 13:44:32 +0200
parents 3c9ba4a694ef
children
comparison
equal deleted inserted replaced
490:962d17c4078c 491:203755bbe0fe
41 41
42 def on_parent(self, instance, parent): 42 def on_parent(self, instance, parent):
43 if parent is not None: 43 if parent is not None:
44 self.font_size = parent.font_size 44 self.font_size = parent.font_size
45 45
46 def _addUrlMarkup(self, text): 46 def _add_url_markup(self, text):
47 text_elts = [] 47 text_elts = []
48 idx = 0 48 idx = 0
49 links = 0 49 links = 0
50 while True: 50 while True:
51 m = sat_strings.RE_URL.search(text[idx:]) 51 m = sat_strings.RE_URL.search(text[idx:])
72 def on_text(self, instance, text): 72 def on_text(self, instance, text):
73 # do NOT call the method if self.markup is set 73 # do NOT call the method if self.markup is set
74 # this would result in infinite loop (because self.text 74 # this would result in infinite loop (because self.text
75 # is changed if an URL is found, and in this case markup too) 75 # is changed if an URL is found, and in this case markup too)
76 if text and not self.markup: 76 if text and not self.markup:
77 self._addUrlMarkup(text) 77 self._add_url_markup(text)
78 78
79 def on_ref_press(self, ref): 79 def on_ref_press(self, ref):
80 url = self.ref_urls[ref] 80 url = self.ref_urls[ref]
81 G.local_platform.open_url(url, self) 81 G.local_platform.open_url(url, self)
82 82
113 self.add_widget(label) 113 self.add_widget(label)
114 else: 114 else:
115 xhtml = ET.fromstring(xhtml.encode()) 115 xhtml = ET.fromstring(xhtml.encode())
116 self.current_wid = None 116 self.current_wid = None
117 self.styles = [] 117 self.styles = []
118 self._callParseMethod(xhtml) 118 self._call_parse_method(xhtml)
119 if len(self.children) > 1: 119 if len(self.children) > 1:
120 self._do_split_labels() 120 self._do_split_labels()
121 121
122 def escape(self, text): 122 def escape(self, text):
123 """mark that a text need to be escaped (i.e. no markup)""" 123 """mark that a text need to be escaped (i.e. no markup)"""
135 styles = [] 135 styles = []
136 tag = False 136 tag = False
137 new_text = [] 137 new_text = []
138 current_tag = [] 138 current_tag = []
139 current_value = [] 139 current_value = []
140 current_wid = self._createText() 140 current_wid = self._create_text()
141 value = False 141 value = False
142 close = False 142 close = False
143 # we will parse the text and create a new widget 143 # we will parse the text and create a new widget
144 # on each new word (actually each space) 144 # on each new word (actually each space)
145 # FIXME: handle '\n' and other white chars 145 # FIXME: handle '\n' and other white chars
182 new_text.append('[/{}]'.format(t)) 182 new_text.append('[/{}]'.format(t))
183 current_wid.text = ''.join(new_text) 183 current_wid.text = ''.join(new_text)
184 new_text = [] 184 new_text = []
185 self.add_widget(current_wid) 185 self.add_widget(current_wid)
186 log.debug("new widget: {}".format(current_wid.text)) 186 log.debug("new widget: {}".format(current_wid.text))
187 current_wid = self._createText() 187 current_wid = self._create_text()
188 for t, v in styles: 188 for t, v in styles:
189 new_text.append('[{tag}{value}]'.format( 189 new_text.append('[{tag}{value}]'.format(
190 tag = t, 190 tag = t,
191 value = '={}'.format(v) if v else '')) 191 value = '={}'.format(v) if v else ''))
192 else: 192 else:
205 self.splitted = True 205 self.splitted = True
206 log.debug("split OK") 206 log.debug("split OK")
207 207
208 # XHTML parsing methods 208 # XHTML parsing methods
209 209
210 def _callParseMethod(self, e): 210 def _call_parse_method(self, e):
211 """Call the suitable method to parse the element 211 """Call the suitable method to parse the element
212 212
213 self.xhtml_[tag] will be called if it exists, else 213 self.xhtml_[tag] will be called if it exists, else
214 self.xhtml_generic will be used 214 self.xhtml_generic will be used
215 @param e(ET.Element): element to parse 215 @param e(ET.Element): element to parse
219 except AttributeError: 219 except AttributeError:
220 log.warning(f"Unhandled XHTML tag: {e.tag}") 220 log.warning(f"Unhandled XHTML tag: {e.tag}")
221 method = self.xhtml_generic 221 method = self.xhtml_generic
222 method(e) 222 method(e)
223 223
224 def _addStyle(self, tag, value=None, append_to_list=True): 224 def _add_style(self, tag, value=None, append_to_list=True):
225 """add a markup style to label 225 """add a markup style to label
226 226
227 @param tag(unicode): markup tag 227 @param tag(unicode): markup tag
228 @param value(unicode): markup value if suitable 228 @param value(unicode): markup value if suitable
229 @param append_to_list(bool): if True style we be added to self.styles 229 @param append_to_list(bool): if True style we be added to self.styles
230 self.styles is needed to keep track of styles to remove 230 self.styles is needed to keep track of styles to remove
231 should most probably be set to True 231 should most probably be set to True
232 """ 232 """
233 label = self._getLabel() 233 label = self._get_label()
234 label.text += '[{tag}{value}]'.format( 234 label.text += '[{tag}{value}]'.format(
235 tag = tag, 235 tag = tag,
236 value = '={}'.format(value) if value else '' 236 value = '={}'.format(value) if value else ''
237 ) 237 )
238 if append_to_list: 238 if append_to_list:
239 self.styles.append((tag, value)) 239 self.styles.append((tag, value))
240 240
241 def _removeStyle(self, tag, remove_from_list=True): 241 def _remove_style(self, tag, remove_from_list=True):
242 """remove a markup style from the label 242 """remove a markup style from the label
243 243
244 @param tag(unicode): markup tag to remove 244 @param tag(unicode): markup tag to remove
245 @param remove_from_list(bool): if True, remove from self.styles too 245 @param remove_from_list(bool): if True, remove from self.styles too
246 should most probably be set to True 246 should most probably be set to True
247 """ 247 """
248 label = self._getLabel() 248 label = self._get_label()
249 label.text += '[/{tag}]'.format( 249 label.text += '[/{tag}]'.format(
250 tag = tag 250 tag = tag
251 ) 251 )
252 if remove_from_list: 252 if remove_from_list:
253 for rev_idx, style in enumerate(reversed(self.styles)): 253 for rev_idx, style in enumerate(reversed(self.styles)):
254 if style[0] == tag: 254 if style[0] == tag:
255 tag_idx = len(self.styles) - 1 - rev_idx 255 tag_idx = len(self.styles) - 1 - rev_idx
256 del self.styles[tag_idx] 256 del self.styles[tag_idx]
257 break 257 break
258 258
259 def _getLabel(self): 259 def _get_label(self):
260 """get current Label if it exists, or create a new one""" 260 """get current Label if it exists, or create a new one"""
261 if not isinstance(self.current_wid, Label): 261 if not isinstance(self.current_wid, Label):
262 self._addLabel() 262 self._add_label()
263 return self.current_wid 263 return self.current_wid
264 264
265 def _addLabel(self): 265 def _add_label(self):
266 """add a new Label 266 """add a new Label
267 267
268 current styles will be closed and reopened if needed 268 current styles will be closed and reopened if needed
269 """ 269 """
270 self._closeLabel() 270 self._close_label()
271 self.current_wid = self._createText() 271 self.current_wid = self._create_text()
272 for tag, value in self.styles: 272 for tag, value in self.styles:
273 self._addStyle(tag, value, append_to_list=False) 273 self._add_style(tag, value, append_to_list=False)
274 self.add_widget(self.current_wid) 274 self.add_widget(self.current_wid)
275 275
276 def _createText(self): 276 def _create_text(self):
277 label = SimpleXHTMLWidgetText(color=self.color, markup=True) 277 label = SimpleXHTMLWidgetText(color=self.color, markup=True)
278 self.bind(color=label.setter('color')) 278 self.bind(color=label.setter('color'))
279 label.bind(texture_size=label.setter('size')) 279 label.bind(texture_size=label.setter('size'))
280 return label 280 return label
281 281
282 def _closeLabel(self): 282 def _close_label(self):
283 """close current style tags in current label 283 """close current style tags in current label
284 284
285 needed when you change label to keep style between 285 needed when you change label to keep style between
286 different widgets 286 different widgets
287 """ 287 """
288 if isinstance(self.current_wid, Label): 288 if isinstance(self.current_wid, Label):
289 for tag, value in reversed(self.styles): 289 for tag, value in reversed(self.styles):
290 self._removeStyle(tag, remove_from_list=False) 290 self._remove_style(tag, remove_from_list=False)
291 291
292 def _parseCSS(self, e): 292 def _parse_css(self, e):
293 """parse CSS found in "style" attribute of element 293 """parse CSS found in "style" attribute of element
294 294
295 self._css_styles will be created and contained markup styles added by this method 295 self._css_styles will be created and contained markup styles added by this method
296 @param e(ET.Element): element which may have a "style" attribute 296 @param e(ET.Element): element which may have a "style" attribute
297 """ 297 """
311 log.warning(f"Unhandled CSS: {prop}") 311 log.warning(f"Unhandled CSS: {prop}")
312 else: 312 else:
313 method(e, value) 313 method(e, value)
314 self._css_styles = self.styles[styles_limit:] 314 self._css_styles = self.styles[styles_limit:]
315 315
316 def _closeCSS(self): 316 def _close_css(self):
317 """removed CSS styles 317 """removed CSS styles
318 318
319 styles in self._css_styles will be removed 319 styles in self._css_styles will be removed
320 and the attribute will be deleted 320 and the attribute will be deleted
321 """ 321 """
322 for tag, __ in reversed(self._css_styles): 322 for tag, __ in reversed(self._css_styles):
323 self._removeStyle(tag) 323 self._remove_style(tag)
324 del self._css_styles 324 del self._css_styles
325 325
326 def xhtml_generic(self, elem, style=True, markup=None): 326 def xhtml_generic(self, elem, style=True, markup=None):
327 """Generic method for adding HTML elements 327 """Generic method for adding HTML elements
328 328
335 if markup is not None: 335 if markup is not None:
336 if isinstance(markup, str): 336 if isinstance(markup, str):
337 tag, value = markup, None 337 tag, value = markup, None
338 else: 338 else:
339 tag, value = markup 339 tag, value = markup
340 self._addStyle(tag, value) 340 self._add_style(tag, value)
341 style_ = 'style' in elem.attrib and style 341 style_ = 'style' in elem.attrib and style
342 if style_: 342 if style_:
343 self._parseCSS(elem) 343 self._parse_css(elem)
344 344
345 # then content 345 # then content
346 if elem.text: 346 if elem.text:
347 self._getLabel().text += escape_markup(elem.text) 347 self._get_label().text += escape_markup(elem.text)
348 348
349 # we parse the children 349 # we parse the children
350 for child in elem: 350 for child in elem:
351 self._callParseMethod(child) 351 self._call_parse_method(child)
352 352
353 # closing CSS style and markup 353 # closing CSS style and markup
354 if style_: 354 if style_:
355 self._closeCSS() 355 self._close_css()
356 if markup is not None: 356 if markup is not None:
357 self._removeStyle(tag) 357 self._remove_style(tag)
358 358
359 # and the tail, which is regular text 359 # and the tail, which is regular text
360 if elem.tail: 360 if elem.tail:
361 self._getLabel().text += escape_markup(elem.tail) 361 self._get_label().text += escape_markup(elem.tail)
362 362
363 # method handling XHTML elements 363 # method handling XHTML elements
364 364
365 def xhtml_br(self, elem): 365 def xhtml_br(self, elem):
366 label = self._getLabel() 366 label = self._get_label()
367 label.text+='\n' 367 label.text+='\n'
368 self.xhtml_generic(elem, style=False) 368 self.xhtml_generic(elem, style=False)
369 369
370 def xhtml_em(self, elem): 370 def xhtml_em(self, elem):
371 self.xhtml_generic(elem, markup='i') 371 self.xhtml_generic(elem, markup='i')
404 self.xhtml_generic(elem, markup='b') 404 self.xhtml_generic(elem, markup='b')
405 405
406 # methods handling CSS properties 406 # methods handling CSS properties
407 407
408 def css_color(self, elem, value): 408 def css_color(self, elem, value):
409 self._addStyle("color", css_color.parse(value)) 409 self._add_style("color", css_color.parse(value))
410 410
411 def css_text_decoration(self, elem, value): 411 def css_text_decoration(self, elem, value):
412 if value == 'underline': 412 if value == 'underline':
413 self._addStyle('u') 413 self._add_style('u')
414 elif value == 'line-through': 414 elif value == 'line-through':
415 self._addStyle('s') 415 self._add_style('s')
416 else: 416 else:
417 log.warning("unhandled text decoration: {}".format(value)) 417 log.warning("unhandled text decoration: {}".format(value))