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