home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / pytho152.zip / emx / lib / python1.5 / urlparse.py < prev    next >
Text File  |  2000-08-10  |  7KB  |  255 lines

  1. # Parse (absolute and relative) URLs.  See RFC 1808: "Relative Uniform
  2. # Resource Locators", by R. Fielding, UC Irvine, June 1995.
  3.  
  4. # Standard/builtin Python modules
  5. import string
  6. from string import joinfields, splitfields, find, rfind
  7.  
  8. # A classification of schemes ('' means apply by default)
  9. uses_relative = ['ftp', 'http', 'gopher', 'nntp', 'wais', 'file',
  10.          'https', 'shttp',
  11.          'prospero', '']
  12. uses_netloc = ['ftp', 'http', 'gopher', 'nntp', 'telnet', 'wais',
  13.            'file',
  14.            'https', 'shttp', 'snews',
  15.            'prospero', '']
  16. non_hierarchical = ['gopher', 'hdl', 'mailto', 'news', 'telnet', 'wais',
  17.             'snews',
  18.             ]
  19. uses_params = ['ftp', 'hdl', 'prospero', 'http',
  20.            'https', 'shttp',
  21.            '']
  22. uses_query = ['http', 'wais',
  23.           'https', 'shttp',
  24.           'gopher',
  25.           '']
  26. uses_fragment = ['ftp', 'hdl', 'http', 'gopher', 'news', 'nntp', 'wais',
  27.          'https', 'shttp', 'snews',
  28.          'file', 'prospero', '']
  29.  
  30. # Characters valid in scheme names
  31. scheme_chars = string.letters + string.digits + '+-.'
  32.  
  33. MAX_CACHE_SIZE = 20
  34. _parse_cache = {}
  35.  
  36. def clear_cache():
  37.     """Clear the parse cache."""
  38.     global _parse_cache
  39.     _parse_cache = {}
  40.  
  41.  
  42. # Parse a URL into 6 components:
  43. # <scheme>://<netloc>/<path>;<params>?<query>#<fragment>
  44. # Return a 6-tuple: (scheme, netloc, path, params, query, fragment).
  45. # Note that we don't break the components up in smaller bits
  46. # (e.g. netloc is a single string) and we don't expand % escapes.
  47. def urlparse(url, scheme = '', allow_fragments = 1):
  48.     key = url, scheme, allow_fragments
  49.     cached = _parse_cache.get(key, None)
  50.     if cached:
  51.         return cached
  52.     if len(_parse_cache) >= MAX_CACHE_SIZE:    # avoid runaway growth
  53.         clear_cache()
  54.     find = string.find
  55.     netloc = path = params = query = fragment = ''
  56.     i = find(url, ':')
  57.     if i > 0:
  58.         if url[:i] == 'http': # optimize the common case
  59.             scheme = string.lower(url[:i])
  60.             url = url[i+1:]
  61.             if url[:2] == '//':
  62.                 i = find(url, '/', 2)
  63.                 if i < 0:
  64.                     i = len(url)
  65.                 netloc = url[2:i]
  66.                 url = url[i:]
  67.             if allow_fragments:
  68.                 i = string.rfind(url, '#')
  69.                 if i >= 0:
  70.                     fragment = url[i+1:]
  71.                     url = url[:i]
  72.             i = find(url, '?')
  73.             if i >= 0:
  74.                 query = url[i+1:]
  75.                 url = url[:i]
  76.             i = find(url, ';')
  77.             if i >= 0:
  78.                 params = url[i+1:]
  79.                 url = url[:i]
  80.             tuple = scheme, netloc, url, params, query, fragment
  81.             _parse_cache[key] = tuple
  82.             return tuple
  83.         for c in url[:i]:
  84.             if c not in scheme_chars:
  85.                 break
  86.         else:
  87.             scheme, url = string.lower(url[:i]), url[i+1:]
  88.     if scheme in uses_netloc:
  89.         if url[:2] == '//':
  90.             i = find(url, '/', 2)
  91.             if i < 0:
  92.                 i = len(url)
  93.             netloc, url = url[2:i], url[i:]
  94.     if allow_fragments and scheme in uses_fragment:
  95.         i = string.rfind(url, '#')
  96.         if i >= 0:
  97.             url, fragment = url[:i], url[i+1:]
  98.     if scheme in uses_query:
  99.         i = find(url, '?')
  100.         if i >= 0:
  101.             url, query = url[:i], url[i+1:]
  102.     if scheme in uses_params:
  103.         i = find(url, ';')
  104.         if i >= 0:
  105.             url, params = url[:i], url[i+1:]
  106.     tuple = scheme, netloc, url, params, query, fragment
  107.     _parse_cache[key] = tuple
  108.     return tuple
  109.  
  110. # Put a parsed URL back together again.  This may result in a slightly
  111. # different, but equivalent URL, if the URL that was parsed originally
  112. # had redundant delimiters, e.g. a ? with an empty query (the draft
  113. # states that these are equivalent).
  114. def urlunparse((scheme, netloc, url, params, query, fragment)):
  115.     if netloc or (scheme in uses_netloc and url[:2] == '//'):
  116.         if url[:1] != '/': url = '/' + url
  117.         url = '//' + (netloc or '') + url
  118.     if scheme:
  119.         url = scheme + ':' + url
  120.     if params:
  121.         url = url + ';' + params
  122.     if query:
  123.         url = url + '?' + query
  124.     if fragment:
  125.         url = url + '#' + fragment
  126.     return url
  127.  
  128. # Join a base URL and a possibly relative URL to form an absolute
  129. # interpretation of the latter.
  130. def urljoin(base, url, allow_fragments = 1):
  131.     if not base:
  132.         return url
  133.     bscheme, bnetloc, bpath, bparams, bquery, bfragment = \
  134.         urlparse(base, '', allow_fragments)
  135.     scheme, netloc, path, params, query, fragment = \
  136.         urlparse(url, bscheme, allow_fragments)
  137.     if scheme != bscheme or scheme not in uses_relative:
  138.         return urlunparse((scheme, netloc, path,
  139.                    params, query, fragment))
  140.     if scheme in uses_netloc:
  141.         if netloc:
  142.             return urlunparse((scheme, netloc, path,
  143.                        params, query, fragment))
  144.         netloc = bnetloc
  145.     if path[:1] == '/':
  146.         return urlunparse((scheme, netloc, path,
  147.                    params, query, fragment))
  148.     if not path:
  149.         return urlunparse((scheme, netloc, bpath,
  150.                    params, query or bquery, fragment))
  151.     i = rfind(bpath, '/')
  152.     if i >= 0:
  153.         path = bpath[:i] + '/' + path
  154.     segments = splitfields(path, '/')
  155.     if segments[-1] == '.':
  156.         segments[-1] = ''
  157.     while '.' in segments:
  158.         segments.remove('.')
  159.     while 1:
  160.         i = 1
  161.         n = len(segments) - 1
  162.         while i < n:
  163.             if segments[i] == '..' and segments[i-1]:
  164.                 del segments[i-1:i+1]
  165.                 break
  166.             i = i+1
  167.         else:
  168.             break
  169.     if len(segments) == 2 and segments[1] == '..' and segments[0] == '':
  170.         segments[-1] = ''
  171.     elif len(segments) >= 2 and segments[-1] == '..':
  172.         segments[-2:] = ['']
  173.     return urlunparse((scheme, netloc, joinfields(segments, '/'),
  174.                params, query, fragment))
  175.  
  176. def urldefrag(url):
  177.     """Removes any existing fragment from URL.
  178.  
  179.     Returns a tuple of the defragmented URL and the fragment.  If
  180.     the URL contained no fragments, the second element is the
  181.     empty string.
  182.     """
  183.     s, n, p, a, q, frag = urlparse(url)
  184.     defrag = urlunparse((s, n, p, a, q, ''))
  185.     return defrag, frag
  186.  
  187.  
  188. test_input = """
  189.       http://a/b/c/d
  190.  
  191.       g:h        = <URL:g:h>
  192.       http:g     = <URL:http://a/b/c/g>
  193.       http:      = <URL:http://a/b/c/d>
  194.       g          = <URL:http://a/b/c/g>
  195.       ./g        = <URL:http://a/b/c/g>
  196.       g/         = <URL:http://a/b/c/g/>
  197.       /g         = <URL:http://a/g>
  198.       //g        = <URL:http://g>
  199.       ?y         = <URL:http://a/b/c/d?y>
  200.       g?y        = <URL:http://a/b/c/g?y>
  201.       g?y/./x    = <URL:http://a/b/c/g?y/./x>
  202.       .          = <URL:http://a/b/c/>
  203.       ./         = <URL:http://a/b/c/>
  204.       ..         = <URL:http://a/b/>
  205.       ../        = <URL:http://a/b/>
  206.       ../g       = <URL:http://a/b/g>
  207.       ../..      = <URL:http://a/>
  208.       ../../g    = <URL:http://a/g>
  209.       ../../../g = <URL:http://a/../g>
  210.       ./../g     = <URL:http://a/b/g>
  211.       ./g/.      = <URL:http://a/b/c/g/>
  212.       /./g       = <URL:http://a/./g>
  213.       g/./h      = <URL:http://a/b/c/g/h>
  214.       g/../h     = <URL:http://a/b/c/h>
  215.       http:g     = <URL:http://a/b/c/g>
  216.       http:      = <URL:http://a/b/c/d>
  217.       http:?y         = <URL:http://a/b/c/d?y>
  218.       http:g?y        = <URL:http://a/b/c/g?y>
  219.       http:g?y/./x    = <URL:http://a/b/c/g?y/./x>
  220. """
  221. # XXX The result for //g is actually http://g/; is this a problem?
  222.  
  223. def test():
  224.     import sys
  225.     base = ''
  226.     if sys.argv[1:]:
  227.         fn = sys.argv[1]
  228.         if fn == '-':
  229.             fp = sys.stdin
  230.         else:
  231.             fp = open(fn)
  232.     else:
  233.         import StringIO
  234.         fp = StringIO.StringIO(test_input)
  235.     while 1:
  236.         line = fp.readline()
  237.         if not line: break
  238.         words = string.split(line)
  239.         if not words:
  240.             continue
  241.         url = words[0]
  242.         parts = urlparse(url)
  243.         print '%-10s : %s' % (url, parts)
  244.         abs = urljoin(base, url)
  245.         if not base:
  246.             base = abs
  247.         wrapped = '<URL:%s>' % abs
  248.         print '%-10s = %s' % (url, wrapped)
  249.         if len(words) == 3 and words[1] == '=':
  250.             if wrapped != words[2]:
  251.                 print 'EXPECTED', words[2], '!!!!!!!!!!'
  252.  
  253. if __name__ == '__main__':
  254.     test()
  255.