~ubuntu-branches/ubuntu/lucid/exaile/lucid

« back to all changes in this revision

Viewing changes to plugins/podcasts/_feedparser.py

  • Committer: Bazaar Package Importer
  • Author(s): Andrew Starr-Bochicchio
  • Date: 2010-02-12 19:51:01 UTC
  • mfrom: (1.1.11 upstream)
  • Revision ID: james.westby@ubuntu.com-20100212195101-8jt3tculxcl92e6v
Tags: 0.3.1~b1-0ubuntu1
* New upstream release.
* Adjust exaile.install for new plugins.
* debian/control:
 - Drop unneeded python-dev Build-Dep.
 - Bump Standards-Version to 3.8.4 
* debian/rules: No empty po files to delete.

Show diffs side-by-side

added added

removed removed

Lines of Context:
217
217
        if not self.has_key(key):
218
218
            self[key] = value
219
219
        return self[key]
220
 
        
 
220
 
221
221
    def has_key(self, key):
222
222
        try:
223
223
            return hasattr(self, key) or UserDict.has_key(self, key)
224
224
        except AttributeError:
225
225
            return False
226
 
        
 
226
 
227
227
    def __getattr__(self, key):
228
228
        try:
229
229
            return self.__dict__[key]
299
299
                  'http://purl.org/atom/ns#': '',
300
300
                  'http://www.w3.org/2005/Atom': '',
301
301
                  'http://purl.org/rss/1.0/modules/rss091#': '',
302
 
                  
 
302
 
303
303
                  'http://webns.net/mvcb/':                               'admin',
304
304
                  'http://purl.org/rss/1.0/modules/aggregation/':         'ag',
305
305
                  'http://purl.org/rss/1.0/modules/annotate/':            'annotate',
353
353
    can_contain_relative_uris = ['content', 'title', 'summary', 'info', 'tagline', 'subtitle', 'copyright', 'rights', 'description']
354
354
    can_contain_dangerous_markup = ['content', 'title', 'summary', 'info', 'tagline', 'subtitle', 'copyright', 'rights', 'description']
355
355
    html_types = ['text/html', 'application/xhtml+xml']
356
 
    
 
356
 
357
357
    def __init__(self, baseuri=None, baselang=None, encoding='utf-8'):
358
358
        if _debug: sys.stderr.write('initializing FeedParser\n')
359
359
        if not self._matchnamespaces:
393
393
        # normalize attrs
394
394
        attrs = [(k.lower(), v) for k, v in attrs]
395
395
        attrs = [(k, k in ('rel', 'type') and v.lower() or v) for k, v in attrs]
396
 
        
 
396
 
397
397
        # track xml:base and xml:lang
398
398
        attrsD = dict(attrs)
399
399
        baseuri = attrsD.get('xml:base', attrsD.get('base')) or self.baseuri
411
411
        self.lang = lang
412
412
        self.basestack.append(self.baseuri)
413
413
        self.langstack.append(lang)
414
 
        
 
414
 
415
415
        # track namespaces
416
416
        for prefix, uri in attrs:
417
417
            if prefix.startswith('xmlns:'):
449
449
            self.intextinput = 0
450
450
        if (not prefix) and tag not in ('title', 'link', 'description', 'url', 'href', 'width', 'height'):
451
451
            self.inimage = 0
452
 
        
 
452
 
453
453
        # call special handler (if defined) or default handler
454
454
        methodname = '_start_' + prefix + suffix
455
455
        try:
570
570
        elif contentType == 'xhtml':
571
571
            contentType = 'application/xhtml+xml'
572
572
        return contentType
573
 
    
 
573
 
574
574
    def trackNamespace(self, prefix, uri):
575
575
        loweruri = uri.lower()
576
576
        if (prefix, loweruri) == (None, 'http://my.netscape.com/rdf/simple/0.9/') and not self.version:
591
591
 
592
592
    def resolveURI(self, uri):
593
593
        return _urljoin(self.baseuri or '', uri)
594
 
    
 
594
 
595
595
    def decodeEntities(self, element, data):
596
596
        return data
597
597
 
601
601
    def pop(self, element, stripWhitespace=1):
602
602
        if not self.elementstack: return
603
603
        if self.elementstack[-1][0] != element: return
604
 
        
 
604
 
605
605
        element, expectingText, pieces = self.elementstack.pop()
606
606
        output = ''.join(pieces)
607
607
        if stripWhitespace:
616
616
                pass
617
617
            except binascii.Incomplete:
618
618
                pass
619
 
                
 
619
 
620
620
        # resolve relative URIs
621
621
        if (element in self.can_be_relative_uri) and output:
622
622
            output = self.resolveURI(output)
623
 
        
 
623
 
624
624
        # decode entities within embedded markup
625
625
        if not self.contentparams.get('base64', 0):
626
626
            output = self.decodeEntities(element, output)
639
639
        if self.mapContentType(self.contentparams.get('type', 'text/html')) in self.html_types:
640
640
            if element in self.can_contain_relative_uris:
641
641
                output = _resolveRelativeURIs(output, self.baseuri, self.encoding)
642
 
        
 
642
 
643
643
        # sanitize embedded markup
644
644
        if self.mapContentType(self.contentparams.get('type', 'text/html')) in self.html_types:
645
645
            if element in self.can_contain_dangerous_markup:
654
654
        # categories/tags/keywords/whatever are handled in _end_category
655
655
        if element == 'category':
656
656
            return output
657
 
        
 
657
 
658
658
        # store output in appropriate place(s)
659
659
        if self.inentry and not self.insource:
660
660
            if element == 'content':
701
701
        self.incontent -= 1
702
702
        self.contentparams.clear()
703
703
        return value
704
 
        
 
704
 
705
705
    def _mapToStandardPrefix(self, name):
706
706
        colonpos = name.find(':')
707
707
        if colonpos <> -1:
710
710
            prefix = self.namespacemap.get(prefix, prefix)
711
711
            name = prefix + ':' + suffix
712
712
        return name
713
 
        
 
713
 
714
714
    def _getAttribute(self, attrsD, name):
715
715
        return attrsD.get(self._mapToStandardPrefix(name))
716
716
 
738
738
                pass
739
739
            attrsD['href'] = href
740
740
        return attrsD
741
 
    
 
741
 
742
742
    def _save(self, key, value):
743
743
        context = self._getContext()
744
744
        context.setdefault(key, value)
757
757
                self.version = 'rss20'
758
758
            else:
759
759
                self.version = 'rss'
760
 
    
 
760
 
761
761
    def _start_dlhottitles(self, attrsD):
762
762
        self.version = 'hotrss'
763
763
 
775
775
            self._start_link({})
776
776
            self.elementstack[-1][-1] = attrsD['href']
777
777
            self._end_link()
778
 
    
 
778
 
779
779
    def _start_feed(self, attrsD):
780
780
        self.infeed = 1
781
781
        versionmap = {'0.1': 'atom01',
792
792
    def _end_channel(self):
793
793
        self.infeed = 0
794
794
    _end_feed = _end_channel
795
 
    
 
795
 
796
796
    def _start_image(self, attrsD):
797
797
        self.inimage = 1
798
798
        self.push('image', 0)
799
799
        context = self._getContext()
800
800
        context.setdefault('image', FeedParserDict())
801
 
            
 
801
 
802
802
    def _end_image(self):
803
803
        self.pop('image')
804
804
        self.inimage = 0
809
809
        context = self._getContext()
810
810
        context.setdefault('textinput', FeedParserDict())
811
811
    _start_textInput = _start_textinput
812
 
    
 
812
 
813
813
    def _end_textinput(self):
814
814
        self.pop('textinput')
815
815
        self.intextinput = 0
1000
1000
        self.popContent('subtitle')
1001
1001
    _end_tagline = _end_subtitle
1002
1002
    _end_itunes_subtitle = _end_subtitle
1003
 
            
 
1003
 
1004
1004
    def _start_rights(self, attrsD):
1005
1005
        self.pushContent('rights', attrsD, 'text/plain', 1)
1006
1006
    _start_dc_rights = _start_rights
1094
1094
        if value:
1095
1095
            self.elementstack[-1][2].append(value)
1096
1096
        self.pop('license')
1097
 
        
 
1097
 
1098
1098
    def _start_creativecommons_license(self, attrsD):
1099
1099
        self.push('license', 1)
1100
1100
 
1118
1118
        self.push('category', 1)
1119
1119
    _start_dc_subject = _start_category
1120
1120
    _start_keywords = _start_category
1121
 
        
 
1121
 
1122
1122
    def _end_itunes_keywords(self):
1123
1123
        for term in self.pop('itunes_keywords').split():
1124
1124
            self._addTag(term, 'http://www.itunes.com/', None)
1125
 
        
 
1125
 
1126
1126
    def _start_itunes_category(self, attrsD):
1127
1127
        self._addTag(attrsD.get('text'), 'http://www.itunes.com/', None)
1128
1128
        self.push('category', 1)
1129
 
        
 
1129
 
1130
1130
    def _end_category(self):
1131
1131
        value = self.pop('category')
1132
1132
        if not value: return
1142
1142
 
1143
1143
    def _start_cloud(self, attrsD):
1144
1144
        self._getContext()['cloud'] = FeedParserDict(attrsD)
1145
 
        
 
1145
 
1146
1146
    def _start_link(self, attrsD):
1147
1147
        attrsD.setdefault('rel', 'alternate')
1148
1148
        attrsD.setdefault('type', 'text/html')
1244
1244
        context = self._getContext()
1245
1245
        if context.has_key('generator_detail'):
1246
1246
            context['generator_detail']['name'] = value
1247
 
            
 
1247
 
1248
1248
    def _start_admin_generatoragent(self, attrsD):
1249
1249
        self.push('generator', 1)
1250
1250
        value = self._getAttribute(attrsD, 'rdf:resource')
1259
1259
        if value:
1260
1260
            self.elementstack[-1][2].append(value)
1261
1261
        self.pop('errorreportsto')
1262
 
        
 
1262
 
1263
1263
    def _start_summary(self, attrsD):
1264
1264
        context = self._getContext()
1265
1265
        if context.has_key('summary'):
1277
1277
            self.popContent(self._summaryKey or 'summary')
1278
1278
        self._summaryKey = None
1279
1279
    _end_itunes_summary = _end_summary
1280
 
        
 
1280
 
1281
1281
    def _start_enclosure(self, attrsD):
1282
1282
        attrsD = self._itsAnHrefDamnIt(attrsD)
1283
1283
        self._getContext().setdefault('enclosures', []).append(FeedParserDict(attrsD))
1286
1286
            context = self._getContext()
1287
1287
            if not context.get('id'):
1288
1288
                context['id'] = href
1289
 
            
 
1289
 
1290
1290
    def _start_source(self, attrsD):
1291
1291
        self.insource = 1
1292
1292
 
1328
1328
        self.push('itunes_image', 0)
1329
1329
        self._getContext()['image'] = FeedParserDict({'href': attrsD.get('href')})
1330
1330
    _start_itunes_link = _start_itunes_image
1331
 
        
 
1331
 
1332
1332
    def _end_itunes_block(self):
1333
1333
        value = self.pop('itunes_block', 0)
1334
1334
        self._getContext()['itunes_block'] = (value == 'yes') and 1 or 0
1345
1345
            _FeedParserMixin.__init__(self, baseuri, baselang, encoding)
1346
1346
            self.bozo = 0
1347
1347
            self.exc = None
1348
 
        
 
1348
 
1349
1349
        def startPrefixMapping(self, prefix, uri):
1350
1350
            self.trackNamespace(prefix, uri)
1351
 
        
 
1351
 
1352
1352
        def startElementNS(self, name, qname, attrs):
1353
1353
            namespace, localname = name
1354
1354
            lowernamespace = str(namespace or '').lower()
1405
1405
        def error(self, exc):
1406
1406
            self.bozo = 1
1407
1407
            self.exc = exc
1408
 
            
 
1408
 
1409
1409
        def fatalError(self, exc):
1410
1410
            self.error(exc)
1411
1411
            raise exc
1413
1413
class _BaseHTMLProcessor(sgmllib.SGMLParser):
1414
1414
    elements_no_end_tag = ['area', 'base', 'basefont', 'br', 'col', 'frame', 'hr',
1415
1415
      'img', 'input', 'isindex', 'link', 'meta', 'param']
1416
 
    
 
1416
 
1417
1417
    def __init__(self, encoding):
1418
1418
        self.encoding = encoding
1419
1419
        if _debug: sys.stderr.write('entering BaseHTMLProcessor, encoding=%s\n' % self.encoding)
1420
1420
        sgmllib.SGMLParser.__init__(self)
1421
 
        
 
1421
 
1422
1422
    def reset(self):
1423
1423
        self.pieces = []
1424
1424
        sgmllib.SGMLParser.reset(self)
1429
1429
            return '<' + tag + ' />'
1430
1430
        else:
1431
1431
            return '<' + tag + '></' + tag + '>'
1432
 
        
 
1432
 
1433
1433
    def feed(self, data):
1434
1434
        data = re.compile(r'<!((?!DOCTYPE|--|\[))', re.IGNORECASE).sub(r'&lt;!\1', data)
1435
1435
        #data = re.sub(r'<(\S+?)\s*?/>', self._shorttag_replace, data) # bug [ 1399464 ] Bad regexp for _shorttag_replace
1436
 
        data = re.sub(r'<([^<\s]+?)\s*/>', self._shorttag_replace, data) 
 
1436
        data = re.sub(r'<([^<\s]+?)\s*/>', self._shorttag_replace, data)
1437
1437
        data = data.replace('&#39;', "'")
1438
1438
        data = data.replace('&#34;', '"')
1439
1439
        if self.encoding and type(data) == type(u''):
1473
1473
        # called for each character reference, e.g. for '&#160;', ref will be '160'
1474
1474
        # Reconstruct the original character reference.
1475
1475
        self.pieces.append('&#%(ref)s;' % locals())
1476
 
        
 
1476
 
1477
1477
    def handle_entityref(self, ref):
1478
1478
        # called for each entity reference, e.g. for '&copy;', ref will be 'copy'
1479
1479
        # Reconstruct the original entity reference.
1485
1485
        # Store the original text verbatim.
1486
1486
        if _debug: sys.stderr.write('_BaseHTMLProcessor, handle_text, text=%s\n' % text)
1487
1487
        self.pieces.append(text)
1488
 
        
 
1488
 
1489
1489
    def handle_comment(self, text):
1490
1490
        # called for each HTML comment, e.g. <!-- insert Javascript code here -->
1491
1491
        # Reconstruct the original comment.
1492
1492
        self.pieces.append('<!--%(text)s-->' % locals())
1493
 
        
 
1493
 
1494
1494
    def handle_pi(self, text):
1495
1495
        # called for each processing instruction, e.g. <?instruction>
1496
1496
        # Reconstruct original processing instruction.
1502
1502
        #     "http://www.w3.org/TR/html4/loose.dtd">
1503
1503
        # Reconstruct original DOCTYPE
1504
1504
        self.pieces.append('<!%(text)s>' % locals())
1505
 
        
 
1505
 
1506
1506
    _new_declname_match = re.compile(r'[a-zA-Z][-_.a-zA-Z0-9:]*\s*').match
1507
1507
    def _scan_name(self, i, declstartpos):
1508
1508
        rawdata = self.rawdata
1548
1548
            data = data.replace('&quot;', '"')
1549
1549
            data = data.replace('&apos;', "'")
1550
1550
        return data
1551
 
        
 
1551
 
1552
1552
class _RelativeURIResolver(_BaseHTMLProcessor):
1553
1553
    relative_uris = [('a', 'href'),
1554
1554
                     ('applet', 'codebase'),
1582
1582
 
1583
1583
    def resolveURI(self, uri):
1584
1584
        return _urljoin(self.baseuri, uri)
1585
 
    
 
1585
 
1586
1586
    def unknown_starttag(self, tag, attrs):
1587
1587
        attrs = self.normalize_attrs(attrs)
1588
1588
        attrs = [(key, ((tag, key) in self.relative_uris) and self.resolveURI(value) or value) for key, value in attrs]
1589
1589
        _BaseHTMLProcessor.unknown_starttag(self, tag, attrs)
1590
 
        
 
1590
 
1591
1591
def _resolveRelativeURIs(htmlSource, baseURI, encoding):
1592
1592
    if _debug: sys.stderr.write('entering _resolveRelativeURIs\n')
1593
1593
    p = _RelativeURIResolver(baseURI, encoding)
1620
1620
    def reset(self):
1621
1621
        _BaseHTMLProcessor.reset(self)
1622
1622
        self.unacceptablestack = 0
1623
 
        
 
1623
 
1624
1624
    def unknown_starttag(self, tag, attrs):
1625
1625
        if not tag in self.acceptable_elements:
1626
1626
            if tag in self.unacceptable_elements_with_end_tag:
1629
1629
        attrs = self.normalize_attrs(attrs)
1630
1630
        attrs = [(key, value) for key, value in attrs if key in self.acceptable_attributes]
1631
1631
        _BaseHTMLProcessor.unknown_starttag(self, tag, attrs)
1632
 
        
 
1632
 
1633
1633
    def unknown_endtag(self, tag):
1634
1634
        if not tag in self.acceptable_elements:
1635
1635
            if tag in self.unacceptable_elements_with_end_tag:
1715
1715
    http_error_300 = http_error_302
1716
1716
    http_error_303 = http_error_302
1717
1717
    http_error_307 = http_error_302
1718
 
        
 
1718
 
1719
1719
    def http_error_401(self, req, fp, code, msg, headers):
1720
1720
        # Check if
1721
1721
        # - server requires digest auth, AND
1820
1820
            return opener.open(request)
1821
1821
        finally:
1822
1822
            opener.close() # JohnD
1823
 
    
 
1823
 
1824
1824
    # try to open with native open function (if url_file_stream_or_string is a filename)
1825
1825
    try:
1826
1826
        return open(url_file_stream_or_string)
1834
1834
def registerDateHandler(func):
1835
1835
    '''Register a date handler function (takes string, returns 9-tuple date in GMT)'''
1836
1836
    _date_handlers.insert(0, func)
1837
 
    
 
1837
 
1838
1838
# ISO-8601 date parsing routines written by Fazal Majid.
1839
1839
# The ISO 8601 standard is very convoluted and irregular - a full ISO 8601
1840
1840
# parser is beyond the scope of feedparser and would be a worthwhile addition
1845
1845
# Please note the order in templates is significant because we need a
1846
1846
# greedy match.
1847
1847
_iso8601_tmpl = ['YYYY-?MM-?DD', 'YYYY-MM', 'YYYY-?OOO',
1848
 
                'YY-?MM-?DD', 'YY-?OOO', 'YYYY', 
 
1848
                'YY-?MM-?DD', 'YY-?OOO', 'YYYY',
1849
1849
                '-YY-?MM', '-OOO', '-YY',
1850
1850
                '--MM-?DD', '--MM',
1851
1851
                '---DD',
1944
1944
    # Many implementations have bugs, but we'll pretend they don't.
1945
1945
    return time.localtime(time.mktime(tm))
1946
1946
registerDateHandler(_parse_date_iso8601)
1947
 
    
 
1947
 
1948
1948
# 8-bit date handling routines written by ytrewq1.
1949
1949
_korean_year  = u'\ub144' # b3e2 in euc-kr
1950
1950
_korean_month = u'\uc6d4' # bff9 in euc-kr
2035
2035
   u'\u03a4\u03b5\u03c4': u'Wed', # d4e5f4 in iso-8859-7
2036
2036
   u'\u03a0\u03b5\u03bc': u'Thu', # d0e5ec in iso-8859-7
2037
2037
   u'\u03a0\u03b1\u03c1': u'Fri', # d0e1f1 in iso-8859-7
2038
 
   u'\u03a3\u03b1\u03b2': u'Sat', # d3e1e2 in iso-8859-7   
 
2038
   u'\u03a3\u03b1\u03b2': u'Sat', # d3e1e2 in iso-8859-7
2039
2039
  }
2040
2040
 
2041
2041
_greek_date_format_re = \
2221
2221
# 'ET' is equivalent to 'EST', etc.
2222
2222
_additional_timezones = {'AT': -400, 'ET': -500, 'CT': -600, 'MT': -700, 'PT': -800}
2223
2223
rfc822._timezones.update(_additional_timezones)
2224
 
registerDateHandler(_parse_date_rfc822)    
 
2224
registerDateHandler(_parse_date_rfc822)
2225
2225
 
2226
2226
def _parse_date(dateString):
2227
2227
    '''Parses a variety of date formats into a 9-tuple in GMT'''
2244
2244
 
2245
2245
    http_headers is a dictionary
2246
2246
    xml_data is a raw string (not Unicode)
2247
 
    
 
2247
 
2248
2248
    This is so much trickier than it sounds, it's not even funny.
2249
2249
    According to RFC 3023 ('XML Media Types'), if the HTTP Content-Type
2250
2250
    is application/xml, application/*+xml,
2263
2263
    served with a Content-Type of text/* and no charset parameter
2264
2264
    must be treated as us-ascii.  (We now do this.)  And also that it
2265
2265
    must always be flagged as non-well-formed.  (We now do this too.)
2266
 
    
 
2266
 
2267
2267
    If Content-Type is unspecified (input was local file or non-HTTP source)
2268
2268
    or unrecognized (server just got it totally wrong), then go by the
2269
2269
    encoding given in the XML prefix of the document and default to
2270
2270
    'iso-8859-1' as per the HTTP specification (RFC 2616).
2271
 
    
 
2271
 
2272
2272
    Then, assuming we didn't find a character encoding in the HTTP headers
2273
2273
    (and the HTTP Content-type allowed us to look in the body), we need
2274
2274
    to sniff the first few bytes of the XML data and try to determine
2374
2374
    else:
2375
2375
        true_encoding = xml_encoding or 'utf-8'
2376
2376
    return true_encoding, http_encoding, xml_encoding, sniffed_xml_encoding, acceptable_content_type
2377
 
    
 
2377
 
2378
2378
def _toUTF8(data, encoding):
2379
2379
    '''Changes an XML data stream on the fly to specify a new encoding
2380
2380
 
2445
2445
        version = None
2446
2446
    data = doctype_pattern.sub('', data)
2447
2447
    return version, data
2448
 
    
 
2448
 
2449
2449
def parse(url_file_stream_or_string, etag=None, modified=None, agent=None, referrer=None, handlers=[]):
2450
2450
    '''Parse a feed from a URL, file, stream, or string'''
2451
2451
    result = FeedParserDict()
2517
2517
            bozo_message = 'no Content-type specified'
2518
2518
        result['bozo'] = 1
2519
2519
        result['bozo_exception'] = NonXMLContentType(bozo_message)
2520
 
        
 
2520
 
2521
2521
    result['version'], data = _stripDoctype(data)
2522
2522
 
2523
2523
    baseuri = http_headers.get('content-location', result.get('href'))