quixote html.py,1.9,1.10

Neil Schemenauer <nascheme-fVcApmY9cLvQ3/1i3zOLAti2O/[email protected]>
Newsgroups gmane.comp.web.quixote.cvs
Message-ID <[email protected]>
Update of /home/cvs/quixote
In directory hewson:/tmp/cvs-serv20326

Modified Files:
	html.py 
Log Message:
Add htmltext class and supporting functions (htmlescape, htmltag, href).


Index: html.py
===================================================================
RCS file: /home/cvs/quixote/html.py,v
retrieving revision 1.9
retrieving revision 1.10
diff -u -d -r1.9 -r1.10
--- html.py	9 Jul 2002 19:56:59 -0000	1.9
+++ html.py	14 Oct 2002 22:59:07 -0000	1.10
@@ -42,37 +42,219 @@
 
 """
 
-# created 20010821, NAS (from mems.ui.lib.util and mems.ui.lib.html)
-
 __revision__ = "$Id$"
 
+import sys
 import urllib
-from types import UnicodeType
+from types import UnicodeType, TupleType, StringType
+import re
 
+if sys.hexversion < 0x20200b1:
 
-def html_quote(value, fallback=None):
-    """html_quote(value : any [, fallback : string]) -> string
+    # inefficient 2.2 compatibility hacks
 
-    Quotes 'value' for use in an HTML page.  The special characters &,
-    <, > are replaced by SGML entities.  If value is None, then the
-    behavior depends on the fallback argument.  If it is not supplied
-    then an error is raised.  Otherwise, the fallback value is returned
-    unquoted.
+    class object:
+        pass
+
+    class staticmethod:
+        def __init__(self, func):
+            self.__call__ = func
+
+    def zip(*args):
+        return map(None, *args)
+
+    def classof(o):
+        if hasattr(o, "__class__"):
+            return o.__class__
+        else:
+            return type(o)
+
+else:
+    
+    classof = type
+
+
+_format_codes = 'diouxXeEfFgGcrs%'
+_format_re = re.compile(r'%%[^%s]*([%s])' % (_format_codes, _format_codes))
+
+
+class htmltext(object):
     """
-    if value is None:
-        if fallback is None:
-            raise ValueError, "value is None and no fallback supplied"
+
+    """
+
+    __slots__ = ['s']
+
+    def quote(s):
+        s = s.replace("&", "&amp;")
+        s = s.replace("<", "&lt;")
+        s = s.replace(">", "&gt;")
+        s = s.replace('"', "&quot;")
+        return s
+    quote = staticmethod(quote)
+
+    def __new__(klass, s):
+        inst = object.__new__(klass)
+        inst.s = str(s)
+        return inst
+
+    if sys.hexversion < 0x20200b1:
+        def __init__(self, s):
+            self.s = str(s)
+
+    # XXX make read-only
+    #def __setattr__(self, name, value):
+    #    raise AttributeError, 'immutable object'
+
+    def __getstate__(self):
+        raise ValueError, 'htmltext objects should not be pickled'
+
+    def __repr__(self):
+        return '<htmltext %r>' % self.s
+
+    def __str__(self):
+        return self.s
+
+    def __len__(self):
+        return len(self.s)
+
+    def __cmp__(self, other):
+        return cmp(self.s, other)
+
+    def __hash__(self):
+        return hash(self.s)
+
+    def __mod__(self, args):
+        codes = [code[-1] for code in _format_re.findall(self.s)
+                    if code != '%']
+        if len(codes) == 1 and not isinstance(args, TupleType):
+            args = (args,)
+        quoted_args = []
+        for arg, code in zip(args, codes):
+            if classof(arg) is self.__class__:
+                quoted_args.append(arg)
+            elif code == 's':
+                quoted_args.append(self.quote(str(arg)))
+            elif code == 'r':
+                quoted_args.append(_QuotedRepr(arg, self.quote))
+            elif code == 'c':
+                raise ValueError, "htmltext does not allow '%c' format"
+            else:
+                quoted_args.append(arg)
+        return self.__class__(self.s % tuple(quoted_args))
+
+    def __add__(self, other):
+        if isinstance(other, StringType):
+            return self.__class__(self.s + self.quote(other))
+        elif classof(other) is self.__class__:
+            return self.__class__(self.s + other.s)
         else:
-            return fallback
-    if isinstance(value,  UnicodeType):
-        value = value.encode('iso-8859-1')
+            return NotImplemented
+
+    def __radd__(self, other):
+        if isinstance(other, StringType):
+            return self.__class__(self.quote(other) + self.s)
+        else:
+            return NotImplemented
+
+    def __mul__(self, n):
+        return self.__class__(self.s * n)
+
+    def join(self, items):
+        quoted_items = []
+        for item in items:
+            if classof(item) is self.__class__:
+                quoted_items.append(str(item))
+            elif isinstance(item, StringType):
+                quoted_items.append(self.quote(item))
+            else:
+                raise TypeError, 'join request string arguments'
+        return self.__class__(self.s.join(quoted_items))
+        
+    def startswith(self, s):
+        if isinstance(s, htmltext):
+            s = s.s
+        else:
+            s = self.quote(s)
+        return self.s.startswith(s)
+
+    def endswith(self, s):
+        if isinstance(s, htmltext):
+            s = s.s
+        else:
+            s = self.quote(s)
+        return self.s.endswith(s)
+
+    def replace(self, old, new, maxsplit=-1):
+        if isinstance(old, htmltext):
+            old = old.s
+        else:
+            old = self.quote(old)
+        if isinstance(new, htmltext):
+            new = new.s
+        else:
+            new = self.quote(new)
+        return self.__class__(self.s.replace(old, new))
+        
+    def lower(self):
+        return self.__class__(self.s.lower())
+
+    def upper(self):
+        return self.__class__(self.s.upper())
+
+class _QuotedRepr:
+    # helper for htmltext class
+    def __init__(self, value, quote):
+        self.value = value
+        self.quote = quote
+
+    def __repr__(self):
+        return self.quote(`self.value`)
+
+
+def htmlescape(s):
+    """htmlescape(s) -> htmltext
+
+    Return an 'htmltext' object using the argument.  If the argument is not
+    already a 'htmltext' object then the HTML markup characters ", <, >, and &
+    are first escaped.
+    """
+    if classof(s) is htmltext:
+        return s
+    elif isinstance(s,  UnicodeType):
+        s = s.encode('iso-8859-1')
     else:
-        value = str(value)
-    value = value.replace("&", "&amp;") # must be done first
-    value = value.replace("<", "&lt;")
-    value = value.replace(">", "&gt;")
-    value = value.replace('"', "&quot;")
-    return value
+        s = str(s)
+    # inline htmltext.quote for speed
+    s = s.replace("&", "&amp;") # must be done first
+    s = s.replace("<", "&lt;")
+    s = s.replace(">", "&gt;")
+    s = s.replace('"', "&quot;")
+    return htmltext(s)
+
+
+ValuelessAttr = ["valueless_attr"] # magic singleton object
+
+def htmltag (tag, xml_end=0, **attrs):
+    """Create a HTML tag.
+    """
+    r = "<%s" % tag
+    for (attr, val) in attrs.items():
+        if val is ValuelessAttr:
+            val = attr
+        if val is not None:
+            r += ' %s="%s"' % (attr, htmltext.quote(str(val)))
+    if xml_end:
+        r += " />"
+    else:
+        r += ">"
+    return htmltext(r)
+
+
+def href (url, text, title=None, **attrs):
+    return (htmltag("a", href=url, title=title, **attrs) +
+            htmltext(text) +
+            htmltext("</a>"))
 
 
 def url_quote(value, fallback=None):
@@ -95,6 +277,36 @@
     return urllib.quote(value)
 
 
+#
+# The rest of this module is for Quixote applications that were written
+# before 'htmltext'.  If you are writing a new application, ignore them.
+#
+
+def html_quote(value, fallback=None):
+    """html_quote(value : any [, fallback : string]) -> str
+
+    Quotes 'value' for use in an HTML page.  The special characters &,
+    <, > are replaced by SGML entities.  If value is None, then the
+    behavior depends on the fallback argument.  If it is not supplied
+    then an error is raised.  Otherwise, the fallback value is returned
+    unquoted.
+    """
+    if value is None:
+        if fallback is None:
+            raise ValueError, "value is None and no fallback supplied"
+        else:
+            return fallback
+    elif isinstance(value,  UnicodeType):
+        value = value.encode('iso-8859-1')
+    else:
+        value = str(value)
+    value = value.replace("&", "&amp;") # must be done first
+    value = value.replace("<", "&lt;")
+    value = value.replace(">", "&gt;")
+    value = value.replace('"', "&quot;")
+    return value
+
+
 def value_quote(value):
     """Quote HTML attribute values.  This function is of marginal
     utility since html_quote can be used.
@@ -113,8 +325,6 @@
     return render_tag("a", href=url, title=title, name=name,
                       **kwargs) + str(text) + "</a>"
 
-
-ValuelessAttr = ["valueless_attr"] # magic singleton object
 
 def render_tag (tag, xml_end=0, **attrs):
     r = "<%s" % tag
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.