1
2 r"""
3 :Copyright:
4
5 Copyright 2006 - 2015
6 Andr\xe9 Malo or his licensors, as applicable
7
8 :License:
9
10 Licensed under the Apache License, Version 2.0 (the "License");
11 you may not use this file except in compliance with the License.
12 You may obtain a copy of the License at
13
14 http://www.apache.org/licenses/LICENSE-2.0
15
16 Unless required by applicable law or agreed to in writing, software
17 distributed under the License is distributed on an "AS IS" BASIS,
18 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 See the License for the specific language governing permissions and
20 limitations under the License.
21
22 ===========
23 CSS Tools
24 ===========
25
26 CSS Tools.
27 """
28 if __doc__:
29
30 __doc__ = __doc__.encode('ascii').decode('unicode_escape')
31 __author__ = r"Andr\xe9 Malo".encode('ascii').decode('unicode_escape')
32 __docformat__ = "restructuredtext en"
33
34 from .. import filters as _filters
35 from ._util import norm_enc as _norm_enc
36
37
39 """
40 Cleanup a single CSS buffer
41
42 This methods removes CDATA and starting/finishing comment containers.
43
44 :Parameters:
45 `style` : ``basestring``
46 Buffer to cleanup
47
48 `encoding` : ``str``
49 Encoding in case that toescape is a ``str``. If omitted or
50 ``None``, no encoding is applied and `style` is expected to be
51 ASCII compatible.
52
53 :Return: The cleaned up buffer, typed as input
54 :Rtype: ``basestring``
55 """
56 isuni = isinstance(style, unicode)
57 if not isuni:
58
59
60 if encoding is None or _norm_enc(encoding) == 'ascii':
61 encoding = 'latin-1'
62 style = str(style).decode(encoding)
63 style = style.strip()
64 if style.startswith(u'<!--'):
65 style = style[4:]
66 if style.endswith(u'-->'):
67 style = style[:-3]
68 style = style.strip()
69 if style.startswith(u'/*'):
70 pos = style.find(u'*/')
71 if pos >= 0:
72 style = style[pos + 2:]
73 if style.endswith(u'*/'):
74 style = style[::-1]
75 pos = style.find(u'*/')
76 if pos >= 0:
77 style = style[pos + 2:]
78 style = style[::-1]
79 style = style.strip()
80 if style.startswith(u'<![CDATA['):
81 style = style[len(u'<![CDATA['):]
82 if style.endswith(u']]>'):
83 style = style[:-3]
84 style = style.strip()
85 if isuni:
86 return style
87 return style.encode(encoding)
88
89
90 -def cdata(style, encoding=None):
91 """
92 Add a failsafe CDATA container around a style
93
94 See <http://lists.w3.org/Archives/Public/www-html/2002Apr/0053.html>
95 for details.
96
97 :Parameters:
98 `style` : ``basestring``
99 JS to contain
100
101 `encoding` : ``str``
102 Encoding in case that toescape is a ``str``. If omitted or
103 ``None``, no encoding is applied and `style` is expected to be
104 ASCII compatible.
105
106 :Return: The contained style, typed as input
107 :Rtype: ``basestring``
108 """
109 isuni = isinstance(style, unicode)
110 if not isuni:
111
112
113 if encoding is None or _norm_enc(encoding) == 'ascii':
114 encoding = 'latin-1'
115 style = str(style).decode(encoding)
116 style = cleanup(style)
117 if style:
118 style = u'<!--/*--><![CDATA[/*><!--*/\n%s\n/*]]>*/-->' % style
119 if isuni:
120 return style
121 return style.encode(encoding)
122
123
124 -def minify(style, encoding=None):
125 """
126 Minify CSS (using `rcssmin`_)
127
128 .. _rcssmin: http://opensource.perlig.de/rcssmin/
129
130 :Parameters:
131 `style` : ``basestring``
132 CSS to minify
133
134 `encoding` : ``str``
135 Encoding in case that toescape is a ``str``. If omitted or
136 ``None``, no encoding is applied and `style` is expected to be
137 ASCII compatible.
138
139 :Return: Minified CSS, typed as input
140 :Rtype: ``basestring``
141 """
142 from . import rcssmin as _rcssmin
143
144 isuni = isinstance(style, unicode)
145 if not isuni and encoding is not None:
146
147
148 if _norm_enc(encoding) == 'ascii':
149 encoding = 'latin-1'
150 return _rcssmin.cssmin(style.decode(encoding)).encode(encoding)
151 return _rcssmin.cssmin(style)
152
153
155 """
156 TDI filter for modifying inline css
157
158 :IVariables:
159 `_collecting` : ``bool``
160 Currently collecting CSS text?
161
162 `_buffer` : ``list``
163 Collection buffer
164
165 `_starttag` : ``tuple`` or ``None``
166 Original style starttag parameters
167
168 `_modify` : callable
169 Modifier function
170
171 `_attribute` : ``str``
172 ``tdi`` attribute name or ``None`` (if standalone)
173
174 `_strip` : ``bool``
175 Strip empty style elements?
176 """
177
178 - def __init__(self, builder, modifier, strip_empty=True, standalone=False):
179 """
180 Initialization
181
182 :Parameters:
183 `builder` : `tdi.interfaces.BuildingListenerInterface`
184 Builder
185
186 `modifier` : callable
187 Modifier function. Takes a style and returns the (possibly)
188 modified result.
189
190 `strip_empty` : ``bool``
191 Strip empty style elements?
192
193 `standalone` : ``bool``
194 Standalone context? In this case, we won't watch out for TDI
195 attributes.
196 """
197 super(CSSInlineFilter, self).__init__(builder)
198 self._collecting = False
199 self._buffer = []
200 self._starttag = None
201 self._modify = modifier
202 self._normalize = self.builder.decoder.normalize
203 if standalone:
204 self._attribute = None
205 else:
206 self._attribute = self._normalize(
207 self.builder.analyze.attribute
208 )
209 self._strip = strip_empty
210
212 """
213 Handle starttag
214
215 Style starttags are delayed until the endtag is found. The whole
216 element is then evaluated (and possibly thrown away).
217
218 :See: `tdi.interfaces.ListenerInterface`
219 """
220 if not closed and self._normalize(name) == 'style':
221 self._collecting = True
222 self._buffer = []
223 self._starttag = name, attr, closed, data
224 else:
225 self.builder.handle_starttag(name, attr, closed, data)
226
228 """
229 Handle endtag
230
231 When currently collecting, it must be a style endtag. The style
232 element content is then cleaned up (using `cleanup`) and then
233 modified (using the modifiy function passed during initialization).
234 The result replaces the original. If it's empty and the starttag
235 does not provide a ``tdi`` attribute and the filter was
236 configured to do so: the whole element is thrown away.
237
238 :See: `tdi.interfaces.ListenerInterface`
239 """
240 if self._collecting:
241 normalize = self._normalize
242 if normalize(name) != 'style':
243 raise AssertionError("Invalid event stream")
244
245 self._collecting = False
246 style, self._buffer = ''.join(self._buffer), []
247 style = self._modify(cleanup(style))
248
249 if not style and self._strip:
250 attrdict = dict((
251 normalize(name), val
252 ) for name, val in self._starttag[1])
253 if self._attribute is None or self._attribute not in attrdict:
254 return
255
256 self.builder.handle_starttag(*self._starttag)
257 self._starttag = None
258 self.builder.handle_text(style)
259
260 self.builder.handle_endtag(name, data)
261
262 - def handle_text(self, data):
263 """
264 Handle text
265
266 While collecting style text, the received data is buffered.
267 Otherwise the event is just passed through.
268
269 :See: `tdi.interfaces.ListenerInterface`
270 """
271 if not self._collecting:
272 return self.builder.handle_text(data)
273 self._buffer.append(data)
274
275
277 """
278 TDI Filter for minifying inline CSS
279
280 :Parameters:
281 `minifier` : callable``
282 Minifier function. If omitted or ``None``, the `builtin minifier`_ is
283 applied.
284
285 .. _builtin minifier: http://opensource.perlig.de/rcssmin/
286
287 `standalone` : ``bool``
288 Standalone context? In this case, we won't watch out for TDI
289 attributes.
290 """
291
292
293 if minifier is None:
294 minifier = minify
295 return CSSInlineFilter(builder, minify, standalone=standalone)
296
297
299 """
300 TDI filter for adding failsafe CDATA containers around CSS styles
301
302 See <http://lists.w3.org/Archives/Public/www-html/2002Apr/0053.html>
303 for details.
304
305 :Parameters:
306 `standalone` : ``bool``
307 Standalone context? In this case, we won't watch out for TDI
308 attributes.
309 """
310
311
312 return CSSInlineFilter(builder, cdata, standalone=standalone)
313