Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

"""MIME-Type Parser 

 

This module provides basic functions for handling mime-types. It can handle 

matching mime-types against a list of media-ranges. See section 14.1 of  

the HTTP specification [RFC 2616] for a complete explaination. 

 

   http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1 

 

Contents: 

    - parse_mime_type():   Parses a mime-type into it's component parts. 

    - parse_media_range(): Media-ranges are mime-types with wild-cards and a  

    'q' quality parameter. 

    - quality():           Determines the quality ('q') of a mime-type when  

    compared against a list of media-ranges. 

    - quality_parsed():    Just like quality() except the second parameter must 

     be pre-parsed. 

    - best_match():        Choose the mime-type with the highest quality ('q') 

     from a list of candidates.  

""" 

 

__version__ = "0.1.1" 

__author__ = 'Joe Gregorio' 

__email__ = "joe@bitworking.org" 

__credits__ = "" 

 

def parse_mime_type(mime_type): 

    """Carves up a mime_type and returns a tuple of the 

       (type, subtype, params) where 'params' is a dictionary 

       of all the parameters for the media range. 

       For example, the media range 'application/xhtml;q=0.5' would 

       get parsed into: 

 

       ('application', 'xhtml', {'q', '0.5'}) 

       """ 

    parts = mime_type.split(";") 

    params = dict([tuple([s.strip() for s in param.split("=")])\ 

            for param in parts[1:]]) 

    (type, subtype) = parts[0].split("/") 

    return (type.strip(), subtype.strip(), params) 

 

def parse_media_range(range): 

    """Carves up a media range and returns a tuple of the 

       (type, subtype, params) where 'params' is a dictionary 

       of all the parameters for the media range. 

       For example, the media range 'application/*;q=0.5' would 

       get parsed into: 

 

       ('application', '*', {'q', '0.5'}) 

 

       In addition this function also guarantees that there  

       is a value for 'q' in the params dictionary, filling it 

       in with a proper default if necessary. 

       """ 

    (type, subtype, params) = parse_mime_type(range) 

    if not 'q' in params or not params['q'] or \ 

            not float(params['q']) or float(params['q']) > 1\ 

            or float(params['q']) < 0: 

        params['q'] = '1' 

    return (type, subtype, params) 

 

def quality_parsed(mime_type, parsed_ranges): 

    """Find the best match for a given mime_type against  

       a list of media_ranges that have already been  

       parsed by parse_media_range(). Returns the  

       'q' quality parameter of the best match, 0 if no 

       match was found. This function bahaves the same as quality() 

       except that 'parsed_ranges' must be a list of 

       parsed media ranges. """ 

    best_fitness = -1 

    best_match = "" 

    best_fit_q = 0 

    (target_type, target_subtype, target_params) =\ 

            parse_media_range(mime_type) 

    for (type, subtype, params) in parsed_ranges: 

        param_matches = reduce(lambda x, y: x + y, [1 for (key, value) in \ 

                target_params.iteritems() if key != 'q' and \ 

                key in params and value == params[key]], 0) 

        if (type == target_type or type == '*' or target_type == '*') and \ 

                (subtype == target_subtype or subtype == '*' or target_subtype == '*'): 

            fitness = (type == target_type) and 100 or 0 

            fitness += (subtype == target_subtype) and 10 or 0 

            fitness += param_matches 

            if fitness > best_fitness: 

                best_fitness = fitness 

                best_fit_q = params['q'] 

 

    return float(best_fit_q) 

 

def quality(mime_type, ranges): 

    """Returns the quality 'q' of a mime_type when compared 

    against the media-ranges in ranges. For example: 

 

    >>> quality('text/html','text/*;q=0.3, text/html;q=0.7, text/html;level=1, ' 

    'text/html;level=2;q=0.4, */*;q=0.5') 

    0.7 

 

    """ 

    parsed_ranges = [parse_media_range(r) for r in ranges.split(",")] 

    return quality_parsed(mime_type, parsed_ranges) 

 

def best_match(supported, header): 

    """Takes a list of supported mime-types and finds the best 

    match for all the media-ranges listed in header. The value of 

    header must be a string that conforms to the format of the  

    HTTP Accept: header. The value of 'supported' is a list of 

    mime-types. 

 

    >>> best_match(['application/xbel+xml', 'text/xml'], 'text/*;q=0.5,*/*; q=0.1') 

    'text/xml' 

    """ 

    parsed_header = [parse_media_range(r) for r in header.split(",")] 

    weighted_matches = [(quality_parsed(mime_type, parsed_header), mime_type)\ 

            for mime_type in supported] 

    weighted_matches.sort() 

    return weighted_matches[-1][0] and weighted_matches[-1][1] or '' 

 

if __name__ == "__main__": 

    import unittest 

 

    class TestMimeParsing(unittest.TestCase): 

 

        def test_parse_media_range(self): 

            self.assert_(('application', 'xml', {'q': '1'}) == parse_media_range('application/xml;q=1')) 

            self.assertEqual(('application', 'xml', {'q': '1'}), parse_media_range('application/xml')) 

            self.assertEqual(('application', 'xml', {'q': '1'}), parse_media_range('application/xml;q=')) 

            self.assertEqual(('application', 'xml', {'q': '1'}), parse_media_range('application/xml ; q=')) 

            self.assertEqual(('application', 'xml', {'q': '1', 'b': 'other'}), parse_media_range('application/xml ; q=1;b=other')) 

            self.assertEqual(('application', 'xml', {'q': '1', 'b': 'other'}), parse_media_range('application/xml ; q=2;b=other')) 

 

        def test_rfc_2616_example(self): 

            accept = "text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5" 

            self.assertEqual(1, quality("text/html;level=1", accept)) 

            self.assertEqual(0.7, quality("text/html", accept)) 

            self.assertEqual(0.3, quality("text/plain", accept)) 

            self.assertEqual(0.5, quality("image/jpeg", accept)) 

            self.assertEqual(0.4, quality("text/html;level=2", accept)) 

            self.assertEqual(0.7, quality("text/html;level=3", accept)) 

 

        def test_best_match(self): 

            mime_types_supported = ['application/xbel+xml', 'application/xml'] 

            # direct match 

            self.assertEqual(best_match(mime_types_supported, 'application/xbel+xml'), 'application/xbel+xml') 

            # direct match with a q parameter 

            self.assertEqual(best_match(mime_types_supported, 'application/xbel+xml; q=1'), 'application/xbel+xml') 

            # direct match of our second choice with a q parameter 

            self.assertEqual(best_match(mime_types_supported, 'application/xml; q=1'), 'application/xml') 

            # match using a subtype wildcard 

            self.assertEqual(best_match(mime_types_supported, 'application/*; q=1'), 'application/xml') 

            # match using a type wildcard 

            self.assertEqual(best_match(mime_types_supported, '*/*'), 'application/xml') 

 

            mime_types_supported = ['application/xbel+xml', 'text/xml'] 

            # match using a type versus a lower weighted subtype 

            self.assertEqual(best_match(mime_types_supported, 'text/*;q=0.5,*/*; q=0.1'), 'text/xml') 

            # fail to match anything 

            self.assertEqual(best_match(mime_types_supported, 'text/html,application/atom+xml; q=0.9'), '') 

 

        def test_support_wildcards(self): 

            mime_types_supported = ['image/*', 'application/xml'] 

            # match using a type wildcard 

            self.assertEqual(best_match(mime_types_supported, 'image/png'), 'image/*') 

            # match using a wildcard for both requested and supported  

            self.assertEqual(best_match(mime_types_supported, 'image/*'), 'image/*') 

 

    unittest.main()