1.1 --- a/MoinSupport.py Fri Jan 04 22:57:15 2013 +0100
1.2 +++ b/MoinSupport.py Sat Jan 19 16:08:29 2013 +0100
1.3 @@ -2,7 +2,7 @@
1.4 """
1.5 MoinMoin - MoinSupport library (derived from EventAggregatorSupport)
1.6
1.7 - @copyright: 2008, 2009, 2010, 2011, 2012 by Paul Boddie <paul@boddie.org.uk>
1.8 + @copyright: 2008, 2009, 2010, 2011, 2012, 2013 by Paul Boddie <paul@boddie.org.uk>
1.9 @copyright: 2000-2004 Juergen Hermann <jh@web.de>,
1.10 2005-2008 MoinMoin:ThomasWaldmann.
1.11 @license: GNU GPL (v2 or later), see COPYING.txt for details.
1.12 @@ -25,16 +25,6 @@
1.13
1.14 __version__ = "0.2"
1.15
1.16 -# Content type parsing.
1.17 -
1.18 -encoding_regexp_str = ur'(?P<content_type>[^\s;]*)(?:;\s*charset=(?P<encoding>[-A-Za-z0-9]+))?'
1.19 -encoding_regexp = re.compile(encoding_regexp_str)
1.20 -
1.21 -# Accept header parsing.
1.22 -
1.23 -accept_regexp_str = ur';\s*q='
1.24 -accept_regexp = re.compile(accept_regexp_str)
1.25 -
1.26 # Extraction of shared fragments.
1.27
1.28 marker_regexp_str = r"([{]{3,}|[}]{3,})"
1.29 @@ -625,282 +615,6 @@
1.30
1.31 send_headers(headers)
1.32
1.33 -# Content/media type and preferences support.
1.34 -
1.35 -class MediaRange:
1.36 -
1.37 - "A content/media type value which supports whole categories of data."
1.38 -
1.39 - def __init__(self, media_range, accept_parameters=None):
1.40 - self.media_range = media_range
1.41 - self.accept_parameters = accept_parameters or {}
1.42 -
1.43 - parts = media_range.split(";")
1.44 - self.media_type = parts[0]
1.45 - self.parameters = getMappingFromParameterStrings(parts[1:])
1.46 -
1.47 - # The media type is divided into category and subcategory.
1.48 -
1.49 - parts = self.media_type.split("/")
1.50 - self.category = parts[0]
1.51 - self.subcategory = "/".join(parts[1:])
1.52 -
1.53 - def get_parts(self):
1.54 -
1.55 - "Return the category, subcategory parts."
1.56 -
1.57 - return self.category, self.subcategory
1.58 -
1.59 - def get_specificity(self):
1.60 -
1.61 - """
1.62 - Return the specificity of the media type in terms of the scope of the
1.63 - category and subcategory, and also in terms of any qualifying
1.64 - parameters.
1.65 - """
1.66 -
1.67 - if "*" in self.get_parts():
1.68 - return -list(self.get_parts()).count("*")
1.69 - else:
1.70 - return len(self.parameters)
1.71 -
1.72 - def permits(self, other):
1.73 -
1.74 - """
1.75 - Return whether this media type permits the use of the 'other' media type
1.76 - if suggested as suitable content.
1.77 - """
1.78 -
1.79 - if not isinstance(other, MediaRange):
1.80 - other = MediaRange(other)
1.81 -
1.82 - category = categoryPermits(self.category, other.category)
1.83 - subcategory = categoryPermits(self.subcategory, other.subcategory)
1.84 -
1.85 - if category and subcategory:
1.86 - if "*" not in (category, subcategory):
1.87 - return not self.parameters or self.parameters == other.parameters
1.88 - else:
1.89 - return True
1.90 - else:
1.91 - return False
1.92 -
1.93 - def __eq__(self, other):
1.94 -
1.95 - """
1.96 - Return whether this media type is effectively the same as the 'other'
1.97 - media type.
1.98 - """
1.99 -
1.100 - if not isinstance(other, MediaRange):
1.101 - other = MediaRange(other)
1.102 -
1.103 - category = categoryMatches(self.category, other.category)
1.104 - subcategory = categoryMatches(self.subcategory, other.subcategory)
1.105 -
1.106 - if category and subcategory:
1.107 - if "*" not in (category, subcategory):
1.108 - return self.parameters == other.parameters or \
1.109 - not self.parameters or not other.parameters
1.110 - else:
1.111 - return True
1.112 - else:
1.113 - return False
1.114 -
1.115 - def __ne__(self, other):
1.116 - return not self.__eq__(other)
1.117 -
1.118 - def __hash__(self):
1.119 - return hash(self.media_range)
1.120 -
1.121 - def __repr__(self):
1.122 - return "MediaRange(%r)" % self.media_range
1.123 -
1.124 -def categoryMatches(this, that):
1.125 -
1.126 - """
1.127 - Return the basis of a match between 'this' and 'that' or False if the given
1.128 - categories do not match.
1.129 - """
1.130 -
1.131 - return (this == "*" or this == that) and this or \
1.132 - that == "*" and that or False
1.133 -
1.134 -def categoryPermits(this, that):
1.135 -
1.136 - """
1.137 - Return whether 'this' category permits 'that' category. Where 'this' is a
1.138 - wildcard ("*"), 'that' should always match. A value of False is returned if
1.139 - the categories do not otherwise match.
1.140 - """
1.141 -
1.142 - return (this == "*" or this == that) and this or False
1.143 -
1.144 -def getMappingFromParameterStrings(l):
1.145 -
1.146 - """
1.147 - Return a mapping representing the list of "name=value" strings given by 'l'.
1.148 - """
1.149 -
1.150 - parameters = {}
1.151 -
1.152 - for parameter in l:
1.153 - parts = parameter.split("=")
1.154 - name = parts[0].strip()
1.155 - value = "=".join(parts[1:]).strip()
1.156 - parameters[name] = value
1.157 -
1.158 - return parameters
1.159 -
1.160 -def getContentPreferences(accept):
1.161 -
1.162 - """
1.163 - Return a mapping from media types to parameters for content/media types
1.164 - extracted from the given 'accept' header value. The mapping is returned in
1.165 - the form of a list of (media type, parameters) tuples.
1.166 -
1.167 - See: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
1.168 - """
1.169 -
1.170 - preferences = []
1.171 -
1.172 - for field in accept.split(","):
1.173 -
1.174 - # The media type with parameters (defined by the "media-range") is
1.175 - # separated from any other parameters (defined as "accept-extension"
1.176 - # parameters) by a quality parameter.
1.177 -
1.178 - fparts = accept_regexp.split(field)
1.179 -
1.180 - # The first part is always the media type.
1.181 -
1.182 - media_type = fparts[0].strip()
1.183 -
1.184 - # Any other parts can be interpreted as extension parameters.
1.185 -
1.186 - if len(fparts) > 1:
1.187 - fparts = ("q=" + ";q=".join(fparts[1:])).split(";")
1.188 - else:
1.189 - fparts = []
1.190 -
1.191 - # Each field in the preferences can incorporate parameters separated by
1.192 - # semicolon characters.
1.193 -
1.194 - parameters = getMappingFromParameterStrings(fparts)
1.195 - media_range = MediaRange(media_type, parameters)
1.196 - preferences.append(media_range)
1.197 -
1.198 - return ContentPreferences(preferences)
1.199 -
1.200 -class ContentPreferences:
1.201 -
1.202 - "A wrapper around content preference information."
1.203 -
1.204 - def __init__(self, preferences):
1.205 - self.preferences = preferences
1.206 -
1.207 - def __iter__(self):
1.208 - return iter(self.preferences)
1.209 -
1.210 - def get_ordered(self, by_quality=0):
1.211 -
1.212 - """
1.213 - Return a list of content/media types in descending order of preference.
1.214 - If 'by_quality' is set to a true value, the "q" value will be used as
1.215 - the primary measure of preference; otherwise, only the specificity will
1.216 - be considered.
1.217 - """
1.218 -
1.219 - ordered = {}
1.220 -
1.221 - for media_range in self.preferences:
1.222 - specificity = media_range.get_specificity()
1.223 -
1.224 - if by_quality:
1.225 - q = float(media_range.accept_parameters.get("q", "1"))
1.226 - key = q, specificity
1.227 - else:
1.228 - key = specificity
1.229 -
1.230 - if not ordered.has_key(key):
1.231 - ordered[key] = []
1.232 -
1.233 - ordered[key].append(media_range)
1.234 -
1.235 - # Return the preferences in descending order of quality and specificity.
1.236 -
1.237 - keys = ordered.keys()
1.238 - keys.sort(reverse=True)
1.239 - return [ordered[key] for key in keys]
1.240 -
1.241 - def get_acceptable_types(self, available):
1.242 -
1.243 - """
1.244 - Return content/media types from those in the 'available' list supported
1.245 - by the known preferences grouped by preference level in descending order
1.246 - of preference.
1.247 - """
1.248 -
1.249 - matches = {}
1.250 - available = set(available[:])
1.251 -
1.252 - for level in self.get_ordered():
1.253 - for media_range in level:
1.254 -
1.255 - # Attempt to match available types.
1.256 -
1.257 - found = set()
1.258 - for available_type in available:
1.259 - if media_range.permits(available_type):
1.260 - q = float(media_range.accept_parameters.get("q", "1"))
1.261 - if not matches.has_key(q):
1.262 - matches[q] = []
1.263 - matches[q].append(available_type)
1.264 - found.add(available_type)
1.265 -
1.266 - # Stop looking for matches for matched available types.
1.267 -
1.268 - if found:
1.269 - available.difference_update(found)
1.270 -
1.271 - # Sort the matches in descending order of quality.
1.272 -
1.273 - all_q = matches.keys()
1.274 -
1.275 - if all_q:
1.276 - all_q.sort(reverse=True)
1.277 - return [matches[q] for q in all_q]
1.278 - else:
1.279 - return []
1.280 -
1.281 - def get_preferred_types(self, available):
1.282 -
1.283 - """
1.284 - Return the preferred content/media types from those in the 'available'
1.285 - list, given the known preferences.
1.286 - """
1.287 -
1.288 - preferred = self.get_acceptable_types(available)
1.289 - if preferred:
1.290 - return preferred[0]
1.291 - else:
1.292 - return []
1.293 -
1.294 -# Content type parsing.
1.295 -
1.296 -def getContentTypeAndEncoding(content_type):
1.297 -
1.298 - """
1.299 - Return a tuple with the content/media type and encoding, extracted from the
1.300 - given 'content_type' header value.
1.301 - """
1.302 -
1.303 - m = encoding_regexp.search(content_type)
1.304 - if m:
1.305 - return m.group("content_type"), m.group("encoding")
1.306 - else:
1.307 - return None, None
1.308 -
1.309 # Page access functions.
1.310
1.311 def getPageURL(page):