Commits

Janto Dreijer committed e79bb2f

reorganise into standard svn structure

Comments (0)

Files changed (27)

+You will need to have the Google App Engine installed to do anything useful.
+
+The first time you load the package listing things might be a bit slow while package info is retrieved.
+You might even experience the following errors depending on your internet speed:
+    DownloadError: ApplicationError: 2 timed out
+If this keeps happening, check that your is http_proxy is set.
+
+If you find a bug, let me know and I'l fix it.
+
+Janto Dreijer
+jantod@gmail.com

code/PyRSS2Gen.py

+"""PyRSS2Gen - A Python library for generating RSS 2.0 feeds."""
+
+__name__ = "PyRSS2Gen"
+__version__ = (1, 0, 0)
+__author__ = "Andrew Dalke <dalke@dalkescientific.com>"
+
+_generator_name = __name__ + "-" + ".".join(map(str, __version__))
+
+import datetime
+
+# Could make this the base class; will need to add 'publish'
+class WriteXmlMixin:
+    def write_xml(self, outfile, encoding = "iso-8859-1"):
+        from xml.sax import saxutils
+        handler = saxutils.XMLGenerator(outfile, encoding)
+        handler.startDocument()
+        self.publish(handler)
+        handler.endDocument()
+
+    def to_xml(self, encoding = "iso-8859-1"):
+        try:
+            import cStringIO as StringIO
+        except ImportError:
+            import StringIO
+        f = StringIO.StringIO()
+        self.write_xml(f, encoding)
+        return f.getvalue()
+
+
+def _element(handler, name, obj, d = {}):
+    if isinstance(obj, basestring) or obj is None:
+        # special-case handling to make the API easier
+        # to use for the common case.
+        handler.startElement(name, d)
+        if obj is not None:
+            handler.characters(obj)
+        handler.endElement(name)
+    else:
+        # It better know how to emit the correct XML.
+        obj.publish(handler)
+
+def _opt_element(handler, name, obj):
+    if obj is None:
+        return
+    _element(handler, name, obj)
+
+
+def _format_date(dt):
+    """convert a datetime into an RFC 822 formatted date
+
+    Input date must be in GMT.
+    """
+    # Looks like:
+    #   Sat, 07 Sep 2002 00:00:01 GMT
+    # Can't use strftime because that's locale dependent
+    #
+    # Isn't there a standard way to do this for Python?  The
+    # rfc822 and email.Utils modules assume a timestamp.  The
+    # following is based on the rfc822 module.
+    return "%s, %02d %s %04d %02d:%02d:%02d GMT" % (
+            ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"][dt.weekday()],
+            dt.day,
+            ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
+             "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][dt.month-1],
+            dt.year, dt.hour, dt.minute, dt.second)
+
+        
+##
+# A couple simple wrapper objects for the fields which
+# take a simple value other than a string.
+class IntElement:
+    """implements the 'publish' API for integers
+
+    Takes the tag name and the integer value to publish.
+    
+    (Could be used for anything which uses str() to be published
+    to text for XML.)
+    """
+    element_attrs = {}
+    def __init__(self, name, val):
+        self.name = name
+        self.val = val
+    def publish(self, handler):
+        handler.startElement(self.name, self.element_attrs)
+        handler.characters(str(self.val))
+        handler.endElement(self.name)
+
+class DateElement:
+    """implements the 'publish' API for a datetime.datetime
+
+    Takes the tag name and the datetime to publish.
+
+    Converts the datetime to RFC 2822 timestamp (4-digit year).
+    """
+    def __init__(self, name, dt):
+        self.name = name
+        self.dt = dt
+    def publish(self, handler):
+        _element(handler, self.name, _format_date(self.dt))
+####
+
+class Category:
+    """Publish a category element"""
+    def __init__(self, category, domain = None):
+        self.category = category
+        self.domain = domain
+    def publish(self, handler):
+        d = {}
+        if self.domain is not None:
+            d["domain"] = self.domain
+        _element(handler, "category", self.category, d)
+
+class Cloud:
+    """Publish a cloud"""
+    def __init__(self, domain, port, path,
+                 registerProcedure, protocol):
+        self.domain = domain
+        self.port = port
+        self.path = path
+        self.registerProcedure = registerProcedure
+        self.protocol = protocol
+    def publish(self, handler):
+        _element(handler, "cloud", None, {
+            "domain": self.domain,
+            "port": str(self.port),
+            "path": self.path,
+            "registerProcedure": self.registerProcedure,
+            "protocol": self.protocol})
+
+class Image:
+    """Publish a channel Image"""
+    element_attrs = {}
+    def __init__(self, url, title, link,
+                 width = None, height = None, description = None):
+        self.url = url
+        self.title = title
+        self.link = link
+        self.width = width
+        self.height = height
+        self.description = description
+        
+    def publish(self, handler):
+        handler.startElement("image", self.element_attrs)
+
+        _element(handler, "url", self.url)
+        _element(handler, "title", self.title)
+        _element(handler, "link", self.link)
+
+        width = self.width
+        if isinstance(width, int):
+            width = IntElement("width", width)
+        _opt_element(handler, "width", width)
+        
+        height = self.height
+        if isinstance(height, int):
+            height = IntElement("height", height)
+        _opt_element(handler, "height", height)
+
+        _opt_element(handler, "description", self.description)
+
+        handler.endElement("image")
+
+class Guid:
+    """Publish a guid
+
+    Defaults to being a permalink, which is the assumption if it's
+    omitted.  Hence strings are always permalinks.
+    """
+    def __init__(self, guid, isPermaLink = 1):
+        self.guid = guid
+        self.isPermaLink = isPermaLink
+    def publish(self, handler):
+        d = {}
+        if self.isPermaLink:
+            d["isPermaLink"] = "true"
+        else:
+            d["isPermaLink"] = "false"
+        _element(handler, "guid", self.guid, d)
+
+class TextInput:
+    """Publish a textInput
+
+    Apparently this is rarely used.
+    """
+    element_attrs = {}
+    def __init__(self, title, description, name, link):
+        self.title = title
+        self.description = description
+        self.name = name
+        self.link = link
+
+    def publish(self, handler):
+        handler.startElement("textInput", self.element_attrs)
+        _element(handler, "title", self.title)
+        _element(handler, "description", self.description)
+        _element(handler, "name", self.name)
+        _element(handler, "link", self.link)
+        handler.endElement("textInput")
+        
+
+class Enclosure:
+    """Publish an enclosure"""
+    def __init__(self, url, length, type):
+        self.url = url
+        self.length = length
+        self.type = type
+    def publish(self, handler):
+        _element(handler, "enclosure", None,
+                 {"url": self.url,
+                  "length": str(self.length),
+                  "type": self.type,
+                  })
+
+class Source:
+    """Publish the item's original source, used by aggregators"""
+    def __init__(self, name, url):
+        self.name = name
+        self.url = url
+    def publish(self, handler):
+        _element(handler, "source", self.name, {"url": self.url})
+
+class SkipHours:
+    """Publish the skipHours
+
+    This takes a list of hours, as integers.
+    """
+    element_attrs = {}
+    def __init__(self, hours):
+        self.hours = hours
+    def publish(self, handler):
+        if self.hours:
+            handler.startElement("skipHours", self.element_attrs)
+            for hour in self.hours:
+                _element(handler, "hour", str(hour))
+            handler.endElement("skipHours")
+
+class SkipDays:
+    """Publish the skipDays
+
+    This takes a list of days as strings.
+    """
+    element_attrs = {}
+    def __init__(self, days):
+        self.days = days
+    def publish(self, handler):
+        if self.days:
+            handler.startElement("skipDays", self.element_attrs)
+            for day in self.days:
+                _element(handler, "day", day)
+            handler.endElement("skipDays")
+
+class RSS2(WriteXmlMixin):
+    """The main RSS class.
+
+    Stores the channel attributes, with the "category" elements under
+    ".categories" and the RSS items under ".items".
+    """
+    
+    rss_attrs = {"version": "2.0"}
+    element_attrs = {}
+    def __init__(self,
+                 title,
+                 link,
+                 description,
+
+                 language = None,
+                 copyright = None,
+                 managingEditor = None,
+                 webMaster = None,
+                 pubDate = None,  # a datetime, *in* *GMT*
+                 lastBuildDate = None, # a datetime
+                 
+                 categories = None, # list of strings or Category
+                 generator = _generator_name,
+                 docs = "http://blogs.law.harvard.edu/tech/rss",
+                 cloud = None,    # a Cloud
+                 ttl = None,      # integer number of minutes
+
+                 image = None,     # an Image
+                 rating = None,    # a string; I don't know how it's used
+                 textInput = None, # a TextInput
+                 skipHours = None, # a SkipHours with a list of integers
+                 skipDays = None,  # a SkipDays with a list of strings
+
+                 items = None,     # list of RSSItems
+                 ):
+        self.title = title
+        self.link = link
+        self.description = description
+        self.language = language
+        self.copyright = copyright
+        self.managingEditor = managingEditor
+
+        self.webMaster = webMaster
+        self.pubDate = pubDate
+        self.lastBuildDate = lastBuildDate
+        
+        if categories is None:
+            categories = []
+        self.categories = categories
+        self.generator = generator
+        self.docs = docs
+        self.cloud = cloud
+        self.ttl = ttl
+        self.image = image
+        self.rating = rating
+        self.textInput = textInput
+        self.skipHours = skipHours
+        self.skipDays = skipDays
+
+        if items is None:
+            items = []
+        self.items = items
+
+    def publish(self, handler):
+        handler.startElement("rss", self.rss_attrs)
+        handler.startElement("channel", self.element_attrs)
+        _element(handler, "title", self.title)
+        _element(handler, "link", self.link)
+        _element(handler, "description", self.description)
+
+        self.publish_extensions(handler)
+        
+        _opt_element(handler, "language", self.language)
+        _opt_element(handler, "copyright", self.copyright)
+        _opt_element(handler, "managingEditor", self.managingEditor)
+        _opt_element(handler, "webMaster", self.webMaster)
+
+        pubDate = self.pubDate
+        if isinstance(pubDate, datetime.datetime):
+            pubDate = DateElement("pubDate", pubDate)
+        _opt_element(handler, "pubDate", pubDate)
+
+        lastBuildDate = self.lastBuildDate
+        if isinstance(lastBuildDate, datetime.datetime):
+            lastBuildDate = DateElement("lastBuildDate", lastBuildDate)
+        _opt_element(handler, "lastBuildDate", lastBuildDate)
+
+        for category in self.categories:
+            if isinstance(category, basestring):
+                category = Category(category)
+            category.publish(handler)
+
+        _opt_element(handler, "generator", self.generator)
+        _opt_element(handler, "docs", self.docs)
+
+        if self.cloud is not None:
+            self.cloud.publish(handler)
+
+        ttl = self.ttl
+        if isinstance(self.ttl, int):
+            ttl = IntElement("ttl", ttl)
+        _opt_element(handler, "tt", ttl)
+
+        if self.image is not None:
+            self.image.publish(handler)
+
+        _opt_element(handler, "rating", self.rating)
+        if self.textInput is not None:
+            self.textInput.publish(handler)
+        if self.skipHours is not None:
+            self.skipHours.publish(handler)
+        if self.skipDays is not None:
+            self.skipDays.publish(handler)
+
+        for item in self.items:
+            item.publish(handler)
+
+        handler.endElement("channel")
+        handler.endElement("rss")
+
+    def publish_extensions(self, handler):
+        # Derived classes can hook into this to insert
+        # output after the three required fields.
+        pass
+
+    
+    
+class RSSItem(WriteXmlMixin):
+    """Publish an RSS Item"""
+    element_attrs = {}
+    def __init__(self,
+                 title = None,  # string
+                 link = None,   # url as string
+                 description = None, # string
+                 author = None,      # email address as string
+                 categories = None,  # list of string or Category
+                 comments = None,  # url as string
+                 enclosure = None, # an Enclosure
+                 guid = None,    # a unique string
+                 pubDate = None, # a datetime
+                 source = None,  # a Source
+                 ):
+        
+        if title is None and description is None:
+            raise TypeError(
+                "must define at least one of 'title' or 'description'")
+        self.title = title
+        self.link = link
+        self.description = description
+        self.author = author
+        if categories is None:
+            categories = []
+        self.categories = categories
+        self.comments = comments
+        self.enclosure = enclosure
+        self.guid = guid
+        self.pubDate = pubDate
+        self.source = source
+        # It sure does get tedious typing these names three times...
+
+    def publish(self, handler):
+        handler.startElement("item", self.element_attrs)
+        _opt_element(handler, "title", self.title)
+        _opt_element(handler, "link", self.link)
+        self.publish_extensions(handler)
+        _opt_element(handler, "description", self.description)
+        _opt_element(handler, "author", self.author)
+
+        for category in self.categories:
+            if isinstance(category, basestring):
+                category = Category(category)
+            category.publish(handler)
+        
+        _opt_element(handler, "comments", self.comments)
+        if self.enclosure is not None:
+            self.enclosure.publish(handler)
+        _opt_element(handler, "guid", self.guid)
+
+        pubDate = self.pubDate
+        if isinstance(pubDate, datetime.datetime):
+            pubDate = DateElement("pubDate", pubDate)
+        _opt_element(handler, "pubDate", pubDate)
+
+        if self.source is not None:
+            self.source.publish(handler)
+        
+        handler.endElement("item")
+
+    def publish_extensions(self, handler):
+        # Derived classes can hook into this to insert
+        # output after the title and link elements
+        pass
+application: scikits
+version: 1
+runtime: python
+api_version: 1
+
+handlers:
+- url: /favicon.ico
+  static_files: static/images/favicon.ico
+  upload: static/images/favicon.ico
+
+- url: /static
+  static_dir: static
+
+- url: .*
+  script: scikits.py
+"""
+Network Utilities
+(from web.py)
+"""
+
+__all__ = [
+  "validipaddr", "validipport", "validip", "validaddr", 
+  "urlquote",
+  "httpdate", "parsehttpdate", 
+  "htmlquote", "htmlunquote", "websafe",
+]
+
+import urllib, time
+try: import datetime
+except ImportError: pass
+
+def validipaddr(address):
+    """returns True if `address` is a valid IPv4 address"""
+    try:
+        octets = address.split('.')
+        assert len(octets) == 4
+        for x in octets:
+            assert 0 <= int(x) <= 255
+    except (AssertionError, ValueError):
+        return False
+    return True
+
+def validipport(port):
+    """returns True if `port` is a valid IPv4 port"""
+    try:
+        assert 0 <= int(port) <= 65535
+    except (AssertionError, ValueError):
+        return False
+    return True
+
+def validip(ip, defaultaddr="0.0.0.0", defaultport=8080):
+    """returns `(ip_address, port)` from string `ip_addr_port`"""
+    addr = defaultaddr
+    port = defaultport
+    
+    ip = ip.split(":", 1)
+    if len(ip) == 1:
+        if not ip[0]:
+            pass
+        elif validipaddr(ip[0]):
+            addr = ip[0]
+        elif validipport(ip[0]):
+            port = int(ip[0])
+        else:
+            raise ValueError, ':'.join(ip) + ' is not a valid IP address/port'
+    elif len(ip) == 2:
+        addr, port = ip
+        if not validipaddr(addr) and validipport(port):
+            raise ValueError, ':'.join(ip) + ' is not a valid IP address/port'
+        port = int(port)
+    else:
+        raise ValueError, ':'.join(ip) + ' is not a valid IP address/port'
+    return (addr, port)
+
+def validaddr(string_):
+    """
+    returns either (ip_address, port) or "/path/to/socket" from string_
+    
+        >>> validaddr('/path/to/socket')
+        '/path/to/socket'
+        >>> validaddr('8000')
+        ('0.0.0.0', 8000)
+        >>> validaddr('127.0.0.1')
+        ('127.0.0.1', 8080)
+        >>> validaddr('127.0.0.1:8000')
+        ('127.0.0.1', 8000)
+        >>> validaddr('fff')
+        Traceback (most recent call last):
+            ...
+        ValueError: fff is not a valid IP address/port
+    """
+    if '/' in string_:
+        return string_
+    else:
+        return validip(string_)
+
+def urlquote(val):
+    """
+    Quotes a string for use in a URL.
+    
+        >>> urlquote('://?f=1&j=1')
+        '%3A//%3Ff%3D1%26j%3D1'
+        >>> urlquote(None)
+        ''
+        >>> urlquote(u'\u203d')
+        '%E2%80%BD'
+    """
+    if val is None: return ''
+    if not isinstance(val, unicode): val = str(val)
+    else: val = val.encode('utf-8')
+    return urllib.quote(val)
+
+def httpdate(date_obj):
+    """
+    Formats a datetime object for use in HTTP headers.
+    
+        >>> import datetime
+        >>> httpdate(datetime.datetime(1970, 1, 1, 1, 1, 1))
+        'Thu, 01 Jan 1970 01:01:01 GMT'
+    """
+    return date_obj.strftime("%a, %d %b %Y %H:%M:%S GMT")
+
+def parsehttpdate(string_):
+    """
+    Parses an HTTP date into a datetime object.
+
+        >>> parsehttpdate('Thu, 01 Jan 1970 01:01:01 GMT')
+        datetime.datetime(1970, 1, 1, 1, 1, 1)
+    """
+    try:
+        t = time.strptime(string_, "%a, %d %b %Y %H:%M:%S %Z")
+    except ValueError:
+        return None
+    return datetime.datetime(*t[:6])
+
+def htmlquote(text):
+    """
+    Encodes `text` for raw use in HTML.
+    
+        >>> htmlquote("<'&\\">")
+        '&lt;&#39;&amp;&quot;&gt;'
+    """
+    text = text.replace("&", "&amp;") # Must be done first!
+    text = text.replace("<", "&lt;")
+    text = text.replace(">", "&gt;")
+    text = text.replace("'", "&#39;")
+    text = text.replace('"', "&quot;")
+    return text
+
+def htmlunquote(text):
+    """
+    Decodes `text` that's HTML quoted.
+
+        >>> htmlunquote('&lt;&#39;&amp;&quot;&gt;')
+        '<\\'&">'
+    """
+    text = text.replace("&quot;", '"')
+    text = text.replace("&#39;", "'")
+    text = text.replace("&gt;", ">")
+    text = text.replace("&lt;", "<")
+    text = text.replace("&amp;", "&") # Must be done last!
+    return text
+
+def websafe(val):
+    """
+    Converts `val` so that it's safe for use in UTF-8 HTML.
+    
+        >>> websafe("<'&\\">")
+        '&lt;&#39;&amp;&quot;&gt;'
+        >>> websafe(None)
+        ''
+        >>> websafe(u'\u203d')
+        '\\xe2\\x80\\xbd'
+    """
+    if val is None:
+        return ''
+    if isinstance(val, unicode):
+        val = val.encode('utf-8')
+    val = str(val)
+    return htmlquote(val)
+
+if __name__ == "__main__":
+    import doctest
+    doctest.testmod()
+#!/usr/bin/python
+"""An RDF/XML Parser. Sean B. Palmer, 2003-05. GPL 2. Thanks to bitsko."""
+
+import sys, re, urllib, cStringIO, xml.sax, xml.sax.handler
+try: from uripath import join as urijoin
+except ImportError: from urlparse import urljoin as urijoin
+
+class Namespace(unicode): 
+   def __getattr__(self, name): return self + name
+
+rdf = Namespace("http://www.w3.org/1999/02/22-rdf-syntax-ns#")
+x = Namespace("http://www.w3.org/XML/1998/namespace")
+
+class Element(object): 
+   def __init__(self, (pfx, n), qn, attrs, p=None, base=None, qnames=True): 
+      self.base = attrs.x.get(x.base) or (p and p.base) or base or ''
+      self.language = attrs.x.get(x.lang) or (p and p.language) or ''
+      self.URI = pfx + n
+      self.qname = (qnames and qn) or 'foo:bar'
+      self.attrs = attrs or {}
+      self.parent = p
+      self.children, self.text, self.xtext = [], '', ['', '', '']
+
+   def __getitem__(self, attr): 
+      return self.attrs[attr]
+
+class Attribs(dict): 
+   def __init__(self, attrs, qnames=True):
+      self.x = {}
+      self.qnames = []
+      for name, value in attrs.items():
+         p, n = name
+         if p == x: self.x[p + n] = value
+         else: dict.__setitem__(self, (p or rdf) + n, value)
+   def __repr__(self): 
+      return ''.join([' %s="%s"' % (q, self.val(q)) for q in self.qnames])
+
+r_id = re.compile(r'^i([rd]+)')
+r_quot = re.compile(r'([^\\])"')
+
+class RDFParser(xml.sax.handler.ContentHandler): 
+   def __init__(self, sink, base=None, qnames=True): 
+      self.triple = sink.triple
+      self.stack = []
+      self.base = base or ''
+      self.genID = 0
+      self.qnames = qnames
+      self.disallowed = [rdf.RDF, rdf.ID, rdf.about, rdf.bagID, 
+           rdf.parseType, rdf.resource, rdf.nodeID, rdf.datatype, 
+           rdf.li, rdf.aboutEach, rdf.aboutEachPrefix]
+
+   def startElementNS(self, n, q, a): 
+      if self.stack: e = Element(n, q, Attribs(a, self.qnames), 
+         self.stack[-1], qnames=self.qnames)
+      else: e = Element(n, q, Attribs(a, self.qnames), 
+         base=self.base, qnames=self.qnames)
+      e.xtext[0] += '<'+e.qname+((`e.attrs`.strip() and `e.attrs`) or '')+'>'
+      self.stack += [e]
+	
+   def characters(self, chars): 
+      self.stack[-1].text += chars
+      self.stack[-1].xtext[1] += chars
+	
+   def endElementNS(self, name, qname): 
+      element = self.stack.pop()
+      element.xtext[2] += '</'+element.qname+'>'
+      if self.stack: 
+         self.stack[-1].children += [element]
+         self.stack[-1].xtext[1] += ''.join(element.xtext)
+      else: self.document(element)
+
+   def uri(self, u): 
+      return "<%s>" % u
+
+   def bNode(self, label=None): 
+      if label: 
+         if not label[0].isalpha(): label = 'b' + label
+         return '_:' + r_id.sub('ir\g<1>', label)
+      self.genID = self.genID + 1
+      return '_:id%s' % (self.genID - 1)
+
+   def literal(self, s, lang=None, dtype=None): 
+      if lang and dtype: raise "ParseError", "Can't have both"
+      return ''.join(('"%s"' % r_quot.sub('\g<1>\\"', `unicode(s)`[2:-1]), 
+          lang and ("@" + lang) or '', dtype and ("^^<%s>" % dtype) or ''))
+
+   def document(self, doc): 
+      assert doc.URI == rdf.RDF, "Couldn't find rdf:RDF element"
+      for element in doc.children: self.nodeElement(element)
+
+   def nodeElement(self, e): 
+      assert e.URI not in self.disallowed, "Disallowed element used as node"
+
+      if e.attrs.has_key(rdf.ID): 
+         e.subject = self.uri(urijoin(e.base, "#" + e[rdf.ID]))
+      elif e.attrs.has_key(rdf.about): 
+         e.subject = self.uri(urijoin(e.base, e[rdf.about]))
+      elif e.attrs.has_key(rdf.nodeID): e.subject = self.bNode(e[rdf.nodeID])
+      elif not hasattr(e, 'subject'): e.subject = self.bNode()
+
+      if e.URI != rdf.Description: 
+         self.triple(e.subject, self.uri(rdf.type), self.uri(e.URI))
+      if e.attrs.has_key(rdf.type): 
+         self.triple(e.subject, self.uri(rdf.type), self.uri(e[rdf.type]))
+      for attr in e.attrs.keys(): 
+         if attr not in self.disallowed + [rdf.type]: 
+            objt = self.literal(e[attr], e.language)
+            self.triple(e.subject, self.uri(attr), objt)
+
+      for element in e.children: 
+         self.propertyElt(element)
+
+   def propertyElt(self, e): 
+      if e.URI == rdf.li: 
+         if not hasattr(e.parent, 'liCounter'): e.parent.liCounter = 1
+         e.URI = rdf + '_' + str(e.parent.liCounter)
+         e.parent.liCounter += 1
+
+      if len(e.children) == 1 and not e.attrs.has_key(rdf.parseType): 
+         self.resourcePropertyElt(e)
+      elif len(e.children) == 0 and e.text: 
+         self.literalPropertyElt(e)
+      elif e.attrs.has_key(rdf.parseType): 
+         if e[rdf.parseType] == "Resource": 
+            self.parseTypeResourcePropertyElt(e)
+         elif e[rdf.parseType] == "Collection": 
+            self.parseTypeCollectionPropertyElt(e)
+         else: self.parseTypeLiteralOrOtherPropertyElt(e)
+      elif not e.text: self.emptyPropertyElt(e)
+
+   def resourcePropertyElt(self, e): 
+      n = e.children[0]
+      self.nodeElement(n)
+      self.triple(e.parent.subject, self.uri(e.URI), n.subject)
+      if e.attrs.has_key(rdf.ID): 
+         i = self.uri(urijoin(e.base, ('#' + e.attrs[rdf.ID])))
+         self.reify(i, e.parent.subject, self.uri(e.URI), n.subject)
+
+   def reify(self, r, s, p, o): 
+      self.triple(r, self.uri(rdf.subject), s)
+      self.triple(r, self.uri(rdf.predicate), p)
+      self.triple(r, self.uri(rdf.object), o)
+      self.triple(r, self.uri(rdf.type), self.uri(rdf.Statement))
+
+   def literalPropertyElt(self, e): 
+      o = self.literal(e.text, e.language, e.attrs.get(rdf.datatype))
+      self.triple(e.parent.subject, self.uri(e.URI), o)
+      if e.attrs.has_key(rdf.ID): 
+         i = self.uri(urijoin(e.base, ('#' + e.attrs[rdf.ID])))
+         self.reify(i, e.parent.subject, self.uri(e.URI), o)
+
+   def parseTypeLiteralOrOtherPropertyElt(self, e): 
+      o = self.literal(e.xtext[1], e.language, rdf.XMLLiteral)
+      self.triple(e.parent.subject, self.uri(e.URI), o)
+      if e.attrs.has_key(rdf.ID): 
+         e.subject = i = self.uri(urijoin(e.base, ('#' + e.attrs[rdf.ID])))
+         self.reify(i, e.parent.subject, self.uri(e.URI), o)
+
+   def parseTypeResourcePropertyElt(self, e): 
+      n = self.bNode()
+      self.triple(e.parent.subject, self.uri(e.URI), n)
+      if e.attrs.has_key(rdf.ID): 
+         e.subject = i = self.uri(urijoin(e.base, ('#' + e.attrs[rdf.ID])))
+         self.reify(i, e.parent.subject, self.uri(e.URI), n)
+      c = Element((rdf, 'Description'), e.qname, e.attrs, e.parent, e.base)
+      c.subject = n
+      for child in e.children: 
+         child.parent = c
+         c.children += [child]
+      self.nodeElement(c)
+
+   def parseTypeCollectionPropertyElt(self, e): 
+      for element in e.children: 
+         self.nodeElement(element)
+      s = [self.bNode() for f in e.children]
+      if not s: 
+         self.triple(e.parent.subject, self.uri(e.URI), self.uri(rdf.nil))
+      else: 
+         self.triple(e.parent.subject, self.uri(e.URI), s[0])
+         for n in s: self.triple(n, self.uri(rdf.type), self.uri(rdf.List))
+         for i in range(len(s)): 
+            self.triple(s[i], self.uri(rdf.first), e.children[i].subject) 
+         for i in range(len(s) - 1): 
+            self.triple(s[i], self.uri(rdf.rest), s[i+1])
+         self.triple(s[-1], self.uri(rdf.rest), self.uri(rdf.nil))
+
+   def emptyPropertyElt(self, e): 
+      if e.attrs.keys() in ([], [rdf.ID]): 
+         r = self.literal(e.text, e.language) # was o
+         self.triple(e.parent.subject, self.uri(e.URI), r)
+      else: 
+         if e.attrs.has_key(rdf.resource): 
+            r = self.uri(urijoin(e.base, e[rdf.resource]))
+         elif e.attrs.has_key(rdf.nodeID): r = self.bNode(e[rdf.nodeID])
+         else: r = self.bNode()
+
+         for a in filter(lambda n: n not in self.disallowed, e.attrs.keys()): 
+            if a != rdf.type: 
+               o = self.literal(e.attrs[a], e.language)
+               self.triple(r, self.uri(a), o)
+            else: self.triple(r, self.uri(rdf.type), self.uri(e.attrs[a]))
+         self.triple(e.parent.subject, self.uri(e.URI), r)
+      if e.attrs.has_key(rdf.ID): 
+         i = self.uri(urijoin(e.base, ('#' + e.attrs[rdf.ID])))
+         self.reify(i, e.parent.subject, self.uri(e.URI), r)
+
+class Sink(object): 
+   def __init__(self): self.result = ""
+   def triple(self, s, p, o): self.result += "%s %s %s .\n" % (s, p, o)
+   def write(self): print self.result.rstrip().encode('utf-8')
+
+def parseRDF(s, base=None, sink=None): 
+   sink = sink or Sink()
+   parser = xml.sax.make_parser()
+   parser.start_namespace_decl("xml", x)
+   parser.setFeature(xml.sax.handler.feature_namespaces, 1)
+   try: parser.setFeature(xml.sax.handler.feature_namespace_prefixes, 1)
+   except xml.sax._exceptions.SAXNotSupportedException: pass
+   except xml.sax._exceptions.SAXNotRecognizedException: pass
+   parser.setContentHandler(RDFParser(sink, base, qnames=False))
+   parser.parse(cStringIO.StringIO(s))
+   return sink
+
+def parseURI(uri, sink=None): 
+   return parseRDF(urllib.urlopen(uri).read(), base=uri, sink=sink)
+
+if __name__=="__main__": 
+   if len(sys.argv) != 2: print __doc__
+   else: parseURI(sys.argv[1]).write()
+#!/usr/bin/env python
+
+from __future__ import division
+
+from google.appengine.api import users, urlfetch, memcache, mail
+from google.appengine.ext import webapp, db
+from google.appengine.api import users
+
+from tools import *
+
+import templates
+
+"""
+
+logger-level rule-of-thumbs
+	everything goes into debug by default, higher levels are interesting to human
+	debug
+		normal operation
+	info
+		something is unique to this request e.g. forcing bypass of cache
+	warn
+		e.g. connection to a remote server lost
+	error
+		unrecoverable
+		additional try-catch info
+
+"""
+
+def get_url(url, force_fetch=False):
+	result = memcache.get(url)
+	if result is None or force_fetch:
+		logger.debug("fetching %s" % url)
+		result = urlfetch.fetch(url)
+		assert memcache.set(key=url, value=result, time=60*60*5), url
+	else:
+		logger.debug("cache hit for %s" % url)
+	return result
+
+class Page(webapp.RequestHandler):
+
+	name = ""
+
+	def __init__(self):
+		self.init_time = time.time()
+		webapp.RequestHandler.__init__(self)
+		self.logger = logging.getLogger(self.name)
+
+		# load from templates.py
+		for t in [
+			"header_template",
+			"footer_template",
+			"main_page_template",
+			"about_template",
+			"package_info_template",
+		]:
+			template_name = t.rsplit("_", 1)[0]
+			text = getattr(templates, t)
+
+			template = PageTemplate.all().filter("name =", template_name).get()
+			if template:
+				pass
+			else:
+				self.logger.info("loading %s from templates.py into datastore" % template_name)
+				template = PageTemplate(name=template_name, text=text, modified=datetime.datetime.now())
+				template.put()
+
+	def write(self, text):
+		self.response.out.write(text)
+
+	def print_header(self):
+		title = self.name
+		search_box_html = SearchPage.search_box()
+		self.write(get_template("header") % locals())
+
+	def print_footer(self):
+		# http://code.google.com/apis/analytics/docs/gaTrackingOverview.html
+		google_analytics = """
+			<script type="text/javascript">
+			var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
+			document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
+			</script>
+			<script type="text/javascript">
+			try {
+			var pageTracker = _gat._getTracker("UA-6588556-1");
+			pageTracker._trackPageview();
+			} catch(err) {}</script>
+		"""
+		#~ if os.environ.get("REMOTE_ADDR") == "127.0.0.1":
+			#~ google_analytics = "<!-- google analytics skipped when accessed from local -->"
+		if ON_DEV_SERVER:
+			google_analytics = "<!-- google analytics skipped when not run at google -->"
+		load_time = time.time() - self.init_time
+		self.write(get_template("footer") % locals())
+
+	def print_menu(self):
+		pass
+
+class MainPage(Page):
+
+	name = "main"
+
+	def get(self):
+		self.print_header()
+		self.print_menu()
+		self.write(get_template("main_page")% locals())
+		self.print_footer()
+
+class PackagesPage(Page):
+
+	name = "scikits"
+
+	def get(self):
+		self.print_header()
+		self.print_menu()
+
+		packages = sorted(System.packages().values())
+
+		# force fetch of some package
+		if packages:
+			key = "next_package_fetch_index"
+			n = memcache.get(key)
+			if n is None:
+				n = 0
+			memcache.set(key, (n+1) % len(packages))
+			n %= len(packages) # in case a kit is removed from repo
+			package = packages[n]
+			self.logger.info("forcing fetch of package: %s" % package.name)
+			package.info(force_fetch=True)
+			self.write("<!-- forced fetch of package: %s -->\n" % package.name)
+
+		self.write("<h1>SciKits Index</h1>")
+
+		self.write("<p>")
+		self.write(table_of_packages(packages))
+		self.write("</p>")
+
+		self.print_footer()
+
+class PackageInfoPage(Page):
+	name = "package info"
+
+	def get(self, *args):
+		"""
+		@name
+		"""
+
+		package_name = self.request.get("name")
+		if args:
+			package_name = args[0]
+
+		# done before printing header to build title
+		package = System.packages().get(package_name)
+		if package is None:
+			package_name = "scikits.%s" % package_name
+			package = System.packages().get(package_name)
+		if package is None:
+			self.error(404)
+			self.write("404 not found")
+			return
+		self.name = package.name
+
+		self.print_header()
+		self.print_menu()
+
+		self.write(package.to_html())
+
+		self.print_footer()
+
+def make_link(url):
+	return '<a href="%s">%s</a>' % (url, url)
+
+def table_of_packages(packages):
+	result = ["<table>"]
+	odd = False
+	for p in packages:
+		odd = not odd
+		if odd:
+			result.append('<tr style="background-color: #f8f8f8">')
+		else:
+			result.append('<tr>')
+		d = p.info()
+		r = """
+		<td><a href="/%(name)s">%(short_name)s</a></td>
+		<td>%(shortdesc)s</td>
+		</tr>
+		""" % dictadd(p.__dict__, d, locals())
+		result.append(r)
+	result.append("</table>")
+	return "\n".join(result)
+
+class Package(object):
+	def __init__(self, repo_url):
+		"""
+		init should cache minimal information. actual information extraction should be done on page requests. with memcaching where needed.
+		"""
+		self.repo_url = repo_url
+
+		self.name = "scikits.%s" % os.path.split(self.repo_url)[1]
+
+		self.readme_filename = os.path.join(self.repo_url, "README")
+
+		url = os.path.join(self.repo_url, "setup.py")
+		result = get_url(url)
+		if result.status_code != 200:
+			self.valid = False
+			return
+
+		self.valid = True
+
+	def __cmp__(self, other):
+		return cmp(self.name, other.name)
+
+	def info(self, force_fetch=False):
+		d = dict(
+			name=self.name,
+			shortdesc="",
+			description="",
+			homepage="",
+			revision="",
+			people="",
+			)
+		doap_result = get_url("http://pypi.python.org/pypi?:action=doap&name=%s" % self.name, force_fetch=force_fetch)
+		if doap_result.status_code == 200:
+
+			doap_text = doap_result.content
+			#~ http://wiki.python.org/moin/PyPIXmlRpc?highlight=(CategoryDocumentation)
+			try:
+				tuples = rdfToPython(doap_text)
+			except:
+				logger.error(doap_text)
+				raise
+			d["people"] = []
+			for subject, predicate, ob in tuples:
+
+				if predicate == "<http://xmlns.com/foaf/0.1/name>":
+					d["people"].append(ob[1:-1])
+					continue
+
+				#~ logger.warn((subject, predicate, ob))
+				name = re.findall(r"doap#(.+)>", predicate)
+				if not name:
+					continue
+				name = name[0]
+				value = ob
+
+				if name == "homepage":
+					value = value[1:-1] # strip angle brackets
+				else:
+					if not (value.startswith('"') and value.endswith('"')):
+						continue
+					value = value[1:-1] # strip quotes
+
+				d[name] = value
+
+			d["people"] = ", ".join(d["people"])
+
+			download_page = d.get("download-page", "") or ("http://pypi.python.org/pypi/%(name)s" % d)
+			d["download_link"] = make_link(download_page)
+
+		else:
+
+			d["download_link"] = "<code>svn checkout %s</code>" % make_link(self.repo_url)
+
+		d["short_name"] = d["name"].split(".")[-1]
+
+		return d
+
+	def to_html(self):
+		d = self.info()
+
+		#~ from docutils.core import publish_parts
+		#~ parts = publish_parts(
+			#~ source=d["description"],
+			#~ settings_overrides={'file_insertion_enabled': 0, 'raw_enabled': 0},
+			#~ writer_name='html')
+		#~ logger.debug(parts)
+		#~ escaped_description = parts["fragment"]
+
+		#~ if "WARNING" in escaped_description:
+		escaped_description = htmlquote(d["description"]).replace(r"\n", "<br />\n")
+
+		revision = d.get("revision")
+		revision = ("version " + revision) if revision else ""
+
+		return get_template("package_info") % dictadd(self.__dict__, d, locals())
+
+def fetch_dir_links(url):
+	result = get_url(url)
+	if result.status_code != 200:
+		return
+
+	items = re.findall('<a href="(.+?)/">.+?</a>', result.content)
+	return [os.path.join(url, item) for item in items if not item.startswith("http://") and not item.startswith("..")]
+
+class System:
+
+	@classmethod
+	def init(self):
+		pass
+
+	@classmethod
+	def packages(self):
+		packages = memcache.get("packages")
+		if packages is None:
+			packages = {}
+			for url in fetch_dir_links(REPO_PATH):
+				logger.debug(url)
+				package = Package(repo_url=url)
+				if package.valid: # setup.py was not found
+					packages[package.name] = package
+		return packages
+
+#~ class RecentChangesPage(Page):
+	#~ def get(self):
+		#~ rss = RSS2.RSS2(
+			#~ title = "",
+			#~ link = "",
+			#~ description = "",
+
+			#~ lastBuildDate = datetime.datetime.now(),
+
+			#~ items = [
+
+				#~ RSS2.RSSItem(
+					#~ title = "PyRSS2Gen-0.0 released",
+					#~ link = "http://www.dalkescientific.com/news/030906-PyRSS2Gen.html",
+					#~ description = "Dalke Scientific today announced PyRSS2Gen-0.0, a library for generating RSS feeds for Python.  ",
+					#~ guid = RSS2.Guid("http://www.dalkescientific.com/news/"
+					#~ "030906-PyRSS2Gen.html"),
+					#~ pubDate = datetime.datetime(2003, 9, 6, 21, 31)
+					#~ ),
+
+			#~ ])
+
+		#~ rss.write_xml(self)
+
+class SearchPage(Page):
+	name="search"
+
+	@classmethod
+	def search_box(self, default="", prepend=""):
+		default = htmlquote(default)
+		return """
+			<form action="/search" method="get">
+			%(prepend)s
+			<input type="text" name="text" value="%(default)s"/>
+			<input type="submit" value="Go" />
+			</form>
+		""" % locals()
+
+	def get(self):
+		"""
+		@text
+		"""
+		self.print_header()
+		self.print_menu()
+		text = self.request.get("text").strip()
+
+		self.write("<p>")
+		self.write(self.search_box(default=text))
+		self.write("</p>")
+
+		packages = []
+		for package in System.packages().values():
+			d = package.info()
+			if any(text in field for field in package.info().values()):
+				packages.append(package)
+				continue
+
+		self.write("<p>search results for <strong>%s</strong>:<br />" % (htmlquote(text) or "all"))
+		if packages:
+			self.write(table_of_packages(packages))
+		else:
+			self.write("<strong>no matches</strong>")
+		self.write("</p>")
+
+		self.print_footer()
+
+def get_template(name):
+	#~ return getattr(templates, name+"_template")
+	template = PageTemplate.all().filter("name =", name).get()
+	return template.text
+
+class PageTemplate(db.Model):
+	name = db.StringProperty(required=True)
+	text = db.TextProperty()
+	modified = db.DateTimeProperty(auto_now=False)
+	username = db.TextProperty()
+
+def collect_templates():
+	result = []
+	query = PageTemplate.all()
+	for template in query.order("name"):
+		template_name = template.name
+		template_text = htmlquote(template.text).strip()
+		result.append('''
+%(template_name)s_template = """
+%(template_text)s
+"""
+	'''.strip() % locals())
+	return "\n".join(result)
+
+class AdminPage(Page):
+	name="admin"
+	def get(self):
+		"""
+		@template_name
+		@template_text
+
+		@email_backup
+		"""
+		self.print_header()
+		self.print_menu()
+
+		user = users.get_current_user()
+		self.write("<p>")
+		if not user:
+			self.write('only site admins allowed here.')
+			self.write('<a href="%s">sign in</a>' % users.create_login_url("/admin"))
+			self.print_footer()
+			return
+		if not users.is_current_user_admin():
+			self.write('only site admin allowed here.')
+			self.write('<a href="%s">sign out</a>.' % users.create_logout_url("/admin"))
+			self.print_footer()
+			return
+		self.write("welcome %s. " % user.nickname())
+		self.write('<a href="%s">sign out</a>.' % users.create_logout_url("/admin"))
+		self.write("</p>")
+
+		self.write("<p>")
+		if self.request.get("email_backup") == "yes":
+			t = datetime.datetime.now()
+			address = users.get_current_user().email()
+			if address:
+				mail.send_mail(sender="jantod@gmail.com",
+					to=address,
+					subject="backup %s" % t,
+					body="""Here's the backup""",
+					attachments=[("templates.py.rss", collect_templates())] #XXX google keeps escaping the <> chars! arg!
+				)
+				self.write("sent backup to %s at %s" % (address, t))
+		self.write("""
+		<form action="/admin" method="post">
+		<input type="hidden" name="email_backup" value="yes">
+		<input type="submit" value="Email me a backup" />
+		</form>
+		</p>
+		""")
+
+		template_name = self.request.get("template_name")
+		template = PageTemplate.all().filter("name =", template_name).get()
+		saved = False
+		if template:
+			self.write("<p>")
+			template_text = self.request.get("template_text")
+			if template_text.strip():
+				template.text = template_text
+				template.modified = datetime.datetime.now()
+				template.username = user.nickname()
+				template.put()
+				saved = True
+			if saved:
+				self.write("<strong>saved</strong><br />")
+			self.write("last_modified(<em>%s</em>) = %s" % (template.name, template.modified))
+			self.write("</p>")
+
+		query = PageTemplate.all()
+		for template in query.order("name"):
+			modified = template.modified
+			template_name = template.name
+			template_text = htmlquote(template.text)
+			self.write("""
+<h2>%(template_name)s</h2>
+<p>
+<form action="/admin" method="post">
+<textarea name="template_text" cols="80" rows="20">%(template_text)s</textarea>
+<input type="hidden" name="template_name" value="%(template_name)s">
+<input type="submit" value="Save" />
+modified %(modified)s
+</form>
+</p>
+			""" % locals())
+
+		self.print_footer()
+
+	post = get
+
+class AboutPage(Page):
+	name = "about"
+	def get(self):
+		self.print_header()
+		self.print_menu()
+		self.write(get_template("about") % locals())
+		self.print_footer()
+
+
+application = webapp.WSGIApplication([
+	('/', MainPage),
+
+	('/scikits', PackagesPage),
+	('/(scikits[.].+)', PackageInfoPage),
+
+	('/about', AboutPage),
+
+	#~ ('/recent_changes', RecentChangesPage),
+	('/search', SearchPage),
+	('/admin', AdminPage),
+
+	('/(.+)', PackageInfoPage),
+	], debug=True)
+
+def main():
+	System.init()
+	wsgiref.handlers.CGIHandler().run(application)
+
+if __name__ == '__main__':
+	main()

code/static/images/contents.png

Added
New image

code/static/images/download_large.png

Added
New image

code/static/images/favicon.ico

Added
New image

code/static/images/file.png

Added
New image

code/static/images/icon_package.gif

Added
New image

code/static/images/icon_package_get.gif

Added
New image

code/static/images/logo.png

Added
New image

code/static/images/navigation.png

Added
New image

code/static/jquery.acts_as_tree_table.css

+/* ActsAsTreeTable
+ * ========================================================================= */
+
+/* Expander
+ * ------------------------------------------------------------------------- */
+.acts_as_tree_table tr td .expander {
+	background-image: url(/static/images/bullet_toggle_minus.png);
+	background-position: left center;
+	background-repeat: no-repeat;
+	cursor: pointer;
+	padding: 0;
+	zoom: 1; /* Hack for IE, works in IE7, I refuse to check in IE6 or older */
+}
+
+.acts_as_tree_table tr.collapsed td .expander {
+	background-image: url(/static/images/bullet_toggle_plus.png);
+}
+
+.acts_as_tree_table tr.expanded td .expander {
+	background-image: url(/static/images/bullet_toggle_minus.png);
+}

code/static/jquery.acts_as_tree_table.js

+/*
+ * jQuery ActsAsTreeTable plugin 1.2
+ * =================================
+ *
+ * License
+ * -------
+ *
+ * Copyright (c) 2008 Ludo van den Boom
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+(function($) {
+	
+	// The options for this plugin should be available to all functions in this
+	// plugin.
+	var options;
+	
+	// TableTree Plugin: display a tree in a table.
+	//
+	// TODO Look into possibility to add alternating row colors
+	$.fn.acts_as_tree_table = function(opts) {
+		options = $.extend({}, $.fn.acts_as_tree_table.defaults, opts);
+		
+		return this.each(function() {
+			var table = $(this);
+			
+			// Add class to enable styles specific to this plugin.
+			table.addClass("acts_as_tree_table");
+			
+			// Walk through each 'node' that is part of the tree, enabling tree
+			// behavior on parent/branch nodes.
+			table.children("tbody").children("tbody tr").each(function() {
+				var node = $(this);
+				
+				// Every node in the tree that has child nodes is marked as a 'parent',
+				// unless this has already been done manually.
+				if(node.not(".parent") && children_of(node).size() > 0) {
+					node.addClass("parent");
+				}
+				
+				// Make each .parent row collapsable by adding some html to the column
+				// that is displayed as tree.
+				if(node.is(".parent")) {
+					init_parent(node);
+				}
+			});
+		});
+	};
+	
+	// Default options
+	$.fn.acts_as_tree_table.defaults = {
+		expandable: true,
+		default_state: 'expanded',
+		indent: 19,
+		tree_column: 0
+	};
+	
+	// Extend function to jQuery
+	$.fn.collapse = function() {
+  	collapse(this);
+	};
+
+	// Extend function to jQuery
+	$.fn.expand = function() {
+		expand(this);
+	};
+
+	// Extend function to jQuery
+	$.fn.toggleBranch = function() {
+		toggle(this);
+	};
+	
+	// === Private Methods
+	
+	// Select all children of a node.	
+	function children_of(node) {
+		return $("tr.child-of-" + node[0].id);
+	};
+	
+	// Hide all descendants of a node.
+	function collapse(node) {
+		children_of(node).each(function() {
+			var child = $(this);
+
+			// Recursively collapse any descending nodes too
+			collapse(child);
+
+			child.hide();
+		});
+	};
+	
+	// Show all children of a node.
+	function expand(node) {
+		children_of(node).each(function() {
+			var child = $(this);
+			
+			// Recursively expand any descending nodes that are parents which where
+			// expanded before too.
+			if(child.is(".expanded.parent")) {
+				expand(child);
+			}
+			
+			child.show();
+		});
+	};
+
+	// Add stuff to cell that contains stuff to make the tree collapsable.
+	function init_parent(node) {
+		// Select cell in column that should display the tree
+		var cell = $(node.children("td")[options.tree_column]);
+
+		// Calculate left padding
+		var padding = parseInt(cell.css("padding-left")) + options.indent;
+		
+		children_of(node).each(function() {
+			$($(this).children("td")[options.tree_column]).css("padding-left", padding + "px");
+		});
+
+		if(options.expandable) {
+			// Add clickable expander buttons (plus and minus icon thingies)
+			//
+			// Why do I use a z-index on the expander?
+			// Otherwise the expander would not be visible in Safari/Webkit browsers.
+			cell.prepend('<span style="margin-left: -' + options.indent + 'px; padding-left: ' + options.indent + 'px" class="expander"></span>');
+			var expander = $(cell[0].firstChild);
+			expander.click(function() { toggle(node); });
+			
+			// Check for a class set explicitly by the user, otherwise set the default class
+			if( !(node.is(".expanded") || node.is(".collapsed")) ) {
+			  node.addClass(options.default_state);
+			}
+			
+			// Apply the default state
+			if(node.is(".collapsed")) {
+				collapse(node);
+			} else if (node.is(".expanded")) {
+				expand(node);
+			}
+		}
+	};
+	
+	// Toggle a node
+	function toggle(node) {
+		if(node.is(".collapsed")) {
+			node.removeClass("collapsed");
+			node.addClass("expanded");
+			expand(node);
+		}
+		else {
+			node.removeClass("expanded");
+			node.addClass("collapsed");
+			collapse(node);
+		}
+	};
+	
+})(jQuery);

code/static/jquery.corners.js

+/*!
+ * jQuery Corners 0.3
+ * Copyright (c) 2008 David Turnbull, Steven Wittens
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ */
+
+jQuery.fn.corners = function(options) {
+  var doneClass = 'rounded_by_jQuery_corners'; /* To prevent double rounding */
+  var settings = parseOptions(options);
+  var webkitAvailable = false;
+  try {
+    webkitAvailable = (document.body.style.WebkitBorderRadius !== undefined);
+    /* Google Chrome corners look awful */
+    var versionIndex = navigator.userAgent.indexOf('Chrome');
+    if (versionIndex >= 0) webkitAvailable = false;
+  } catch(err) {}
+  var mozillaAvailable = false;
+  try {
+    mozillaAvailable = (document.body.style.MozBorderRadius !== undefined);
+    /* Firefox 2 corners look worse */
+    var versionIndex = navigator.userAgent.indexOf('Firefox');
+    if (versionIndex >= 0 && parseInt(navigator.userAgent.substring(versionIndex+8)) < 3) mozillaAvailable = false;
+  } catch(err) {}
+  return this.each(function(i,e){
+    $e = jQuery(e);
+    if ($e.hasClass(doneClass)) return;
+    $e.addClass(doneClass);
+    var classScan = /{(.*)}/.exec(e.className);
+    var s = classScan ? parseOptions(classScan[1], settings) : settings;
+    var nodeName = e.nodeName.toLowerCase();
+    if (nodeName=='input') e = changeInput(e);
+    if (webkitAvailable && s.webkit) roundWebkit(e, s);
+    else if(mozillaAvailable && s.mozilla && (s.sizex == s.sizey)) roundMozilla(e, s);
+    else {
+      var bgColor = backgroundColor(e.parentNode);
+      var fgColor = backgroundColor(e);
+      switch (nodeName) {
+        case 'a':
+        case 'input':
+          roundLink(e, s, bgColor, fgColor);
+          break;
+        default:
+          roundDiv(e, s, bgColor, fgColor);
+          break;
+      }
+    }
+  });
+  
+  function roundWebkit(e, s) {
+    var radius = '' + s.sizex + 'px ' + s.sizey + 'px';
+    var $e = jQuery(e);
+    if (s.tl) $e.css('WebkitBorderTopLeftRadius', radius);
+    if (s.tr) $e.css('WebkitBorderTopRightRadius', radius);
+    if (s.bl) $e.css('WebkitBorderBottomLeftRadius', radius);
+    if (s.br) $e.css('WebkitBorderBottomRightRadius', radius);
+  }
+  
+  function roundMozilla(e, s)
+  {  
+    var radius = '' + s.sizex + 'px';
+    var $e = jQuery(e);
+    if (s.tl) $e.css('-moz-border-radius-topleft', radius);
+    if (s.tr) $e.css('-moz-border-radius-topright', radius);
+    if (s.bl) $e.css('-moz-border-radius-bottomleft', radius);
+    if (s.br) $e.css('-moz-border-radius-bottomright', radius);  
+  }
+  
+  function roundLink(e, s, bgColor, fgColor) {
+    var table = tableElement("table");
+    var tbody = tableElement("tbody");
+    table.appendChild(tbody);
+    var tr1 = tableElement("tr");
+    var td1 = tableElement("td", "top");
+    tr1.appendChild(td1);
+    var tr2 = tableElement("tr");
+    var td2 = relocateContent(e, s, tableElement("td"));
+    tr2.appendChild(td2);
+    var tr3 = tableElement("tr");
+    var td3 = tableElement("td", "bottom");
+    tr3.appendChild(td3);
+    if (s.tl||s.tr) {
+      tbody.appendChild(tr1);
+      addCorners(td1, s, bgColor, fgColor, true);
+    }
+    tbody.appendChild(tr2);
+    if (s.bl||s.br) {
+      tbody.appendChild(tr3);
+      addCorners(td3, s, bgColor, fgColor, false);
+    }
+    e.appendChild(table);
+    /* Clicking on $('a>table') in IE will trigger onclick but not the href  */
+    if (jQuery.browser.msie) table.onclick = ieLinkBypass;
+    /* Firefox 2 will render garbage unless we hide the overflow here */
+    e.style.overflow = 'hidden';
+  }
+  
+  function ieLinkBypass() {
+    if (!this.parentNode.onclick) this.parentNode.click();
+  }
+  
+  function changeInput(e) {
+    var a1 = document.createElement("a");
+    a1.id = e.id;
+    a1.className = e.className;
+    if (e.onclick) {
+      a1.href = 'javascript:'
+      a1.onclick = e.onclick;
+    } else {
+      jQuery(e).parent('form').each(function() {a1.href = this.action;});
+      a1.onclick = submitForm;
+    }
+    var a2 = document.createTextNode(e.value);
+    a1.appendChild(a2);
+    e.parentNode.replaceChild(a1, e);
+    return a1;
+  }
+
+  function submitForm() {
+    jQuery(this).parent('form').each(function() {this.submit()});
+    return false;
+  }
+
+  function roundDiv(e, s, bgColor, fgColor) {
+    var div = relocateContent(e, s, document.createElement('div'));
+    e.appendChild(div);
+    if (s.tl||s.tr) addCorners(e, s, bgColor, fgColor, true);
+    if (s.bl||s.br) addCorners(e, s, bgColor, fgColor, false);
+  }
+  
+  function relocateContent(e, s, d) {
+    var $e = jQuery(e);
+    var c;
+    while(c=e.firstChild) d.appendChild(c);
+    if (e.style.height) {
+      var h = parseInt($e.css('height'));
+      d.style.height = h + 'px';
+      h += parseInt($e.css('padding-top')) + parseInt($e.css('padding-bottom'));
+      e.style.height = h + 'px';
+    }
+    if (e.style.width) {
+      var w = parseInt($e.css('width'));
+      d.style.width = w + 'px';
+      w += parseInt($e.css('padding-left')) + parseInt($e.css('padding-right'));
+      e.style.width = w + 'px';
+    }
+    d.style.paddingLeft = $e.css('padding-left');
+    d.style.paddingRight = $e.css('padding-right');
+    if (s.tl||s.tr) {
+      d.style.paddingTop = adjustedPadding(e, s, $e.css('padding-top'), true);
+    } else {
+      d.style.paddingTop = $e.css('padding-top');
+    }
+    if (s.bl||s.br) {
+      d.style.paddingBottom = adjustedPadding(e, s, $e.css('padding-bottom'), false);
+    } else {
+      d.style.paddingBottom = $e.css('padding-bottom');
+    }
+    e.style.padding = 0;
+    return d;
+  }
+  
+  function adjustedPadding(e, s, pad, top) {
+    if (pad.indexOf("px") < 0) {
+      try {
+        //TODO Make this check work otherwise remove it
+        console.error('%s padding not in pixels', (top ? 'top' : 'bottom'), e);
+      }
+      catch(err) {}
+      pad = s.sizey + 'px';
+    }
+    pad = parseInt(pad);
+    if (pad - s.sizey < 0) {
+      try {
+        console.error('%s padding is %ipx for %ipx corner:', (top ? 'top' : 'bottom'), pad, s.sizey, e);
+      }
+      catch(err) {}
+      pad = s.sizey;
+    }
+    return pad - s.sizey + 'px';
+  }
+
+  function tableElement(kind, valign) {
+    var e = document.createElement(kind)
+    e.style.border = 'none';
+    e.style.borderCollapse = 'collapse';
+    e.style.borderSpacing = 0;
+    e.style.padding = 0;
+    e.style.margin = 0;
+    if (valign) e.style.verticalAlign = valign;
+    return e;
+  }
+  
+  function backgroundColor(e) {
+    try {
+      var c = jQuery.css(e, "background-color");
+      if ( c.match(/^(transparent|rgba\(0,\s*0,\s*0,\s*0\))$/i) && e.parentNode )
+         return backgroundColor(e.parentNode);
+      if (c==null)
+        return "#ffffff";
+      if (c.indexOf("rgb") > -1)
+    	  c = rgb2hex(c);
+      if (c.length == 4)
+  	    c = hexShort2hex(c);
+      return c;
+    } catch(err) {
+      return "#ffffff";
+    }
+  }
+  
+  function hexShort2hex(c) {
+    return '#' +
+    c.substring(1,2) +
+    c.substring(1,2) +
+    c.substring(2,3) +
+    c.substring(2,3) +
+    c.substring(3,4) +
+    c.substring(3,4);
+  }
+
+  function rgb2hex(c) {
+  	var x = 255;
+  	var hex = '';
+  	var i;
+  	var regexp=/([0-9]+)[, ]+([0-9]+)[, ]+([0-9]+)/;
+  	var array=regexp.exec(c);
+  	for(i=1;i<4;i++) hex += ('0'+parseInt(array[i]).toString(16)).slice(-2);
+  	return '#'+hex;
+  }
+  
+  function parseOptions(options, settings) {
+    var options = options || '';
+    var s = {sizex:5, sizey:5, tl: false, tr: false, bl: false, br: false, webkit:true, mozilla: true, transparent:false};
+    if (settings) {
+      s.sizex = settings.sizex;
+      s.sizey = settings.sizey;
+      s.webkit = settings.webkit;
+      s.transparent = settings.transparent;
+      s.mozilla = settings.mozilla;
+    }
+    var sizex_set = false;
+    var corner_set = false;
+    jQuery.each(options.split(' '), function(idx, option) {
+      option = option.toLowerCase();
+      var i = parseInt(option);
+      if (i > 0 && option == i + 'px') {
+        s.sizey = i;
+        if (!sizex_set) s.sizex = i;
+        sizex_set = true;
+      } else switch (option) {
+        case 'no-native': s.webkit = s.mozilla = false; break;
+        case 'webkit': s.webkit = true; break;
+        case 'no-webkit': s.webkit = false; break;
+        case 'mozilla': s.mozilla = true; break;
+        case 'no-mozilla': s.mozilla = false; break;
+        case 'anti-alias': s.transparent = false; break;
+        case 'transparent': s.transparent = true; break;
+        case 'top': corner_set = s.tl = s.tr = true; break;
+        case 'right': corner_set = s.tr = s.br = true; break;
+        case 'bottom': corner_set = s.bl = s.br = true; break;
+        case 'left': corner_set = s.tl = s.bl = true; break;
+        case 'top-left': corner_set = s.tl = true; break;
+        case 'top-right': corner_set = s.tr = true; break;
+        case 'bottom-left': corner_set = s.bl = true; break;
+        case 'bottom-right': corner_set = s.br = true; break;
+      }
+    });
+    if (!corner_set) {
+      if (!settings) {
+        s.tl = s.tr = s.bl = s.br = true;
+      } else {
+        s.tl = settings.tl;
+        s.tr = settings.tr;
+        s.bl = settings.bl;
+        s.br = settings.br;
+      }
+    }
+    return s;
+  }
+  
+  function alphaBlend(a, b, alpha) {
+    var ca = Array(
+      parseInt('0x' + a.substring(1, 3)),
+      parseInt('0x' + a.substring(3, 5)),
+      parseInt('0x' + a.substring(5, 7))
+    );
+    var cb = Array(
+      parseInt('0x' + b.substring(1, 3)),
+      parseInt('0x' + b.substring(3, 5)),
+      parseInt('0x' + b.substring(5, 7))
+    );
+    r = '0' + Math.round(ca[0] + (cb[0] - ca[0])*alpha).toString(16);
+    g = '0' + Math.round(ca[1] + (cb[1] - ca[1])*alpha).toString(16);
+    b = '0' + Math.round(ca[2] + (cb[2] - ca[2])*alpha).toString(16);
+    return '#'
+      + r.substring(r.length - 2)
+      + g.substring(g.length - 2)
+      + b.substring(b.length - 2);
+  }
+
+  function addCorners(e, s, bgColor, fgColor, top) {
+    if (s.transparent) addTransparentCorners(e, s, bgColor, top);
+    else addAntiAliasedCorners(e, s, bgColor, fgColor, top);
+  }
+  
+  function addAntiAliasedCorners(e, s, bgColor, fgColor, top) {
+    var i, j;
+    var d = document.createElement("div");
+    d.style.fontSize = '1px';
+    d.style.backgroundColor = bgColor;
+    var lastarc = 0;
+    for (i = 1; i <= s.sizey; i++) {
+      var coverage, arc2, arc3;
+      // Find intersection of arc with bottom of pixel row
+      arc = Math.sqrt(1.0 - Math.pow(1.0 - i / s.sizey, 2)) * s.sizex;
+      // Calculate how many pixels are bg, fg and blended.
+      var n_bg = s.sizex - Math.ceil(arc);
+      var n_fg = Math.floor(lastarc);
+      var n_aa = s.sizex - n_bg - n_fg;
+      // Create pixel row wrapper
+      var x = document.createElement("div");
+      var y = d;
+      x.style.margin = "0px " + n_bg + "px";
+      x.style.height = '1px';
+      x.style.overflow = 'hidden';
+      // Create the pixel divs for a row (at least one)
+      for (j = 1; j <= n_aa; j++) {
+        // Calculate coverage per pixel (approximates arc within the pixel)
+        if (j == 1) {
+          if (j == n_aa) {
+            // Single pixel
+            coverage = ((arc + lastarc) * .5) - n_fg;
+          }
+          else {
+            // First in a run
+            arc2 = Math.sqrt(1.0 - Math.pow(1.0 - (n_bg + 1) / s.sizex, 2)) * s.sizey;
+            coverage = (arc2 - (s.sizey - i)) * (arc - n_fg - n_aa + 1) * .5;
+          }
+        }
+        else if (j == n_aa) {
+          // Last in a run
+          arc2 = Math.sqrt(1.0 - Math.pow((s.sizex - n_bg - j + 1) / s.sizex, 2)) * s.sizey;
+          coverage = 1.0 - (1.0 - (arc2 - (s.sizey - i))) * (1.0 - (lastarc - n_fg)) * .5;
+        }
+        else {
+          // Middle of a run
+          arc3 = Math.sqrt(1.0 - Math.pow((s.sizex - n_bg - j) / s.sizex, 2)) * s.sizey;
+          arc2 = Math.sqrt(1.0 - Math.pow((s.sizex - n_bg - j + 1) / s.sizex, 2)) * s.sizey;
+          coverage = ((arc2 + arc3) * .5) - (s.sizey - i);
+        }
+        
+        addCornerDiv(s, x, y, top, alphaBlend(bgColor, fgColor, coverage));
+        y = x;
+        var x = y.cloneNode(false);
+        x.style.margin = "0px 1px";
+      }
+      addCornerDiv(s, x, y, top, fgColor);
+      lastarc = arc;
+    }
+    if (top)
+      e.insertBefore(d, e.firstChild);
+    else
+      e.appendChild(d);
+  }
+  
+  function addCornerDiv(s, x, y, top, color) {
+    if (top && !s.tl) x.style.marginLeft = 0;
+    if (top && !s.tr) x.style.marginRight = 0;
+    if (!top && !s.bl) x.style.marginLeft = 0;
+    if (!top && !s.br) x.style.marginRight = 0;
+    x.style.backgroundColor = color;
+    if (top)
+      y.appendChild(x);
+    else
+      y.insertBefore(x, y.firstChild);
+  }
+
+  function addTransparentCorners(e, s, bgColor, top) {
+    var d = document.createElement("div");
+    d.style.fontSize = '1px';
+    var strip = document.createElement('div');
+    strip.style.overflow = 'hidden';
+    strip.style.height = '1px';
+    strip.style.borderColor = bgColor;
+    strip.style.borderStyle = 'none solid';
+    var sizex = s.sizex-1;
+    var sizey = s.sizey-1;
+    if (!sizey) sizey = 1; /* hint for 1x1 */
+    for (var i=0; i < s.sizey; i++) {
+      var w = sizex - Math.floor(Math.sqrt(1.0 - Math.pow(1.0 - i / sizey, 2)) * sizex);
+      if (i==2 && s.sizex==6 && s.sizey==6) w = 2; /* hint for 6x6 */
+      var x = strip.cloneNode(false);
+      x.style.borderWidth = '0 '+ w +'px';
+      if (top) x.style.borderWidth = '0 '+(s.tr?w:0)+'px 0 '+(s.tl?w:0)+'px';
+      else x.style.borderWidth = '0 '+(s.br?w:0)+'px 0 '+(s.bl?w:0)+'px';
+      top ? d.appendChild(x) : d.insertBefore(x, d.firstChild);
+    } 
+    if (top)
+      e.insertBefore(d, e.firstChild);
+    else
+      e.appendChild(d);
+  }
+
+
+}

code/static/jquery.corners.min.js

+/*
+ * jQuery Corners 0.3
+ * Copyright (c) 2008 David Turnbull, Steven Wittens
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ */
+jQuery.fn.corners=function(C){var N="rounded_by_jQuery_corners";var V=B(C);var F=false;try{F=(document.body.style.WebkitBorderRadius!==undefined);var Y=navigator.userAgent.indexOf("Chrome");if(Y>=0){F=false}}catch(E){}var W=false;try{W=(document.body.style.MozBorderRadius!==undefined);var Y=navigator.userAgent.indexOf("Firefox");if(Y>=0&&parseInt(navigator.userAgent.substring(Y+8))<3){W=false}}catch(E){}return this.each(function(b,h){$e=jQuery(h);if($e.hasClass(N)){return }$e.addClass(N);var a=/{(.*)}/.exec(h.className);var c=a?B(a[1],V):V;var j=h.nodeName.toLowerCase();if(j=="input"){h=O(h)}if(F&&c.webkit){K(h,c)}else{if(W&&c.mozilla&&(c.sizex==c.sizey)){M(h,c)}else{var d=D(h.parentNode);var f=D(h);switch(j){case"a":case"input":Z(h,c,d,f);break;default:R(h,c,d,f);break}}}});function K(d,c){var a=""+c.sizex+"px "+c.sizey+"px";var b=jQuery(d);if(c.tl){b.css("WebkitBorderTopLeftRadius",a)}if(c.tr){b.css("WebkitBorderTopRightRadius",a)}if(c.bl){b.css("WebkitBorderBottomLeftRadius",a)}if(c.br){b.css("WebkitBorderBottomRightRadius",a)}}function M(d,c){var a=""+c.sizex+"px";var b=jQuery(d);if(c.tl){b.css("-moz-border-radius-topleft",a)}if(c.tr){b.css("-moz-border-radius-topright",a)}if(c.bl){b.css("-moz-border-radius-bottomleft",a)}if(c.br){b.css("-moz-border-radius-bottomright",a)}}function Z(k,n,l,a){var m=S("table");var i=S("tbody");m.appendChild(i);var j=S("tr");var d=S("td","top");j.appendChild(d);var h=S("tr");var c=T(k,n,S("td"));h.appendChild(c);var f=S("tr");var b=S("td","bottom");f.appendChild(b);if(n.tl||n.tr){i.appendChild(j);X(d,n,l,a,true)}i.appendChild(h);if(n.bl||n.br){i.appendChild(f);X(b,n,l,a,false)}k.appendChild(m);if(jQuery.browser.msie){m.onclick=Q}k.style.overflow="hidden"}function Q(){if(!this.parentNode.onclick){this.parentNode.click()}}function O(c){var b=document.createElement("a");b.id=c.id;b.className=c.className;if(c.onclick){b.href="javascript:";b.onclick=c.onclick}else{jQuery(c).parent("form").each(function(){b.href=this.action});b.onclick=I}var a=document.createTextNode(c.value);b.appendChild(a);c.parentNode.replaceChild(b,c);return b}function I(){jQuery(this).parent("form").each(function(){this.submit()});return false}function R(d,a,b,c){var f=T(d,a,document.createElement("div"));d.appendChild(f);if(a.tl||a.tr){X(d,a,b,c,true)}if(a.bl||a.br){X(d,a,b,c,false)}}function T(j,i,k){var b=jQuery(j);var l;while(l=j.firstChild){k.appendChild(l)}if(j.style.height){var f=parseInt(b.css("height"));k.style.height=f+"px";f+=parseInt(b.css("padding-top"))+parseInt(b.css("padding-bottom"));j.style.height=f+"px"}if(j.style.width){var a=parseInt(b.css("width"));k.style.width=a+"px";a+=parseInt(b.css("padding-left"))+parseInt(b.css("padding-right"));j.style.width=a+"px"}k.style.paddingLeft=b.css("padding-left");k.style.paddingRight=b.css("padding-right");if(i.tl||i.tr){k.style.paddingTop=U(j,i,b.css("padding-top"),true)}else{k.style.paddingTop=b.css("padding-top")}if(i.bl||i.br){k.style.paddingBottom=U(j,i,b.css("padding-bottom"),false)}else{k.style.paddingBottom=b.css("padding-bottom")}j.style.padding=0;return k}function U(f,a,d,c){if(d.indexOf("px")<0){try{console.error("%s padding not in pixels",(c?"top":"bottom"),f)}catch(b){}d=a.sizey+"px"}d=parseInt(d);if(d-a.sizey<0){try{console.error("%s padding is %ipx for %ipx corner:",(c?"top":"bottom"),d,a.sizey,f)}catch(b){}d=a.sizey}return d-a.sizey+"px"}function S(b,a){var c=document.createElement(b);c.style.border="none";c.style.borderCollapse="collapse";c.style.borderSpacing=0;c.style.padding=0;c.style.margin=0;if(a){c.style.verticalAlign=a}return c}function D(b){try{var d=jQuery.css(b,"background-color");if(d.match(/^(transparent|rgba\(0,\s*0,\s*0,\s*0\))$/i)&&b.parentNode){return D(b.parentNode)}if(d==null){return"#ffffff"}if(d.indexOf("rgb")>-1){d=A(d)}if(d.length==4){d=L(d)}return d}catch(a){return"#ffffff"}}function L(a){return"#"+a.substring(1,2)+a.substring(1,2)+a.substring(2,3)+a.substring(2,3)+a.substring(3,4)+a.substring(3,4)}function A(h){var a=255;var d="";var b;var e=/([0-9]+)[, ]+([0-9]+)[, ]+([0-9]+)/;var f=e.exec(h);for(b=1;b<4;b++){d+=("0"+parseInt(f[b]).toString(16)).slice(-2)}return"#"+d}function B(b,d){var b=b||"";var c={sizex:5,sizey:5,tl:false,tr:false,bl:false,br:false,webkit:true,mozilla:true,transparent:false};if(d){c.sizex=d.sizex;c.sizey=d.sizey;c.webkit=d.webkit;c.transparent=d.transparent;c.mozilla=d.mozilla}var a=false;var e=false;jQuery.each(b.split(" "),function(f,j){j=j.toLowerCase();var h=parseInt(j);if(h>0&&j==h+"px"){c.sizey=h;if(!a){c.sizex=h}a=true}else{switch(j){case"no-native":c.webkit=c.mozilla=false;break;case"webkit":c.webkit=true;break;case"no-webkit":c.webkit=false;break;case"mozilla":c.mozilla=true;break;case"no-mozilla":c.mozilla=false;break;case"anti-alias":c.transparent=false;break;case"transparent":c.transparent=true;break;case"top":e=c.tl=c.tr=true;break;case"right":e=c.tr=c.br=true;break;case"bottom":e=c.bl=c.br=true;break;case"left":e=c.tl=c.bl=true;break;case"top-left":e=c.tl=true;break;case"top-right":e=c.tr=true;break;case"bottom-left":e=c.bl=true;break;case"bottom-right":e=c.br=true;break}}});if(!e){if(!d){c.tl=c.tr=c.bl=c.br=true}else{c.tl=d.tl;c.tr=d.tr;c.bl=d.bl;c.br=d.br}}return c}function P(f,d,h){var e=Array(parseInt("0x"+f.substring(1,3)),parseInt("0x"+f.substring(3,5)),parseInt("0x"+f.substring(5,7)));var c=Array(parseInt("0x"+d.substring(1,3)),parseInt("0x"+d.substring(3,5)),parseInt("0x"+d.substring(5,7)));r="0"+Math.round(e[0]+(c[0]-e[0])*h).toString(16);g="0"+Math.round(e[1]+(c[1]-e[1])*h).toString(16);d="0"+Math.round(e[2]+(c[2]-e[2])*h).toString(16);return"#"+r.substring(r.length-2)+g.substring(g.length-2)+d.substring(d.length-2)}function X(f,a,b,d,c){if(a.transparent){G(f,a,b,c)}else{J(f,a,b,d,c)}}function J(k,z,p,a,n){var h,f;var l=document.createElement("div");l.style.fontSize="1px";l.style.backgroundColor=p;var b=0;for(h=1;h<=z.sizey;h++){var u,t,q;arc=Math.sqrt(1-Math.pow(1-h/z.sizey,2))*z.sizex;var c=z.sizex-Math.ceil(arc);var w=Math.floor(b);var v=z.sizex-c-w;var o=document.createElement("div");var m=l;o.style.margin="0px "+c+"px";o.style.height="1px";o.style.overflow="hidden";for(f=1;f<=v;f++){if(f==1){if(f==v){u=((arc+b)*0.5)-w}else{t=Math.sqrt(1-Math.pow(1-(c+1)/z.sizex,2))*z.sizey;u=(t-(z.sizey-h))*(arc-w-v+1)*0.5}}else{if(f==v){t=Math.sqrt(1-Math.pow((z.sizex-c-f+1)/z.sizex,2))*z.sizey;u=1-(1-(t-(z.sizey-h)))*(1-(b-w))*0.5}else{q=Math.sqrt(1-Math.pow((z.sizex-c-f)/z.sizex,2))*z.sizey;t=Math.sqrt(1-Math.pow((z.sizex-c-f+1)/z.sizex,2))*z.sizey;u=((t+q)*0.5)-(z.sizey-h)}}H(z,o,m,n,P(p,a,u));m=o;var o=m.cloneNode(false);o.style.margin="0px 1px"}H(z,o,m,n,a);b=arc}if(n){k.insertBefore(l,k.firstChild)}else{k.appendChild(l)}}function H(c,a,e,d,b){if(d&&!c.tl){a.style.marginLeft=0}if(d&&!c.tr){a.style.marginRight=0}if(!d&&!c.bl){a.style.marginLeft=0}if(!d&&!c.br){a.style.marginRight=0}a.style.backgroundColor=b;if(d){e.appendChild(a)}else{e.insertBefore(a,e.firstChild)}}function G(c,o,l,h){var f=document.createElement("div");f.style.fontSize="1px";var a=document.createElement("div");a.style.overflow="hidden";a.style.height="1px";a.style.borderColor=l;a.style.borderStyle="none solid";var m=o.sizex-1;var j=o.sizey-1;if(!j){j=1}for(var b=0;b<o.sizey;b++){var n=m-Math.floor(Math.sqrt(1-Math.pow(1-b/j,2))*m);if(b==2&&o.sizex==6&&o.sizey==6){n=2}var k=a.cloneNode(false);k.style.borderWidth="0 "+n+"px";if(h){k.style.borderWidth="0 "+(o.tr?n:0)+"px 0 "+(o.tl?n:0)+"px"}else{k.style.borderWidth="0 "+(o.br?n:0)+"px 0 "+(o.bl?n:0)+"px"}h?f.appendChild(k):f.insertBefore(k,f.firstChild)}if(h){c.insertBefore(f,c.firstChild)}else{c.appendChild(f)}}};

code/static/jquery.js

+/*
+ * jQuery 1.2.6 - New Wave Javascript
+ *
+ * Copyright (c) 2008 John Resig (jquery.com)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * $Date: 2008-05-24 14:22:17 -0400 (Sat, 24 May 2008) $
+ * $Rev: 5685 $
+ */
+(function(){var _jQuery=window.jQuery,_$=window.$;var jQuery=window.jQuery=window.$=function(selector,context){return new jQuery.fn.init(selector,context);};var quickExpr=/^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/,isSimple=/^.[^:#\[\.]*$/,undefined;jQuery.fn=jQuery.prototype={init:function(selector,context){selector=selector||document;if(selector.nodeType){this[0]=selector;this.length=1;return this;}if(typeof selector=="string"){var match=quickExpr.exec(selector);if(match&&(match[1]||!context)){if(match[1])selector=jQuery.clean([match[1]],context);else{var elem=document.getElementById(match[3]);if(elem){if(elem.id!=match[3])return jQuery().find(selector);return jQuery(elem);}selector=[];}}else
+return jQuery(context).find(selector);}else if(jQuery.isFunction(selector))return jQuery(document)[jQuery.fn.ready?"ready":"load"](selector);return this.setArray(jQuery.makeArray(selector));},jquery:"1.2.6",size:function(){return this.length;},length:0,get:function(num){return num==undefined?jQuery.makeArray(this):this[num];},pushStack:function(elems){var ret=jQuery(elems);ret.prevObject=this;return ret;},setArray:function(elems){this.length=0;Array.prototype.push.apply(this,elems);return this;},each:function(callback,args){return jQuery.each(this,callback,args);},index:function(elem){var ret=-1;return jQuery.inArray(elem&&elem.jquery?elem[0]:elem,this);},attr:function(name,value,type){var options=name;if(name.constructor==String)if(value===undefined)return this[0]&&jQuery[type||"attr"](this[0],name);else{options={};options[name]=value;}return this.each(function(i){for(name in options)jQuery.attr(type?this.style:this,name,jQuery.prop(this,options[name],type,i,name));});},css:function(key,value){if((key=='width'||key=='height')&&parseFloat(value)<0)value=undefined;return this.attr(key,value,"curCSS");},text:function(text){if(typeof text!="object"&&text!=null)return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(text));var ret="";jQuery.each(text||this,function(){jQuery.each(this.childNodes,function(){if(this.nodeType!=8)ret+=this.nodeType!=1?this.nodeValue:jQuery.fn.text([this]);});});return ret;},wrapAll:function(html){if(this[0])jQuery(html,this[0].ownerDocument).clone().insertBefore(this[0]).map(function(){var elem=this;while(elem.firstChild)elem=elem.firstChild;return elem;}).append(this);return this;},wrapInner:function(html){return this.each(function(){jQuery(this).contents().wrapAll(html);});},wrap:function(html){return this.each(function(){jQuery(this).wrapAll(html);});},append:function(){return this.domManip(arguments,true,false,function(elem){if(this.nodeType==1)this.appendChild(elem);});},prepend:function(){return this.domManip(arguments,true,true,function(elem){if(this.nodeType==1)this.insertBefore(elem,this.firstChild);});},before:function(){return this.domManip(arguments,false,false,function(elem){this.parentNode.insertBefore(elem,this);});},after:function(){return this.domManip(arguments,false,true,function(elem){this.parentNode.insertBefore(elem,this.nextSibling);});},end:function(){return this.prevObject||jQuery([]);},find:function(selector){var elems=jQuery.map(this,function(elem){return jQuery.find(selector,elem);});return this.pushStack(/[^+>] [^+>]/.test(selector)||selector.indexOf("..")>-1?jQuery.unique(elems):elems);},clone:function(events){var ret=this.map(function(){if(jQuery.browser.msie&&!jQuery.isXMLDoc(this)){var clone=this.cloneNode(true),container=document.createElement("div");container.appendChild(clone);return jQuery.clean([container.innerHTML])[0];}else
+return this.cloneNode(true);});var clone=ret.find("*").andSelf().each(function(){if(this[expando]!=undefined)this[expando]=null;});if(events===true)this.find("*").andSelf().each(function(i){if(this.nodeType==3)return;var events=jQuery.data(this,"events");for(var type in events)for(var handler in events[type])jQuery.event.add(clone[i],type,events[type][handler],events[type][handler].data);});return ret;},filter:function(selector){return this.pushStack(jQuery.isFunction(selector)&&jQuery.grep(this,function(elem,i){return selector.call(elem,i);})||jQuery.multiFilter(selector,this));},not:function(selector){if(selector.constructor==String)if(isSimple.test(selector))return this.pushStack(jQuery.multiFilter(selector,this,true));else
+selector=jQuery.multiFilter(selector,this);var isArrayLike=selector.length&&selector[selector.length-1]!==undefined&&!selector.nodeType;return this.filter(function(){return isArrayLike?jQuery.inArray(this,selector)<0:this!=selector;});},add:function(selector){return this.pushStack(jQuery.unique(jQuery.merge(this.get(),typeof selector=='string'?jQuery(selector):jQuery.makeArray(selector))));},is:function(selector){return!!selector&&jQuery.multiFilter(selector,this).length>0;},hasClass:function(selector){return this.is("."+selector);},val:function(value){if(value==undefined){if(this.length){var elem=this[0];if(jQuery.nodeName(elem,"select")){var index=elem.selectedIndex,values=[],options=elem.options,one=elem.type=="select-one";if(index<0)return null;for(var i=one?index:0,max=one?index+1:options.length;i<max;i++){var option=options[i];if(option.selected){value=jQuery.browser.msie&&!option.attributes.value.specified?option.text:option.value;if(one)return value;values.push(value);}}return values;}else
+return(this[0].value||"").replace(/\r/g,"");}return undefined;}if(value.constructor==Number)value+='';return this.each(function(){if(this.nodeType!=1)return;if(value.constructor==Array&&/radio|checkbox/.test(this.type))this.checked=(jQuery.inArray(this.value,value)>=0||jQuery.inArray(this.name,value)>=0);else if(jQuery.nodeName(this,"select")){var values=jQuery.makeArray(value);jQuery("option",this).each(function(){this.selected=(jQuery.inArray(this.value,values)>=0||jQuery.inArray(this.text,values)>=0);});if(!values.length)this.selectedIndex=-1;}else
+this.value=value;});},html:function(value){return value==undefined?(this[0]?this[0].innerHTML:null):this.empty().append(value);},replaceWith:function(value){return this.after(value).remove();},eq:function(i){return this.slice(i,i+1);},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments));},map:function(callback){return this.pushStack(jQuery.map(this,function(elem,i){return callback.call(elem,i,elem);}));},andSelf:function(){return this.add(this.prevObject);},data:function(key,value){var parts=key.split(".");parts[1]=parts[1]?"."+parts[1]:"";if(value===undefined){var data=this.triggerHandler("getData"+parts[1]+"!",[parts[0]]);if(data===undefined&&this.length)data=jQuery.data(this[0],key);return data===undefined&&parts[1]?this.data(parts[0]):data;}else
+return this.trigger("setData"+parts[1]+"!",[parts[0],value]).each(function(){jQuery.data(this,key,value);});},removeData:function(key){return this.each(function(){jQuery.removeData(this,key);});},domManip:function(args,table,reverse,callback){var clone=this.length>1,elems;return this.each(function(){if(!elems){elems=jQuery.clean(args,this.ownerDocument);if(reverse)elems.reverse();}var obj=this;if(table&&jQuery.nodeName(this,"table")&&jQuery.nodeName(elems[0],"tr"))obj=this.getElementsByTagName("tbody")[0]||this.appendChild(this.ownerDocument.createElement("tbody"));var scripts=jQuery([]);jQuery.each(elems,function(){var elem=clone?jQuery(this).clone(true)[0]:this;if(jQuery.nodeName(elem,"script"))scripts=scripts.add(elem);else{if(elem.nodeType==1)scripts=scripts.add(jQuery("script",elem).remove());callback.call(obj,elem);}});scripts.each(evalScript);});}};jQuery.fn.init.prototype=jQuery.fn;function evalScript(i,elem){if(elem.src)jQuery.ajax({url:elem.src,async:false,dataType:"script"});else
+jQuery.globalEval(elem.text||elem.textContent||elem.innerHTML||"");if(elem.parentNode)elem.parentNode.removeChild(elem);}function now(){return+new Date;}jQuery.extend=jQuery.fn.extend=function(){var target=arguments[0]||{},i=1,length=arguments.length,deep=false,options;if(target.constructor==Boolean){deep=target;target=arguments[1]||{};i=2;}if(typeof target!="object"&&typeof target!="function")target={};if(length==i){target=this;--i;}for(;i<length;i++)if((options=arguments[i])!=null)for(var name in options){var src=target[name],copy=options[name];if(target===copy)continue;if(deep&&copy&&typeof copy=="object"&&!copy.nodeType)target[name]=jQuery.extend(deep,src||(copy.length!=null?[]:{}),copy);else if(copy!==undefined)target[name]=copy;}return target;};var expando="jQuery"+now(),uuid=0,windowData={},exclude=/z-?index|font-?weight|opacity|zoom|line-?height/i,defaultView=document.defaultView||{};jQuery.extend({noConflict:function(deep){window.$=_$;if(deep)window.jQuery=_jQuery;return jQuery;},isFunction:function(fn){return!!fn&&typeof fn!="string"&&!fn.nodeName&&fn.constructor!=Array&&/^[\s[]?function/.test(fn+"");},isXMLDoc:function(elem){return elem.documentElement&&!elem.body||elem.tagName&&elem.ownerDocument&&!elem.ownerDocument.body;},globalEval:function(data){data=jQuery.trim(data);if(data){var head=document.getElementsByTagName("head")[0]||document.documentElement,script=document.createElement("script");script.type="text/javascript";if(jQuery.browser.msie)script.text=data;else
+script.appendChild(document.createTextNode(data));head.insertBefore(script,head.firstChild);head.removeChild(script);}},nodeName:function(elem,name){return elem.nodeName&&elem.nodeName.toUpperCase()==name.toUpperCase();},cache:{},data:function(elem,name,data){elem=elem==window?windowData:elem;var id=elem[expando];if(!id)id=elem[expando]=++uuid;if(name&&!jQuery.cache[id])jQuery.cache[id]={};if(data!==undefined)jQuery.cache[id][name]=data;return name?jQuery.cache[id][name]:id;},removeData:function(elem,name){elem=elem==window?windowData:elem;var id=elem[expando];if(name){if(jQuery.cache[id]){delete jQuery.cache[id][name];name="";for(name in jQuery.cache[id])break;if(!name)jQuery.removeData(elem);}}else{try{delete elem[expando];}catch(e){if(elem.removeAttribute)elem.removeAttribute(expando);}delete jQuery.cache[id];}},each:function(object,callback,args){var name,i=0,length=object.length;if(args){if(length==undefined){for(name in object)if(callback.apply(object[name],args)===false)break;}else
+for(;i<length;)if(callback.apply(object[i++],args)===false)break;}else{if(length==undefined){for(name in object)if(callback.call(object[name],name,object[name])===false)break;}else
+for(var value=object[0];i<length&&callback.call(value,i,value)!==false;value=object[++i]){}}return object;},prop:function(elem,value,type,i,name){if(jQuery.isFunction(value))value=value.call(elem,i);return value&&value.constructor==Number&&type=="curCSS"&&!exclude.test(name)?value+"px":value;},className:{add:function(elem,classNames){jQuery.each((classNames||"").split(/\s+/),function(i,className){if(elem.nodeType==1&&!jQuery.className.has(elem.className,className))elem.className+=(elem.className?" ":"")+className;});},remove:function(elem,classNames){if(elem.nodeType==1)elem.className=classNames!=undefined?jQuery.grep(elem.className.split(/\s+/),function(className){return!jQuery.className.has(classNames,className);}).join(" "):"";},has:function(elem,className){return jQuery.inArray(className,(elem.className||elem).toString().split(/\s+/))>-1;}},swap:function(elem,options,callback){var old={};for(var name in options){old[name]=elem.style[name];elem.style[name]=options[name];}callback.call(elem);for(var name in options)elem.style[name]=old[name];},css:function(elem,name,force){if(name=="width"||name=="height"){var val,props={position:"absolute",visibility:"hidden",display:"block"},which=name=="width"?["Left","Right"]:["Top","Bottom"];function getWH(){val=name=="width"?elem.offsetWidth:elem.offsetHeight;var padding=0,border=0;jQuery.each(which,function(){padding+=parseFloat(jQuery.curCSS(elem,"padding"+this,true))||0;border+=parseFloat(jQuery.curCSS(elem,"border"+this+"Width",true))||0;});val-=Math.round(padding+border);}if(jQuery(elem).is(":visible"))getWH();else
+jQuery.swap(elem,props,getWH);return Math.max(0,val);}return jQuery.curCSS(elem,name,force);},curCSS:function(elem,name,force){var ret,style=elem.style;function color(elem){if(!jQuery.browser.safari)return false;var ret=defaultView.getComputedStyle(elem,null);return!ret||ret.getPropertyValue("color")=="";}if(name=="opacity"&&jQuery.browser.msie){ret=jQuery.attr(style,"opacity");return ret==""?"1":ret;}if(jQuery.browser.opera&&name=="display"){var save=style.outline;style.outline="0 solid black";style.outline=save;}if(name.match(/float/i))name=styleFloat;if(!force&&style&&style[name])ret=style[name];else if(defaultView.getComputedStyle){if(name.match(/float/i))name="float";name=name.replace(/([A-Z])/g,"-$1").toLowerCase();var computedStyle=defaultView.getComputedStyle(elem,null);if(computedStyle&&!color(elem))ret=computedStyle.getPropertyValue(name);else{var swap=[],stack=[],a=elem,i=0;for(;a&&color(a);a=a.parentNode)stack.unshift(a);for(;i<stack.length;i++)if(color(stack[i])){swap[i]=stack[i].style.display;stack[i].style.display="block";}ret=name=="display"&&swap[stack.length-1]!=null?"none":(computedStyle&&computedStyle.getPropertyValue(name))||"";for(i=0;i<swap.length;i++)if(swap[i]!=null)stack[i].style.display=swap[i];}if(name=="opacity"&&ret=="")ret="1";}else if(elem.currentStyle){var camelCase=name.replace(/\-(\w)/g,function(all,letter){return letter.toUpperCase();});ret=elem.currentStyle[name]||elem.currentStyle[camelCase];if(!/^\d+(px)?$/i.test(ret)&&/^\d/.test(ret)){var left=style.left,rsLeft=elem.runtimeStyle.left;elem.runtimeStyle.left=elem.currentStyle.left;style.left=ret||0;ret=style.pixelLeft+"px";style.left=left;elem.runtimeStyle.left=rsLeft;}}return ret;},clean:function(elems,context){var ret=[];context=context||document;if(typeof context.createElement=='undefined')context=context.ownerDocument||context[0]&&context[0].ownerDocument||document;jQuery.each(elems,function(i,elem){if(!elem)return;if(elem.constructor==Number)elem+='';if(typeof elem=="string"){elem=elem.replace(/(<(\w+)[^>]*?)\/>/g,function(all,front,tag){return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?all:front+"></"+tag+">";});var tags=jQuery.trim(elem).toLowerCase(),div=context.createElement("div");var wrap=!tags.indexOf("<opt")&&[1,"<select multiple='multiple'>","</select>"]||!tags.indexOf("<leg")&&[1,"<fieldset>","</fieldset>"]||tags.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"<table>","</table>"]||!tags.indexOf("<tr")&&[2,"<table><tbody>","</tbody></table>"]||(!tags.indexOf("<td")||!tags.indexOf("<th"))&&[3,"<table><tbody><tr>","</tr></tbody></table>"]||!tags.indexOf("<col")&&[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"]||jQuery.browser.msie&&[1,"div<div>","</div>"]||[0,"",""];div.innerHTML=wrap[1]+elem+wrap[2];while(wrap[0]--)div=div.lastChild;if(jQuery.browser.msie){var tbody=!tags.indexOf("<table")&&tags.indexOf("<tbody")<0?div.firstChild&&div.firstChild.childNodes:wrap[1]=="<table>"&&tags.indexOf("<tbody")<0?div.childNodes:[];for(var j=tbody.length-1;j>=0;--j)if(jQuery.nodeName(tbody[j],"tbody")&&!tbody[j].childNodes.length)tbody[j].parentNode.removeChild(tbody[j]);if(/^\s/.test(elem))div.insertBefore(context.createTextNode(elem.match(/^\s*/)[0]),div.firstChild);}elem=jQuery.makeArray(div.childNodes);}if(elem.length===0&&(!jQuery.nodeName(elem,"form")&&!jQuery.nodeName(elem,"select")))return;if(elem[0]==undefined||jQuery.nodeName(elem,"form")||elem.options)ret.push(elem);else
+ret=jQuery.merge(ret,elem);});return ret;},attr:function(elem,name,value){if(!elem||elem.nodeType==3||elem.nodeType==8)return undefined;var notxml=!jQuery.isXMLDoc(elem),set=value!==undefined,msie=jQuery.browser.msie;name=notxml&&jQuery.props[name]||name;if(elem.tagName){var special=/href|src|style/.test(name);if(name=="selected"&&jQuery.browser.safari)elem.parentNode.selectedIndex;if(name in elem&&notxml&&!special){if(set){if(name=="type"&&jQuery.nodeName(elem,"input")&&elem.parentNode)throw"type property can't be changed";elem[name]=value;}if(jQuery.nodeName(elem,"form")&&elem.getAttributeNode(name))return elem.getAttributeNode(name).nodeValue;return elem[name];}if(msie&&notxml&&name=="style")return jQuery.attr(elem.style,"cssText",value);if(set)elem.setAttribute(name,""+value);var attr=msie&&notxml&&special?elem.getAttribute(name,2):elem.getAttribute(name);return attr===null?undefined:attr;}if(msie&&name=="opacity"){if(set){elem.zoom=1;elem.filter=(elem.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(value)+''=="NaN"?"":"alpha(opacity="+value*100+")");}return elem.filter&&elem.filter.indexOf("opacity=")>=0?(parseFloat(elem.filter.match(/opacity=([^)]*)/)[1])/100)+'':"";}name=name.replace(/-([a-z])/ig,function(all,letter){return letter.toUpperCase();});if(set)elem[name]=value;return elem[name];},trim:function(text){return(text||"").replace(/^\s+|\s+$/g,"");},makeArray:function(array){var ret=[];if(array!=null){var i=array.length;if(i==null||array.split||array.setInterval||array.call)ret[0]=array;else
+while(i)ret[--i]=array[i];}return ret;},inArray:function(elem,array){for(var i=0,length=array.length;i<length;i++)if(array[i]===elem)return i;return-1;},merge:function(first,second){var i=0,elem,pos=first.length;if(jQuery.browser.msie){while(elem=second[i++])if(elem.nodeType!=8)first[pos++]=elem;}else
+while(elem=second[i++])first[pos++]=elem;return first;},unique:function(array){var ret=[],done={};try{for(var i=0,length=array.length;i<length;i++){var id=jQuery.data(array[i]);if(!done[id]){done[id]=true;ret.push(array[i]);}}}catch(e){ret=array;}return ret;},grep:function(elems,callback,inv){var ret=[];for(var i=0,length=elems.length;i<length;i++)if(!inv!=!callback(elems[i],i))ret.push(elems[i]);return ret;},map:function(elems,callback){var ret=[];for(var i=0,length=elems.length;i<length;i++){var value=callback(elems[i],i);if(value!=null)ret[ret.length]=value;}return ret.concat.apply([],ret);}});var userAgent=navigator.userAgent.toLowerCase();jQuery.browser={version:(userAgent.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/)||[])[1],safari:/webkit/.test(userAgent),opera:/opera/.test(userAgent),msie:/msie/.test(userAgent)&&!/opera/.test(userAgent),mozilla:/mozilla/.test(userAgent)&&!/(compatible|webkit)/.test(userAgent)};var styleFloat=jQuery.browser.msie?"styleFloat":"cssFloat";jQuery.extend({boxModel:!jQuery.browser.msie||document.compatMode=="CSS1Compat",props:{"for":"htmlFor","class":"className","float":styleFloat,cssFloat:styleFloat,styleFloat:styleFloat,readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing"}});jQuery.each({parent:function(elem){return elem.parentNode;},parents:function(elem){return jQuery.dir(elem,"parentNode");},next:function(elem){return jQuery.nth(elem,2,"nextSibling");},prev:function(elem){return jQuery.nth(elem,2,"previousSibling");},nextAll:function(elem){return jQuery.dir(elem,"nextSibling");},prevAll:function(elem){return jQuery.dir(elem,"previousSibling");},siblings:function(elem){return jQuery.sibling(elem.parentNode.firstChild,elem);},children:function(elem){return jQuery.sibling(elem.firstChild);},contents:function(elem){return jQuery.nodeName(elem,"iframe")?elem.contentDocument||elem.contentWindow.document:jQuery.makeArray(elem.childNodes);}},function(name,fn){jQuery.fn[name]=function(selector){var ret=jQuery.map(this,fn);if(selector&&typeof selector=="string")ret=jQuery.multiFilter(selector,ret);return this.pushStack(jQuery.unique(ret));};});jQuery.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(name,original){jQuery.fn[name]=function(){var args=arguments;return this.each(function(){for(var i=0,length=args.length;i<length;i++)jQuery(args[i])[original](this);});};});jQuery.each({removeAttr:function(name){jQuery.attr(this,name,"");if(this.nodeType==1)this.removeAttribute(name);},addClass:function(classNames){jQuery.className.add(this,classNames);},removeClass:function(classNames){jQuery.className.remove(this,classNames);},toggleClass:function(classNames){jQuery.className[jQuery.className.has(this,classNames)?"remove":"add"](this,classNames);},remove:function(selector){if(!selector||jQuery.filter(selector,[this]).r.length){jQuery("*",this).add(this).each(function(){jQuery.event.remove(this);jQuery.removeData(this);});if(this.parentNode)this.parentNode.removeChild(this);}},empty:function(){jQuery(">*",this).remove();while(this.firstChild)this.removeChild(this.firstChild);}},function(name,fn){jQuery.fn[name]=function(){return this.each(fn,arguments);};});jQuery.each(["Height","Width"],function(i,name){var type=name.toLowerCase();jQuery.fn[type]=function(size){return this[0]==window?jQuery.browser.opera&&document.body["client"+name]||jQuery.browser.safari&&window["inner"+name]||document.compatMode=="CSS1Compat"&&document.documentElement["client"+name]||document.body["client"+name]:this[0]==document?Math.max(Math.max(document.body["scroll"+name],document.documentElement["scroll"+name]),Math.max(document.body["offset"+name],document.documentElement["offset"+name])):size==undefined?(this.length?jQuery.css(this[0],type):null):this.css(type,size.constructor==String?size:size+"px");};});function num(elem,prop){return elem[0]&&parseInt(jQuery.curCSS(elem[0],prop,true),10)||0;}var chars=jQuery.browser.safari&&parseInt(jQuery.browser.version)<417?"(?:[\\w*_-]|\\\\.)":"(?:[\\w\u0128-\uFFFF*_-]|\\\\.)",quickChild=new RegExp("^>\\s*("+chars+"+)"),quickID=new RegExp("^("+chars+"+)(#)("+chars+"+)"),quickClass=new RegExp("^([#.]?)("+chars+"*)");jQuery.extend({expr:{"":function(a,i,m){return m[2]=="*"||jQuery.nodeName(a,m[2]);},"#":function(a,i,m){return a.getAttribute("id")==m[2];},":":{lt:function(a,i,m){return i<m[3]-0;},gt:function(a,i,m){return i>m[3]-0;},nth:function(a,i,m){return m[3]-0==i;},eq:function(a,i,m){return m[3]-0==i;},first:function(a,i){return i==0;},last:function(a,i,m,r){return i==r.length-1;},even:function(a,i){return i%2==0;},odd:function(a,i){return i%2;},"first-child":function(a){return a.parentNode.getElementsByTagName("*")[0]==a;},"last-child":function(a){return jQuery.nth(a.parentNode.lastChild,1,"previousSibling")==a;},"only-child":function(a){return!jQuery.nth(a.parentNode.lastChild,2,"previousSibling");},parent:function(a){return a.firstChild;},empty:function(a){return!a.firstChild;},contains:function(a,i,m){return(a.textContent||a.innerText||jQuery(a).text()||"").indexOf(m[3])>=0;},visible:function(a){return"hidden"!=a.type&&jQuery.css(a,"display")!="no