1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/ContentTypeSupport.py Sat Jan 19 16:08:29 2013 +0100
1.3 @@ -0,0 +1,297 @@
1.4 +# -*- coding: iso-8859-1 -*-
1.5 +"""
1.6 + MoinMoin - ContentTypeSupport library
1.7 +
1.8 + @copyright: 2012, 2013 by Paul Boddie <paul@boddie.org.uk>
1.9 + @license: GNU GPL (v2 or later), see COPYING.txt for details.
1.10 +"""
1.11 +
1.12 +import re
1.13 +
1.14 +# Content type parsing.
1.15 +
1.16 +encoding_regexp_str = ur'(?P<content_type>[^\s;]*)(?:;\s*charset=(?P<encoding>[-A-Za-z0-9]+))?'
1.17 +encoding_regexp = re.compile(encoding_regexp_str)
1.18 +
1.19 +# Accept header parsing.
1.20 +
1.21 +accept_regexp_str = ur';\s*q='
1.22 +accept_regexp = re.compile(accept_regexp_str)
1.23 +
1.24 +# Content/media type and preferences support.
1.25 +
1.26 +class MediaRange:
1.27 +
1.28 + "A content/media type value which supports whole categories of data."
1.29 +
1.30 + def __init__(self, media_range, accept_parameters=None):
1.31 + self.media_range = media_range
1.32 + self.accept_parameters = accept_parameters or {}
1.33 +
1.34 + parts = media_range.split(";")
1.35 + self.media_type = parts[0]
1.36 + self.parameters = getMappingFromParameterStrings(parts[1:])
1.37 +
1.38 + # The media type is divided into category and subcategory.
1.39 +
1.40 + parts = self.media_type.split("/")
1.41 + self.category = parts[0]
1.42 + self.subcategory = "/".join(parts[1:])
1.43 +
1.44 + def get_parts(self):
1.45 +
1.46 + "Return the category, subcategory parts."
1.47 +
1.48 + return self.category, self.subcategory
1.49 +
1.50 + def get_specificity(self):
1.51 +
1.52 + """
1.53 + Return the specificity of the media type in terms of the scope of the
1.54 + category and subcategory, and also in terms of any qualifying
1.55 + parameters.
1.56 + """
1.57 +
1.58 + if "*" in self.get_parts():
1.59 + return -list(self.get_parts()).count("*")
1.60 + else:
1.61 + return len(self.parameters)
1.62 +
1.63 + def permits(self, other):
1.64 +
1.65 + """
1.66 + Return whether this media type permits the use of the 'other' media type
1.67 + if suggested as suitable content.
1.68 + """
1.69 +
1.70 + if not isinstance(other, MediaRange):
1.71 + other = MediaRange(other)
1.72 +
1.73 + category = categoryPermits(self.category, other.category)
1.74 + subcategory = categoryPermits(self.subcategory, other.subcategory)
1.75 +
1.76 + if category and subcategory:
1.77 + if "*" not in (category, subcategory):
1.78 + return not self.parameters or self.parameters == other.parameters
1.79 + else:
1.80 + return True
1.81 + else:
1.82 + return False
1.83 +
1.84 + def __eq__(self, other):
1.85 +
1.86 + """
1.87 + Return whether this media type is effectively the same as the 'other'
1.88 + media type.
1.89 + """
1.90 +
1.91 + if not isinstance(other, MediaRange):
1.92 + other = MediaRange(other)
1.93 +
1.94 + category = categoryMatches(self.category, other.category)
1.95 + subcategory = categoryMatches(self.subcategory, other.subcategory)
1.96 +
1.97 + if category and subcategory:
1.98 + if "*" not in (category, subcategory):
1.99 + return self.parameters == other.parameters or \
1.100 + not self.parameters or not other.parameters
1.101 + else:
1.102 + return True
1.103 + else:
1.104 + return False
1.105 +
1.106 + def __ne__(self, other):
1.107 + return not self.__eq__(other)
1.108 +
1.109 + def __hash__(self):
1.110 + return hash(self.media_range)
1.111 +
1.112 + def __repr__(self):
1.113 + return "MediaRange(%r)" % self.media_range
1.114 +
1.115 +def categoryMatches(this, that):
1.116 +
1.117 + """
1.118 + Return the basis of a match between 'this' and 'that' or False if the given
1.119 + categories do not match.
1.120 + """
1.121 +
1.122 + return (this == "*" or this == that) and this or \
1.123 + that == "*" and that or False
1.124 +
1.125 +def categoryPermits(this, that):
1.126 +
1.127 + """
1.128 + Return whether 'this' category permits 'that' category. Where 'this' is a
1.129 + wildcard ("*"), 'that' should always match. A value of False is returned if
1.130 + the categories do not otherwise match.
1.131 + """
1.132 +
1.133 + return (this == "*" or this == that) and this or False
1.134 +
1.135 +def getMappingFromParameterStrings(l):
1.136 +
1.137 + """
1.138 + Return a mapping representing the list of "name=value" strings given by 'l'.
1.139 + """
1.140 +
1.141 + parameters = {}
1.142 +
1.143 + for parameter in l:
1.144 + parts = parameter.split("=")
1.145 + name = parts[0].strip()
1.146 + value = "=".join(parts[1:]).strip()
1.147 + parameters[name] = value
1.148 +
1.149 + return parameters
1.150 +
1.151 +def getContentPreferences(accept):
1.152 +
1.153 + """
1.154 + Return a mapping from media types to parameters for content/media types
1.155 + extracted from the given 'accept' header value. The mapping is returned in
1.156 + the form of a list of (media type, parameters) tuples.
1.157 +
1.158 + See: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
1.159 + """
1.160 +
1.161 + preferences = []
1.162 +
1.163 + for field in accept.split(","):
1.164 +
1.165 + # The media type with parameters (defined by the "media-range") is
1.166 + # separated from any other parameters (defined as "accept-extension"
1.167 + # parameters) by a quality parameter.
1.168 +
1.169 + fparts = accept_regexp.split(field)
1.170 +
1.171 + # The first part is always the media type.
1.172 +
1.173 + media_type = fparts[0].strip()
1.174 +
1.175 + # Any other parts can be interpreted as extension parameters.
1.176 +
1.177 + if len(fparts) > 1:
1.178 + fparts = ("q=" + ";q=".join(fparts[1:])).split(";")
1.179 + else:
1.180 + fparts = []
1.181 +
1.182 + # Each field in the preferences can incorporate parameters separated by
1.183 + # semicolon characters.
1.184 +
1.185 + parameters = getMappingFromParameterStrings(fparts)
1.186 + media_range = MediaRange(media_type, parameters)
1.187 + preferences.append(media_range)
1.188 +
1.189 + return ContentPreferences(preferences)
1.190 +
1.191 +class ContentPreferences:
1.192 +
1.193 + "A wrapper around content preference information."
1.194 +
1.195 + def __init__(self, preferences):
1.196 + self.preferences = preferences
1.197 +
1.198 + def __iter__(self):
1.199 + return iter(self.preferences)
1.200 +
1.201 + def get_ordered(self, by_quality=0):
1.202 +
1.203 + """
1.204 + Return a list of content/media types in descending order of preference.
1.205 + If 'by_quality' is set to a true value, the "q" value will be used as
1.206 + the primary measure of preference; otherwise, only the specificity will
1.207 + be considered.
1.208 + """
1.209 +
1.210 + ordered = {}
1.211 +
1.212 + for media_range in self.preferences:
1.213 + specificity = media_range.get_specificity()
1.214 +
1.215 + if by_quality:
1.216 + q = float(media_range.accept_parameters.get("q", "1"))
1.217 + key = q, specificity
1.218 + else:
1.219 + key = specificity
1.220 +
1.221 + if not ordered.has_key(key):
1.222 + ordered[key] = []
1.223 +
1.224 + ordered[key].append(media_range)
1.225 +
1.226 + # Return the preferences in descending order of quality and specificity.
1.227 +
1.228 + keys = ordered.keys()
1.229 + keys.sort(reverse=True)
1.230 + return [ordered[key] for key in keys]
1.231 +
1.232 + def get_acceptable_types(self, available):
1.233 +
1.234 + """
1.235 + Return content/media types from those in the 'available' list supported
1.236 + by the known preferences grouped by preference level in descending order
1.237 + of preference.
1.238 + """
1.239 +
1.240 + matches = {}
1.241 + available = set(available[:])
1.242 +
1.243 + for level in self.get_ordered():
1.244 + for media_range in level:
1.245 +
1.246 + # Attempt to match available types.
1.247 +
1.248 + found = set()
1.249 + for available_type in available:
1.250 + if media_range.permits(available_type):
1.251 + q = float(media_range.accept_parameters.get("q", "1"))
1.252 + if not matches.has_key(q):
1.253 + matches[q] = []
1.254 + matches[q].append(available_type)
1.255 + found.add(available_type)
1.256 +
1.257 + # Stop looking for matches for matched available types.
1.258 +
1.259 + if found:
1.260 + available.difference_update(found)
1.261 +
1.262 + # Sort the matches in descending order of quality.
1.263 +
1.264 + all_q = matches.keys()
1.265 +
1.266 + if all_q:
1.267 + all_q.sort(reverse=True)
1.268 + return [matches[q] for q in all_q]
1.269 + else:
1.270 + return []
1.271 +
1.272 + def get_preferred_types(self, available):
1.273 +
1.274 + """
1.275 + Return the preferred content/media types from those in the 'available'
1.276 + list, given the known preferences.
1.277 + """
1.278 +
1.279 + preferred = self.get_acceptable_types(available)
1.280 + if preferred:
1.281 + return preferred[0]
1.282 + else:
1.283 + return []
1.284 +
1.285 +# Content type parsing.
1.286 +
1.287 +def getContentTypeAndEncoding(content_type):
1.288 +
1.289 + """
1.290 + Return a tuple with the content/media type and encoding, extracted from the
1.291 + given 'content_type' header value.
1.292 + """
1.293 +
1.294 + m = encoding_regexp.search(content_type)
1.295 + if m:
1.296 + return m.group("content_type"), m.group("encoding")
1.297 + else:
1.298 + return None, None
1.299 +
1.300 +# vim: tabstop=4 expandtab shiftwidth=4
2.1 --- a/MoinSupport.py Fri Jan 04 22:57:15 2013 +0100
2.2 +++ b/MoinSupport.py Sat Jan 19 16:08:29 2013 +0100
2.3 @@ -2,7 +2,7 @@
2.4 """
2.5 MoinMoin - MoinSupport library (derived from EventAggregatorSupport)
2.6
2.7 - @copyright: 2008, 2009, 2010, 2011, 2012 by Paul Boddie <paul@boddie.org.uk>
2.8 + @copyright: 2008, 2009, 2010, 2011, 2012, 2013 by Paul Boddie <paul@boddie.org.uk>
2.9 @copyright: 2000-2004 Juergen Hermann <jh@web.de>,
2.10 2005-2008 MoinMoin:ThomasWaldmann.
2.11 @license: GNU GPL (v2 or later), see COPYING.txt for details.
2.12 @@ -25,16 +25,6 @@
2.13
2.14 __version__ = "0.2"
2.15
2.16 -# Content type parsing.
2.17 -
2.18 -encoding_regexp_str = ur'(?P<content_type>[^\s;]*)(?:;\s*charset=(?P<encoding>[-A-Za-z0-9]+))?'
2.19 -encoding_regexp = re.compile(encoding_regexp_str)
2.20 -
2.21 -# Accept header parsing.
2.22 -
2.23 -accept_regexp_str = ur';\s*q='
2.24 -accept_regexp = re.compile(accept_regexp_str)
2.25 -
2.26 # Extraction of shared fragments.
2.27
2.28 marker_regexp_str = r"([{]{3,}|[}]{3,})"
2.29 @@ -625,282 +615,6 @@
2.30
2.31 send_headers(headers)
2.32
2.33 -# Content/media type and preferences support.
2.34 -
2.35 -class MediaRange:
2.36 -
2.37 - "A content/media type value which supports whole categories of data."
2.38 -
2.39 - def __init__(self, media_range, accept_parameters=None):
2.40 - self.media_range = media_range
2.41 - self.accept_parameters = accept_parameters or {}
2.42 -
2.43 - parts = media_range.split(";")
2.44 - self.media_type = parts[0]
2.45 - self.parameters = getMappingFromParameterStrings(parts[1:])
2.46 -
2.47 - # The media type is divided into category and subcategory.
2.48 -
2.49 - parts = self.media_type.split("/")
2.50 - self.category = parts[0]
2.51 - self.subcategory = "/".join(parts[1:])
2.52 -
2.53 - def get_parts(self):
2.54 -
2.55 - "Return the category, subcategory parts."
2.56 -
2.57 - return self.category, self.subcategory
2.58 -
2.59 - def get_specificity(self):
2.60 -
2.61 - """
2.62 - Return the specificity of the media type in terms of the scope of the
2.63 - category and subcategory, and also in terms of any qualifying
2.64 - parameters.
2.65 - """
2.66 -
2.67 - if "*" in self.get_parts():
2.68 - return -list(self.get_parts()).count("*")
2.69 - else:
2.70 - return len(self.parameters)
2.71 -
2.72 - def permits(self, other):
2.73 -
2.74 - """
2.75 - Return whether this media type permits the use of the 'other' media type
2.76 - if suggested as suitable content.
2.77 - """
2.78 -
2.79 - if not isinstance(other, MediaRange):
2.80 - other = MediaRange(other)
2.81 -
2.82 - category = categoryPermits(self.category, other.category)
2.83 - subcategory = categoryPermits(self.subcategory, other.subcategory)
2.84 -
2.85 - if category and subcategory:
2.86 - if "*" not in (category, subcategory):
2.87 - return not self.parameters or self.parameters == other.parameters
2.88 - else:
2.89 - return True
2.90 - else:
2.91 - return False
2.92 -
2.93 - def __eq__(self, other):
2.94 -
2.95 - """
2.96 - Return whether this media type is effectively the same as the 'other'
2.97 - media type.
2.98 - """
2.99 -
2.100 - if not isinstance(other, MediaRange):
2.101 - other = MediaRange(other)
2.102 -
2.103 - category = categoryMatches(self.category, other.category)
2.104 - subcategory = categoryMatches(self.subcategory, other.subcategory)
2.105 -
2.106 - if category and subcategory:
2.107 - if "*" not in (category, subcategory):
2.108 - return self.parameters == other.parameters or \
2.109 - not self.parameters or not other.parameters
2.110 - else:
2.111 - return True
2.112 - else:
2.113 - return False
2.114 -
2.115 - def __ne__(self, other):
2.116 - return not self.__eq__(other)
2.117 -
2.118 - def __hash__(self):
2.119 - return hash(self.media_range)
2.120 -
2.121 - def __repr__(self):
2.122 - return "MediaRange(%r)" % self.media_range
2.123 -
2.124 -def categoryMatches(this, that):
2.125 -
2.126 - """
2.127 - Return the basis of a match between 'this' and 'that' or False if the given
2.128 - categories do not match.
2.129 - """
2.130 -
2.131 - return (this == "*" or this == that) and this or \
2.132 - that == "*" and that or False
2.133 -
2.134 -def categoryPermits(this, that):
2.135 -
2.136 - """
2.137 - Return whether 'this' category permits 'that' category. Where 'this' is a
2.138 - wildcard ("*"), 'that' should always match. A value of False is returned if
2.139 - the categories do not otherwise match.
2.140 - """
2.141 -
2.142 - return (this == "*" or this == that) and this or False
2.143 -
2.144 -def getMappingFromParameterStrings(l):
2.145 -
2.146 - """
2.147 - Return a mapping representing the list of "name=value" strings given by 'l'.
2.148 - """
2.149 -
2.150 - parameters = {}
2.151 -
2.152 - for parameter in l:
2.153 - parts = parameter.split("=")
2.154 - name = parts[0].strip()
2.155 - value = "=".join(parts[1:]).strip()
2.156 - parameters[name] = value
2.157 -
2.158 - return parameters
2.159 -
2.160 -def getContentPreferences(accept):
2.161 -
2.162 - """
2.163 - Return a mapping from media types to parameters for content/media types
2.164 - extracted from the given 'accept' header value. The mapping is returned in
2.165 - the form of a list of (media type, parameters) tuples.
2.166 -
2.167 - See: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
2.168 - """
2.169 -
2.170 - preferences = []
2.171 -
2.172 - for field in accept.split(","):
2.173 -
2.174 - # The media type with parameters (defined by the "media-range") is
2.175 - # separated from any other parameters (defined as "accept-extension"
2.176 - # parameters) by a quality parameter.
2.177 -
2.178 - fparts = accept_regexp.split(field)
2.179 -
2.180 - # The first part is always the media type.
2.181 -
2.182 - media_type = fparts[0].strip()
2.183 -
2.184 - # Any other parts can be interpreted as extension parameters.
2.185 -
2.186 - if len(fparts) > 1:
2.187 - fparts = ("q=" + ";q=".join(fparts[1:])).split(";")
2.188 - else:
2.189 - fparts = []
2.190 -
2.191 - # Each field in the preferences can incorporate parameters separated by
2.192 - # semicolon characters.
2.193 -
2.194 - parameters = getMappingFromParameterStrings(fparts)
2.195 - media_range = MediaRange(media_type, parameters)
2.196 - preferences.append(media_range)
2.197 -
2.198 - return ContentPreferences(preferences)
2.199 -
2.200 -class ContentPreferences:
2.201 -
2.202 - "A wrapper around content preference information."
2.203 -
2.204 - def __init__(self, preferences):
2.205 - self.preferences = preferences
2.206 -
2.207 - def __iter__(self):
2.208 - return iter(self.preferences)
2.209 -
2.210 - def get_ordered(self, by_quality=0):
2.211 -
2.212 - """
2.213 - Return a list of content/media types in descending order of preference.
2.214 - If 'by_quality' is set to a true value, the "q" value will be used as
2.215 - the primary measure of preference; otherwise, only the specificity will
2.216 - be considered.
2.217 - """
2.218 -
2.219 - ordered = {}
2.220 -
2.221 - for media_range in self.preferences:
2.222 - specificity = media_range.get_specificity()
2.223 -
2.224 - if by_quality:
2.225 - q = float(media_range.accept_parameters.get("q", "1"))
2.226 - key = q, specificity
2.227 - else:
2.228 - key = specificity
2.229 -
2.230 - if not ordered.has_key(key):
2.231 - ordered[key] = []
2.232 -
2.233 - ordered[key].append(media_range)
2.234 -
2.235 - # Return the preferences in descending order of quality and specificity.
2.236 -
2.237 - keys = ordered.keys()
2.238 - keys.sort(reverse=True)
2.239 - return [ordered[key] for key in keys]
2.240 -
2.241 - def get_acceptable_types(self, available):
2.242 -
2.243 - """
2.244 - Return content/media types from those in the 'available' list supported
2.245 - by the known preferences grouped by preference level in descending order
2.246 - of preference.
2.247 - """
2.248 -
2.249 - matches = {}
2.250 - available = set(available[:])
2.251 -
2.252 - for level in self.get_ordered():
2.253 - for media_range in level:
2.254 -
2.255 - # Attempt to match available types.
2.256 -
2.257 - found = set()
2.258 - for available_type in available:
2.259 - if media_range.permits(available_type):
2.260 - q = float(media_range.accept_parameters.get("q", "1"))
2.261 - if not matches.has_key(q):
2.262 - matches[q] = []
2.263 - matches[q].append(available_type)
2.264 - found.add(available_type)
2.265 -
2.266 - # Stop looking for matches for matched available types.
2.267 -
2.268 - if found:
2.269 - available.difference_update(found)
2.270 -
2.271 - # Sort the matches in descending order of quality.
2.272 -
2.273 - all_q = matches.keys()
2.274 -
2.275 - if all_q:
2.276 - all_q.sort(reverse=True)
2.277 - return [matches[q] for q in all_q]
2.278 - else:
2.279 - return []
2.280 -
2.281 - def get_preferred_types(self, available):
2.282 -
2.283 - """
2.284 - Return the preferred content/media types from those in the 'available'
2.285 - list, given the known preferences.
2.286 - """
2.287 -
2.288 - preferred = self.get_acceptable_types(available)
2.289 - if preferred:
2.290 - return preferred[0]
2.291 - else:
2.292 - return []
2.293 -
2.294 -# Content type parsing.
2.295 -
2.296 -def getContentTypeAndEncoding(content_type):
2.297 -
2.298 - """
2.299 - Return a tuple with the content/media type and encoding, extracted from the
2.300 - given 'content_type' header value.
2.301 - """
2.302 -
2.303 - m = encoding_regexp.search(content_type)
2.304 - if m:
2.305 - return m.group("content_type"), m.group("encoding")
2.306 - else:
2.307 - return None, None
2.308 -
2.309 # Page access functions.
2.310
2.311 def getPageURL(page):
3.1 --- a/README.txt Fri Jan 04 22:57:15 2013 +0100
3.2 +++ b/README.txt Sat Jan 19 16:08:29 2013 +0100
3.3 @@ -84,6 +84,8 @@
3.4 * Added form field dictionary manipulation in the 1.9 compatibility class.
3.5 * Added parameterisation of the formatText function so that paragraphs may
3.6 be generated for formatted text.
3.7 + * Added various content/media type functions from EventAggregator and other
3.8 + projects to ContentTypeSupport.
3.9
3.10 Release Procedures
3.11 ------------------