1
2 u"""
3 :Copyright:
4
5 Copyright 2006 - 2013
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 Javascript Tools
24 ==================
25
26 Javascript Tools.
27 """
28 __author__ = u"Andr\xe9 Malo"
29 __docformat__ = "restructuredtext en"
30
31 import re as _re
32
33 from tdi import filters as _filters
34 from tdi import _htmldecode
35 from tdi.tools._util import norm_enc as _norm_enc
36
37
39 """ Make bigsub """
40 sub = _re.compile(r'(?<!\\)((?:\\\\)*)\\U([0-9a-fA-F]{8})').sub
41 int_ = int
42 return lambda v: sub(lambda m: "%s\\u%04x\\u%04x" % (
43 m.group(1),
44 ((((int_(m.group(2), 16) - 0x10000) >> 10) & 0x3FF) + 0xD800),
45 ((int_(m.group(2), 16) & 0x3FF) + 0xDC00),
46 ), v)
47
48 _big_sub_b = _make_big_sub_b()
49
50
52 """ Make bigsub """
53 sub = _re.compile(ur'(?<!\\)((?:\\\\)*)\\U([0-9a-fA-F]{8})').sub
54 int_ = int
55 return lambda v: sub(lambda m: u"%s\\u%04x\\u%04x" % (
56 m.group(1),
57 ((((int_(m.group(2), 16) - 0x10000) >> 10) & 0x3FF) + 0xD800),
58 ((int_(m.group(2), 16) & 0x3FF) + 0xDC00),
59 ), v)
60
61 _big_sub = _make_big_sub()
62
63
65 """ Make small sub """
66 sub = _re.compile(ur'(?<!\\)((?:\\\\)*)\\x([0-9a-fA-F]{2})').sub
67 return lambda v: sub(lambda m: u"%s\\u00%s" % (
68 m.group(1), m.group(2)
69 ), v)
70
71 _small_sub = _make_small_sub()
72
73
75 """ Make escape_inlined """
76 dash_sub = _re.compile(ur'-(-+)').sub
77 dash_sub_b = _re.compile(r'-(-+)').sub
78 len_, str_, unicode_, isinstance_ = len, str, unicode, isinstance
79 norm_enc = _norm_enc
80
81 subber = lambda m: u'-' + u'\\-' * len_(m.group(1))
82 subber_b = lambda m: '-' + '\\-' * len_(m.group(1))
83
84 def escape_inlined(toescape, encoding=None):
85 """
86 Escape value for inlining
87
88 :Parameters:
89 `toescape` : ``basestring``
90 The value to escape
91
92 `encoding` : ``str``
93 Encoding in case that toescape is a ``str``. If omitted or
94 ``None``, no encoding is applied and `toescape` is expected to be
95 ASCII compatible.
96
97 :Return: The escaped value, typed as input
98 :Rtype: ``basestring``
99 """
100 if isinstance_(toescape, unicode_):
101 return (dash_sub(subber, toescape)
102 .replace(u'</', u'<\\/')
103 .replace(u']]>', u']\\]>')
104 )
105 elif encoding is None:
106 return (dash_sub_b(subber_b, str_(toescape))
107 .replace('</', '<\\/')
108 .replace(']]>', ']\\]>')
109 )
110
111
112 if norm_enc(encoding) == 'ascii':
113 encoding = 'latin-1'
114 return (dash_sub(subber, str_(toescape).decode(encoding))
115 .replace(u'</', u'<\\/')
116 .replace(u']]>', u']\\]>')
117 ).encode(encoding)
118
119 return escape_inlined
120
121 escape_inlined = _make_escape_inlined()
122
123
125 """ Make escape_string function """
126 big_sub_b = _big_sub_b
127 unicode_, str_, isinstance_ = unicode, str, isinstance
128 escape_inlined_, norm_enc = escape_inlined, _norm_enc
129
130 need_solid = '\\'.encode('string_escape') == '\\'
131 need_solid_u = u'\\'.encode('unicode_escape') == '\\'
132 need_apos = "'".encode('string_escape') == "'"
133 need_apos_u = u"'".encode('unicode_escape') == "'"
134 need_quote = '"'.encode('string_escape') == '"'
135 need_quote_u = u'"'.encode('unicode_escape') == '"'
136
137 def escape_string(toescape, inlined=True, encoding=None):
138 """
139 Escape a string for JS output (to be inserted into a JS string)
140
141 This function is one of the building blocks of the
142 `tdi.tools.javascript.replace` function. You probably shouldn't
143 use it directly in your rendering code.
144
145 :See:
146 - `tdi.tools.javascript.fill`
147 - `tdi.tools.javascript.fill_attr`
148 - `tdi.tools.javascript.replace`
149
150 :Parameters:
151 `toescape` : ``basestring``
152 The string to escape
153
154 `inlined` : ``bool``
155 Do additional escapings (possibly needed for inlining the script
156 within a HTML page)?
157
158 `encoding` : ``str``
159 Encoding in case that toescape is a ``str``. If omitted or
160 ``None``, no encoding is applied and `toescape` is expected to be
161 ASCII compatible.
162
163 :Return: The escaped string (ascii)
164 :Rtype: ``str``
165 """
166
167
168 isuni = isinstance_(toescape, unicode_)
169 if isuni or encoding is not None:
170 if not isuni:
171
172
173 if norm_enc(encoding) == 'ascii':
174 encoding = 'latin-1'
175 toescape = str_(toescape).decode(encoding)
176 if need_solid_u:
177 toescape = toescape.replace(u'\\', u'\\\\')
178 result = big_sub_b(toescape.encode('unicode_escape'))
179 if need_apos_u:
180 result = result.replace("'", "\\'")
181 if need_quote_u:
182 result = result.replace('"', '\\"')
183 else:
184 result = str_(toescape)
185 if need_solid:
186 result = result.replace('\\', '\\\\')
187 result = result.encode('string_escape')
188 if need_apos:
189 result = result.replace("'", "\\'")
190 if need_quote:
191 result = result.replace('"', '\\"')
192
193 if inlined:
194 return escape_inlined_(result)
195 return result
196
197 return escape_string
198
199 escape_string = _make_escape_string()
200
201
203 """ Make replace function """
204
205
206
207 default_sub = _re.compile(ur'__(?P<name>[^_]*(?:_[^_]+)*)__').sub
208 escape_string_, getattr_, unicode_ = escape_string, getattr, unicode
209 isinstance_, escape_inlined_, str_ = isinstance, escape_inlined, str
210 big_sub, small_sub, norm_enc = _big_sub, _small_sub, _norm_enc
211
212 def replace(script, holders, pattern=None, as_json=True, inlined=True,
213 encoding=None):
214 """
215 Replace javascript values
216
217 See `fill` and `fill_attr` for more specific usage.
218
219 This functions provides safe (single pass) javascript value
220 replacement::
221
222 filled = javascript.replace(script_template, dict(
223 a=10,
224 b=u'Andr\\xe9',
225 c=javascript.SimpleJSON(dict(foo='bar')),
226 ))
227
228 Where script_template is something like::
229
230 // more script...
231 var count = __a__;
232 var name = '__b__';
233 var param = __c__;
234 // more script...
235
236 :See:
237 - `fill`
238 - `fill_attr`
239
240 :Parameters:
241 `script` : ``basestring``
242 Script content to modify
243
244 `holders` : ``dict``
245 Placeholders mappings (name -> value). If a placeholder is found
246 within the script which has no mapping, it's simply left as-is.
247 If `as_json` is true, the values are checked if they have an
248 ``as_json`` method. *If* they do have one, the method is called
249 and the result (of type ``unicode``) is used as replacement.
250 Otherwise the mapped value is piped through the `escape_string`
251 function and that result is used as replacement. ``as_json`` is
252 passed a boolean ``inlined`` parameter which indicates whether the
253 method should escape for inline usage or not.
254
255 Use the `LiteralJSON` class for passing any JSON content literally
256 to the script. There is also a `SimpleJSON` class for converting
257 complex structures to JSON using the simplejson converter. You may
258 pass your own classes as well, of course, as long as they provide
259 a proper ``as_json()`` method.
260
261 `pattern` : ``unicode`` or compiled ``re`` object
262 Placeholder name pattern. If omitted or ``None``, the pattern is
263 (simplified [#]_): ``__(?P<name>.+)__``, i.e. the placeholder name
264 enclosed in double-underscores. The name group is expected.
265
266 .. [#] The actual pattern is: ``__(?P<name>[^_]*(?:_[^_]+)*)__``
267
268 `as_json` : ``bool``
269 Check the placeholder values for an ``as_json`` method? See the
270 description of the `holders` parameter for details.
271
272 `inlined` : ``bool``
273 Escape simple content for being inlined (e.g.
274 no CDATA endmarkers, ``</script>``).
275
276 `encoding` : ``str``
277 Script encoding if `script` is a ``str``. If omitted or ``None``,
278 the script is expected to be ASCII compatible.
279
280 If ``script`` is of type ``unicode``, the encoding is applied to
281 ``as_json`` method return values. This is to make sure, the JSON
282 stuff is encoded safely. If omitted or ``None``, ASCII is assumed.
283 JSON result characters not fitting into the this encoding are
284 escaped (\\uxxxx).
285
286 :Return: The modified script, typed as input
287 :Rtype: ``basestring``
288 """
289
290 if not holders:
291 return script
292 isuni = isinstance_(script, unicode_)
293 if isuni:
294 if encoding is None:
295 json_encoding = 'ascii'
296 else:
297 json_encoding = encoding
298 else:
299 if encoding is None:
300 encoding = 'latin-1'
301 json_encoding = 'ascii'
302 else:
303 json_encoding = encoding
304
305
306 if norm_enc(encoding) == 'ascii':
307 encoding = 'latin-1'
308 script = str_(script).decode(encoding)
309 if pattern is None:
310 pattern = default_sub
311 else:
312 pattern = _re.compile(pattern).sub
313
314 def simple_subber(match):
315 """ Substitution function without checking .as_json() """
316 name = match.group(u'name')
317 if name and name in holders:
318 return escape_string_(holders[name],
319 encoding=encoding, inlined=inlined
320 ).decode('ascii')
321 return match.group(0)
322
323 def json_subber(match):
324 """ Substitution function with .as_json() checking """
325 name = match.group(u'name')
326 if name and name in holders:
327 value = holders[name]
328 method = getattr_(value, 'as_json', None)
329 if method is None:
330 return escape_string_(value,
331 encoding=encoding, inlined=inlined
332 ).decode('ascii')
333 value = small_sub(big_sub(unicode_(method(inlined=False))
334 .encode(json_encoding, 'backslashreplace')
335 .decode(json_encoding)
336 ))
337 if inlined:
338 return escape_inlined_(value)
339 return value
340 return match.group(0)
341
342 script = pattern(as_json and json_subber or simple_subber, script)
343 if not isuni:
344 return script.encode(encoding)
345 return script
346
347 return replace
348
349 replace = _make_replace()
350
351
352 -def fill(node, holders, pattern=None, as_json=True):
353 """
354 Replace javascript values in a script node
355
356 This functions provides safe (single pass) javascript value
357 replacement (utilizing the `replace` function)::
358
359 javascript.fill(node, dict(
360 a=10,
361 b=u'Andr\\xe9',
362 c=javascript.SimpleJSON(dict(foo='bar')),
363 ))
364
365 Where `node` is something like::
366
367 <script tdi="name">
368 var count = __a__;
369 var name = '__b__';
370 var param = __c__;
371 </script>
372
373 :See:
374 - `fill_attr`
375 - `replace`
376
377 :Parameters:
378 `node` : TDI node
379 The script node
380
381 `holders` : ``dict``
382 Placeholders mappings (name -> value). If a placeholder is found
383 within the script which has no mapping, it's simply left as-is.
384 If `as_json` is true, the values are checked if they have an
385 ``as_json`` method. *If* they do have one, the method is called
386 and the result (of type ``unicode``) is used as replacement.
387 Otherwise the mapped value is piped through the `escape_string`
388 function and the result is used as replacement. ``as_json`` is
389 passed a boolean ``inlined`` parameter which indicates whether the
390 method should escape for inline usage or not.
391
392 Use the `LiteralJSON` class for passing any JSON content literally
393 to the script. There is also a `SimpleJSON` class for converting
394 complex structures to JSON using the simplejson converter. You may
395 pass your own classes as well, of course, as long as they provide
396 a proper ``as_json()`` method.
397
398 `pattern` : ``unicode`` or compiled ``re`` object
399 Placeholder name pattern. If omitted or ``None``, the pattern is
400 (simplified [#]_): ``__(?P<name>.+)__``, i.e. the placeholder name
401 enclosed in double-underscores. The name group is expected.
402
403 .. [#] The actual pattern is: ``__(?P<name>[^_]*(?:_[^_]+)*)__``
404
405 `as_json` : ``bool``
406 Check the placeholder values for an ``as_json`` method? See the
407 description of the `holders` parameter for details.
408 """
409 node.raw.content = replace(node.raw.content, holders,
410 pattern=pattern,
411 as_json=as_json,
412 inlined=True,
413 encoding=node.raw.encoder.encoding,
414 )
415
416
417 -def fill_attr(node, attr, holders, pattern=None, as_json=True):
418 """
419 Replace javascript values in a script attribute
420
421 This functions provides safe (single pass) javascript value
422 replacement (utilizing the `replace` function)::
423
424 javascript.fill_attr(node, u'onclick', dict(
425 a=10,
426 b=u'Andr\\xe9',
427 c=javascript.SimpleJSON(dict(foo='bar')),
428 ))
429
430 Where `node` is something like::
431
432 <div onclick="return foo(__a__)">...</div>
433
434 :See:
435 - `fill`
436 - `replace`
437
438 :Parameters:
439 `node` : TDI node
440 The script node
441
442 `attr` : ``basestring``
443 The name of the attribute
444
445 `holders` : ``dict``
446 Placeholders mappings (name -> value). If a placeholder is found
447 within the script which has no mapping, it's simply left as-is.
448 If `as_json` is true, the values are checked if they have an
449 ``as_json`` method. *If* they do have one, the method is called
450 and the result (of type ``unicode``) is used as replacement.
451 Otherwise the mapped value is piped through the `escape_string`
452 function and that result is used as replacement. ``as_json`` is
453 passed a boolean ``inlined`` parameter which indicates whether the
454 method should escape for inline usage or not.
455
456 Use the `LiteralJSON` class for passing any JSON content literally
457 to the script. There is also a `SimpleJSON` class for converting
458 complex structures to JSON using the simplejson converter. You may
459 pass your own classes as well, of course, as long as they provide
460 a proper ``as_json()`` method.
461
462 `pattern` : ``unicode`` or compiled ``re`` object
463 Placeholder name pattern. If omitted or ``None``, the pattern is
464 (simplified [#]_): ``__(?P<name>.+)__``, i.e. the placeholder name
465 enclosed in double-underscores. The name group is expected.
466
467 .. [#] The actual pattern is: ``__(?P<name>[^_]*(?:_[^_]+)*)__``
468
469 `as_json` : ``bool``
470 Check the placeholder values for an ``as_json`` method? See the
471 description of the `holders` parameter for details.
472 """
473 encoding = node.raw.encoder.encoding
474 node[attr] = replace(
475 _htmldecode.decode(node[attr], encoding=encoding), holders,
476 pattern=pattern,
477 as_json=as_json,
478 inlined=False,
479 encoding=encoding,
480 )
481
482
484 """
485 Literal JSON container for use with `replace` or `fill`
486
487 The container just passes its input back through as_json().
488
489 :IVariables:
490 `_json` : JSON input
491 JSON input
492
493 `_inlined` : ``bool``
494 Escape for inlining?
495
496 `_encoding` : ``str``
497 Encoding of `_json`
498 """
499
500 - def __init__(self, json, inlined=False, encoding=None):
501 """
502 Initialization
503
504 :Parameters:
505 `json` : ``basestring``
506 JSON to output
507
508 `inlined` : ``bool``
509 Escape for inlining? See `escape_inlined` for details.
510
511 `encoding` : ``str``
512 Encoding of `json`, in case it's a ``str``. If omitted or ``None``
513 and `json` is ``str``, `json` is expected to be UTF-8 encoded (or
514 ASCII only, which is compatible here)
515 """
516 self._json = json
517 self._inlined = bool(inlined)
518 self._encoding = encoding
519
521 """ Debug representation """
522 return "%s(%r, inlined=%r, encoding=%r)" % (
523 self.__class__.__name__,
524 self._json, self._inlined, self._encoding
525 )
526
528 """
529 Content as JSON
530
531 :Parameters:
532 `inlined` : ``bool`` or ``None``
533 escape for inlining? If omitted or ``None``, the default value
534 from construction is used.
535
536 :Return: JSON string
537 :Rtype: ``unicode``
538 """
539 json = self._json
540 if inlined is None:
541 inlined = self._inlined
542 if inlined:
543 json = escape_inlined(json, encoding=self._encoding)
544 if not isinstance(json, unicode):
545 encoding = self._encoding
546 if encoding is None:
547 encoding = 'utf-8'
548 json = json.decode(encoding)
549 return (json
550 .replace(u'\u2028', u'\\u2028')
551 .replace(u'\u2029', u'\\u2029')
552 )
553
554 __unicode__ = as_json
555
557 """ JSON as ``str`` (UTF-8 encoded) """
558 return self.as_json().encode('utf-8')
559
560
562 """
563 JSON generator for use with `replace` or `fill`
564
565 This class uses simplejson for generating JSON output.
566
567 The encoder looks for either the ``json`` module or, if that fails, for
568 the ``simplejson`` module. If both fail, an ImportError is raised from the
569 `as_json` method.
570
571 :IVariables:
572 `_content` : any
573 Wrapped content
574
575 `_inlined` : ``bool``
576 Escape for inlining?
577
578 `_str_encoding` : ``str``
579 Str encoding
580 """
581
582 - def __init__(self, content, inlined=False, str_encoding='latin-1'):
583 """
584 Initialization
585
586 :Parameters:
587 `content` : any
588 Content to wrap for json conversion
589
590 `inlined` : ``bool``
591 Is it going to be inlined? Certain sequences are escaped then.
592
593 `str_encoding` : ``str``
594 Encoding to be applied on ``str`` content parts. Latin-1 is
595 a failsafe default here, because it always translates. It may be
596 wrong though.
597 """
598 self._content = content
599 self._inlined = bool(inlined)
600 self._str_encoding = str_encoding
601
603 """ Debug representation """
604 return "%s(%r, %r)" % (
605 self.__class__.__name__, self._content, bool(self._inlined)
606 )
607
609 """
610 Content as JSON
611
612 :Parameters:
613 `inlined` : ``bool`` or ``None``
614 escape for inlining? If omitted or ``None``, the default value
615 from construction is used.
616
617 :Return: The JSON encoded content
618 :Rtype: ``unicode``
619 """
620 try:
621 import json as _json
622 except ImportError:
623 import simplejson as _json
624 json = _json.dumps(self._content,
625 separators=(',', ':'),
626 ensure_ascii=False,
627 encoding=self._str_encoding,
628 )
629 if isinstance(json, str):
630 json = json.decode(self._str_encoding)
631 if inlined is None:
632 inlined = self._inlined
633 if inlined:
634 json = escape_inlined(json)
635 return (json
636 .replace(u'\u2028', u'\\u2028')
637 .replace(u'\u2029', u'\\u2029')
638 )
639
640 __unicode__ = as_json
641
643 """
644 JSON as ``str`` (UTF-8 encoded)
645
646 :Return: JSON string
647 :Rtype: ``str``
648 """
649 return self.as_json().encode('utf-8')
650
651
652 -def cleanup(script, encoding=None):
653 """
654 Cleanup single JS buffer
655
656 This method attempts to remove CDATA and starting/finishing comment
657 containers.
658
659 :Parameters:
660 `script` : ``basestring``
661 Buffer to cleanup
662
663 `encoding` : ``str``
664 Encoding in case that toescape is a ``str``. If omitted or
665 ``None``, no encoding is applied and `script` is expected to be
666 ASCII compatible.
667
668 :Return: The cleaned up buffer, typed as input
669 :Rtype: ``basestring``
670 """
671
672
673 isuni = isinstance(script, unicode)
674 if not isuni:
675
676
677 if encoding is None or _norm_enc(encoding) == 'ascii':
678 encoding = 'latin-1'
679 script = str(script).decode(encoding)
680 script = script.strip()
681 if script.startswith(u'<!--'):
682 script = script[4:]
683 if script.endswith(u'-->'):
684 script = script[:-3]
685 script = script.strip()
686 if script.startswith(u'//'):
687 pos = script.find(u'\n')
688 if pos >= 0:
689 script = script[pos + 1:]
690 script = script[::-1]
691 pos = script.find(u'\n')
692 if pos >= 0:
693 line = script[:pos].strip()
694 else:
695 line = script.strip()
696 pos = len(line)
697 if line.endswith(u'//'):
698 script = script[pos + 1:]
699 script = script[::-1].strip()
700 if script.startswith(u'<![CDATA['):
701 script = script[len(u'<![CDATA['):]
702 if script.endswith(u']]>'):
703 script = script[:-3]
704 script = script.strip()
705 if script.endswith(u'-->'):
706 script = script[:-3]
707 script = script.strip()
708 if isuni:
709 return script
710 return script.encode(encoding)
711
712
713 -def cdata(script, encoding=None):
714 """
715 Add a failsafe CDATA container around a script
716
717 See <http://lists.w3.org/Archives/Public/www-html/2002Apr/0053.html>
718 for details.
719
720 :Parameters:
721 `script` : ``basestring``
722 JS to contain
723
724 `encoding` : ``str``
725 Encoding in case that toescape is a ``str``. If omitted or
726 ``None``, no encoding is applied and `script` is expected to be
727 ASCII compatible.
728
729 :Return: The contained JS, typed as input
730 :Rtype: ``basestring``
731 """
732 isuni = isinstance(script, unicode)
733 if not isuni:
734
735
736 if encoding is None or _norm_enc(encoding) == 'ascii':
737 encoding = 'latin-1'
738 script = str(script).decode(encoding)
739 script = cleanup(script)
740 if script:
741 script = u'<!--//--><![CDATA[//><!--\n%s\n//--><!]]>' % script
742 if isuni:
743 return script
744 return script.encode(encoding)
745
746
747 -def minify(script, encoding=None):
748 """
749 Minify a script (using `rjsmin`_)
750
751 .. _rjsmin: http://opensource.perlig.de/rjsmin/
752
753 :Parameters:
754 `script` : ``basestring``
755 JS to minify
756
757 `encoding` : ``str``
758 Encoding in case that toescape is a ``str``. If omitted or
759 ``None``, no encoding is applied and `script` is expected to be
760 ASCII compatible.
761
762 :Return: The minified JS, typed as input
763 :Rtype: ``basestring``
764 """
765 from tdi.tools import rjsmin as _rjsmin
766
767 isuni = isinstance(script, unicode)
768 if not isuni and encoding is not None:
769
770
771 if _norm_enc(encoding) == 'ascii':
772 encoding = 'latin-1'
773 return _rjsmin.jsmin(script.decode(encoding)).encode(encoding)
774 return _rjsmin.jsmin(script)
775
776
778 """
779 TDI filter for modifying inline javascript
780
781 :IVariables:
782 `_collecting` : ``bool``
783 Currently collecting javascript text?
784
785 `_buffer` : ``list``
786 Collection buffer
787
788 `_starttag` : ``tuple`` or ``None``
789 Original script starttag parameters
790
791 `_modify` : callable
792 Modifier function
793
794 `_attribute` : ``str``
795 ``tdi`` attribute name or ``None`` (if standalone)
796
797 `_strip` : ``bool``
798 Strip empty script elements?
799 """
800
801 - def __init__(self, builder, modifier, strip_empty=True, standalone=False):
802 """
803 Initialization
804
805 :Parameters:
806 `builder` : `tdi.interfaces.BuildingListenerInterface`
807 Builder
808
809 `modifier` : callable
810 Modifier function. Takes a script and returns the (possibly)
811 modified result.
812
813 `strip_empty` : ``bool``
814 Strip empty script elements?
815
816 `standalone` : ``bool``
817 Standalone context? In this case, we won't watch out for TDI
818 attributes.
819 """
820 super(JSInlineFilter, self).__init__(builder)
821 self._collecting = False
822 self._buffer = []
823 self._starttag = None
824 self._modify = modifier
825 self._normalize = self.builder.decoder.normalize
826 if standalone:
827 self._attribute = None
828 else:
829 self._attribute = self._normalize(
830 self.builder.analyze.attribute
831 )
832 self._strip = strip_empty
833
835 """
836 Handle starttag
837
838 Script starttags are delayed until the endtag is found. The whole
839 element is then evaluated (and possibly thrown away).
840
841 :See: `tdi.interfaces.ListenerInterface`
842 """
843 if not closed and self._normalize(name) == 'script':
844 self._collecting = True
845 self._buffer = []
846 self._starttag = name, attr, closed, data
847 else:
848 self.builder.handle_starttag(name, attr, closed, data)
849
851 """
852 Handle endtag
853
854 When currently collecting, it must be a script endtag. The script
855 element content is then modified (using the modifiy function passed
856 during initialization). The result replaces the original. If it's
857 empty and the starttag neither provides ``src`` nor ``tdi`` attributes
858 and the filter was configured to do so: the whole element is thrown
859 away.
860
861 :See: `tdi.interfaces.ListenerInterface`
862 """
863 normalize = self._normalize
864 if self._collecting:
865 if normalize(name) != 'script':
866 raise AssertionError("Invalid event stream")
867
868 self._collecting = False
869 script, self._buffer = ''.join(self._buffer), []
870 script = self._modify(script)
871
872 if not script and self._strip:
873 attrdict = dict((normalize(name), val)
874 for name, val in self._starttag[1]
875 )
876 if normalize('src') not in attrdict:
877 if self._attribute is None or \
878 self._attribute not in attrdict:
879 return
880
881 self.builder.handle_starttag(*self._starttag)
882 self._starttag = None
883 self.builder.handle_text(script)
884
885 self.builder.handle_endtag(name, data)
886
887 - def handle_text(self, data):
888 """
889 Handle text
890
891 While collecting javascript text, the received data is buffered.
892 Otherwise the event is just passed through.
893
894 :See: `tdi.interfaces.ListenerInterface`
895 """
896 if not self._collecting:
897 return self.builder.handle_text(data)
898 self._buffer.append(data)
899
900
902 """
903 TDI Filter for minifying inline javascript
904
905 :Parameters:
906 `minifier` : callable
907 Minifier function. If omitted or ``None``, the builtin minifier (see
908 `minify`) is applied.
909
910 `standalone` : ``bool``
911 Standalone context? In this case, we won't watch out for TDI
912 attributes.
913 """
914
915 if minifier is None:
916 minifier = minify
917 work = lambda x, m=minifier, c=cleanup: m(c(x))
918 return JSInlineFilter(builder, work, standalone=standalone)
919
920
922 """
923 TDI filter for adding failsafe CDATA containers around scripts
924
925 :Parameters:
926 `standalone` : ``bool``
927 Standalone context? In this case, we won't watch out for TDI
928 attributes.
929
930 See <http://lists.w3.org/Archives/Public/www-html/2002Apr/0053.html>
931 for details.
932 """
933 return JSInlineFilter(builder, cdata, standalone=standalone)
934