8 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
8 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
9 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS |
9 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS |
10 # FOR A PARTICULAR PURPOSE. |
10 # FOR A PARTICULAR PURPOSE. |
11 # |
11 # |
12 |
12 |
13 __docformat__ = 'restructuredtext' |
13 """PyAMS_utils.text module |
|
14 |
|
15 This module provides text manipulation and conversion functions, as well as a set of TALES |
|
16 extensions (see :py:class:`ITALESExtension <pyams_utils.interfaces.tales.ITALESExtension>`). |
|
17 """ |
14 |
18 |
15 import html |
19 import html |
16 |
20 |
17 import docutils.core |
21 import docutils.core |
18 from markdown import markdown |
22 from markdown import markdown |
19 from pyramid.interfaces import IRequest |
23 from pyramid.interfaces import IRequest |
20 from zope.interface import Interface |
24 from zope.interface import Interface |
21 from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary |
25 from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary |
22 |
26 |
23 from pyams_utils import _ |
|
24 from pyams_utils.adapter import ContextRequestAdapter, ContextRequestViewAdapter, adapter_config |
27 from pyams_utils.adapter import ContextRequestAdapter, ContextRequestViewAdapter, adapter_config |
25 from pyams_utils.interfaces.tales import ITALESExtension |
28 from pyams_utils.interfaces.tales import ITALESExtension |
26 from pyams_utils.interfaces.text import IHTMLRenderer |
29 from pyams_utils.interfaces.text import IHTMLRenderer |
27 from pyams_utils.request import check_request |
30 from pyams_utils.request import check_request |
28 from pyams_utils.vocabulary import vocabulary_config |
31 from pyams_utils.vocabulary import vocabulary_config |
29 |
32 |
30 |
33 |
31 def get_text_start(text, length, max=0): |
34 __docformat__ = 'restructuredtext' |
|
35 |
|
36 |
|
37 from pyams_utils import _ |
|
38 |
|
39 |
|
40 def get_text_start(text, length, maxlen=0): |
32 """Get first words of given text with maximum given length |
41 """Get first words of given text with maximum given length |
33 |
42 |
34 If *max* is specified, text is shortened only if remaining text is longer this value |
43 If *max* is specified, text is shortened only if remaining text is longer this value |
35 |
44 |
36 :param str text: initial text |
45 :param str text: initial text |
37 :param integer length: maximum length of resulting text |
46 :param integer length: maximum length of resulting text |
38 :param integer max: if > 0, *text* is shortened only if remaining text is longer than max |
47 :param integer maxlen: if > 0, *text* is shortened only if remaining text is longer than max |
39 |
48 |
40 >>> from pyams_utils.text import get_text_start |
49 >>> from pyams_utils.text import get_text_start |
41 >>> get_text_start('This is a long string', 10) |
50 >>> get_text_start('This is a long string', 10) |
42 'This is a…' |
51 'This is a…' |
43 >>> get_text_start('This is a long string', 20) |
52 >>> get_text_start('This is a long string', 20) |
50 return result |
59 return result |
51 index = length - 1 |
60 index = length - 1 |
52 text_length = len(result) |
61 text_length = len(result) |
53 while (index > 0) and (result[index] != ' '): |
62 while (index > 0) and (result[index] != ' '): |
54 index -= 1 |
63 index -= 1 |
55 if (index > 0) and (text_length > index + max): |
64 if (index > 0) and (text_length > index + maxlen): |
56 return result[:index] + '…' |
65 return result[:index] + '…' |
57 return text |
66 return text |
58 |
67 |
59 |
68 |
60 @adapter_config(name='truncate', context=(Interface, Interface, Interface), provides=ITALESExtension) |
69 @adapter_config(name='truncate', context=(Interface, Interface, Interface), |
|
70 provides=ITALESExtension) |
61 class TruncateCharsTalesExtension(ContextRequestViewAdapter): |
71 class TruncateCharsTalesExtension(ContextRequestViewAdapter): |
62 """extension:truncate(value, length, max) TALES expression |
72 """extension:truncate(value, length, max) TALES expression |
63 |
73 |
64 Truncates a sentence if it is longer than the specified 'length' characters. |
74 Truncates a sentence if it is longer than the specified 'length' characters. |
65 Truncated strings will end with an ellipsis character (“…”) |
75 Truncated strings will end with an ellipsis character (“…”) |
66 See also 'get_text_start' |
76 See also 'get_text_start' |
67 """ |
77 """ |
68 |
78 |
69 @staticmethod |
79 @staticmethod |
70 def render(value, length=50, max=0): |
80 def render(value, length=50, maxlen=0): |
|
81 """Render TALES extension; |
|
82 see :py:class:`ITALESExtension <pyams_utils.interfaces.tales.ITALESExtension>` |
|
83 """ |
71 if not value: |
84 if not value: |
72 return '' |
85 return '' |
73 return get_text_start(value, length, max=max) |
86 return get_text_start(value, length, maxlen=maxlen) |
74 |
87 |
75 |
88 |
76 @adapter_config(name='raw', context=(str, IRequest), provides=IHTMLRenderer) |
89 @adapter_config(name='raw', context=(str, IRequest), provides=IHTMLRenderer) |
77 class BaseHTMLRenderer(ContextRequestAdapter): |
90 class BaseHTMLRenderer(ContextRequestAdapter): |
78 """Raw text HTML renderer |
91 """Raw text HTML renderer |
79 |
92 |
80 This renderer renders input text 'as is', mainly for use in a <pre> tag. |
93 This renderer renders input text 'as is', mainly for use in a <pre> tag. |
81 """ |
94 """ |
82 |
95 |
83 def render(self, **kwargs): |
96 def render(self, **kwargs): # pylint: disable=unused-argument |
|
97 """Convert raw code as HTML""" |
84 return self.context |
98 return self.context |
85 |
99 |
86 |
100 |
87 @adapter_config(name='text', context=(str, IRequest), provides=IHTMLRenderer) |
101 @adapter_config(name='text', context=(str, IRequest), provides=IHTMLRenderer) |
88 class TextRenderer(BaseHTMLRenderer): |
102 class TextRenderer(BaseHTMLRenderer): |
150 def text_to_html(text, renderer='text'): |
164 def text_to_html(text, renderer='text'): |
151 """Convert text to HTML using the given renderer |
165 """Convert text to HTML using the given renderer |
152 |
166 |
153 Renderer name can be any registered HTML renderer adapter. |
167 Renderer name can be any registered HTML renderer adapter. |
154 |
168 |
155 You can provide several renderers by giving their names separated by semicolon; renderers will then |
169 You can provide several renderers by giving their names separated by semicolon; renderers |
156 act as in a pipe, each renderer transforming output of the previous one. |
170 will then act as in a pipe, each renderer transforming output of the previous one. |
157 """ |
171 """ |
158 request = check_request() |
172 request = check_request() |
159 registry = request.registry |
173 registry = request.registry |
160 for renderer_name in renderer.split(';'): |
174 for renderer_name in renderer.split(';'): |
161 renderer = registry.queryMultiAdapter((text, request), IHTMLRenderer, name=renderer_name) |
175 renderer = registry.queryMultiAdapter((text, request), IHTMLRenderer, name=renderer_name) |
162 if renderer is not None: |
176 if renderer is not None: |
163 text = renderer.render() or text |
177 text = renderer.render() or text |
164 return text |
178 return text |
165 |
179 |
166 |
180 |
167 empty_marker = object() |
181 EMPTY_MARKER = object() |
168 |
182 |
169 |
183 |
170 @adapter_config(name='html', context=(Interface, Interface, Interface), provides=ITALESExtension) |
184 @adapter_config(name='html', context=(Interface, Interface, Interface), provides=ITALESExtension) |
171 class HTMLTalesExtension(ContextRequestViewAdapter): |
185 class HTMLTalesExtension(ContextRequestViewAdapter): |
172 """*extension:html* TALES expression |
186 """*extension:html* TALES expression |
173 |
187 |
174 If first *context* argument of the renderer is an object for which an :py:class:`IHTMLRenderer` |
188 If first *context* argument of the renderer is an object for which an |
175 adapter can be found, this adapter is used to render the context to HTML; if *context* is a string, |
189 :py:class:`IHTMLRenderer <pyams_utils.interfaces.text.IHTMLRenderer>` |
176 it is converted to HTML using the renderer defined as second parameter; otherwise, context is just |
190 adapter can be found, this adapter is used to render the context to HTML; if *context* is a |
177 converted to string using the :py:func:`str` function. |
191 string, it is converted to HTML using the renderer defined as second parameter; otherwise, |
178 |
192 context is just converted to string using the :py:func:`str` function. |
179 You can provide several renderers by giving their names separated by semicolon; renderers will then |
193 |
180 act as in a pipe, each renderer transforming output of the previous one. |
194 You can provide several renderers by giving their names separated by semicolon; renderers |
181 """ |
195 will then act as in a pipe, each renderer transforming output of the previous one. |
182 |
196 """ |
183 def render(self, context=empty_marker, renderer='text'): |
197 |
184 if context is empty_marker: |
198 def render(self, context=EMPTY_MARKER, renderer='text'): |
|
199 """Render TALES extension; |
|
200 see :py:class:`ITALESExtension <pyams_utils.interfaces.tales.ITALESExtension>` |
|
201 """ |
|
202 if context is EMPTY_MARKER: |
185 context = self.context |
203 context = self.context |
186 if not context: |
204 if not context: |
187 return '' |
205 return '' |
188 registry = self.request.registry |
206 registry = self.request.registry |
189 adapter = registry.queryMultiAdapter((context, self.request, self.view), IHTMLRenderer) |
207 adapter = registry.queryMultiAdapter((context, self.request, self.view), IHTMLRenderer) |
190 if adapter is None: |
208 if adapter is None: |
191 adapter = registry.queryMultiAdapter((context, self.request), IHTMLRenderer) |
209 adapter = registry.queryMultiAdapter((context, self.request), IHTMLRenderer) |
192 if adapter is not None: |
210 if adapter is not None: |
193 return adapter.render() |
211 return adapter.render() |
194 elif isinstance(context, str): |
212 if isinstance(context, str): |
195 return text_to_html(context, renderer) |
213 return text_to_html(context, renderer) |
196 else: |
214 return str(context) |
197 return str(context) |
|
198 |
215 |
199 |
216 |
200 PYAMS_HTML_RENDERERS_VOCABULARY = 'PyAMS HTML renderers' |
217 PYAMS_HTML_RENDERERS_VOCABULARY = 'PyAMS HTML renderers' |
201 |
218 |
202 |
219 |
203 @vocabulary_config(name=PYAMS_HTML_RENDERERS_VOCABULARY) |
220 @vocabulary_config(name=PYAMS_HTML_RENDERERS_VOCABULARY) |
204 class RenderersVocabulary(SimpleVocabulary): |
221 class RenderersVocabulary(SimpleVocabulary): |
205 """Text renderers vocabulary""" |
222 """Text renderers vocabulary""" |
206 |
223 |
207 def __init__(self, context=None): |
224 def __init__(self, context=None): # pylint: disable=unused-argument |
208 request = check_request() |
225 request = check_request() |
209 registry = request.registry |
226 registry = request.registry |
210 translate = request.localizer.translate |
227 translate = request.localizer.translate |
211 terms = [] |
228 terms = [] |
212 append = terms.append |
229 append = terms.append |
224 breaks with given CSS class. |
241 breaks with given CSS class. |
225 """ |
242 """ |
226 |
243 |
227 @staticmethod |
244 @staticmethod |
228 def render(value, css_class='', character='|', start_tag=None, end_tag=None): |
245 def render(value, css_class='', character='|', start_tag=None, end_tag=None): |
|
246 """Render TALES extension; |
|
247 see :py:class:`ITALESExtension <pyams_utils.interfaces.tales.ITALESExtension>` |
|
248 """ |
229 if not value: |
249 if not value: |
230 return '' |
250 return '' |
231 br = '<br {0} />'.format('class="{0}"'.format(css_class) if css_class else '') |
251 br_tag = '<br {0} />'.format('class="{0}"'.format(css_class) if css_class else '') |
232 elements = value.split(character) |
252 elements = value.split(character) |
233 if start_tag: |
253 if start_tag: |
234 elements[0] = '<{0}>{1}</{0}>'.format(start_tag, elements[0]) |
254 elements[0] = '<{0}>{1}</{0}>'.format(start_tag, elements[0]) |
235 if end_tag: |
255 if end_tag: |
236 elements[-1] = '<{0}>{1}</{0}>'.format(end_tag, elements[-1]) |
256 elements[-1] = '<{0}>{1}</{0}>'.format(end_tag, elements[-1]) |
237 return br.join(elements) |
257 return br_tag.join(elements) |