1 # Copyright 2010-2023 Kurt McKee <contactme@kurtmckee.org> 2 # Copyright 2002-2008 Mark Pilgrim 3 # All rights reserved. 4 # 5 # This file is a part of feedparser. 6 # 7 # Redistribution and use in source and binary forms, with or without 8 # modification, are permitted provided that the following conditions are met: 9 # 10 # * Redistributions of source code must retain the above copyright notice, 11 # this list of conditions and the following disclaimer. 12 # * Redistributions in binary form must reproduce the above copyright notice, 13 # this list of conditions and the following disclaimer in the documentation 14 # and/or other materials provided with the distribution. 15 # 16 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' 17 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 20 # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 # POSSIBILITY OF SUCH DAMAGE. 27 28 import warnings 29 30 31 class FeedParserDict(dict): 32 keymap = { 33 'channel': 'feed', 34 'items': 'entries', 35 'guid': 'id', 36 'date': 'updated', 37 'date_parsed': 'updated_parsed', 38 'description': ['summary', 'subtitle'], 39 'description_detail': ['summary_detail', 'subtitle_detail'], 40 'url': ['href'], 41 'modified': 'updated', 42 'modified_parsed': 'updated_parsed', 43 'issued': 'published', 44 'issued_parsed': 'published_parsed', 45 'copyright': 'rights', 46 'copyright_detail': 'rights_detail', 47 'tagline': 'subtitle', 48 'tagline_detail': 'subtitle_detail', 49 } 50 51 def __getitem__(self, key): 52 """ 53 :return: A :class:`FeedParserDict`. 54 """ 55 56 if key == 'category': 57 try: 58 return dict.__getitem__(self, 'tags')[0]['term'] 59 except IndexError: 60 raise KeyError("object doesn't have key 'category'") 61 elif key == 'enclosures': 62 norel = lambda link: FeedParserDict([(name, value) for (name, value) in link.items() if name != 'rel']) 63 return [ 64 norel(link) 65 for link in dict.__getitem__(self, 'links') 66 if link['rel'] == 'enclosure' 67 ] 68 elif key == 'license': 69 for link in dict.__getitem__(self, 'links'): 70 if link['rel'] == 'license' and 'href' in link: 71 return link['href'] 72 elif key == 'updated': 73 # Temporarily help developers out by keeping the old 74 # broken behavior that was reported in issue 310. 75 # This fix was proposed in issue 328. 76 if ( 77 not dict.__contains__(self, 'updated') 78 and dict.__contains__(self, 'published') 79 ): 80 warnings.warn( 81 "To avoid breaking existing software while " 82 "fixing issue 310, a temporary mapping has been created " 83 "from `updated` to `published` if `updated` doesn't " 84 "exist. This fallback will be removed in a future version " 85 "of feedparser.", 86 DeprecationWarning, 87 ) 88 return dict.__getitem__(self, 'published') 89 return dict.__getitem__(self, 'updated') 90 elif key == 'updated_parsed': 91 if ( 92 not dict.__contains__(self, 'updated_parsed') 93 and dict.__contains__(self, 'published_parsed') 94 ): 95 warnings.warn( 96 "To avoid breaking existing software while " 97 "fixing issue 310, a temporary mapping has been created " 98 "from `updated_parsed` to `published_parsed` if " 99 "`updated_parsed` doesn't exist. This fallback will be " 100 "removed in a future version of feedparser.", 101 DeprecationWarning, 102 ) 103 return dict.__getitem__(self, 'published_parsed') 104 return dict.__getitem__(self, 'updated_parsed') 105 else: 106 realkey = self.keymap.get(key, key) 107 if isinstance(realkey, list): 108 for k in realkey: 109 if dict.__contains__(self, k): 110 return dict.__getitem__(self, k) 111 elif dict.__contains__(self, realkey): 112 return dict.__getitem__(self, realkey) 113 return dict.__getitem__(self, key) 114 115 def __contains__(self, key): 116 if key in ('updated', 'updated_parsed'): 117 # Temporarily help developers out by keeping the old 118 # broken behavior that was reported in issue 310. 119 # This fix was proposed in issue 328. 120 return dict.__contains__(self, key) 121 try: 122 self.__getitem__(key) 123 except KeyError: 124 return False 125 else: 126 return True 127 128 has_key = __contains__ 129 130 def get(self, key, default=None): 131 """ 132 :return: A :class:`FeedParserDict`. 133 """ 134 135 try: 136 return self.__getitem__(key) 137 except KeyError: 138 return default 139 140 def __setitem__(self, key, value): 141 key = self.keymap.get(key, key) 142 if isinstance(key, list): 143 key = key[0] 144 return dict.__setitem__(self, key, value) 145 146 def setdefault(self, k, default): 147 if k not in self: 148 self[k] = default 149 return default 150 return self[k] 151 152 def __getattr__(self, key): 153 # __getattribute__() is called first; this will be called 154 # only if an attribute was not already found 155 try: 156 return self.__getitem__(key) 157 except KeyError: 158 raise AttributeError("object has no attribute '%s'" % key) 159 160 def __hash__(self): 161 # This is incorrect behavior -- dictionaries shouldn't be hashable. 162 # Note to self: remove this behavior in the future. 163 return id(self) 164