13 import decimal |
13 import decimal |
14 import locale |
14 import locale |
15 import re |
15 import re |
16 from urllib import quote as urlquote |
16 from urllib import quote as urlquote |
17 from cStringIO import StringIO |
17 from cStringIO import StringIO |
18 from xml.parsers.expat import ExpatError |
18 from xml.sax.saxutils import unescape |
19 from copy import deepcopy |
19 from copy import deepcopy |
20 |
20 |
21 import simplejson |
21 import simplejson |
22 |
22 |
23 from mx.DateTime import DateTimeType, DateTimeDeltaType |
23 from mx.DateTime import DateTimeType, DateTimeDeltaType |
24 |
24 |
25 from logilab.common.textutils import unormalize |
25 from logilab.common.textutils import unormalize |
|
26 from logilab.mtconverter import html_escape |
26 |
27 |
27 def ustrftime(date, fmt='%Y-%m-%d'): |
28 def ustrftime(date, fmt='%Y-%m-%d'): |
28 """like strftime, but returns a unicode string instead of an encoded |
29 """like strftime, but returns a unicode string instead of an encoded |
29 string which may be problematic with localized date. |
30 string which may be problematic with localized date. |
30 |
31 |
114 def safe_cut(text, length): |
115 def safe_cut(text, length): |
115 """returns a string of length <length> based on <text>, removing any html |
116 """returns a string of length <length> based on <text>, removing any html |
116 tags from given text if cut is necessary.""" |
117 tags from given text if cut is necessary.""" |
117 if text is None: |
118 if text is None: |
118 return u'' |
119 return u'' |
119 text_nohtml = remove_html_tags(text) |
120 noenttext = unescape(text) |
|
121 text_nohtml = remove_html_tags(noenttext) |
120 # try to keep html tags if text is short enough |
122 # try to keep html tags if text is short enough |
121 if len(text_nohtml) <= length: |
123 if len(text_nohtml) <= length: |
122 return text |
124 return text |
123 # else if un-tagged text is too long, cut it |
125 # else if un-tagged text is too long, cut it |
124 return text_nohtml[:length-3] + u'...' |
126 return html_escape(text_nohtml[:length] + u'...') |
|
127 |
|
128 fallback_safe_cut = safe_cut |
125 |
129 |
126 |
130 |
127 try: |
131 try: |
128 from lxml import etree |
132 from lxml import etree |
129 except (ImportError, AttributeError): |
133 except (ImportError, AttributeError): |
150 """returns an html document of length <length> based on <text>, |
154 """returns an html document of length <length> based on <text>, |
151 and cut is necessary. |
155 and cut is necessary. |
152 """ |
156 """ |
153 if text is None: |
157 if text is None: |
154 return u'' |
158 return u'' |
155 textParse = etree.HTML(text) |
159 dom = etree.HTML(text) |
156 compteur = 0 |
160 curlength = 0 |
157 |
161 add_ellipsis = False |
158 for element in textParse.iter(): |
162 for element in dom.iter(): |
159 if compteur > length: |
163 if curlength >= length: |
160 parent = element.getparent() |
164 parent = element.getparent() |
161 parent.remove(element) |
165 parent.remove(element) |
|
166 if curlength == length and (element.text or element.tail): |
|
167 add_ellipsis = True |
162 else: |
168 else: |
163 if element.text is not None: |
169 if element.text is not None: |
164 text_resum = text_cut_letters(element.text,length) |
170 element.text = cut(element.text, length - curlength) |
165 len_text_resum = len(''.join(text_resum.split())) |
171 curlength += len(element.text) |
166 compteur = compteur + len_text_resum |
|
167 element.text = text_resum |
|
168 |
|
169 if element.tail is not None: |
172 if element.tail is not None: |
170 if compteur < length: |
173 if curlength < length: |
171 text_resum = text_cut_letters(element.tail,length) |
174 element.tail = cut(element.tail, length - curlength) |
172 len_text_resum = len(''.join(text_resum.split())) |
175 curlength += len(element.tail) |
173 compteur = compteur + len_text_resum |
176 elif curlength == length: |
174 element.tail = text_resum |
177 element.tail = '...' |
175 else: |
178 else: |
176 element.tail = '' |
179 element.tail = '' |
177 |
180 text = etree.tounicode(dom[0])[6:-7] # remove wrapping <body></body> |
178 div = etree.HTML('<div></div>')[0][0] |
181 if add_ellipsis: |
179 listNode = textParse[0].getchildren() |
182 return text + u'...' |
180 for node in listNode: |
183 return text |
181 div.append(deepcopy(node)) |
184 |
182 return etree.tounicode(div) |
185 def text_cut(text, nbwords=30): |
|
186 """from the given plain text, return a text with at least <nbwords> words, |
|
187 trying to go to the end of the current sentence. |
|
188 |
|
189 Note that spaces are normalized. |
|
190 """ |
|
191 if text is None: |
|
192 return u'' |
|
193 words = text.split() |
|
194 text = ' '.join(words) # normalize spaces |
|
195 minlength = len(' '.join(words[:nbwords])) |
|
196 textlength = text.find('.', minlength) + 1 |
|
197 if textlength == 0: # no point found |
|
198 textlength = minlength |
|
199 return text[:textlength] |
|
200 |
|
201 def cut(text, length): |
|
202 """returns a string of a maximum length <length> based on <text> |
|
203 (approximatively, since if text has been cut, '...' is added to the end of the string, |
|
204 resulting in a string of len <length> + 3) |
|
205 """ |
|
206 if text is None: |
|
207 return u'' |
|
208 if len(text) <= length: |
|
209 return text |
|
210 # else if un-tagged text is too long, cut it |
|
211 return text[:length] + u'...' |
|
212 |
183 |
213 |
184 |
214 |
185 # HTML generation helper functions ############################################ |
215 # HTML generation helper functions ############################################ |
186 |
|
187 from logilab.mtconverter import html_escape |
|
188 |
216 |
189 def tooltipize(text, tooltip, url=None): |
217 def tooltipize(text, tooltip, url=None): |
190 """make an HTML tooltip""" |
218 """make an HTML tooltip""" |
191 url = url or '#' |
219 url = url or '#' |
192 return u'<a href="%s" title="%s">%s</a>' % (url, tooltip, text) |
220 return u'<a href="%s" title="%s">%s</a>' % (url, tooltip, text) |
218 if extraparams: |
246 if extraparams: |
219 params.append(simplejson.dumps(extraparams)) |
247 params.append(simplejson.dumps(extraparams)) |
220 if swap: |
248 if swap: |
221 params.append('true') |
249 params.append('true') |
222 return "javascript: replacePageChunk(%s);" % ', '.join(params) |
250 return "javascript: replacePageChunk(%s);" % ', '.join(params) |
223 |
|
224 def text_cut(text, nbwords=30): |
|
225 if text is None: |
|
226 return u'' |
|
227 minlength = len(' '.join(text.split()[:nbwords])) |
|
228 textlength = text.find('.', minlength) + 1 |
|
229 if textlength == 0: # no point found |
|
230 textlength = minlength |
|
231 return text[:textlength] |
|
232 |
|
233 def text_cut_letters(text, nbletters): |
|
234 if text is None: |
|
235 return u'' |
|
236 if len(''.join(text.split())) <= nbletters: |
|
237 return text |
|
238 else: |
|
239 text_nospace = ''.join(text.split()) |
|
240 textlength=text.find('.') + 1 |
|
241 |
|
242 if textlength==0: |
|
243 textlength=text.find(' ', nbletters+5) |
|
244 |
|
245 return text[:textlength] |
|
246 |
|
247 def cut(text, length): |
|
248 """returns a string of length <length> based on <text> |
|
249 post: |
|
250 len(__return__) <= length |
|
251 """ |
|
252 if text is None: |
|
253 return u'' |
|
254 if len(text) <= length: |
|
255 return text |
|
256 # else if un-tagged text is too long, cut it |
|
257 return text[:length-3] + u'...' |
|
258 |
251 |
259 |
252 |
260 from StringIO import StringIO |
253 from StringIO import StringIO |
261 |
254 |
262 def ureport_as_html(layout): |
255 def ureport_as_html(layout): |