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