home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2011 October / maximum-cd-2011-10.iso / DiscContents / digsby_setup.exe / lib / feedparser.pyo (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2011-06-22  |  86.4 KB  |  2,966 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyo (Python 2.6)
  3.  
  4. __version__ = '4.1'
  5. __license__ = "Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification,\nare permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice,\n  this list of conditions and the following disclaimer.\n* Redistributions in binary form must reproduce the above copyright notice,\n  this list of conditions and the following disclaimer in the documentation\n  and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS'\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\nARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\nLIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\nCONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\nSUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\nINTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\nCONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\nARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGE."
  6. __author__ = 'Mark Pilgrim <http://diveintomark.org/>'
  7. __contributors__ = [
  8.     'Jason Diamond <http://injektilo.org/>',
  9.     'John Beimler <http://john.beimler.org/>',
  10.     'Fazal Majid <http://www.majid.info/mylos/weblog/>',
  11.     'Aaron Swartz <http://aaronsw.com/>',
  12.     'Kevin Marks <http://epeus.blogspot.com/>']
  13. _debug = 0
  14. USER_AGENT = 'UniversalFeedParser/%s +http://feedparser.org/' % __version__
  15. ACCEPT_HEADER = 'application/atom+xml,application/rdf+xml,application/rss+xml,application/x-netcdf,application/xml;q=0.9,text/xml;q=0.2,*/*;q=0.1'
  16. PREFERRED_XML_PARSERS = [
  17.     'drv_libxml2']
  18. TIDY_MARKUP = 0
  19. PREFERRED_TIDY_INTERFACES = [
  20.     'uTidy',
  21.     'mxTidy']
  22. import sgmllib
  23. import re
  24. import sys
  25. import copy
  26. import urlparse
  27. import time
  28. import rfc822
  29. import types
  30. import cgi
  31. import urllib
  32. import urllib2
  33.  
  34. try:
  35.     from cStringIO import StringIO as _StringIO
  36. except:
  37.     from StringIO import StringIO as _StringIO
  38.  
  39.  
  40. try:
  41.     import gzip
  42. except:
  43.     gzip = None
  44.  
  45.  
  46. try:
  47.     import zlib
  48. except:
  49.     zlib = None
  50.  
  51.  
  52. try:
  53.     import xml.sax as xml
  54.     xml.sax.make_parser(PREFERRED_XML_PARSERS)
  55.     from xml.sax.saxutils import escape as _xmlescape
  56.     _XML_AVAILABLE = 1
  57. except:
  58.     _XML_AVAILABLE = 0
  59.     
  60.     def _xmlescape(data):
  61.         data = data.replace('&', '&')
  62.         data = data.replace('>', '>')
  63.         data = data.replace('<', '<')
  64.         return data
  65.  
  66.  
  67.  
  68. try:
  69.     import base64
  70.     import binascii
  71. except:
  72.     base64 = binascii = None
  73.  
  74.  
  75. try:
  76.     import cjkcodecs.aliases as cjkcodecs
  77. except:
  78.     pass
  79.  
  80.  
  81. try:
  82.     import iconv_codec
  83. except:
  84.     pass
  85.  
  86.  
  87. try:
  88.     import chardet
  89.     if _debug:
  90.         import chardet.constants as chardet
  91.         chardet.constants._debug = 1
  92. except:
  93.     chardet = None
  94.  
  95.  
  96. class ThingsNobodyCaresAboutButMe(Exception):
  97.     pass
  98.  
  99.  
  100. class CharacterEncodingOverride(ThingsNobodyCaresAboutButMe):
  101.     pass
  102.  
  103.  
  104. class CharacterEncodingUnknown(ThingsNobodyCaresAboutButMe):
  105.     pass
  106.  
  107.  
  108. class NonXMLContentType(ThingsNobodyCaresAboutButMe):
  109.     pass
  110.  
  111.  
  112. class UndeclaredNamespace(Exception):
  113.     pass
  114.  
  115. sgmllib.tagfind = re.compile('[a-zA-Z][-_.:a-zA-Z0-9]*')
  116. sgmllib.special = re.compile('<!')
  117. sgmllib.charref = re.compile('&#(x?[0-9A-Fa-f]+)[^0-9A-Fa-f]')
  118. SUPPORTED_VERSIONS = {
  119.     '': 'unknown',
  120.     'rss090': 'RSS 0.90',
  121.     'rss091n': 'RSS 0.91 (Netscape)',
  122.     'rss091u': 'RSS 0.91 (Userland)',
  123.     'rss092': 'RSS 0.92',
  124.     'rss093': 'RSS 0.93',
  125.     'rss094': 'RSS 0.94',
  126.     'rss20': 'RSS 2.0',
  127.     'rss10': 'RSS 1.0',
  128.     'rss': 'RSS (unknown version)',
  129.     'atom01': 'Atom 0.1',
  130.     'atom02': 'Atom 0.2',
  131.     'atom03': 'Atom 0.3',
  132.     'atom10': 'Atom 1.0',
  133.     'atom': 'Atom (unknown version)',
  134.     'cdf': 'CDF',
  135.     'hotrss': 'Hot RSS' }
  136.  
  137. try:
  138.     UserDict = dict
  139. except NameError:
  140.     from UserDict import UserDict
  141.     
  142.     def dict(aList):
  143.         rc = { }
  144.         for k, v in aList:
  145.             rc[k] = v
  146.         
  147.         return rc
  148.  
  149.  
  150.  
  151. class FeedParserDict(UserDict):
  152.     keymap = {
  153.         'channel': 'feed',
  154.         'items': 'entries',
  155.         'guid': 'id',
  156.         'date': 'updated',
  157.         'date_parsed': 'updated_parsed',
  158.         'description': [
  159.             'subtitle',
  160.             'summary'],
  161.         'url': [
  162.             'href'],
  163.         'modified': 'updated',
  164.         'modified_parsed': 'updated_parsed',
  165.         'issued': 'published',
  166.         'issued_parsed': 'published_parsed',
  167.         'copyright': 'rights',
  168.         'copyright_detail': 'rights_detail',
  169.         'tagline': 'subtitle',
  170.         'tagline_detail': 'subtitle_detail' }
  171.     
  172.     def __getitem__(self, key):
  173.         if key == 'category':
  174.             return UserDict.__getitem__(self, 'tags')[0]['term']
  175.         if key == 'categories':
  176.             return [ (tag['scheme'], tag['term']) for tag in UserDict.__getitem__(self, 'tags') ]
  177.         realkey = self.keymap.get(key, key)
  178.         if UserDict.has_key(self, key):
  179.             return UserDict.__getitem__(self, key)
  180.         return UserDict.__getitem__(self, realkey)
  181.  
  182.     
  183.     def __setitem__(self, key, value):
  184.         for k in self.keymap.keys():
  185.             if key == k:
  186.                 key = self.keymap[k]
  187.                 if type(key) == types.ListType:
  188.                     key = key[0]
  189.                 
  190.             type(key) == types.ListType
  191.         
  192.         return UserDict.__setitem__(self, key, value)
  193.  
  194.     
  195.     def get(self, key, default = None):
  196.         if self.has_key(key):
  197.             return self[key]
  198.         return default
  199.  
  200.     
  201.     def setdefault(self, key, value):
  202.         if not self.has_key(key):
  203.             self[key] = value
  204.         
  205.         return self[key]
  206.  
  207.     
  208.     def has_key(self, key):
  209.         
  210.         try:
  211.             if not hasattr(self, key):
  212.                 pass
  213.             return UserDict.has_key(self, key)
  214.         except AttributeError:
  215.             return False
  216.  
  217.  
  218.     
  219.     def __getattr__(self, key):
  220.         
  221.         try:
  222.             return self.__dict__[key]
  223.         except KeyError:
  224.             pass
  225.  
  226.         
  227.         try:
  228.             return self.__getitem__(key)
  229.         except:
  230.             raise AttributeError, "object has no attribute '%s'" % key
  231.  
  232.  
  233.     
  234.     def __setattr__(self, key, value):
  235.         if key.startswith('_') or key == 'data':
  236.             self.__dict__[key] = value
  237.         else:
  238.             return self.__setitem__(key, value)
  239.         return key == 'data'
  240.  
  241.     
  242.     def __contains__(self, key):
  243.         return self.has_key(key)
  244.  
  245.  
  246.  
  247. def zopeCompatibilityHack():
  248.     global FeedParserDict, FeedParserDict
  249.     del FeedParserDict
  250.     
  251.     def FeedParserDict(aDict = None):
  252.         rc = { }
  253.         if aDict:
  254.             rc.update(aDict)
  255.         
  256.         return rc
  257.  
  258.  
  259. _ebcdic_to_ascii_map = None
  260.  
  261. def _ebcdic_to_ascii(s):
  262.     global _ebcdic_to_ascii_map
  263.     if not _ebcdic_to_ascii_map:
  264.         emap = (0, 1, 2, 3, 156, 9, 134, 127, 151, 141, 142, 11, 12, 13, 14, 15, 16, 17, 18, 19, 157, 133, 8, 135, 24, 25, 146, 143, 28, 29, 30, 31, 128, 129, 130, 131, 132, 10, 23, 27, 136, 137, 138, 139, 140, 5, 6, 7, 144, 145, 22, 147, 148, 149, 150, 4, 152, 153, 154, 155, 20, 21, 158, 26, 32, 160, 161, 162, 163, 164, 165, 166, 167, 168, 91, 46, 60, 40, 43, 33, 38, 169, 170, 171, 172, 173, 174, 175, 176, 177, 93, 36, 42, 41, 59, 94, 45, 47, 178, 179, 180, 181, 182, 183, 184, 185, 124, 44, 37, 95, 62, 63, 186, 187, 188, 189, 190, 191, 192, 193, 194, 96, 58, 35, 64, 39, 61, 34, 195, 97, 98, 99, 100, 101, 102, 103, 104, 105, 196, 197, 198, 199, 200, 201, 202, 106, 107, 108, 109, 110, 111, 112, 113, 114, 203, 204, 205, 206, 207, 208, 209, 126, 115, 116, 117, 118, 119, 120, 121, 122, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 123, 65, 66, 67, 68, 69, 70, 71, 72, 73, 232, 233, 234, 235, 236, 237, 125, 74, 75, 76, 77, 78, 79, 80, 81, 82, 238, 239, 240, 241, 242, 243, 92, 159, 83, 84, 85, 86, 87, 88, 89, 90, 244, 245, 246, 247, 248, 249, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 250, 251, 252, 253, 254, 255)
  265.         import string
  266.         _ebcdic_to_ascii_map = string.maketrans(''.join(map(chr, range(256))), ''.join(map(chr, emap)))
  267.     
  268.     return s.translate(_ebcdic_to_ascii_map)
  269.  
  270. _urifixer = re.compile('^([A-Za-z][A-Za-z0-9+-.]*://)(/*)(.*?)')
  271.  
  272. def _urljoin(base, uri):
  273.     uri = _urifixer.sub('\\1\\3', uri)
  274.     return urlparse.urljoin(base, uri)
  275.  
  276.  
  277. class _FeedParserMixin:
  278.     namespaces = {
  279.         '': '',
  280.         'http://backend.userland.com/rss': '',
  281.         'http://blogs.law.harvard.edu/tech/rss': '',
  282.         'http://purl.org/rss/1.0/': '',
  283.         'http://my.netscape.com/rdf/simple/0.9/': '',
  284.         'http://example.com/newformat#': '',
  285.         'http://example.com/necho': '',
  286.         'http://purl.org/echo/': '',
  287.         'uri/of/echo/namespace#': '',
  288.         'http://purl.org/pie/': '',
  289.         'http://purl.org/atom/ns#': '',
  290.         'http://www.w3.org/2005/Atom': '',
  291.         'http://purl.org/rss/1.0/modules/rss091#': '',
  292.         'http://webns.net/mvcb/': 'admin',
  293.         'http://purl.org/rss/1.0/modules/aggregation/': 'ag',
  294.         'http://purl.org/rss/1.0/modules/annotate/': 'annotate',
  295.         'http://media.tangent.org/rss/1.0/': 'audio',
  296.         'http://backend.userland.com/blogChannelModule': 'blogChannel',
  297.         'http://web.resource.org/cc/': 'cc',
  298.         'http://backend.userland.com/creativeCommonsRssModule': 'creativeCommons',
  299.         'http://purl.org/rss/1.0/modules/company': 'co',
  300.         'http://purl.org/rss/1.0/modules/content/': 'content',
  301.         'http://my.theinfo.org/changed/1.0/rss/': 'cp',
  302.         'http://purl.org/dc/elements/1.1/': 'dc',
  303.         'http://purl.org/dc/terms/': 'dcterms',
  304.         'http://purl.org/rss/1.0/modules/email/': 'email',
  305.         'http://purl.org/rss/1.0/modules/event/': 'ev',
  306.         'http://rssnamespace.org/feedburner/ext/1.0': 'feedburner',
  307.         'http://freshmeat.net/rss/fm/': 'fm',
  308.         'http://xmlns.com/foaf/0.1/': 'foaf',
  309.         'http://www.w3.org/2003/01/geo/wgs84_pos#': 'geo',
  310.         'http://postneo.com/icbm/': 'icbm',
  311.         'http://purl.org/rss/1.0/modules/image/': 'image',
  312.         'http://www.itunes.com/DTDs/PodCast-1.0.dtd': 'itunes',
  313.         'http://example.com/DTDs/PodCast-1.0.dtd': 'itunes',
  314.         'http://purl.org/rss/1.0/modules/link/': 'l',
  315.         'http://search.yahoo.com/mrss': 'media',
  316.         'http://madskills.com/public/xml/rss/module/pingback/': 'pingback',
  317.         'http://prismstandard.org/namespaces/1.2/basic/': 'prism',
  318.         'http://www.w3.org/1999/02/22-rdf-syntax-ns#': 'rdf',
  319.         'http://www.w3.org/2000/01/rdf-schema#': 'rdfs',
  320.         'http://purl.org/rss/1.0/modules/reference/': 'ref',
  321.         'http://purl.org/rss/1.0/modules/richequiv/': 'reqv',
  322.         'http://purl.org/rss/1.0/modules/search/': 'search',
  323.         'http://purl.org/rss/1.0/modules/slash/': 'slash',
  324.         'http://schemas.xmlsoap.org/soap/envelope/': 'soap',
  325.         'http://purl.org/rss/1.0/modules/servicestatus/': 'ss',
  326.         'http://hacks.benhammersley.com/rss/streaming/': 'str',
  327.         'http://purl.org/rss/1.0/modules/subscription/': 'sub',
  328.         'http://purl.org/rss/1.0/modules/syndication/': 'sy',
  329.         'http://purl.org/rss/1.0/modules/taxonomy/': 'taxo',
  330.         'http://purl.org/rss/1.0/modules/threading/': 'thr',
  331.         'http://purl.org/rss/1.0/modules/textinput/': 'ti',
  332.         'http://madskills.com/public/xml/rss/module/trackback/': 'trackback',
  333.         'http://wellformedweb.org/commentAPI/': 'wfw',
  334.         'http://purl.org/rss/1.0/modules/wiki/': 'wiki',
  335.         'http://www.w3.org/1999/xhtml': 'xhtml',
  336.         'http://www.w3.org/XML/1998/namespace': 'xml',
  337.         'http://schemas.pocketsoap.com/rss/myDescModule/': 'szf' }
  338.     _matchnamespaces = { }
  339.     can_be_relative_uri = [
  340.         'link',
  341.         'id',
  342.         'wfw_comment',
  343.         'wfw_commentrss',
  344.         'docs',
  345.         'url',
  346.         'href',
  347.         'comments',
  348.         'license',
  349.         'icon',
  350.         'logo']
  351.     can_contain_relative_uris = [
  352.         'content',
  353.         'title',
  354.         'summary',
  355.         'info',
  356.         'tagline',
  357.         'subtitle',
  358.         'copyright',
  359.         'rights',
  360.         'description']
  361.     can_contain_dangerous_markup = [
  362.         'content',
  363.         'title',
  364.         'summary',
  365.         'info',
  366.         'tagline',
  367.         'subtitle',
  368.         'copyright',
  369.         'rights',
  370.         'description']
  371.     html_types = [
  372.         'text/html',
  373.         'application/xhtml+xml']
  374.     
  375.     def __init__(self, baseuri = None, baselang = None, encoding = 'utf-8'):
  376.         if _debug:
  377.             sys.stderr.write('initializing FeedParser\n')
  378.         
  379.         if not self._matchnamespaces:
  380.             for k, v in self.namespaces.items():
  381.                 self._matchnamespaces[k.lower()] = v
  382.             
  383.         
  384.         self.feeddata = FeedParserDict()
  385.         self.encoding = encoding
  386.         self.entries = []
  387.         self.version = ''
  388.         self.namespacesInUse = { }
  389.         self.infeed = 0
  390.         self.inentry = 0
  391.         self.incontent = 0
  392.         self.intextinput = 0
  393.         self.inimage = 0
  394.         self.inauthor = 0
  395.         self.incontributor = 0
  396.         self.inpublisher = 0
  397.         self.insource = 0
  398.         self.sourcedata = FeedParserDict()
  399.         self.contentparams = FeedParserDict()
  400.         self._summaryKey = None
  401.         self.namespacemap = { }
  402.         self.elementstack = []
  403.         self.basestack = []
  404.         self.langstack = []
  405.         if not baseuri:
  406.             pass
  407.         self.baseuri = ''
  408.         if not baselang:
  409.             pass
  410.         self.lang = None
  411.         if baselang:
  412.             self.feeddata['language'] = baselang
  413.         
  414.  
  415.     
  416.     def unknown_starttag(self, tag, attrs):
  417.         if _debug:
  418.             sys.stderr.write('start %s with %s\n' % (tag, attrs))
  419.         
  420.         attrs = [ (k.lower(), v) for k, v in attrs ]
  421.         attrs = [ (k, v) for k, v in attrs ]
  422.         attrsD = dict(attrs)
  423.         if not attrsD.get('xml:base', attrsD.get('base')):
  424.             pass
  425.         baseuri = self.baseuri
  426.         self.baseuri = _urljoin(self.baseuri, baseuri)
  427.         lang = attrsD.get('xml:lang', attrsD.get('lang'))
  428.         if lang == '':
  429.             lang = None
  430.         elif lang is None:
  431.             lang = self.lang
  432.         
  433.         self.lang = lang
  434.         self.basestack.append(self.baseuri)
  435.         self.langstack.append(lang)
  436.         for prefix, uri in attrs:
  437.             if prefix.startswith('xmlns:'):
  438.                 self.trackNamespace(prefix[6:], uri)
  439.                 continue
  440.             None if lang else []
  441.             if prefix == 'xmlns':
  442.                 self.trackNamespace(None, uri)
  443.                 continue
  444.         
  445.         if self.incontent and self.contentparams.has_key('type') and not self.contentparams.get('type', 'xml').endswith('xml'):
  446.             self.contentparams['type'] = 'application/xhtml+xml'
  447.         
  448.         if self.incontent and self.contentparams.get('type') == 'application/xhtml+xml':
  449.             tag = tag.split(':')[-1]
  450.             return tag(''.join % ([], []([ ' %s="%s"' % t for t in attrs ])), escape = 0)
  451.         if tag.find(':') != -1:
  452.             (prefix, suffix) = tag.split(':', 1)
  453.         else:
  454.             prefix = ''
  455.             suffix = tag
  456.         prefix = self.namespacemap.get(prefix, prefix)
  457.         if prefix:
  458.             prefix = prefix + '_'
  459.         
  460.         if not prefix and tag not in ('title', 'link', 'description', 'name'):
  461.             self.intextinput = 0
  462.         
  463.         if not prefix and tag not in ('title', 'link', 'description', 'url', 'href', 'width', 'height'):
  464.             self.inimage = 0
  465.         
  466.         methodname = '_start_' + prefix + suffix
  467.         
  468.         try:
  469.             method = getattr(self, methodname)
  470.             return method(attrsD)
  471.         except AttributeError:
  472.             return self.push(prefix + suffix, 1)
  473.  
  474.  
  475.     
  476.     def unknown_endtag(self, tag):
  477.         if _debug:
  478.             sys.stderr.write('end %s\n' % tag)
  479.         
  480.         if tag.find(':') != -1:
  481.             (prefix, suffix) = tag.split(':', 1)
  482.         else:
  483.             prefix = ''
  484.             suffix = tag
  485.         prefix = self.namespacemap.get(prefix, prefix)
  486.         if prefix:
  487.             prefix = prefix + '_'
  488.         
  489.         methodname = '_end_' + prefix + suffix
  490.         
  491.         try:
  492.             method = getattr(self, methodname)
  493.             method()
  494.         except AttributeError:
  495.             self.pop(prefix + suffix)
  496.  
  497.         if self.incontent and self.contentparams.has_key('type') and not self.contentparams.get('type', 'xml').endswith('xml'):
  498.             self.contentparams['type'] = 'application/xhtml+xml'
  499.         
  500.         if self.incontent and self.contentparams.get('type') == 'application/xhtml+xml':
  501.             tag = tag.split(':')[-1]
  502.             self.handle_data('</%s>' % tag, escape = 0)
  503.         
  504.         if self.basestack:
  505.             self.basestack.pop()
  506.             if self.basestack and self.basestack[-1]:
  507.                 self.baseuri = self.basestack[-1]
  508.             
  509.         
  510.         if self.langstack:
  511.             self.langstack.pop()
  512.             if self.langstack:
  513.                 self.lang = self.langstack[-1]
  514.             
  515.         
  516.  
  517.     
  518.     def handle_charref(self, ref):
  519.         if not self.elementstack:
  520.             return None
  521.         ref = ref.lower()
  522.         if ref in ('34', '38', '39', '60', '62', 'x22', 'x26', 'x27', 'x3c', 'x3e'):
  523.             text = '&#%s;' % ref
  524.         elif ref[0] == 'x':
  525.             c = int(ref[1:], 16)
  526.         else:
  527.             c = int(ref)
  528.         text = unichr(c).encode('utf-8')
  529.         self.elementstack[-1][2].append(text)
  530.  
  531.     
  532.     def handle_entityref(self, ref):
  533.         if not self.elementstack:
  534.             return None
  535.         if _debug:
  536.             sys.stderr.write('entering handle_entityref with %s\n' % ref)
  537.         
  538.         if ref in ('lt', 'gt', 'quot', 'amp', 'apos'):
  539.             text = '&%s;' % ref
  540.         else:
  541.             
  542.             def name2cp(k):
  543.                 import htmlentitydefs
  544.                 if hasattr(htmlentitydefs, 'name2codepoint'):
  545.                     return htmlentitydefs.name2codepoint[k]
  546.                 k = htmlentitydefs.entitydefs[k]
  547.                 if k.startswith('&#') and k.endswith(';'):
  548.                     return int(k[2:-1])
  549.                 return ord(k)
  550.  
  551.             
  552.             try:
  553.                 name2cp(ref)
  554.             except KeyError:
  555.                 text = '&%s;' % ref
  556.  
  557.             text = unichr(name2cp(ref)).encode('utf-8')
  558.         self.elementstack[-1][2].append(text)
  559.  
  560.     
  561.     def handle_data(self, text, escape = 1):
  562.         if not self.elementstack:
  563.             return None
  564.         if escape and self.contentparams.get('type') == 'application/xhtml+xml':
  565.             text = _xmlescape(text)
  566.         
  567.         self.elementstack[-1][2].append(text)
  568.  
  569.     
  570.     def handle_comment(self, text):
  571.         pass
  572.  
  573.     
  574.     def handle_pi(self, text):
  575.         pass
  576.  
  577.     
  578.     def handle_decl(self, text):
  579.         pass
  580.  
  581.     
  582.     def parse_declaration(self, i):
  583.         if _debug:
  584.             sys.stderr.write('entering parse_declaration\n')
  585.         
  586.         if self.rawdata[i:i + 9] == '<![CDATA[':
  587.             k = self.rawdata.find(']]>', i)
  588.             if k == -1:
  589.                 k = len(self.rawdata)
  590.             
  591.             self.handle_data(_xmlescape(self.rawdata[i + 9:k]), 0)
  592.             return k + 3
  593.         k = self.rawdata.find('>', i)
  594.         return k + 1
  595.  
  596.     
  597.     def mapContentType(self, contentType):
  598.         contentType = contentType.lower()
  599.         if contentType == 'text':
  600.             contentType = 'text/plain'
  601.         elif contentType == 'html':
  602.             contentType = 'text/html'
  603.         elif contentType == 'xhtml':
  604.             contentType = 'application/xhtml+xml'
  605.         
  606.         return contentType
  607.  
  608.     
  609.     def trackNamespace(self, prefix, uri):
  610.         loweruri = uri.lower()
  611.         if (prefix, loweruri) == (None, 'http://my.netscape.com/rdf/simple/0.9/') and not (self.version):
  612.             self.version = 'rss090'
  613.         
  614.         if loweruri == 'http://purl.org/rss/1.0/' and not (self.version):
  615.             self.version = 'rss10'
  616.         
  617.         if loweruri == 'http://www.w3.org/2005/atom' and not (self.version):
  618.             self.version = 'atom10'
  619.         
  620.         if loweruri.find('backend.userland.com/rss') != -1:
  621.             uri = 'http://backend.userland.com/rss'
  622.             loweruri = uri
  623.         
  624.         if self._matchnamespaces.has_key(loweruri):
  625.             self.namespacemap[prefix] = self._matchnamespaces[loweruri]
  626.             self.namespacesInUse[self._matchnamespaces[loweruri]] = uri
  627.         elif not prefix:
  628.             pass
  629.         self.namespacesInUse[''] = uri
  630.  
  631.     
  632.     def resolveURI(self, uri):
  633.         if not self.baseuri:
  634.             pass
  635.         return _urljoin('', uri)
  636.  
  637.     
  638.     def decodeEntities(self, element, data):
  639.         return data
  640.  
  641.     
  642.     def push(self, element, expectingText):
  643.         self.elementstack.append([
  644.             element,
  645.             expectingText,
  646.             []])
  647.  
  648.     
  649.     def pop(self, element, stripWhitespace = 1):
  650.         if not self.elementstack:
  651.             return None
  652.         if self.elementstack[-1][0] != element:
  653.             return None
  654.         (element, expectingText, pieces) = self.elementstack.pop()
  655.         output = ''.join(pieces)
  656.         if not expectingText:
  657.             return output
  658.         if base64 and self.contentparams.get('base64', 0):
  659.             
  660.             try:
  661.                 output = base64.decodestring(output)
  662.             except binascii.Error:
  663.                 expectingText
  664.                 expectingText
  665.                 None if stripWhitespace else self.elementstack
  666.             except binascii.Incomplete:
  667.                 expectingText
  668.                 expectingText
  669.                 expectingText
  670.             except:
  671.                 expectingText<EXCEPTION MATCH>binascii.Error
  672.             
  673.  
  674.         expectingText
  675.         if not self.contentparams.get('base64', 0):
  676.             output = self.decodeEntities(element, output)
  677.         
  678.         
  679.         try:
  680.             del self.contentparams['mode']
  681.         except KeyError:
  682.             pass
  683.  
  684.         
  685.         try:
  686.             del self.contentparams['base64']
  687.         except KeyError:
  688.             pass
  689.  
  690.         if self.mapContentType(self.contentparams.get('type', 'text/html')) in self.html_types:
  691.             if element in self.can_contain_relative_uris:
  692.                 output = _resolveRelativeURIs(output, self.baseuri, self.encoding)
  693.             
  694.         
  695.         if self.mapContentType(self.contentparams.get('type', 'text/html')) in self.html_types:
  696.             if element in self.can_contain_dangerous_markup:
  697.                 output = _sanitizeHTML(output, self.encoding)
  698.             
  699.         
  700.         if self.encoding and type(output) != type(u''):
  701.             
  702.             try:
  703.                 output = unicode(output, self.encoding)
  704.  
  705.         
  706.         if element == 'category':
  707.             return output
  708.         if self.inentry and not (self.insource):
  709.             if element == 'content':
  710.                 self.entries[-1].setdefault(element, [])
  711.                 contentparams = copy.deepcopy(self.contentparams)
  712.                 contentparams['value'] = output
  713.                 self.entries[-1][element].append(contentparams)
  714.             elif element == 'link':
  715.                 self.entries[-1][element] = output
  716.                 if output:
  717.                     self.entries[-1]['links'][-1]['href'] = output
  718.                 
  719.             elif element == 'description':
  720.                 element = 'summary'
  721.             
  722.             self.entries[-1][element] = output
  723.             if self.incontent:
  724.                 contentparams = copy.deepcopy(self.contentparams)
  725.                 contentparams['value'] = output
  726.                 self.entries[-1][element + '_detail'] = contentparams
  727.             
  728.         elif (self.infeed or self.insource) and not (self.intextinput) and not (self.inimage):
  729.             context = self._getContext()
  730.             if element == 'description':
  731.                 element = 'subtitle'
  732.             
  733.             context[element] = output
  734.             if element == 'link':
  735.                 context['links'][-1]['href'] = output
  736.             elif self.incontent:
  737.                 contentparams = copy.deepcopy(self.contentparams)
  738.                 contentparams['value'] = output
  739.                 context[element + '_detail'] = contentparams
  740.             
  741.         
  742.         return output
  743.  
  744.     
  745.     def pushContent(self, tag, attrsD, defaultContentType, expectingText):
  746.         self.incontent += 1
  747.         self.contentparams = FeedParserDict({
  748.             'type': self.mapContentType(attrsD.get('type', defaultContentType)),
  749.             'language': self.lang,
  750.             'base': self.baseuri })
  751.         self.contentparams['base64'] = self._isBase64(attrsD, self.contentparams)
  752.         self.push(tag, expectingText)
  753.  
  754.     
  755.     def popContent(self, tag):
  756.         value = self.pop(tag)
  757.         self.incontent -= 1
  758.         self.contentparams.clear()
  759.         return value
  760.  
  761.     
  762.     def _mapToStandardPrefix(self, name):
  763.         colonpos = name.find(':')
  764.         if colonpos != -1:
  765.             prefix = name[:colonpos]
  766.             suffix = name[colonpos + 1:]
  767.             prefix = self.namespacemap.get(prefix, prefix)
  768.             name = prefix + ':' + suffix
  769.         
  770.         return name
  771.  
  772.     
  773.     def _getAttribute(self, attrsD, name):
  774.         return attrsD.get(self._mapToStandardPrefix(name))
  775.  
  776.     
  777.     def _isBase64(self, attrsD, contentparams):
  778.         if attrsD.get('mode', '') == 'base64':
  779.             return 1
  780.         if self.contentparams['type'].startswith('text/'):
  781.             return 0
  782.         if self.contentparams['type'].endswith('+xml'):
  783.             return 0
  784.         if self.contentparams['type'].endswith('/xml'):
  785.             return 0
  786.         return 1
  787.  
  788.     
  789.     def _itsAnHrefDamnIt(self, attrsD):
  790.         href = attrsD.get('url', attrsD.get('uri', attrsD.get('href', None)))
  791.         if href:
  792.             
  793.             try:
  794.                 del attrsD['url']
  795.             except KeyError:
  796.                 pass
  797.  
  798.             
  799.             try:
  800.                 del attrsD['uri']
  801.             except KeyError:
  802.                 pass
  803.  
  804.             attrsD['href'] = href
  805.         
  806.         return attrsD
  807.  
  808.     
  809.     def _save(self, key, value):
  810.         context = self._getContext()
  811.         context.setdefault(key, value)
  812.  
  813.     
  814.     def _start_rss(self, attrsD):
  815.         versionmap = {
  816.             '0.91': 'rss091u',
  817.             '0.92': 'rss092',
  818.             '0.93': 'rss093',
  819.             '0.94': 'rss094' }
  820.         if not self.version:
  821.             attr_version = attrsD.get('version', '')
  822.             version = versionmap.get(attr_version)
  823.             if version:
  824.                 self.version = version
  825.             elif attr_version.startswith('2.'):
  826.                 self.version = 'rss20'
  827.             else:
  828.                 self.version = 'rss'
  829.         
  830.  
  831.     
  832.     def _start_dlhottitles(self, attrsD):
  833.         self.version = 'hotrss'
  834.  
  835.     
  836.     def _start_channel(self, attrsD):
  837.         self.infeed = 1
  838.         self._cdf_common(attrsD)
  839.  
  840.     _start_feedinfo = _start_channel
  841.     
  842.     def _cdf_common(self, attrsD):
  843.         if attrsD.has_key('lastmod'):
  844.             self._start_modified({ })
  845.             self.elementstack[-1][-1] = attrsD['lastmod']
  846.             self._end_modified()
  847.         
  848.         if attrsD.has_key('href'):
  849.             self._start_link({ })
  850.             self.elementstack[-1][-1] = attrsD['href']
  851.             self._end_link()
  852.         
  853.  
  854.     
  855.     def _start_feed(self, attrsD):
  856.         self.infeed = 1
  857.         versionmap = {
  858.             '0.1': 'atom01',
  859.             '0.2': 'atom02',
  860.             '0.3': 'atom03' }
  861.         if not self.version:
  862.             attr_version = attrsD.get('version')
  863.             version = versionmap.get(attr_version)
  864.             if version:
  865.                 self.version = version
  866.             else:
  867.                 self.version = 'atom'
  868.         
  869.  
  870.     
  871.     def _end_channel(self):
  872.         self.infeed = 0
  873.  
  874.     _end_feed = _end_channel
  875.     
  876.     def _start_image(self, attrsD):
  877.         self.inimage = 1
  878.         self.push('image', 0)
  879.         context = self._getContext()
  880.         context.setdefault('image', FeedParserDict())
  881.  
  882.     
  883.     def _end_image(self):
  884.         self.pop('image')
  885.         self.inimage = 0
  886.  
  887.     
  888.     def _start_textinput(self, attrsD):
  889.         self.intextinput = 1
  890.         self.push('textinput', 0)
  891.         context = self._getContext()
  892.         context.setdefault('textinput', FeedParserDict())
  893.  
  894.     _start_textInput = _start_textinput
  895.     
  896.     def _end_textinput(self):
  897.         self.pop('textinput')
  898.         self.intextinput = 0
  899.  
  900.     _end_textInput = _end_textinput
  901.     
  902.     def _start_author(self, attrsD):
  903.         self.inauthor = 1
  904.         self.push('author', 1)
  905.  
  906.     _start_managingeditor = _start_author
  907.     _start_dc_author = _start_author
  908.     _start_dc_creator = _start_author
  909.     _start_itunes_author = _start_author
  910.     
  911.     def _end_author(self):
  912.         self.pop('author')
  913.         self.inauthor = 0
  914.         self._sync_author_detail()
  915.  
  916.     _end_managingeditor = _end_author
  917.     _end_dc_author = _end_author
  918.     _end_dc_creator = _end_author
  919.     _end_itunes_author = _end_author
  920.     
  921.     def _start_itunes_owner(self, attrsD):
  922.         self.inpublisher = 1
  923.         self.push('publisher', 0)
  924.  
  925.     
  926.     def _end_itunes_owner(self):
  927.         self.pop('publisher')
  928.         self.inpublisher = 0
  929.         self._sync_author_detail('publisher')
  930.  
  931.     
  932.     def _start_contributor(self, attrsD):
  933.         self.incontributor = 1
  934.         context = self._getContext()
  935.         context.setdefault('contributors', [])
  936.         context['contributors'].append(FeedParserDict())
  937.         self.push('contributor', 0)
  938.  
  939.     
  940.     def _end_contributor(self):
  941.         self.pop('contributor')
  942.         self.incontributor = 0
  943.  
  944.     
  945.     def _start_dc_contributor(self, attrsD):
  946.         self.incontributor = 1
  947.         context = self._getContext()
  948.         context.setdefault('contributors', [])
  949.         context['contributors'].append(FeedParserDict())
  950.         self.push('name', 0)
  951.  
  952.     
  953.     def _end_dc_contributor(self):
  954.         self._end_name()
  955.         self.incontributor = 0
  956.  
  957.     
  958.     def _start_name(self, attrsD):
  959.         self.push('name', 0)
  960.  
  961.     _start_itunes_name = _start_name
  962.     
  963.     def _end_name(self):
  964.         value = self.pop('name')
  965.         if self.inpublisher:
  966.             self._save_author('name', value, 'publisher')
  967.         elif self.inauthor:
  968.             self._save_author('name', value)
  969.         elif self.incontributor:
  970.             self._save_contributor('name', value)
  971.         elif self.intextinput:
  972.             context = self._getContext()
  973.             context['textinput']['name'] = value
  974.         
  975.  
  976.     _end_itunes_name = _end_name
  977.     
  978.     def _start_width(self, attrsD):
  979.         self.push('width', 0)
  980.  
  981.     
  982.     def _end_width(self):
  983.         value = self.pop('width')
  984.         
  985.         try:
  986.             value = int(value)
  987.         except:
  988.             value = 0
  989.  
  990.         if self.inimage:
  991.             context = self._getContext()
  992.             context['image']['width'] = value
  993.         
  994.  
  995.     
  996.     def _start_height(self, attrsD):
  997.         self.push('height', 0)
  998.  
  999.     
  1000.     def _end_height(self):
  1001.         value = self.pop('height')
  1002.         
  1003.         try:
  1004.             value = int(value)
  1005.         except:
  1006.             value = 0
  1007.  
  1008.         if self.inimage:
  1009.             context = self._getContext()
  1010.             context['image']['height'] = value
  1011.         
  1012.  
  1013.     
  1014.     def _start_url(self, attrsD):
  1015.         self.push('href', 1)
  1016.  
  1017.     _start_homepage = _start_url
  1018.     _start_uri = _start_url
  1019.     
  1020.     def _end_url(self):
  1021.         value = self.pop('href')
  1022.         if self.inauthor:
  1023.             self._save_author('href', value)
  1024.         elif self.incontributor:
  1025.             self._save_contributor('href', value)
  1026.         elif self.inimage:
  1027.             context = self._getContext()
  1028.             context['image']['href'] = value
  1029.         elif self.intextinput:
  1030.             context = self._getContext()
  1031.             context['textinput']['link'] = value
  1032.         
  1033.  
  1034.     _end_homepage = _end_url
  1035.     _end_uri = _end_url
  1036.     
  1037.     def _start_email(self, attrsD):
  1038.         self.push('email', 0)
  1039.  
  1040.     _start_itunes_email = _start_email
  1041.     
  1042.     def _end_email(self):
  1043.         value = self.pop('email')
  1044.         if self.inpublisher:
  1045.             self._save_author('email', value, 'publisher')
  1046.         elif self.inauthor:
  1047.             self._save_author('email', value)
  1048.         elif self.incontributor:
  1049.             self._save_contributor('email', value)
  1050.         
  1051.  
  1052.     _end_itunes_email = _end_email
  1053.     
  1054.     def _getContext(self):
  1055.         if self.insource:
  1056.             context = self.sourcedata
  1057.         elif self.inentry:
  1058.             context = self.entries[-1]
  1059.         else:
  1060.             context = self.feeddata
  1061.         return context
  1062.  
  1063.     
  1064.     def _save_author(self, key, value, prefix = 'author'):
  1065.         context = self._getContext()
  1066.         context.setdefault(prefix + '_detail', FeedParserDict())
  1067.         context[prefix + '_detail'][key] = value
  1068.         self._sync_author_detail()
  1069.  
  1070.     
  1071.     def _save_contributor(self, key, value):
  1072.         context = self._getContext()
  1073.         context.setdefault('contributors', [
  1074.             FeedParserDict()])
  1075.         context['contributors'][-1][key] = value
  1076.  
  1077.     
  1078.     def _sync_author_detail(self, key = 'author'):
  1079.         context = self._getContext()
  1080.         detail = context.get('%s_detail' % key)
  1081.         if detail:
  1082.             name = detail.get('name')
  1083.             email = detail.get('email')
  1084.             if name and email:
  1085.                 context[key] = '%s (%s)' % (name, email)
  1086.             elif name:
  1087.                 context[key] = name
  1088.             elif email:
  1089.                 context[key] = email
  1090.             
  1091.         else:
  1092.             author = context.get(key)
  1093.             if not author:
  1094.                 return None
  1095.             emailmatch = re.search('(([a-zA-Z0-9\\_\\-\\.\\+]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?))', author)
  1096.             if not emailmatch:
  1097.                 return None
  1098.             email = emailmatch.group(0)
  1099.             author = author.replace(email, '')
  1100.             author = author.replace('()', '')
  1101.             author = author.strip()
  1102.             if author and author[-1] == ')':
  1103.                 author = author[:-1]
  1104.             
  1105.             author = author.strip()
  1106.             context.setdefault('%s_detail' % key, FeedParserDict())
  1107.             context['%s_detail' % key]['name'] = author
  1108.             context['%s_detail' % key]['email'] = email
  1109.  
  1110.     
  1111.     def _start_subtitle(self, attrsD):
  1112.         self.pushContent('subtitle', attrsD, 'text/plain', 1)
  1113.  
  1114.     _start_tagline = _start_subtitle
  1115.     _start_itunes_subtitle = _start_subtitle
  1116.     
  1117.     def _end_subtitle(self):
  1118.         self.popContent('subtitle')
  1119.  
  1120.     _end_tagline = _end_subtitle
  1121.     _end_itunes_subtitle = _end_subtitle
  1122.     
  1123.     def _start_rights(self, attrsD):
  1124.         self.pushContent('rights', attrsD, 'text/plain', 1)
  1125.  
  1126.     _start_dc_rights = _start_rights
  1127.     _start_copyright = _start_rights
  1128.     
  1129.     def _end_rights(self):
  1130.         self.popContent('rights')
  1131.  
  1132.     _end_dc_rights = _end_rights
  1133.     _end_copyright = _end_rights
  1134.     
  1135.     def _start_item(self, attrsD):
  1136.         self.entries.append(FeedParserDict())
  1137.         self.push('item', 0)
  1138.         self.inentry = 1
  1139.         self.guidislink = 0
  1140.         id = self._getAttribute(attrsD, 'rdf:about')
  1141.         if id:
  1142.             context = self._getContext()
  1143.             context['id'] = id
  1144.         
  1145.         self._cdf_common(attrsD)
  1146.  
  1147.     _start_entry = _start_item
  1148.     _start_product = _start_item
  1149.     
  1150.     def _end_item(self):
  1151.         self.pop('item')
  1152.         self.inentry = 0
  1153.  
  1154.     _end_entry = _end_item
  1155.     
  1156.     def _start_dc_language(self, attrsD):
  1157.         self.push('language', 1)
  1158.  
  1159.     _start_language = _start_dc_language
  1160.     
  1161.     def _end_dc_language(self):
  1162.         self.lang = self.pop('language')
  1163.  
  1164.     _end_language = _end_dc_language
  1165.     
  1166.     def _start_dc_publisher(self, attrsD):
  1167.         self.push('publisher', 1)
  1168.  
  1169.     _start_webmaster = _start_dc_publisher
  1170.     
  1171.     def _end_dc_publisher(self):
  1172.         self.pop('publisher')
  1173.         self._sync_author_detail('publisher')
  1174.  
  1175.     _end_webmaster = _end_dc_publisher
  1176.     
  1177.     def _start_published(self, attrsD):
  1178.         self.push('published', 1)
  1179.  
  1180.     _start_dcterms_issued = _start_published
  1181.     _start_issued = _start_published
  1182.     
  1183.     def _end_published(self):
  1184.         value = self.pop('published')
  1185.         self._save('published_parsed', _parse_date(value))
  1186.  
  1187.     _end_dcterms_issued = _end_published
  1188.     _end_issued = _end_published
  1189.     
  1190.     def _start_updated(self, attrsD):
  1191.         self.push('updated', 1)
  1192.  
  1193.     _start_modified = _start_updated
  1194.     _start_dcterms_modified = _start_updated
  1195.     _start_pubdate = _start_updated
  1196.     _start_dc_date = _start_updated
  1197.     
  1198.     def _end_updated(self):
  1199.         value = self.pop('updated')
  1200.         parsed_value = _parse_date(value)
  1201.         self._save('updated_parsed', parsed_value)
  1202.  
  1203.     _end_modified = _end_updated
  1204.     _end_dcterms_modified = _end_updated
  1205.     _end_pubdate = _end_updated
  1206.     _end_dc_date = _end_updated
  1207.     
  1208.     def _start_created(self, attrsD):
  1209.         self.push('created', 1)
  1210.  
  1211.     _start_dcterms_created = _start_created
  1212.     
  1213.     def _end_created(self):
  1214.         value = self.pop('created')
  1215.         self._save('created_parsed', _parse_date(value))
  1216.  
  1217.     _end_dcterms_created = _end_created
  1218.     
  1219.     def _start_expirationdate(self, attrsD):
  1220.         self.push('expired', 1)
  1221.  
  1222.     
  1223.     def _end_expirationdate(self):
  1224.         self._save('expired_parsed', _parse_date(self.pop('expired')))
  1225.  
  1226.     
  1227.     def _start_cc_license(self, attrsD):
  1228.         self.push('license', 1)
  1229.         value = self._getAttribute(attrsD, 'rdf:resource')
  1230.         if value:
  1231.             self.elementstack[-1][2].append(value)
  1232.         
  1233.         self.pop('license')
  1234.  
  1235.     
  1236.     def _start_creativecommons_license(self, attrsD):
  1237.         self.push('license', 1)
  1238.  
  1239.     
  1240.     def _end_creativecommons_license(self):
  1241.         self.pop('license')
  1242.  
  1243.     
  1244.     def _addTag(self, term, scheme, label):
  1245.         context = self._getContext()
  1246.         tags = context.setdefault('tags', [])
  1247.         if not term and not scheme and not label:
  1248.             return None
  1249.         value = FeedParserDict({
  1250.             'term': term,
  1251.             'scheme': scheme,
  1252.             'label': label })
  1253.         if value not in tags:
  1254.             tags.append(FeedParserDict({
  1255.                 'term': term,
  1256.                 'scheme': scheme,
  1257.                 'label': label }))
  1258.         
  1259.  
  1260.     
  1261.     def _start_category(self, attrsD):
  1262.         if _debug:
  1263.             sys.stderr.write('entering _start_category with %s\n' % repr(attrsD))
  1264.         
  1265.         term = attrsD.get('term')
  1266.         scheme = attrsD.get('scheme', attrsD.get('domain'))
  1267.         label = attrsD.get('label')
  1268.         self._addTag(term, scheme, label)
  1269.         self.push('category', 1)
  1270.  
  1271.     _start_dc_subject = _start_category
  1272.     _start_keywords = _start_category
  1273.     
  1274.     def _end_itunes_keywords(self):
  1275.         for term in self.pop('itunes_keywords').split():
  1276.             self._addTag(term, 'http://www.itunes.com/', None)
  1277.         
  1278.  
  1279.     
  1280.     def _start_itunes_category(self, attrsD):
  1281.         self._addTag(attrsD.get('text'), 'http://www.itunes.com/', None)
  1282.         self.push('category', 1)
  1283.  
  1284.     
  1285.     def _end_category(self):
  1286.         value = self.pop('category')
  1287.         if not value:
  1288.             return None
  1289.         context = self._getContext()
  1290.         tags = context['tags']
  1291.         if value and len(tags) and not tags[-1]['term']:
  1292.             tags[-1]['term'] = value
  1293.         else:
  1294.             self._addTag(value, None, None)
  1295.  
  1296.     _end_dc_subject = _end_category
  1297.     _end_keywords = _end_category
  1298.     _end_itunes_category = _end_category
  1299.     
  1300.     def _start_cloud(self, attrsD):
  1301.         self._getContext()['cloud'] = FeedParserDict(attrsD)
  1302.  
  1303.     
  1304.     def _start_link(self, attrsD):
  1305.         attrsD.setdefault('rel', 'alternate')
  1306.         attrsD.setdefault('type', 'text/html')
  1307.         attrsD = self._itsAnHrefDamnIt(attrsD)
  1308.         if attrsD.has_key('href'):
  1309.             attrsD['href'] = self.resolveURI(attrsD['href'])
  1310.         
  1311.         if not self.infeed and self.inentry:
  1312.             pass
  1313.         expectingText = self.insource
  1314.         context = self._getContext()
  1315.         context.setdefault('links', [])
  1316.         context['links'].append(FeedParserDict(attrsD))
  1317.         if attrsD['rel'] == 'enclosure':
  1318.             self._start_enclosure(attrsD)
  1319.         
  1320.         if attrsD.has_key('href'):
  1321.             expectingText = 0
  1322.             if attrsD.get('rel') == 'alternate' and self.mapContentType(attrsD.get('type')) in self.html_types:
  1323.                 context['link'] = attrsD['href']
  1324.             
  1325.         else:
  1326.             self.push('link', expectingText)
  1327.  
  1328.     _start_producturl = _start_link
  1329.     
  1330.     def _end_link(self):
  1331.         value = self.pop('link')
  1332.         context = self._getContext()
  1333.         if self.intextinput:
  1334.             context['textinput']['link'] = value
  1335.         
  1336.         if self.inimage:
  1337.             context['image']['link'] = value
  1338.         
  1339.  
  1340.     _end_producturl = _end_link
  1341.     
  1342.     def _start_guid(self, attrsD):
  1343.         self.guidislink = attrsD.get('ispermalink', 'true') == 'true'
  1344.         self.push('id', 1)
  1345.  
  1346.     
  1347.     def _end_guid(self):
  1348.         value = self.pop('id')
  1349.         if self.guidislink:
  1350.             pass
  1351.         self._save('guidislink', not self._getContext().has_key('link'))
  1352.         if self.guidislink:
  1353.             self._save('link', value)
  1354.         
  1355.  
  1356.     
  1357.     def _start_title(self, attrsD):
  1358.         if not self.infeed and self.inentry:
  1359.             pass
  1360.         self.pushContent('title', attrsD, 'text/plain', self.insource)
  1361.  
  1362.     _start_dc_title = _start_title
  1363.     _start_media_title = _start_title
  1364.     
  1365.     def _end_title(self):
  1366.         value = self.popContent('title')
  1367.         context = self._getContext()
  1368.         if self.intextinput:
  1369.             context['textinput']['title'] = value
  1370.         elif self.inimage:
  1371.             context['image']['title'] = value
  1372.         
  1373.  
  1374.     _end_dc_title = _end_title
  1375.     _end_media_title = _end_title
  1376.     
  1377.     def _start_description(self, attrsD):
  1378.         context = self._getContext()
  1379.         if context.has_key('summary'):
  1380.             self._summaryKey = 'content'
  1381.             self._start_content(attrsD)
  1382.         elif not self.infeed and self.inentry:
  1383.             pass
  1384.         self.pushContent('description', attrsD, 'text/html', self.insource)
  1385.  
  1386.     
  1387.     def _start_abstract(self, attrsD):
  1388.         if not self.infeed and self.inentry:
  1389.             pass
  1390.         self.pushContent('description', attrsD, 'text/plain', self.insource)
  1391.  
  1392.     
  1393.     def _end_description(self):
  1394.         if self._summaryKey == 'content':
  1395.             self._end_content()
  1396.         else:
  1397.             value = self.popContent('description')
  1398.             context = self._getContext()
  1399.             if self.intextinput:
  1400.                 context['textinput']['description'] = value
  1401.             elif self.inimage:
  1402.                 context['image']['description'] = value
  1403.             
  1404.         self._summaryKey = None
  1405.  
  1406.     _end_abstract = _end_description
  1407.     
  1408.     def _start_info(self, attrsD):
  1409.         self.pushContent('info', attrsD, 'text/plain', 1)
  1410.  
  1411.     _start_feedburner_browserfriendly = _start_info
  1412.     
  1413.     def _end_info(self):
  1414.         self.popContent('info')
  1415.  
  1416.     _end_feedburner_browserfriendly = _end_info
  1417.     
  1418.     def _start_generator(self, attrsD):
  1419.         if attrsD:
  1420.             attrsD = self._itsAnHrefDamnIt(attrsD)
  1421.             if attrsD.has_key('href'):
  1422.                 attrsD['href'] = self.resolveURI(attrsD['href'])
  1423.             
  1424.         
  1425.         self._getContext()['generator_detail'] = FeedParserDict(attrsD)
  1426.         self.push('generator', 1)
  1427.  
  1428.     
  1429.     def _end_generator(self):
  1430.         value = self.pop('generator')
  1431.         context = self._getContext()
  1432.         if context.has_key('generator_detail'):
  1433.             context['generator_detail']['name'] = value
  1434.         
  1435.  
  1436.     
  1437.     def _start_admin_generatoragent(self, attrsD):
  1438.         self.push('generator', 1)
  1439.         value = self._getAttribute(attrsD, 'rdf:resource')
  1440.         if value:
  1441.             self.elementstack[-1][2].append(value)
  1442.         
  1443.         self.pop('generator')
  1444.         self._getContext()['generator_detail'] = FeedParserDict({
  1445.             'href': value })
  1446.  
  1447.     
  1448.     def _start_admin_errorreportsto(self, attrsD):
  1449.         self.push('errorreportsto', 1)
  1450.         value = self._getAttribute(attrsD, 'rdf:resource')
  1451.         if value:
  1452.             self.elementstack[-1][2].append(value)
  1453.         
  1454.         self.pop('errorreportsto')
  1455.  
  1456.     
  1457.     def _start_summary(self, attrsD):
  1458.         context = self._getContext()
  1459.         if context.has_key('summary'):
  1460.             self._summaryKey = 'content'
  1461.             self._start_content(attrsD)
  1462.         else:
  1463.             self._summaryKey = 'summary'
  1464.             self.pushContent(self._summaryKey, attrsD, 'text/plain', 1)
  1465.  
  1466.     _start_itunes_summary = _start_summary
  1467.     
  1468.     def _end_summary(self):
  1469.         if self._summaryKey == 'content':
  1470.             self._end_content()
  1471.         elif not self._summaryKey:
  1472.             pass
  1473.         self.popContent('summary')
  1474.         self._summaryKey = None
  1475.  
  1476.     _end_itunes_summary = _end_summary
  1477.     
  1478.     def _start_enclosure(self, attrsD):
  1479.         attrsD = self._itsAnHrefDamnIt(attrsD)
  1480.         self._getContext().setdefault('enclosures', []).append(FeedParserDict(attrsD))
  1481.         href = attrsD.get('href')
  1482.         if href:
  1483.             context = self._getContext()
  1484.             if not context.get('id'):
  1485.                 context['id'] = href
  1486.             
  1487.         
  1488.  
  1489.     
  1490.     def _start_source(self, attrsD):
  1491.         self.insource = 1
  1492.  
  1493.     
  1494.     def _end_source(self):
  1495.         self.insource = 0
  1496.         self._getContext()['source'] = copy.deepcopy(self.sourcedata)
  1497.         self.sourcedata.clear()
  1498.  
  1499.     
  1500.     def _start_content(self, attrsD):
  1501.         self.pushContent('content', attrsD, 'text/plain', 1)
  1502.         src = attrsD.get('src')
  1503.         if src:
  1504.             self.contentparams['src'] = src
  1505.         
  1506.         self.push('content', 1)
  1507.  
  1508.     
  1509.     def _start_prodlink(self, attrsD):
  1510.         self.pushContent('content', attrsD, 'text/html', 1)
  1511.  
  1512.     
  1513.     def _start_body(self, attrsD):
  1514.         self.pushContent('content', attrsD, 'application/xhtml+xml', 1)
  1515.  
  1516.     _start_xhtml_body = _start_body
  1517.     
  1518.     def _start_content_encoded(self, attrsD):
  1519.         self.pushContent('content', attrsD, 'text/html', 1)
  1520.  
  1521.     _start_fullitem = _start_content_encoded
  1522.     
  1523.     def _end_content(self):
  1524.         copyToDescription = self.mapContentType(self.contentparams.get('type')) in [
  1525.             'text/plain'] + self.html_types
  1526.         value = self.popContent('content')
  1527.         if copyToDescription:
  1528.             self._save('description', value)
  1529.         
  1530.  
  1531.     _end_body = _end_content
  1532.     _end_xhtml_body = _end_content
  1533.     _end_content_encoded = _end_content
  1534.     _end_fullitem = _end_content
  1535.     _end_prodlink = _end_content
  1536.     
  1537.     def _start_itunes_image(self, attrsD):
  1538.         self.push('itunes_image', 0)
  1539.         self._getContext()['image'] = FeedParserDict({
  1540.             'href': attrsD.get('href') })
  1541.  
  1542.     _start_itunes_link = _start_itunes_image
  1543.     
  1544.     def _end_itunes_block(self):
  1545.         value = self.pop('itunes_block', 0)
  1546.         if not value == 'yes' or 1:
  1547.             pass
  1548.         self._getContext()['itunes_block'] = 0
  1549.  
  1550.     
  1551.     def _end_itunes_explicit(self):
  1552.         value = self.pop('itunes_explicit', 0)
  1553.         if not value == 'yes' or 1:
  1554.             pass
  1555.         self._getContext()['itunes_explicit'] = 0
  1556.  
  1557.  
  1558. if _XML_AVAILABLE:
  1559.     
  1560.     class _StrictFeedParser(_FeedParserMixin, xml.sax.handler.ContentHandler):
  1561.         
  1562.         def __init__(self, baseuri, baselang, encoding):
  1563.             if _debug:
  1564.                 sys.stderr.write('trying StrictFeedParser\n')
  1565.             
  1566.             xml.sax.handler.ContentHandler.__init__(self)
  1567.             _FeedParserMixin.__init__(self, baseuri, baselang, encoding)
  1568.             self.bozo = 0
  1569.             self.exc = None
  1570.  
  1571.         
  1572.         def startPrefixMapping(self, prefix, uri):
  1573.             self.trackNamespace(prefix, uri)
  1574.  
  1575.         
  1576.         def startElementNS(self, name, qname, attrs):
  1577.             (namespace, localname) = name
  1578.             if not namespace:
  1579.                 pass
  1580.             lowernamespace = str('').lower()
  1581.             if lowernamespace.find('backend.userland.com/rss') != -1:
  1582.                 namespace = 'http://backend.userland.com/rss'
  1583.                 lowernamespace = namespace
  1584.             
  1585.             if qname and qname.find(':') > 0:
  1586.                 givenprefix = qname.split(':')[0]
  1587.             else:
  1588.                 givenprefix = None
  1589.             prefix = self._matchnamespaces.get(lowernamespace, givenprefix)
  1590.             if givenprefix:
  1591.                 if (prefix == None or prefix == '' or lowernamespace == '') and not self.namespacesInUse.has_key(givenprefix):
  1592.                     raise UndeclaredNamespace, "'%s' is not associated with a namespace" % givenprefix
  1593.             not self.namespacesInUse.has_key(givenprefix)
  1594.             if prefix:
  1595.                 localname = prefix + ':' + localname
  1596.             
  1597.             localname = str(localname).lower()
  1598.             if _debug:
  1599.                 sys.stderr.write('startElementNS: qname = %s, namespace = %s, givenprefix = %s, prefix = %s, attrs = %s, localname = %s\n' % (qname, namespace, givenprefix, prefix, attrs.items(), localname))
  1600.             
  1601.             attrsD = { }
  1602.             for namespace, attrlocalname in attrs._attrs.items():
  1603.                 attrvalue = None
  1604.                 if not namespace:
  1605.                     pass
  1606.                 lowernamespace = ''.lower()
  1607.                 prefix = self._matchnamespaces.get(lowernamespace, '')
  1608.                 if prefix:
  1609.                     attrlocalname = prefix + ':' + attrlocalname
  1610.                 
  1611.                 attrsD[str(attrlocalname).lower()] = attrvalue
  1612.             
  1613.             for qname in attrs.getQNames():
  1614.                 attrsD[str(qname).lower()] = attrs.getValueByQName(qname)
  1615.             
  1616.             self.unknown_starttag(localname, attrsD.items())
  1617.  
  1618.         
  1619.         def characters(self, text):
  1620.             self.handle_data(text)
  1621.  
  1622.         
  1623.         def endElementNS(self, name, qname):
  1624.             (namespace, localname) = name
  1625.             if not namespace:
  1626.                 pass
  1627.             lowernamespace = str('').lower()
  1628.             if qname and qname.find(':') > 0:
  1629.                 givenprefix = qname.split(':')[0]
  1630.             else:
  1631.                 givenprefix = ''
  1632.             prefix = self._matchnamespaces.get(lowernamespace, givenprefix)
  1633.             if prefix:
  1634.                 localname = prefix + ':' + localname
  1635.             
  1636.             localname = str(localname).lower()
  1637.             self.unknown_endtag(localname)
  1638.  
  1639.         
  1640.         def error(self, exc):
  1641.             self.bozo = 1
  1642.             self.exc = exc
  1643.  
  1644.         
  1645.         def fatalError(self, exc):
  1646.             self.error(exc)
  1647.             raise exc
  1648.  
  1649.  
  1650.  
  1651.  
  1652. class _BaseHTMLProcessor(sgmllib.SGMLParser):
  1653.     elements_no_end_tag = [
  1654.         'area',
  1655.         'base',
  1656.         'basefont',
  1657.         'br',
  1658.         'col',
  1659.         'frame',
  1660.         'hr',
  1661.         'img',
  1662.         'input',
  1663.         'isindex',
  1664.         'link',
  1665.         'meta',
  1666.         'param']
  1667.     
  1668.     def __init__(self, encoding):
  1669.         self.encoding = encoding
  1670.         if _debug:
  1671.             sys.stderr.write('entering BaseHTMLProcessor, encoding=%s\n' % self.encoding)
  1672.         
  1673.         sgmllib.SGMLParser.__init__(self)
  1674.  
  1675.     
  1676.     def reset(self):
  1677.         self.pieces = []
  1678.         sgmllib.SGMLParser.reset(self)
  1679.  
  1680.     
  1681.     def _shorttag_replace(self, match):
  1682.         tag = match.group(1)
  1683.         if tag in self.elements_no_end_tag:
  1684.             return '<' + tag + ' />'
  1685.         return '<' + tag + '></' + tag + '>'
  1686.  
  1687.     
  1688.     def feed(self, data):
  1689.         data = re.compile('<!((?!DOCTYPE|--|\\[))', re.IGNORECASE).sub('<!\\1', data)
  1690.         data = re.sub('<([^<\\s]+?)\\s*/>', self._shorttag_replace, data)
  1691.         data = data.replace(''', "'")
  1692.         data = data.replace('"', '"')
  1693.         if self.encoding and type(data) == type(u''):
  1694.             data = data.encode(self.encoding)
  1695.         
  1696.         sgmllib.SGMLParser.feed(self, data)
  1697.  
  1698.     
  1699.     def normalize_attrs(self, attrs):
  1700.         attrs = [ (k.lower(), v) for k, v in attrs ]
  1701.         attrs = [ (k, v) for k, v in attrs ]
  1702.         return attrs
  1703.  
  1704.     
  1705.     def unknown_starttag(self, tag, attrs):
  1706.         if _debug:
  1707.             sys.stderr.write('_BaseHTMLProcessor, unknown_starttag, tag=%s\n' % tag)
  1708.         
  1709.         uattrs = []
  1710.         for key, value in attrs:
  1711.             if type(value) != type(u''):
  1712.                 value = unicode(value, self.encoding)
  1713.             
  1714.             uattrs.append((unicode(key, self.encoding), value))
  1715.         
  1716.         strattrs = []([ u' %s="%s"' % (key, value) for key, value in uattrs ]).encode(self.encoding)
  1717.  
  1718.     
  1719.     def unknown_endtag(self, tag):
  1720.         if tag not in self.elements_no_end_tag:
  1721.             self.pieces.append('</%(tag)s>' % locals())
  1722.         
  1723.  
  1724.     
  1725.     def handle_charref(self, ref):
  1726.         self.pieces.append('&#%(ref)s;' % locals())
  1727.  
  1728.     
  1729.     def handle_entityref(self, ref):
  1730.         self.pieces.append('&%(ref)s;' % locals())
  1731.  
  1732.     
  1733.     def handle_data(self, text):
  1734.         if _debug:
  1735.             sys.stderr.write('_BaseHTMLProcessor, handle_text, text=%s\n' % text)
  1736.         
  1737.         self.pieces.append(text)
  1738.  
  1739.     
  1740.     def handle_comment(self, text):
  1741.         self.pieces.append('<!--%(text)s-->' % locals())
  1742.  
  1743.     
  1744.     def handle_pi(self, text):
  1745.         self.pieces.append('<?%(text)s>' % locals())
  1746.  
  1747.     
  1748.     def handle_decl(self, text):
  1749.         self.pieces.append('<!%(text)s>' % locals())
  1750.  
  1751.     _new_declname_match = re.compile('[a-zA-Z][-_.a-zA-Z0-9:]*\\s*').match
  1752.     
  1753.     def _scan_name(self, i, declstartpos):
  1754.         rawdata = self.rawdata
  1755.         n = len(rawdata)
  1756.         if i == n:
  1757.             return (None, -1)
  1758.         m = self._new_declname_match(rawdata, i)
  1759.         if m:
  1760.             s = m.group()
  1761.             name = s.strip()
  1762.             if i + len(s) == n:
  1763.                 return (None, -1)
  1764.             return (name.lower(), m.end())
  1765.         self.handle_data(rawdata)
  1766.         return (None, -1)
  1767.  
  1768.     
  1769.     def output(self):
  1770.         return []([ str(p) for p in self.pieces ])
  1771.  
  1772.  
  1773.  
  1774. class _LooseFeedParser(_FeedParserMixin, _BaseHTMLProcessor):
  1775.     
  1776.     def __init__(self, baseuri, baselang, encoding):
  1777.         sgmllib.SGMLParser.__init__(self)
  1778.         _FeedParserMixin.__init__(self, baseuri, baselang, encoding)
  1779.  
  1780.     
  1781.     def decodeEntities(self, element, data):
  1782.         data = data.replace('<', '<')
  1783.         data = data.replace('<', '<')
  1784.         data = data.replace('>', '>')
  1785.         data = data.replace('>', '>')
  1786.         data = data.replace('&', '&')
  1787.         data = data.replace('&', '&')
  1788.         data = data.replace('"', '"')
  1789.         data = data.replace('"', '"')
  1790.         data = data.replace(''', ''')
  1791.         data = data.replace(''', ''')
  1792.         if self.contentparams.has_key('type') and not self.contentparams.get('type', 'xml').endswith('xml'):
  1793.             data = data.replace('<', '<')
  1794.             data = data.replace('>', '>')
  1795.             data = data.replace('&', '&')
  1796.             data = data.replace('"', '"')
  1797.             data = data.replace(''', "'")
  1798.         
  1799.         return data
  1800.  
  1801.  
  1802.  
  1803. class _RelativeURIResolver(_BaseHTMLProcessor):
  1804.     relative_uris = [
  1805.         ('a', 'href'),
  1806.         ('applet', 'codebase'),
  1807.         ('area', 'href'),
  1808.         ('blockquote', 'cite'),
  1809.         ('body', 'background'),
  1810.         ('del', 'cite'),
  1811.         ('form', 'action'),
  1812.         ('frame', 'longdesc'),
  1813.         ('frame', 'src'),
  1814.         ('iframe', 'longdesc'),
  1815.         ('iframe', 'src'),
  1816.         ('head', 'profile'),
  1817.         ('img', 'longdesc'),
  1818.         ('img', 'src'),
  1819.         ('img', 'usemap'),
  1820.         ('input', 'src'),
  1821.         ('input', 'usemap'),
  1822.         ('ins', 'cite'),
  1823.         ('link', 'href'),
  1824.         ('object', 'classid'),
  1825.         ('object', 'codebase'),
  1826.         ('object', 'data'),
  1827.         ('object', 'usemap'),
  1828.         ('q', 'cite'),
  1829.         ('script', 'src')]
  1830.     
  1831.     def __init__(self, baseuri, encoding):
  1832.         _BaseHTMLProcessor.__init__(self, encoding)
  1833.         self.baseuri = baseuri
  1834.  
  1835.     
  1836.     def resolveURI(self, uri):
  1837.         return _urljoin(self.baseuri, uri)
  1838.  
  1839.     
  1840.     def unknown_starttag(self, tag, attrs):
  1841.         attrs = self.normalize_attrs(attrs)
  1842.         attrs = [ (key, value) for key, value in attrs ]
  1843.         _BaseHTMLProcessor.unknown_starttag(self, tag, attrs)
  1844.  
  1845.  
  1846.  
  1847. def _resolveRelativeURIs(htmlSource, baseURI, encoding):
  1848.     if _debug:
  1849.         sys.stderr.write('entering _resolveRelativeURIs\n')
  1850.     
  1851.     p = _RelativeURIResolver(baseURI, encoding)
  1852.     p.feed(htmlSource)
  1853.     return p.output()
  1854.  
  1855.  
  1856. class _HTMLSanitizer(_BaseHTMLProcessor):
  1857.     acceptable_elements = [
  1858.         'a',
  1859.         'abbr',
  1860.         'acronym',
  1861.         'address',
  1862.         'area',
  1863.         'b',
  1864.         'big',
  1865.         'blockquote',
  1866.         'br',
  1867.         'button',
  1868.         'caption',
  1869.         'center',
  1870.         'cite',
  1871.         'code',
  1872.         'col',
  1873.         'colgroup',
  1874.         'dd',
  1875.         'del',
  1876.         'dfn',
  1877.         'dir',
  1878.         'div',
  1879.         'dl',
  1880.         'dt',
  1881.         'em',
  1882.         'fieldset',
  1883.         'font',
  1884.         'form',
  1885.         'h1',
  1886.         'h2',
  1887.         'h3',
  1888.         'h4',
  1889.         'h5',
  1890.         'h6',
  1891.         'hr',
  1892.         'i',
  1893.         'img',
  1894.         'input',
  1895.         'ins',
  1896.         'kbd',
  1897.         'label',
  1898.         'legend',
  1899.         'li',
  1900.         'map',
  1901.         'menu',
  1902.         'ol',
  1903.         'optgroup',
  1904.         'option',
  1905.         'p',
  1906.         'pre',
  1907.         'q',
  1908.         's',
  1909.         'samp',
  1910.         'select',
  1911.         'small',
  1912.         'span',
  1913.         'strike',
  1914.         'strong',
  1915.         'sub',
  1916.         'sup',
  1917.         'table',
  1918.         'tbody',
  1919.         'td',
  1920.         'textarea',
  1921.         'tfoot',
  1922.         'th',
  1923.         'thead',
  1924.         'tr',
  1925.         'tt',
  1926.         'u',
  1927.         'ul',
  1928.         'var']
  1929.     acceptable_attributes = [
  1930.         'abbr',
  1931.         'accept',
  1932.         'accept-charset',
  1933.         'accesskey',
  1934.         'action',
  1935.         'align',
  1936.         'alt',
  1937.         'axis',
  1938.         'border',
  1939.         'cellpadding',
  1940.         'cellspacing',
  1941.         'char',
  1942.         'charoff',
  1943.         'charset',
  1944.         'checked',
  1945.         'cite',
  1946.         'class',
  1947.         'clear',
  1948.         'cols',
  1949.         'colspan',
  1950.         'color',
  1951.         'compact',
  1952.         'coords',
  1953.         'datetime',
  1954.         'dir',
  1955.         'disabled',
  1956.         'enctype',
  1957.         'for',
  1958.         'frame',
  1959.         'headers',
  1960.         'height',
  1961.         'href',
  1962.         'hreflang',
  1963.         'hspace',
  1964.         'id',
  1965.         'ismap',
  1966.         'label',
  1967.         'lang',
  1968.         'longdesc',
  1969.         'maxlength',
  1970.         'media',
  1971.         'method',
  1972.         'multiple',
  1973.         'name',
  1974.         'nohref',
  1975.         'noshade',
  1976.         'nowrap',
  1977.         'prompt',
  1978.         'readonly',
  1979.         'rel',
  1980.         'rev',
  1981.         'rows',
  1982.         'rowspan',
  1983.         'rules',
  1984.         'scope',
  1985.         'selected',
  1986.         'shape',
  1987.         'size',
  1988.         'span',
  1989.         'src',
  1990.         'start',
  1991.         'summary',
  1992.         'tabindex',
  1993.         'target',
  1994.         'title',
  1995.         'type',
  1996.         'usemap',
  1997.         'valign',
  1998.         'value',
  1999.         'vspace',
  2000.         'width']
  2001.     unacceptable_elements_with_end_tag = [
  2002.         'script',
  2003.         'applet']
  2004.     
  2005.     def reset(self):
  2006.         _BaseHTMLProcessor.reset(self)
  2007.         self.unacceptablestack = 0
  2008.  
  2009.     
  2010.     def unknown_starttag(self, tag, attrs):
  2011.         if tag not in self.acceptable_elements:
  2012.             if tag in self.unacceptable_elements_with_end_tag:
  2013.                 self.unacceptablestack += 1
  2014.             
  2015.             return None
  2016.         attrs = self.normalize_attrs(attrs)
  2017.         attrs = _[1]
  2018.         _BaseHTMLProcessor.unknown_starttag(self, tag, attrs)
  2019.  
  2020.     
  2021.     def unknown_endtag(self, tag):
  2022.         if tag not in self.acceptable_elements:
  2023.             if tag in self.unacceptable_elements_with_end_tag:
  2024.                 self.unacceptablestack -= 1
  2025.             
  2026.             return None
  2027.         _BaseHTMLProcessor.unknown_endtag(self, tag)
  2028.  
  2029.     
  2030.     def handle_pi(self, text):
  2031.         pass
  2032.  
  2033.     
  2034.     def handle_decl(self, text):
  2035.         pass
  2036.  
  2037.     
  2038.     def handle_data(self, text):
  2039.         if not self.unacceptablestack:
  2040.             _BaseHTMLProcessor.handle_data(self, text)
  2041.         
  2042.  
  2043.  
  2044.  
  2045. def _sanitizeHTML(htmlSource, encoding):
  2046.     p = _HTMLSanitizer(encoding)
  2047.     p.feed(htmlSource)
  2048.     data = p.output()
  2049.     if TIDY_MARKUP:
  2050.         _tidy = None
  2051.         for tidy_interface in PREFERRED_TIDY_INTERFACES:
  2052.             
  2053.             try:
  2054.                 if tidy_interface == 'uTidy':
  2055.                     _utidy = parseString
  2056.                     import tidy
  2057.                     
  2058.                     def _tidy(data, **kwargs):
  2059.                         return str(_utidy(data, **kwargs))
  2060.  
  2061.                     break
  2062.                 elif tidy_interface == 'mxTidy':
  2063.                     _mxtidy = Tidy
  2064.                     import mx.Tidy
  2065.                     
  2066.                     def _tidy(data, **kwargs):
  2067.                         (nerrors, nwarnings, data, errordata) = _mxtidy.tidy(data, **kwargs)
  2068.                         return data
  2069.  
  2070.                     break
  2071.             continue
  2072.             continue
  2073.  
  2074.         
  2075.         if _tidy:
  2076.             utf8 = type(data) == type(u'')
  2077.             if utf8:
  2078.                 data = data.encode('utf-8')
  2079.             
  2080.             data = _tidy(data, output_xhtml = 1, numeric_entities = 1, wrap = 0, char_encoding = 'utf8')
  2081.             if utf8:
  2082.                 data = unicode(data, 'utf-8')
  2083.             
  2084.             if data.count('<body'):
  2085.                 data = data.split('<body', 1)[1]
  2086.                 if data.count('>'):
  2087.                     data = data.split('>', 1)[1]
  2088.                 
  2089.             
  2090.             if data.count('</body'):
  2091.                 data = data.split('</body', 1)[0]
  2092.             
  2093.         
  2094.     
  2095.     data = data.strip().replace('\r\n', '\n')
  2096.     return data
  2097.  
  2098.  
  2099. class _FeedURLHandler(urllib2.HTTPDigestAuthHandler, urllib2.HTTPRedirectHandler, urllib2.HTTPDefaultErrorHandler):
  2100.     
  2101.     def http_error_default(self, req, fp, code, msg, headers):
  2102.         if code / 100 == 3 and code != 304:
  2103.             return self.http_error_302(req, fp, code, msg, headers)
  2104.         infourl = urllib.addinfourl(fp, headers, req.get_full_url())
  2105.         infourl.status = code
  2106.         return infourl
  2107.  
  2108.     
  2109.     def http_error_302(self, req, fp, code, msg, headers):
  2110.         if headers.dict.has_key('location'):
  2111.             infourl = urllib2.HTTPRedirectHandler.http_error_302(self, req, fp, code, msg, headers)
  2112.         else:
  2113.             infourl = urllib.addinfourl(fp, headers, req.get_full_url())
  2114.         if not hasattr(infourl, 'status'):
  2115.             infourl.status = code
  2116.         
  2117.         return infourl
  2118.  
  2119.     
  2120.     def http_error_301(self, req, fp, code, msg, headers):
  2121.         if headers.dict.has_key('location'):
  2122.             infourl = urllib2.HTTPRedirectHandler.http_error_301(self, req, fp, code, msg, headers)
  2123.         else:
  2124.             infourl = urllib.addinfourl(fp, headers, req.get_full_url())
  2125.         if not hasattr(infourl, 'status'):
  2126.             infourl.status = code
  2127.         
  2128.         return infourl
  2129.  
  2130.     http_error_300 = http_error_302
  2131.     http_error_303 = http_error_302
  2132.     http_error_307 = http_error_302
  2133.     
  2134.     def http_error_401(self, req, fp, code, msg, headers):
  2135.         host = urlparse.urlparse(req.get_full_url())[1]
  2136.         
  2137.         try:
  2138.             (user, passw) = base64.decodestring(req.headers['Authorization'].split(' ')[1]).split(':')
  2139.             realm = re.findall('realm="([^"]*)"', headers['WWW-Authenticate'])[0]
  2140.             self.add_password(realm, host, user, passw)
  2141.             retry = self.http_error_auth_reqed('www-authenticate', host, req, headers)
  2142.             self.reset_retry_count()
  2143.             return retry
  2144.         except:
  2145.             return self.http_error_default(req, fp, code, msg, headers)
  2146.  
  2147.  
  2148.  
  2149.  
  2150. def _open_resource(url_file_stream_or_string, etag, modified, agent, referrer, handlers):
  2151.     if hasattr(url_file_stream_or_string, 'read'):
  2152.         return url_file_stream_or_string
  2153.     if url_file_stream_or_string == '-':
  2154.         return sys.stdin
  2155.     
  2156.     try:
  2157.         return open(url_file_stream_or_string)
  2158.     except:
  2159.         hasattr(url_file_stream_or_string, 'read') if urlparse.urlparse(url_file_stream_or_string)[0] in ('http', 'https', 'ftp') else url_file_stream_or_string == '-'
  2160.  
  2161.     return _StringIO(str(url_file_stream_or_string))
  2162.  
  2163. _date_handlers = []
  2164.  
  2165. def registerDateHandler(func):
  2166.     _date_handlers.insert(0, func)
  2167.  
  2168. _iso8601_tmpl = [
  2169.     'YYYY-?MM-?DD',
  2170.     'YYYY-MM',
  2171.     'YYYY-?OOO',
  2172.     'YY-?MM-?DD',
  2173.     'YY-?OOO',
  2174.     'YYYY',
  2175.     '-YY-?MM',
  2176.     '-OOO',
  2177.     '-YY',
  2178.     '--MM-?DD',
  2179.     '--MM',
  2180.     '---DD',
  2181.     'CC',
  2182.     '']
  2183. _iso8601_re = [ tmpl.replace('YYYY', '(?P<year>\\d{4})').replace('YY', '(?P<year>\\d\\d)').replace('MM', '(?P<month>[01]\\d)').replace('DD', '(?P<day>[0123]\\d)').replace('OOO', '(?P<ordinal>[0123]\\d\\d)').replace('CC', '(?P<century>\\d\\d$)') + '(T?(?P<hour>\\d{2}):(?P<minute>\\d{2})' + '(:(?P<second>\\d{2}))?' + '(?P<tz>[+-](?P<tzhour>\\d{2})(:(?P<tzmin>\\d{2}))?|Z)?)?' for tmpl in _iso8601_tmpl ]
  2184. del tmpl
  2185. _iso8601_matches = [ re.compile(regex).match for regex in _iso8601_re ]
  2186. del regex
  2187.  
  2188. def _parse_date_iso8601(dateString):
  2189.     m = None
  2190.     for _iso8601_match in _iso8601_matches:
  2191.         m = _iso8601_match(dateString)
  2192.         if m:
  2193.             break
  2194.             continue
  2195.     
  2196.     if not m:
  2197.         return None
  2198.     if m.span() == (0, 0):
  2199.         return None
  2200.     params = m.groupdict()
  2201.     ordinal = params.get('ordinal', 0)
  2202.     year = params.get('year', '--')
  2203.     if not year or year == '--':
  2204.         year = time.gmtime()[0]
  2205.     elif len(year) == 2:
  2206.         year = 100 * int(time.gmtime()[0] / 100) + int(year)
  2207.     else:
  2208.         year = int(year)
  2209.     month = params.get('month', '-')
  2210.     if not month or month == '-':
  2211.         if ordinal:
  2212.             month = 1
  2213.         else:
  2214.             month = time.gmtime()[1]
  2215.     
  2216.     month = int(month)
  2217.     day = params.get('day', 0)
  2218.     if not day:
  2219.         if ordinal:
  2220.             day = ordinal
  2221.         elif params.get('century', 0) and params.get('year', 0) or params.get('month', 0):
  2222.             day = 1
  2223.         else:
  2224.             day = time.gmtime()[2]
  2225.     else:
  2226.         day = int(day)
  2227.     if 'century' in params.keys():
  2228.         year = (int(params['century']) - 1) * 100 + 1
  2229.     
  2230.     for field in [
  2231.         'hour',
  2232.         'minute',
  2233.         'second',
  2234.         'tzhour',
  2235.         'tzmin']:
  2236.         if not params.get(field, None):
  2237.             params[field] = 0
  2238.             continue
  2239.     
  2240.     hour = int(params.get('hour', 0))
  2241.     minute = int(params.get('minute', 0))
  2242.     second = int(params.get('second', 0))
  2243.     weekday = 0
  2244.     daylight_savings_flag = 0
  2245.     tm = [
  2246.         year,
  2247.         month,
  2248.         day,
  2249.         hour,
  2250.         minute,
  2251.         second,
  2252.         weekday,
  2253.         ordinal,
  2254.         daylight_savings_flag]
  2255.     tz = params.get('tz')
  2256.     if tz and tz != 'Z':
  2257.         if tz[0] == '-':
  2258.             tm[3] += int(params.get('tzhour', 0))
  2259.             tm[4] += int(params.get('tzmin', 0))
  2260.         elif tz[0] == '+':
  2261.             tm[3] -= int(params.get('tzhour', 0))
  2262.             tm[4] -= int(params.get('tzmin', 0))
  2263.         else:
  2264.             return None
  2265.     tz[0] == '-'
  2266.     return time.localtime(time.mktime(tm))
  2267.  
  2268. registerDateHandler(_parse_date_iso8601)
  2269. _korean_year = u'δàä'
  2270. _korean_month = u'∞¢ö'
  2271. _korean_day = u'∞¥╝'
  2272. _korean_am = u'∞ÿñ∞áä'
  2273. _korean_pm = u'∞ÿñφ¢ä'
  2274. _korean_onblog_date_re = re.compile('(\\d{4})%s\\s+(\\d{2})%s\\s+(\\d{2})%s\\s+(\\d{2}):(\\d{2}):(\\d{2})' % (_korean_year, _korean_month, _korean_day))
  2275. _korean_nate_date_re = re.compile(u'(\\d{4})-(\\d{2})-(\\d{2})\\s+(%s|%s)\\s+(\\d{,2}):(\\d{,2}):(\\d{,2})' % (_korean_am, _korean_pm))
  2276.  
  2277. def _parse_date_onblog(dateString):
  2278.     m = _korean_onblog_date_re.match(dateString)
  2279.     if not m:
  2280.         return None
  2281.     w3dtfdate = '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s:%(second)s%(zonediff)s' % {
  2282.         'year': m.group(1),
  2283.         'month': m.group(2),
  2284.         'day': m.group(3),
  2285.         'hour': m.group(4),
  2286.         'minute': m.group(5),
  2287.         'second': m.group(6),
  2288.         'zonediff': '+09:00' }
  2289.     if _debug:
  2290.         sys.stderr.write('OnBlog date parsed as: %s\n' % w3dtfdate)
  2291.     
  2292.     return _parse_date_w3dtf(w3dtfdate)
  2293.  
  2294. registerDateHandler(_parse_date_onblog)
  2295.  
  2296. def _parse_date_nate(dateString):
  2297.     m = _korean_nate_date_re.match(dateString)
  2298.     if not m:
  2299.         return None
  2300.     hour = int(m.group(5))
  2301.     ampm = m.group(4)
  2302.     if ampm == _korean_pm:
  2303.         hour += 12
  2304.     
  2305.     hour = str(hour)
  2306.     if len(hour) == 1:
  2307.         hour = '0' + hour
  2308.     
  2309.     w3dtfdate = '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s:%(second)s%(zonediff)s' % {
  2310.         'year': m.group(1),
  2311.         'month': m.group(2),
  2312.         'day': m.group(3),
  2313.         'hour': hour,
  2314.         'minute': m.group(6),
  2315.         'second': m.group(7),
  2316.         'zonediff': '+09:00' }
  2317.     if _debug:
  2318.         sys.stderr.write('Nate date parsed as: %s\n' % w3dtfdate)
  2319.     
  2320.     return _parse_date_w3dtf(w3dtfdate)
  2321.  
  2322. registerDateHandler(_parse_date_nate)
  2323. _mssql_date_re = re.compile('(\\d{4})-(\\d{2})-(\\d{2})\\s+(\\d{2}):(\\d{2}):(\\d{2})(\\.\\d+)?')
  2324.  
  2325. def _parse_date_mssql(dateString):
  2326.     m = _mssql_date_re.match(dateString)
  2327.     if not m:
  2328.         return None
  2329.     w3dtfdate = '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s:%(second)s%(zonediff)s' % {
  2330.         'year': m.group(1),
  2331.         'month': m.group(2),
  2332.         'day': m.group(3),
  2333.         'hour': m.group(4),
  2334.         'minute': m.group(5),
  2335.         'second': m.group(6),
  2336.         'zonediff': '+09:00' }
  2337.     if _debug:
  2338.         sys.stderr.write('MS SQL date parsed as: %s\n' % w3dtfdate)
  2339.     
  2340.     return _parse_date_w3dtf(w3dtfdate)
  2341.  
  2342. registerDateHandler(_parse_date_mssql)
  2343. _greek_months = {
  2344.     u'╬Ö╬▒╬╜': u'Jan',
  2345.     u'╬ª╬╡╬▓': u'Feb',
  2346.     u'╬£╬¼╧Ä': u'Mar',
  2347.     u'╬£╬▒╧Ä': u'Mar',
  2348.     u'╬æ╧Ç╧ü': u'Apr',
  2349.     u'╬£╬¼╬╣': u'May',
  2350.     u'╬£╬▒╧è': u'May',
  2351.     u'╬£╬▒╬╣': u'May',
  2352.     u'╬Ö╬┐╧ì╬╜': u'Jun',
  2353.     u'╬Ö╬┐╬╜': u'Jun',
  2354.     u'╬Ö╬┐╧ì╬╗': u'Jul',
  2355.     u'╬Ö╬┐╬╗': u'Jul',
  2356.     u'╬æ╧ì╬│': u'Aug',
  2357.     u'╬æ╧à╬│': u'Aug',
  2358.     u'╬ú╬╡╧Ç': u'Sep',
  2359.     u'╬ƒ╬║╧ä': u'Oct',
  2360.     u'╬¥╬┐╬¡': u'Nov',
  2361.     u'╬¥╬┐╬╡': u'Nov',
  2362.     u'╬ö╬╡╬║': u'Dec' }
  2363. _greek_wdays = {
  2364.     u'╬Ü╧à╧ü': u'Sun',
  2365.     u'╬ö╬╡╧à': u'Mon',
  2366.     u'╬ñ╧ü╬╣': u'Tue',
  2367.     u'╬ñ╬╡╧ä': u'Wed',
  2368.     u'╬á╬╡╬╝': u'Thu',
  2369.     u'╬á╬▒╧ü': u'Fri',
  2370.     u'╬ú╬▒╬▓': u'Sat' }
  2371. _greek_date_format_re = re.compile(u'([^,]+),\\s+(\\d{2})\\s+([^\\s]+)\\s+(\\d{4})\\s+(\\d{2}):(\\d{2}):(\\d{2})\\s+([^\\s]+)')
  2372.  
  2373. def _parse_date_greek(dateString):
  2374.     m = _greek_date_format_re.match(dateString)
  2375.     if not m:
  2376.         return None
  2377.     
  2378.     try:
  2379.         wday = _greek_wdays[m.group(1)]
  2380.         month = _greek_months[m.group(3)]
  2381.     except:
  2382.         m
  2383.         return None
  2384.  
  2385.     rfc822date = '%(wday)s, %(day)s %(month)s %(year)s %(hour)s:%(minute)s:%(second)s %(zonediff)s' % {
  2386.         'wday': wday,
  2387.         'day': m.group(2),
  2388.         'month': month,
  2389.         'year': m.group(4),
  2390.         'hour': m.group(5),
  2391.         'minute': m.group(6),
  2392.         'second': m.group(7),
  2393.         'zonediff': m.group(8) }
  2394.     if _debug:
  2395.         sys.stderr.write('Greek date parsed as: %s\n' % rfc822date)
  2396.     
  2397.     return _parse_date_rfc822(rfc822date)
  2398.  
  2399. registerDateHandler(_parse_date_greek)
  2400. _hungarian_months = {
  2401.     u'janu├ír': u'01',
  2402.     u'febru├íri': u'02',
  2403.     u'm├írcius': u'03',
  2404.     u'├íprilis': u'04',
  2405.     u'm├íujus': u'05',
  2406.     u'j├║nius': u'06',
  2407.     u'j├║lius': u'07',
  2408.     u'augusztus': u'08',
  2409.     u'szeptember': u'09',
  2410.     u'okt├│ber': u'10',
  2411.     u'november': u'11',
  2412.     u'december': u'12' }
  2413. _hungarian_date_format_re = re.compile(u'(\\d{4})-([^-]+)-(\\d{,2})T(\\d{,2}):(\\d{2})((\\+|-)(\\d{,2}:\\d{2}))')
  2414.  
  2415. def _parse_date_hungarian(dateString):
  2416.     m = _hungarian_date_format_re.match(dateString)
  2417.     if not m:
  2418.         return None
  2419.     
  2420.     try:
  2421.         month = _hungarian_months[m.group(2)]
  2422.         day = m.group(3)
  2423.         if len(day) == 1:
  2424.             day = '0' + day
  2425.         
  2426.         hour = m.group(4)
  2427.         if len(hour) == 1:
  2428.             hour = '0' + hour
  2429.     except:
  2430.         m
  2431.         return None
  2432.  
  2433.     w3dtfdate = '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s%(zonediff)s' % {
  2434.         'year': m.group(1),
  2435.         'month': month,
  2436.         'day': day,
  2437.         'hour': hour,
  2438.         'minute': m.group(5),
  2439.         'zonediff': m.group(6) }
  2440.     if _debug:
  2441.         sys.stderr.write('Hungarian date parsed as: %s\n' % w3dtfdate)
  2442.     
  2443.     return _parse_date_w3dtf(w3dtfdate)
  2444.  
  2445. registerDateHandler(_parse_date_hungarian)
  2446.  
  2447. def _parse_date_w3dtf(dateString):
  2448.     
  2449.     def __extract_date(m):
  2450.         year = int(m.group('year'))
  2451.         if year < 100:
  2452.             year = 100 * int(time.gmtime()[0] / 100) + int(year)
  2453.         
  2454.         if year < 1000:
  2455.             return (0, 0, 0)
  2456.         julian = m.group('julian')
  2457.         if julian:
  2458.             julian = int(julian)
  2459.             month = julian / 30 + 1
  2460.             day = julian % 30 + 1
  2461.             jday = None
  2462.             while jday != julian:
  2463.                 t = time.mktime((year, month, day, 0, 0, 0, 0, 0, 0))
  2464.                 jday = time.gmtime(t)[-2]
  2465.                 diff = abs(jday - julian)
  2466.                 if jday > julian:
  2467.                     if diff < day:
  2468.                         day = day - diff
  2469.                     else:
  2470.                         month = month - 1
  2471.                         day = 31
  2472.                 diff < day
  2473.                 if jday < julian:
  2474.                     if day + diff < 28:
  2475.                         day = day + diff
  2476.                     else:
  2477.                         month = month + 1
  2478.                 day + diff < 28
  2479.                 continue
  2480.                 year < 1000
  2481.             return (year, month, day)
  2482.         month = m.group('month')
  2483.         day = 1
  2484.         if month is None:
  2485.             month = 1
  2486.         else:
  2487.             month = int(month)
  2488.             day = m.group('day')
  2489.             if day:
  2490.                 day = int(day)
  2491.             else:
  2492.                 day = 1
  2493.         return (year, month, day)
  2494.  
  2495.     
  2496.     def __extract_time(m):
  2497.         if not m:
  2498.             return (0, 0, 0)
  2499.         hours = m.group('hours')
  2500.         if not hours:
  2501.             return (0, 0, 0)
  2502.         hours = int(hours)
  2503.         minutes = int(m.group('minutes'))
  2504.         seconds = m.group('seconds')
  2505.         return (hours, minutes, seconds)
  2506.  
  2507.     
  2508.     def __extract_tzd(m):
  2509.         if not m:
  2510.             return 0
  2511.         tzd = m.group('tzd')
  2512.         if not tzd:
  2513.             return 0
  2514.         if tzd == 'Z':
  2515.             return 0
  2516.         hours = int(m.group('tzdhours'))
  2517.         minutes = m.group('tzdminutes')
  2518.         offset = (hours * 60 + minutes) * 60
  2519.         if tzd[0] == '+':
  2520.             return -offset
  2521.         return offset
  2522.  
  2523.     __date_re = '(?P<year>\\d\\d\\d\\d)(?:(?P<dsep>-|)(?:(?P<julian>\\d\\d\\d)|(?P<month>\\d\\d)(?:(?P=dsep)(?P<day>\\d\\d))?))?'
  2524.     __tzd_re = '(?P<tzd>[-+](?P<tzdhours>\\d\\d)(?::?(?P<tzdminutes>\\d\\d))|Z)'
  2525.     __tzd_rx = re.compile(__tzd_re)
  2526.     __time_re = '(?P<hours>\\d\\d)(?P<tsep>:|)(?P<minutes>\\d\\d)(?:(?P=tsep)(?P<seconds>\\d\\d(?:[.,]\\d+)?))?' + __tzd_re
  2527.     __datetime_re = '%s(?:T%s)?' % (__date_re, __time_re)
  2528.     __datetime_rx = re.compile(__datetime_re)
  2529.     m = __datetime_rx.match(dateString)
  2530.     if m is None or m.group() != dateString:
  2531.         return None
  2532.     gmt = __extract_date(m) + __extract_time(m) + (0, 0, 0)
  2533.     if gmt[0] == 0:
  2534.         return None
  2535.     return time.gmtime(time.mktime(gmt) + __extract_tzd(m) - time.timezone)
  2536.  
  2537. registerDateHandler(_parse_date_w3dtf)
  2538.  
  2539. def _parse_date_rfc822(dateString):
  2540.     data = dateString.split()
  2541.     if data[0][-1] in (',', '.') or data[0].lower() in rfc822._daynames:
  2542.         del data[0]
  2543.     
  2544.     if len(data) == 4:
  2545.         s = data[3]
  2546.         i = s.find('+')
  2547.         if i > 0:
  2548.             data[3:] = [
  2549.                 s[:i],
  2550.                 s[i + 1:]]
  2551.         else:
  2552.             data.append('')
  2553.         dateString = ' '.join(data)
  2554.     
  2555.     if len(data) < 5:
  2556.         dateString += ' 00:00:00 GMT'
  2557.     
  2558.     tm = rfc822.parsedate_tz(dateString)
  2559.     if tm:
  2560.         return time.gmtime(rfc822.mktime_tz(tm))
  2561.  
  2562. _additional_timezones = {
  2563.     'AT': -400,
  2564.     'ET': -500,
  2565.     'CT': -600,
  2566.     'MT': -700,
  2567.     'PT': -800 }
  2568. rfc822._timezones.update(_additional_timezones)
  2569. registerDateHandler(_parse_date_rfc822)
  2570.  
  2571. def _parse_date(dateString):
  2572.     for handler in _date_handlers:
  2573.         
  2574.         try:
  2575.             date9tuple = handler(dateString)
  2576.             if not date9tuple:
  2577.                 continue
  2578.             
  2579.             if len(date9tuple) != 9:
  2580.                 if _debug:
  2581.                     sys.stderr.write('date handler function must return 9-tuple\n')
  2582.                 
  2583.                 raise ValueError
  2584.             len(date9tuple) != 9
  2585.             map(int, date9tuple)
  2586.             return date9tuple
  2587.         continue
  2588.         except Exception:
  2589.             e = None
  2590.             if _debug:
  2591.                 sys.stderr.write('%s raised %s\n' % (handler.__name__, repr(e)))
  2592.             
  2593.             _debug
  2594.         
  2595.  
  2596.     
  2597.  
  2598.  
  2599. def _getCharacterEncoding(http_headers, xml_data):
  2600.     
  2601.     def _parseHTTPContentType(content_type):
  2602.         if not content_type:
  2603.             pass
  2604.         content_type = ''
  2605.         (content_type, params) = cgi.parse_header(content_type)
  2606.         return (content_type, params.get('charset', '').replace("'", ''))
  2607.  
  2608.     sniffed_xml_encoding = ''
  2609.     xml_encoding = ''
  2610.     true_encoding = ''
  2611.     (http_content_type, http_encoding) = _parseHTTPContentType(http_headers.get('content-type'))
  2612.     
  2613.     try:
  2614.         if xml_data[:4] == 'Lo\xa7\x94':
  2615.             xml_data = _ebcdic_to_ascii(xml_data)
  2616.         elif xml_data[:4] == '\x00<\x00?':
  2617.             sniffed_xml_encoding = 'utf-16be'
  2618.             xml_data = unicode(xml_data, 'utf-16be').encode('utf-8')
  2619.         elif len(xml_data) >= 4 and xml_data[:2] == '\xfe\xff' and xml_data[2:4] != '\x00\x00':
  2620.             sniffed_xml_encoding = 'utf-16be'
  2621.             xml_data = unicode(xml_data[2:], 'utf-16be').encode('utf-8')
  2622.         elif xml_data[:4] == '<\x00?\x00':
  2623.             sniffed_xml_encoding = 'utf-16le'
  2624.             xml_data = unicode(xml_data, 'utf-16le').encode('utf-8')
  2625.         elif len(xml_data) >= 4 and xml_data[:2] == '\xff\xfe' and xml_data[2:4] != '\x00\x00':
  2626.             sniffed_xml_encoding = 'utf-16le'
  2627.             xml_data = unicode(xml_data[2:], 'utf-16le').encode('utf-8')
  2628.         elif xml_data[:4] == '\x00\x00\x00<':
  2629.             sniffed_xml_encoding = 'utf-32be'
  2630.             xml_data = unicode(xml_data, 'utf-32be').encode('utf-8')
  2631.         elif xml_data[:4] == '<\x00\x00\x00':
  2632.             sniffed_xml_encoding = 'utf-32le'
  2633.             xml_data = unicode(xml_data, 'utf-32le').encode('utf-8')
  2634.         elif xml_data[:4] == '\x00\x00\xfe\xff':
  2635.             sniffed_xml_encoding = 'utf-32be'
  2636.             xml_data = unicode(xml_data[4:], 'utf-32be').encode('utf-8')
  2637.         elif xml_data[:4] == '\xff\xfe\x00\x00':
  2638.             sniffed_xml_encoding = 'utf-32le'
  2639.             xml_data = unicode(xml_data[4:], 'utf-32le').encode('utf-8')
  2640.         elif xml_data[:3] == '\xef\xbb\xbf':
  2641.             sniffed_xml_encoding = 'utf-8'
  2642.             xml_data = unicode(xml_data[3:], 'utf-8').encode('utf-8')
  2643.         
  2644.         xml_encoding_match = re.compile('^<\\?.*encoding=[\'"](.*?)[\'"].*\\?>').match(xml_data)
  2645.     except:
  2646.         xml_encoding_match = None
  2647.  
  2648.     if xml_encoding_match:
  2649.         xml_encoding = xml_encoding_match.groups()[0].lower()
  2650.         if sniffed_xml_encoding and xml_encoding in ('iso-10646-ucs-2', 'ucs-2', 'csunicode', 'iso-10646-ucs-4', 'ucs-4', 'csucs4', 'utf-16', 'utf-32', 'utf_16', 'utf_32', 'utf16', 'u16'):
  2651.             xml_encoding = sniffed_xml_encoding
  2652.         
  2653.     
  2654.     acceptable_content_type = 0
  2655.     application_content_types = ('application/xml', 'application/xml-dtd', 'application/xml-external-parsed-entity')
  2656.     text_content_types = ('text/xml', 'text/xml-external-parsed-entity')
  2657.     if (http_content_type in application_content_types or http_content_type.startswith('application/')) and http_content_type.endswith('+xml'):
  2658.         acceptable_content_type = 1
  2659.         if not http_encoding and xml_encoding:
  2660.             pass
  2661.         true_encoding = 'utf-8'
  2662.     elif (http_content_type in text_content_types or http_content_type.startswith('text/')) and http_content_type.endswith('+xml'):
  2663.         acceptable_content_type = 1
  2664.         if not http_encoding:
  2665.             pass
  2666.         true_encoding = 'us-ascii'
  2667.     elif http_content_type.startswith('text/'):
  2668.         if not http_encoding:
  2669.             pass
  2670.         true_encoding = 'us-ascii'
  2671.     elif http_headers and not http_headers.has_key('content-type'):
  2672.         if not xml_encoding:
  2673.             pass
  2674.         true_encoding = 'iso-8859-1'
  2675.     elif not xml_encoding:
  2676.         pass
  2677.     true_encoding = 'utf-8'
  2678.     return (true_encoding, http_encoding, xml_encoding, sniffed_xml_encoding, acceptable_content_type)
  2679.  
  2680.  
  2681. def _toUTF8(data, encoding):
  2682.     if _debug:
  2683.         sys.stderr.write('entering _toUTF8, trying encoding %s\n' % encoding)
  2684.     
  2685.     if len(data) >= 4 and data[:2] == '\xfe\xff' and data[2:4] != '\x00\x00':
  2686.         if _debug:
  2687.             sys.stderr.write('stripping BOM\n')
  2688.             if encoding != 'utf-16be':
  2689.                 sys.stderr.write('trying utf-16be instead\n')
  2690.             
  2691.         
  2692.         encoding = 'utf-16be'
  2693.         data = data[2:]
  2694.     elif len(data) >= 4 and data[:2] == '\xff\xfe' and data[2:4] != '\x00\x00':
  2695.         if _debug:
  2696.             sys.stderr.write('stripping BOM\n')
  2697.             if encoding != 'utf-16le':
  2698.                 sys.stderr.write('trying utf-16le instead\n')
  2699.             
  2700.         
  2701.         encoding = 'utf-16le'
  2702.         data = data[2:]
  2703.     elif data[:3] == '\xef\xbb\xbf':
  2704.         if _debug:
  2705.             sys.stderr.write('stripping BOM\n')
  2706.             if encoding != 'utf-8':
  2707.                 sys.stderr.write('trying utf-8 instead\n')
  2708.             
  2709.         
  2710.         encoding = 'utf-8'
  2711.         data = data[3:]
  2712.     elif data[:4] == '\x00\x00\xfe\xff':
  2713.         if _debug:
  2714.             sys.stderr.write('stripping BOM\n')
  2715.             if encoding != 'utf-32be':
  2716.                 sys.stderr.write('trying utf-32be instead\n')
  2717.             
  2718.         
  2719.         encoding = 'utf-32be'
  2720.         data = data[4:]
  2721.     elif data[:4] == '\xff\xfe\x00\x00':
  2722.         if _debug:
  2723.             sys.stderr.write('stripping BOM\n')
  2724.             if encoding != 'utf-32le':
  2725.                 sys.stderr.write('trying utf-32le instead\n')
  2726.             
  2727.         
  2728.         encoding = 'utf-32le'
  2729.         data = data[4:]
  2730.     
  2731.     newdata = unicode(data, encoding)
  2732.     if _debug:
  2733.         sys.stderr.write('successfully converted %s data to unicode\n' % encoding)
  2734.     
  2735.     declmatch = re.compile('^<\\?xml[^>]*?>')
  2736.     newdecl = "<?xml version='1.0' encoding='utf-8'?>"
  2737.     if declmatch.search(newdata):
  2738.         newdata = declmatch.sub(newdecl, newdata)
  2739.     else:
  2740.         newdata = newdecl + u'\n' + newdata
  2741.     return newdata.encode('utf-8')
  2742.  
  2743.  
  2744. def _stripDoctype(data):
  2745.     entity_pattern = re.compile('<!ENTITY([^>]*?)>', re.MULTILINE)
  2746.     data = entity_pattern.sub('', data)
  2747.     doctype_pattern = re.compile('<!DOCTYPE([^>]*?)>', re.MULTILINE)
  2748.     doctype_results = doctype_pattern.findall(data)
  2749.     if not doctype_results or doctype_results[0]:
  2750.         pass
  2751.     doctype = ''
  2752.     if doctype.lower().count('netscape'):
  2753.         version = 'rss091n'
  2754.     else:
  2755.         version = None
  2756.     data = doctype_pattern.sub('', data)
  2757.     return (version, data)
  2758.  
  2759.  
  2760. def parse(url_file_stream_or_string, etag = None, modified = None, agent = None, referrer = None, handlers = []):
  2761.     result = FeedParserDict()
  2762.     result['feed'] = FeedParserDict()
  2763.     result['entries'] = []
  2764.     if _XML_AVAILABLE:
  2765.         result['bozo'] = 0
  2766.     
  2767.     if type(handlers) == types.InstanceType:
  2768.         handlers = [
  2769.             handlers]
  2770.     
  2771.     
  2772.     try:
  2773.         f = _open_resource(url_file_stream_or_string, etag, modified, agent, referrer, handlers)
  2774.         data = f.read()
  2775.     except Exception:
  2776.         e = None
  2777.         result['bozo'] = 1
  2778.         result['bozo_exception'] = e
  2779.         data = ''
  2780.         f = None
  2781.  
  2782.     if f and data and hasattr(f, 'headers'):
  2783.         if gzip and f.headers.get('content-encoding', '') == 'gzip':
  2784.             
  2785.             try:
  2786.                 data = gzip.GzipFile(fileobj = _StringIO(data)).read()
  2787.             except Exception:
  2788.                 e = None
  2789.                 result['bozo'] = 1
  2790.                 result['bozo_exception'] = e
  2791.                 data = ''
  2792.             except:
  2793.                 None<EXCEPTION MATCH>Exception
  2794.             
  2795.  
  2796.         None<EXCEPTION MATCH>Exception
  2797.         if zlib and f.headers.get('content-encoding', '') == 'deflate':
  2798.             
  2799.             try:
  2800.                 data = zlib.decompress(data, -(zlib.MAX_WBITS))
  2801.             except Exception:
  2802.                 e = None
  2803.                 result['bozo'] = 1
  2804.                 result['bozo_exception'] = e
  2805.                 data = ''
  2806.             except:
  2807.                 None<EXCEPTION MATCH>Exception
  2808.             
  2809.  
  2810.         None<EXCEPTION MATCH>Exception
  2811.     
  2812.     if hasattr(f, 'info'):
  2813.         info = f.info()
  2814.         result['etag'] = info.getheader('ETag')
  2815.         last_modified = info.getheader('Last-Modified')
  2816.         if last_modified:
  2817.             result['modified'] = _parse_date(last_modified)
  2818.         
  2819.     
  2820.     if hasattr(f, 'url'):
  2821.         result['href'] = f.url
  2822.         result['status'] = 200
  2823.     
  2824.     if hasattr(f, 'status'):
  2825.         result['status'] = f.status
  2826.     
  2827.     if hasattr(f, 'headers'):
  2828.         result['headers'] = f.headers.dict
  2829.     
  2830.     if hasattr(f, 'close'):
  2831.         f.close()
  2832.     
  2833.     http_headers = result.get('headers', { })
  2834.     (result['encoding'], http_encoding, xml_encoding, sniffed_xml_encoding, acceptable_content_type) = _getCharacterEncoding(http_headers, data)
  2835.     if http_headers and not acceptable_content_type:
  2836.         if http_headers.has_key('content-type'):
  2837.             bozo_message = '%s is not an XML media type' % http_headers['content-type']
  2838.         else:
  2839.             bozo_message = 'no Content-type specified'
  2840.         result['bozo'] = 1
  2841.         result['bozo_exception'] = NonXMLContentType(bozo_message)
  2842.     
  2843.     (result['version'], data) = _stripDoctype(data)
  2844.     baseuri = http_headers.get('content-location', result.get('href'))
  2845.     baselang = http_headers.get('content-language', None)
  2846.     if result.get('status', 0) == 304:
  2847.         result['version'] = ''
  2848.         result['debug_message'] = 'The feed has not changed since you last checked, ' + 'so the server sent no data.  This is a feature, not a bug!'
  2849.         return result
  2850.     if not data:
  2851.         return result
  2852.     use_strict_parser = 0
  2853.     known_encoding = 0
  2854.     tried_encodings = []
  2855.     for proposed_encoding in (result['encoding'], xml_encoding, sniffed_xml_encoding):
  2856.         if proposed_encoding in tried_encodings:
  2857.             continue
  2858.         
  2859.         tried_encodings.append(proposed_encoding)
  2860.         
  2861.         try:
  2862.             data = _toUTF8(data, proposed_encoding)
  2863.             known_encoding = use_strict_parser = 1
  2864.         continue
  2865.         continue
  2866.  
  2867.     
  2868.     if not known_encoding and chardet:
  2869.         
  2870.         try:
  2871.             proposed_encoding = chardet.detect(data)['encoding']
  2872.             if proposed_encoding and proposed_encoding not in tried_encodings:
  2873.                 tried_encodings.append(proposed_encoding)
  2874.                 data = _toUTF8(data, proposed_encoding)
  2875.                 known_encoding = use_strict_parser = 1
  2876.  
  2877.     
  2878.     if not known_encoding and 'utf-8' not in tried_encodings:
  2879.         
  2880.         try:
  2881.             proposed_encoding = 'utf-8'
  2882.             tried_encodings.append(proposed_encoding)
  2883.             data = _toUTF8(data, proposed_encoding)
  2884.             known_encoding = use_strict_parser = 1
  2885.  
  2886.     
  2887.     if not known_encoding and 'windows-1252' not in tried_encodings:
  2888.         
  2889.         try:
  2890.             proposed_encoding = 'windows-1252'
  2891.             tried_encodings.append(proposed_encoding)
  2892.             data = _toUTF8(data, proposed_encoding)
  2893.             known_encoding = use_strict_parser = 1
  2894.  
  2895.     
  2896.     if not known_encoding:
  2897.         result['bozo'] = 1
  2898.         result['bozo_exception'] = CharacterEncodingUnknown('document encoding unknown, I tried ' + '%s, %s, utf-8, and windows-1252 but nothing worked' % (result['encoding'], xml_encoding))
  2899.         result['encoding'] = ''
  2900.     elif proposed_encoding != result['encoding']:
  2901.         result['bozo'] = 1
  2902.         result['bozo_exception'] = CharacterEncodingOverride('documented declared as %s, but parsed as %s' % (result['encoding'], proposed_encoding))
  2903.         result['encoding'] = proposed_encoding
  2904.     
  2905.     if not _XML_AVAILABLE:
  2906.         use_strict_parser = 0
  2907.     
  2908.     if use_strict_parser:
  2909.         feedparser = _StrictFeedParser(baseuri, baselang, 'utf-8')
  2910.         saxparser = xml.sax.make_parser(PREFERRED_XML_PARSERS)
  2911.         saxparser.setFeature(xml.sax.handler.feature_namespaces, 1)
  2912.         saxparser.setContentHandler(feedparser)
  2913.         saxparser.setErrorHandler(feedparser)
  2914.         source = xml.sax.xmlreader.InputSource()
  2915.         source.setByteStream(_StringIO(data))
  2916.         if hasattr(saxparser, '_ns_stack'):
  2917.             saxparser._ns_stack.append({
  2918.                 'http://www.w3.org/XML/1998/namespace': 'xml' })
  2919.         
  2920.         
  2921.         try:
  2922.             saxparser.parse(source)
  2923.         except Exception:
  2924.             e = None
  2925.             if _debug:
  2926.                 import traceback
  2927.                 traceback.print_stack()
  2928.                 traceback.print_exc()
  2929.                 sys.stderr.write('xml parsing failed\n')
  2930.             
  2931.             result['bozo'] = 1
  2932.             if not feedparser.exc:
  2933.                 pass
  2934.             result['bozo_exception'] = e
  2935.             use_strict_parser = 0
  2936.         except:
  2937.             None<EXCEPTION MATCH>Exception
  2938.         
  2939.  
  2940.     None<EXCEPTION MATCH>Exception
  2941.     if not use_strict_parser:
  2942.         if not known_encoding or 'utf-8':
  2943.             pass
  2944.         feedparser = _LooseFeedParser(baseuri, baselang, '')
  2945.         feedparser.feed(data)
  2946.     
  2947.     result['feed'] = feedparser.feeddata
  2948.     result['entries'] = feedparser.entries
  2949.     if not result['version']:
  2950.         pass
  2951.     result['version'] = feedparser.version
  2952.     result['namespaces'] = feedparser.namespacesInUse
  2953.     return result
  2954.  
  2955. if __name__ == '__main__':
  2956.     zopeCompatibilityHack()
  2957.     from pprint import pprint
  2958.     for url in urls:
  2959.         print url
  2960.         print 
  2961.         result = parse(url)
  2962.         pprint(result)
  2963.         print 
  2964.     
  2965.  
  2966.