Mercurial > libervia-desktop-kivy
annotate cagou/core/simple_xhtml.py @ 292:55e194055aa5
misc: added configuration files to compile Cagou with python-for-android
author | Goffi <goffi@goffi.org> |
---|---|
date | Sun, 05 May 2019 21:34:02 +0200 |
parents | 44752e8031f8 |
children | 86b1cd8121dd |
rev | line source |
---|---|
22 | 1 #!/usr/bin/python |
2 # -*- coding: utf-8 -*- | |
3 | |
4 # Cagou: desktop/mobile frontend for Salut à Toi XMPP client | |
282 | 5 # Copyright (C) 2016-2019 Jérôme Poisson (goffi@goffi.org) |
22 | 6 |
7 # This program is free software: you can redistribute it and/or modify | |
8 # it under the terms of the GNU Affero General Public License as published by | |
9 # the Free Software Foundation, either version 3 of the License, or | |
10 # (at your option) any later version. | |
11 | |
12 # This program is distributed in the hope that it will be useful, | |
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 # GNU Affero General Public License for more details. | |
16 | |
17 # You should have received a copy of the GNU Affero General Public License | |
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 | |
20 | |
21 from sat.core import log as logging | |
22 log = logging.getLogger(__name__) | |
57 | 23 from kivy.uix.stacklayout import StackLayout |
24 from kivy.uix.label import Label | |
25 from kivy.utils import escape_markup | |
185
ab3f5173ef5c
chat, simple XHTML: font size adjustement
Goffi <goffi@goffi.org>
parents:
126
diff
changeset
|
26 from kivy.metrics import sp |
22 | 27 from kivy import properties |
57 | 28 from xml.etree import ElementTree as ET |
106
9909ed7a7a20
moved SimpleXHTMLWidget to a dedicated module
Goffi <goffi@goffi.org>
parents:
105
diff
changeset
|
29 from sat_frontends.tools import css_color, strings as sat_strings |
9909ed7a7a20
moved SimpleXHTMLWidget to a dedicated module
Goffi <goffi@goffi.org>
parents:
105
diff
changeset
|
30 from cagou.core.image import AsyncImage |
100
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
31 import webbrowser |
22 | 32 |
33 | |
57 | 34 class Escape(unicode): |
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 | |
40 | |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
41 class SimpleXHTMLWidgetEscapedText(Label): |
100
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
42 |
185
ab3f5173ef5c
chat, simple XHTML: font size adjustement
Goffi <goffi@goffi.org>
parents:
126
diff
changeset
|
43 def on_parent(self, instance, parent): |
288
44752e8031f8
simple XHTML: fixed crash when parent is set to None + fixed bold restoration when escaped message is modified
Goffi <goffi@goffi.org>
parents:
284
diff
changeset
|
44 if parent is not None: |
44752e8031f8
simple XHTML: fixed crash when parent is set to None + fixed bold restoration when escaped message is modified
Goffi <goffi@goffi.org>
parents:
284
diff
changeset
|
45 self.font_size = parent.font_size |
185
ab3f5173ef5c
chat, simple XHTML: font size adjustement
Goffi <goffi@goffi.org>
parents:
126
diff
changeset
|
46 |
100
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
47 def _addUrlMarkup(self, text): |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
48 text_elts = [] |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
49 idx = 0 |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
50 links = 0 |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
51 while True: |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
52 m = sat_strings.RE_URL.search(text[idx:]) |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
53 if m is not None: |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
54 text_elts.append(escape_markup(m.string[0:m.start()])) |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
55 link_key = u'link_' + unicode(links) |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
56 url = m.group() |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
57 text_elts.append(u'[color=5500ff][ref={link}]{url}[/ref][/color]'.format( |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
58 link = link_key, |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
59 url = url |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
60 )) |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
61 if not links: |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
62 self.ref_urls = {link_key: url} |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
63 else: |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
64 self.ref_urls[link_key] = url |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
65 links += 1 |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
66 idx += m.end() |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
67 else: |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
68 if links: |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
69 text_elts.append(escape_markup(text[idx:])) |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
70 self.markup = True |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
71 self.text = u''.join(text_elts) |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
72 break |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
73 |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
74 def on_text(self, instance, text): |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
75 # do NOT call the method if self.markup is set |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
76 # this would result in infinite loop (because self.text |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
77 # is changed if an URL is found, and in this case markup too) |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
78 if text and not self.markup: |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
79 self._addUrlMarkup(text) |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
80 |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
81 def on_ref_press(self, ref): |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
82 url = self.ref_urls[ref] |
d7447c585603
chat: added url detection on text messages
Goffi <goffi@goffi.org>
parents:
98
diff
changeset
|
83 webbrowser.open(url) |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
84 |
59 | 85 |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
86 class SimpleXHTMLWidgetText(Label): |
185
ab3f5173ef5c
chat, simple XHTML: font size adjustement
Goffi <goffi@goffi.org>
parents:
126
diff
changeset
|
87 |
ab3f5173ef5c
chat, simple XHTML: font size adjustement
Goffi <goffi@goffi.org>
parents:
126
diff
changeset
|
88 def on_parent(self, instance, parent): |
ab3f5173ef5c
chat, simple XHTML: font size adjustement
Goffi <goffi@goffi.org>
parents:
126
diff
changeset
|
89 self.font_size = parent.font_size |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
90 |
59 | 91 |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
92 class SimpleXHTMLWidgetImage(AsyncImage): |
59 | 93 # following properties are desired height/width |
94 # i.e. the ones specified in height/width attributes of <img> | |
95 # (or wanted for whatever reason) | |
96 # set to 0 to ignore them | |
97 target_height = properties.NumericProperty() | |
98 target_width = properties.NumericProperty() | |
99 | |
100 def _get_parent_container(self): | |
101 """get parent SimpleXHTMLWidget instance | |
102 | |
103 @param warning(bool): if True display a log.error if nothing found | |
104 @return (SimpleXHTMLWidget, None): found SimpleXHTMLWidget instance | |
105 """ | |
106 parent = self.parent | |
107 while parent and not isinstance(parent, SimpleXHTMLWidget): | |
108 parent = parent.parent | |
109 if parent is None: | |
110 log.error(u"no SimpleXHTMLWidget parent found") | |
111 return parent | |
112 | |
113 def _on_source_load(self, value): | |
114 # this method is called when image is loaded | |
115 super(SimpleXHTMLWidgetImage, self)._on_source_load(value) | |
116 if self.parent is not None: | |
117 container = self._get_parent_container() | |
118 # image is loaded, we need to recalculate size | |
119 self.on_container_width(container, container.width) | |
120 | |
121 def on_container_width(self, container, container_width): | |
122 """adapt size according to container width | |
123 | |
124 called when parent container (SimpleXHTMLWidget) width change | |
125 """ | |
126 target_size = (self.target_width or self.texture.width, self.target_height or self.texture.height) | |
127 padding = container.padding | |
128 padding_h = (padding[0] + padding[2]) if len(padding) == 4 else padding[0] | |
129 width = container_width - padding_h | |
130 if target_size[0] < width: | |
131 self.size = target_size | |
132 else: | |
133 height = width / self.image_ratio | |
134 self.size = (width, height) | |
135 | |
136 def on_parent(self, instance, parent): | |
137 if parent is not None: | |
138 container = self._get_parent_container() | |
139 container.bind(width=self.on_container_width) | |
140 | |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
141 |
57 | 142 class SimpleXHTMLWidget(StackLayout): |
143 """widget handling simple XHTML parsing""" | |
144 xhtml = properties.StringProperty() | |
145 color = properties.ListProperty([1, 1, 1, 1]) | |
146 # XXX: bold is only used for escaped text | |
147 bold = properties.BooleanProperty(False) | |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
148 content_width = properties.NumericProperty(0) |
185
ab3f5173ef5c
chat, simple XHTML: font size adjustement
Goffi <goffi@goffi.org>
parents:
126
diff
changeset
|
149 font_size = properties.NumericProperty(sp(14)) |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
150 |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
151 # text/XHTML input |
57 | 152 |
153 def on_xhtml(self, instance, xhtml): | |
154 """parse xhtml and set content accordingly | |
155 | |
288
44752e8031f8
simple XHTML: fixed crash when parent is set to None + fixed bold restoration when escaped message is modified
Goffi <goffi@goffi.org>
parents:
284
diff
changeset
|
156 if xhtml is an instance of Escape, a Label with no markup will be used |
57 | 157 """ |
158 self.clear_widgets() | |
159 if isinstance(xhtml, Escape): | |
288
44752e8031f8
simple XHTML: fixed crash when parent is set to None + fixed bold restoration when escaped message is modified
Goffi <goffi@goffi.org>
parents:
284
diff
changeset
|
160 label = SimpleXHTMLWidgetEscapedText( |
44752e8031f8
simple XHTML: fixed crash when parent is set to None + fixed bold restoration when escaped message is modified
Goffi <goffi@goffi.org>
parents:
284
diff
changeset
|
161 text=xhtml, color=self.color, bold=self.bold) |
57 | 162 self.bind(color=label.setter('color')) |
163 self.bind(bold=label.setter('bold')) | |
164 self.add_widget(label) | |
165 else: | |
166 xhtml = ET.fromstring(xhtml.encode('utf-8')) | |
167 self.current_wid = None | |
168 self.styles = [] | |
169 self._callParseMethod(xhtml) | |
170 | |
171 def escape(self, text): | |
172 """mark that a text need to be escaped (i.e. no markup)""" | |
173 return Escape(text) | |
174 | |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
175 # sizing |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
176 |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
177 def on_width(self, instance, width): |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
178 if len(self.children) == 1: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
179 wid = self.children[0] |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
180 if isinstance(wid, Label): |
68 | 181 # we have simple text |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
182 try: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
183 full_width = wid._full_width |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
184 except AttributeError: |
68 | 185 # on first time, we need the required size |
186 # for the full text, without width limit | |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
187 wid.size_hint = (None, None) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
188 wid.texture_update() |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
189 full_width = wid._full_width = wid.texture_size[0] |
68 | 190 |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
191 if full_width > width: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
192 wid.text_size = width, None |
68 | 193 wid.width = width |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
194 else: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
195 wid.text_size = None, None |
68 | 196 wid.texture_update() |
197 wid.width = wid.texture_size[0] | |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
198 self.content_width = wid.width + self.padding[0] + self.padding[2] |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
199 else: |
102
20251d926cc2
chat: fixed bad size_hint setting on XHTML
Goffi <goffi@goffi.org>
parents:
100
diff
changeset
|
200 wid.size_hint = (1, None) |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
201 wid.height = 100 |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
202 self.content_width = self.width |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
203 else: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
204 self._do_complexe_sizing(width) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
205 |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
206 def _do_complexe_sizing(self, width): |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
207 try: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
208 self.splitted |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
209 except AttributeError: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
210 # XXX: to make things easier, we split labels in words |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
211 log.debug(u"split start") |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
212 children = self.children[::-1] |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
213 self.clear_widgets() |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
214 for child in children: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
215 if isinstance(child, Label): |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
216 log.debug(u"label before split: {}".format(child.text)) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
217 styles = [] |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
218 tag = False |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
219 new_text = [] |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
220 current_tag = [] |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
221 current_value = [] |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
222 current_wid = self._createText() |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
223 value = False |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
224 close = False |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
225 # we will parse the text and create a new widget |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
226 # on each new word (actually each space) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
227 # FIXME: handle '\n' and other white chars |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
228 for c in child.text: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
229 if tag: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
230 # we are parsing a markup tag |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
231 if c == u']': |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
232 current_tag_s = u''.join(current_tag) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
233 current_style = (current_tag_s, u''.join(current_value)) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
234 if close: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
235 for idx, s in enumerate(reversed(styles)): |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
236 if s[0] == current_tag_s: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
237 del styles[len(styles) - idx - 1] |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
238 break |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
239 else: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
240 styles.append(current_style) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
241 current_tag = [] |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
242 current_value = [] |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
243 tag = False |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
244 value = False |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
245 close = False |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
246 elif c == u'/': |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
247 close = True |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
248 elif c == u'=': |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
249 value = True |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
250 elif value: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
251 current_value.append(c) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
252 else: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
253 current_tag.append(c) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
254 new_text.append(c) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
255 else: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
256 # we are parsing regular text |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
257 if c == u'[': |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
258 new_text.append(c) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
259 tag = True |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
260 elif c == u' ': |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
261 # new word, we do a new widget |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
262 new_text.append(u' ') |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
263 for t, v in reversed(styles): |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
264 new_text.append(u'[/{}]'.format(t)) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
265 current_wid.text = u''.join(new_text) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
266 new_text = [] |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
267 self.add_widget(current_wid) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
268 log.debug(u"new widget: {}".format(current_wid.text)) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
269 current_wid = self._createText() |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
270 for t, v in styles: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
271 new_text.append(u'[{tag}{value}]'.format( |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
272 tag = t, |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
273 value = u'={}'.format(v) if v else u'')) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
274 else: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
275 new_text.append(c) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
276 if current_wid.text: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
277 # we may have a remaining widget after the parsing |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
278 close_styles = [] |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
279 for t, v in reversed(styles): |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
280 close_styles.append(u'[/{}]'.format(t)) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
281 current_wid.text = u''.join(close_styles) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
282 self.add_widget(current_wid) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
283 log.debug(u"new widget: {}".format(current_wid.text)) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
284 else: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
285 # non Label widgets, we just add them |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
286 self.add_widget(child) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
287 self.splitted = True |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
288 log.debug(u"split OK") |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
289 |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
290 # we now set the content width |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
291 # FIXME: for now we just use the full width |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
292 self.content_width = width |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
293 |
57 | 294 # XHTML parsing methods |
295 | |
296 def _callParseMethod(self, e): | |
297 """call the suitable method to parse the element | |
298 | |
299 self.xhtml_[tag] will be called if it exists, else | |
300 self.xhtml_generic will be used | |
301 @param e(ET.Element): element to parse | |
302 """ | |
303 try: | |
304 method = getattr(self, "xhtml_{}".format(e.tag)) | |
305 except AttributeError: | |
306 log.warning(u"Unhandled XHTML tag: {}".format(e.tag)) | |
307 method = self.xhtml_generic | |
308 method(e) | |
309 | |
310 def _addStyle(self, tag, value=None, append_to_list=True): | |
311 """add a markup style to label | |
312 | |
313 @param tag(unicode): markup tag | |
314 @param value(unicode): markup value if suitable | |
315 @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 | |
317 should most probably be set to True | |
318 """ | |
319 label = self._getLabel() | |
320 label.text += u'[{tag}{value}]'.format( | |
321 tag = tag, | |
322 value = u'={}'.format(value) if value else '' | |
323 ) | |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
324 if append_to_list: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
325 self.styles.append((tag, value)) |
57 | 326 |
327 def _removeStyle(self, tag, remove_from_list=True): | |
328 """remove a markup style from the label | |
329 | |
330 @param tag(unicode): markup tag to remove | |
331 @param remove_from_list(bool): if True, remove from self.styles too | |
332 should most probably be set to True | |
333 """ | |
334 label = self._getLabel() | |
335 label.text += u'[/{tag}]'.format( | |
336 tag = tag | |
337 ) | |
338 if remove_from_list: | |
339 for rev_idx, style in enumerate(reversed(self.styles)): | |
340 if style[0] == tag: | |
341 tag_idx = len(self.styles) - 1 - rev_idx | |
342 del self.styles[tag_idx] | |
343 break | |
344 | |
345 def _getLabel(self): | |
346 """get current Label if it exists, or create a new one""" | |
347 if not isinstance(self.current_wid, Label): | |
348 self._addLabel() | |
349 return self.current_wid | |
350 | |
351 def _addLabel(self): | |
352 """add a new Label | |
353 | |
354 current styles will be closed and reopened if needed | |
355 """ | |
356 self._closeLabel() | |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
357 self.current_wid = self._createText() |
57 | 358 for tag, value in self.styles: |
359 self._addStyle(tag, value, append_to_list=False) | |
360 self.add_widget(self.current_wid) | |
361 | |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
362 def _createText(self): |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
363 label = SimpleXHTMLWidgetText(color=self.color, markup=True) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
364 self.bind(color=label.setter('color')) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
365 label.bind(texture_size=label.setter('size')) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
366 return label |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
367 |
57 | 368 def _closeLabel(self): |
369 """close current style tags in current label | |
370 | |
371 needed when you change label to keep style between | |
372 different widgets | |
373 """ | |
374 if isinstance(self.current_wid, Label): | |
375 for tag, value in reversed(self.styles): | |
376 self._removeStyle(tag, remove_from_list=False) | |
377 | |
378 def _parseCSS(self, e): | |
379 """parse CSS found in "style" attribute of element | |
380 | |
381 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 | |
383 """ | |
384 styles_limit = len(self.styles) | |
385 styles = e.attrib['style'].split(u';') | |
386 for style in styles: | |
387 try: | |
388 prop, value = style.split(u':') | |
389 except ValueError: | |
390 log.warning(u"can't parse style: {}".format(style)) | |
391 continue | |
392 prop = prop.strip().replace(u'-', '_') | |
393 value = value.strip() | |
394 try: | |
395 method = getattr(self, "css_{}".format(prop)) | |
396 except AttributeError: | |
397 log.warning(u"Unhandled CSS: {}".format(prop)) | |
398 else: | |
399 method(e, value) | |
400 self._css_styles = self.styles[styles_limit:] | |
401 | |
402 def _closeCSS(self): | |
403 """removed CSS styles | |
404 | |
405 styles in self._css_styles will be removed | |
406 and the attribute will be deleted | |
407 """ | |
284 | 408 for tag, __ in reversed(self._css_styles): |
57 | 409 self._removeStyle(tag) |
410 del self._css_styles | |
411 | |
412 def xhtml_generic(self, elem, style=True, markup=None): | |
413 """generic method for adding HTML elements | |
414 | |
415 this method handle content, style and children parsing | |
416 @param elem(ET.Element): element to add | |
417 @param style(bool): if True handle style attribute (CSS) | |
418 @param markup(tuple[unicode, (unicode, None)], None): kivy markup to use | |
419 """ | |
420 # we first add markup and CSS style | |
421 if markup is not None: | |
422 if isinstance(markup, basestring): | |
423 tag, value = markup, None | |
424 else: | |
425 tag, value = markup | |
426 self._addStyle(tag, value) | |
427 style_ = 'style' in elem.attrib and style | |
428 if style_: | |
429 self._parseCSS(elem) | |
430 | |
431 # then content | |
432 if elem.text: | |
433 self._getLabel().text += escape_markup(elem.text) | |
434 | |
435 # we parse the children | |
436 for child in elem: | |
437 self._callParseMethod(child) | |
438 | |
439 # closing CSS style and markup | |
440 if style_: | |
441 self._closeCSS() | |
442 if markup is not None: | |
443 self._removeStyle(tag) | |
444 | |
445 # and the tail, which is regular text | |
446 if elem.tail: | |
447 self._getLabel().text += escape_markup(elem.tail) | |
448 | |
449 # method handling XHTML elements | |
450 | |
451 def xhtml_br(self, elem): | |
452 label = self._getLabel() | |
453 label.text+='\n' | |
454 self.xhtml_generic(style=False) | |
455 | |
456 def xhtml_em(self, elem): | |
457 self.xhtml_generic(elem, markup='i') | |
458 | |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
459 def xhtml_img(self, elem): |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
460 try: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
461 src = elem.attrib['src'] |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
462 except KeyError: |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
463 log.warning(u"<img> element without src: {}".format(ET.tostring(elem))) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
464 return |
59 | 465 try: |
466 target_height = int(elem.get(u'height', 0)) | |
467 except ValueError: | |
468 log.warning(u"Can't parse image height: {}".format(elem.get(u'height'))) | |
469 target_height = 0 | |
470 try: | |
471 target_width = int(elem.get(u'width', 0)) | |
472 except ValueError: | |
473 log.warning(u"Can't parse image width: {}".format(elem.get(u'width'))) | |
474 target_width = 0 | |
475 | |
476 img = SimpleXHTMLWidgetImage(source=src, target_height=target_height, target_width=target_width) | |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
477 self.current_wid = img |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
478 self.add_widget(img) |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
479 |
57 | 480 def xhtml_p(self, elem): |
58
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
481 if isinstance(self.current_wid, Label): |
7aa2ffff9067
chat: <img/> tag handling first draft:
Goffi <goffi@goffi.org>
parents:
57
diff
changeset
|
482 self.current_wid.text+="\n\n" |
57 | 483 self.xhtml_generic(elem) |
484 | |
485 def xhtml_span(self, elem): | |
486 self.xhtml_generic(elem) | |
487 | |
488 def xhtml_strong(self, elem): | |
489 self.xhtml_generic(elem, markup='b') | |
490 | |
491 # methods handling CSS properties | |
492 | |
493 def css_color(self, elem, value): | |
494 self._addStyle(u"color", css_color.parse(value)) | |
495 | |
496 def css_text_decoration(self, elem, value): | |
497 if value == u'underline': | |
498 log.warning(u"{} not handled yet, it needs Kivy 1.9.2 to be released".format(value)) | |
499 # FIXME: activate when 1.9.2 is out | |
500 # self._addStyle('u') | |
501 elif value == u'line-through': | |
502 log.warning(u"{} not handled yet, it needs Kivy 1.9.2 to be released".format(value)) | |
503 # FIXME: activate when 1.9.2 is out | |
504 # self._addStyle('s') | |
505 else: | |
506 log.warning(u"unhandled text decoration: {}".format(value)) |