home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2008 February / PCWFEB08.iso / Software / Freeware / Miro 1.0 / Miro_Installer.exe / Miro_Downloader.exe / feedparser.pyc (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2007-11-12  |  99.0 KB  |  3,456 lines

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