Commits

Mike Orr committed 33d9db2

Reimplement ``highlight`` helper using HTML builder.
Deprecate ``highlighter`` argument. Allow search phrase to be a string, a list
of strings, or a regular expression. Add ``case_sensitive``, ``class_``, and
``**attrs`` arguments.

Comments (0)

Files changed (3)

   - Change for SQLAlchemy 0.6. (bug #11)
 * webhelpers.pylonslib:
   - Fix HTML overescaping.  Patch by Marius Gedminas.
+* webhelpers.tools:
+  - Reimplement ``highlight`` using HTML builder. Deprecate ``highlighter`` 
+    argument. Allow search phrase to be a string, a list of strings, or a 
+    regular expression. Add ``case_sensitive``, ``class_``, and ``**attrs``
+    arguments.
 
 
 1.0b1 (2009-11-20)

tests/test_tools.py

 # -*- coding: utf-8 -*-
-from util import WebHelpersTestCase
+import re
 import unittest
 from string24 import Template
 
+# Relative import
+from util import WebHelpersTestCase
+
+from nose.tools import eq_
 from routes import url_for
 
 from webhelpers.html import HTML, literal
         # Failing test: PylonsHQ bug #657
         #self.assertEqual(u'&lt;<a href="http://www.google.com">www.google.com</a>&gt;', auto_link("<www.google.com>"))
 
-    def test_highlighter(self):
-        self.assertEqual("This is a <strong class=\"highlight\">beautiful</strong> morning",
-                         highlight("This is a beautiful morning", "beautiful"))
-        self.assertEqual(
-            "This is a <strong class=\"highlight\">beautiful</strong> morning, but also a <strong class=\"highlight\">beautiful</strong> day",
-            highlight("This is a beautiful morning, but also a beautiful day", "beautiful"))
-        self.assertEqual("This is a <b>beautiful</b> morning, but also a <b>beautiful</b> day",
-                         highlight("This is a beautiful morning, but also a beautiful day",
-                                   "beautiful", r'<b>\1</b>'))
-        self.assertEqual("This text is not changed because we supplied an empty phrase",
-                         highlight("This text is not changed because we supplied an empty phrase",
-                                   None))
-
-    def test_highlighter_with_regex(self):
-        self.assertEqual("This is a <strong class=\"highlight\">beautiful!</strong> morning",
-                     highlight("This is a beautiful! morning", "beautiful!"))
-
-        self.assertEqual("This is a <strong class=\"highlight\">beautiful! morning</strong>",
-                     highlight("This is a beautiful! morning", "beautiful! morning"))
-
-        self.assertEqual("This is a <strong class=\"highlight\">beautiful? morning</strong>",
-                     highlight("This is a beautiful? morning", "beautiful? morning"))
-
     def test_strip_links(self):
         self.assertEqual("on my mind", strip_links("<a href='almost'>on my mind</a>"))
         self.assertEqual("on my mind", strip_links("<A href='almost'>on my mind</A>"))
         self.assertEqual(u"<script type=\"text/javascript\">\n//<![CDATA[\neval(unescape('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%4d%79%20%65%6d%61%69%6c%3c%2f%61%3e%27%29%3b'))\n//]]>\n</script>",
                          mail_to("me@domain.com", "My email", encode = "javascript", replace_at = "(at)", replace_dot = "(dot)"))
 
+
+class TestHighlightHelper(WebHelpersTestCase):
+    def test_highlight(self):
+        eq_("This is a <strong class=\"highlight\">beautiful</strong> morning",
+                         highlight("This is a beautiful morning", "beautiful"))
+        eq_(
+            "This is a <strong class=\"highlight\">beautiful</strong> morning, but also a <strong class=\"highlight\">beautiful</strong> day",
+            highlight("This is a beautiful morning, but also a beautiful day", "beautiful"))
+        eq_("This text is not changed because we supplied an empty phrase",
+                         highlight("This text is not changed because we supplied an empty phrase",
+                                   None))
+
+    def test_highlight_with_regex(self):
+        eq_("This is a <strong class=\"highlight\">beautiful!</strong> morning",
+                     highlight("This is a beautiful! morning", "beautiful!"))
+
+        eq_("This is a <strong class=\"highlight\">beautiful! morning</strong>",
+                     highlight("This is a beautiful! morning", "beautiful! morning"))
+
+        eq_("This is a <strong class=\"highlight\">beautiful? morning</strong>",
+                     highlight("This is a beautiful? morning", "beautiful? morning"))
+
+    def test_highlight_phrases(self):
+        eq_('The c<strong class="highlight">at</strong> in the h<strong class="highlight">at</strong>.',
+            highlight("The cat in the hat.", "at"))
+        eq_('The <strong class="highlight">cat</strong> in the <strong class="highlight">hat</strong>.',
+            highlight("The cat in the hat.", ["cat", "hat"]))
+        eq_('The <strong class="highlight">cat</strong> is <strong class="highlight">cut</strong>.',
+            highlight("The cat is cut.", re.compile(R"c.t")))
+
+    def test_highlight_args(self):
+        eq_('The c<strong class="highlight">at</strong> in the h<strong class="highlight">at</strong>.',
+            highlight("The cat in the hat.", "AT"))
+        eq_('The cat in the hat.',
+            highlight("The cat in the hat.", "AT", case_sensitive=True))
+        eq_('The c<strong style="color:red">at</strong> in the h<strong style="color:red">at</strong>.',
+            highlight("The cat in the hat.", "at", class_=None,
+                style="color:red"))
+
+
 if __name__ == '__main__':
     suite = map(unittest.makeSuite, [
         TestToolsHelper,
+        TestHighlightHelper,
         TestURLHelper,
         ])
     for testsuite in suite:

webhelpers/html/tools.py

 
 import re
 import urllib
+import warnings
 
 from webhelpers.html import HTML, literal, lit_sub, escape
 from webhelpers.html.tags import convert_boolean_attrs
         return tag
 
 
-def highlight(text, phrase, 
-              highlighter='<strong class="highlight">\\1</strong>'):
-    """Highlight the ``phrase`` where it is found in the ``text``.
+
+def highlight(text, phrase, highlighter=None, case_sensitive=False, 
+    class_="highlight", **attrs):
+    """Highlight all occurrences of ``phrase`` in ``text``.
+
+    This inserts "<strong class="highlight">...</strong>" around every
+    occurrence.
+
+    Arguments:
     
-    The highlighted phrase will be surrounded by the highlighter, 
-    by default::
+    ``text``: 
+        The full text.
     
-        <strong class="highlight">I'm a highlight phrase</strong>
-    
-    ``highlighter``
-        Defines the highlighting phrase. This argument should be a 
-        single-quoted string with ``\\1`` where the phrase is supposed 
-        to be inserted.
-        
-    Note: The ``phrase`` is sanitized to include only letters, digits, 
-    and spaces before use.
+    ``phrase``: 
+        A phrase to find in the text. This may be a string, a list of strings, 
+        or a compiled regular expression. If a string, it's regex-escaped and
+        compiled. If a list, all of the strings will be highlighted.  This is
+        done by regex-escaping all elements and then joining them using the
+        regex "|" token.
 
-    Example::
+    ``highlighter``:
+        Deprecated.  A replacement expression for the regex substitution.
+        This was deprecated because it bypasses the HTML builder and creates
+        tags via string mangling.  The previous default was '<strong
+        class="highlight">\\1</strong>', which mimics the normal behavior of
+        this function.  ``phrase`` must be a string if ``highlighter`` is
+        specified.  Overrides ``class_`` and ``attrs_`` arguments.
 
-        >>> highlight('You searched for: Pylons', 'Pylons')
-        'You searched for: <strong class="highlight">Pylons</strong>'
-        
+    ``case_sensitive``:
+        If false (default), the phrases are searched in a case-insensitive
+        manner. No effect if ``phrase`` is a regex object.
+
+    ``class_``:
+        CSS class for the <strong> tag.
+
+    ``**attrs``:
+        Additional HTML attributes for the <strong> tag.
+
+    Changed in WebHelpers 1.0b2: new implementation using HTML builder.
+    Allow ``phrase`` to be list or regex.  Deprecate ``highlighter`` and
+    change its default value to None. Add ``case_sensitive``, ``class_``,
+    and ``**attrs`` arguments.
     """
     if not phrase or not text:
         return text
-    highlight_re = re.compile('(%s)' % re.escape(phrase), re.I)
-    if hasattr(text, '__html__'):
-        return literal(highlight_re.sub(highlighter, text))
+    if case_sensitive:
+        flags = 0   # No flags.
     else:
-        return highlight_re.sub(highlighter, text)
+        flags = re.IGNORECASE
+    if highlighter:
+        warnings.warn("the ``highlighter`` argument is deprecated",
+            DeprecationWarning)
+        pat = "(%s)" % re.escape(phrase)
+        rx = re.compile(pat, flags)
+        return lit_sub(rx, highlighter, text)
+    if isinstance(phrase, basestring):
+        pat = re.escape(phrase)
+        rx = re.compile(pat, flags)
+    elif isinstance(phrase, (list, tuple)):
+        parts = [re.escape(x) for x in phrase]
+        pat = "|".join(parts)
+        rx = re.compile(pat, flags)
+    else:
+        rx = phrase
+    def repl(m):
+        return HTML.strong(m.group(), class_=class_, **attrs)
+    return lit_sub(rx, repl, text)
 
 
 def auto_link(text, link="all", **href_options):
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.