Ticket #3332: mimeview_refactoring-r3507.diff
| File mimeview_refactoring-r3507.diff, 82.1 kB (added by cboos, 2 years ago) |
|---|
-
trac/attachment.py
26 26 from trac.config import BoolOption, IntOption 27 27 from trac.core import * 28 28 from trac.env import IEnvironmentSetupParticipant 29 from trac.mimeview import *29 from trac.mimeview.api import Mimeview, MimeType, FileMimeContent 30 30 from trac.util import get_reporter_id, create_unique_file 31 31 from trac.util.datefmt import format_datetime, pretty_timedelta 32 32 from trac.util.markup import Markup, html … … 124 124 def parent_href(self, req): 125 125 return req.href(self.parent_type, self.parent_id) 126 126 127 def _get_title(self): 127 def _get_title(self): # TODO: should extend to other `parent_type`s 128 128 return '%s%s: %s' % (self.parent_type == 'ticket' and '#' or '', 129 129 self.parent_id, self.filename) 130 130 title = property(_get_title) … … 227 227 228 228 select = classmethod(select) 229 229 230 def open(self): 230 def open(self): # deprecate? 231 231 self.env.log.debug('Trying to open attachment at %s', self.path) 232 232 try: 233 233 fd = open(self.path, 'rb') … … 505 505 'author': get_reporter_id(req)} 506 506 507 507 def _render_view(self, req, attachment): 508 # FIXME: perm_map should extend to other `parent_type`s 508 509 perm_map = {'ticket': 'TICKET_VIEW', 'wiki': 'WIKI_VIEW'} 509 510 req.perm.assert_permission(perm_map[attachment.parent_type]) 510 511 … … 522 523 if req.perm.has_permission(perm_map[attachment.parent_type]): 523 524 req.hdf['attachment.can_delete'] = 1 524 525 525 fd = attachment.open() 526 try: 527 mimeview = Mimeview(self.env) 526 format = req.args.get('format') 527 raw_href = attachment.href(req, format='raw') 528 528 529 # MIME type detection 530 str_data = fd.read(1000) 531 fd.seek(0) 532 533 binary = is_binary(str_data) 534 mime_type = mimeview.get_mimetype(attachment.filename, str_data) 529 mimecontent = FileMimeContent(self.env, attachment.path, raw_href, 530 'Attachment') 531 can_render_as_txt = self.render_unsafe_content and \ 532 not mimecontent.is_binary 535 533 536 # Eventually send the file directly 537 format = req.args.get('format') 538 if format in ('raw', 'txt'): 539 if not self.render_unsafe_content and not binary: 540 # Force browser to download HTML/SVG/etc pages that may 541 # contain malicious code enabling XSS attacks 542 req.send_header('Content-Disposition', 'attachment;' + 543 'filename=' + attachment.filename) 544 if not mime_type or (self.render_unsafe_content and \ 545 not binary and format == 'txt'): 546 mime_type = 'text/plain' 547 if 'charset=' not in mime_type: 548 charset = mimeview.get_charset(str_data, mime_type) 549 mime_type = mime_type + '; charset=' + charset 550 req.send_file(attachment.path, mime_type) 534 # Eventually send the file directly 535 if format in ('raw', 'txt'): 536 if not self.render_unsafe_content and not mimecontent.is_binary: 537 # Force browser to download HTML/SVG/etc pages that may 538 # contain malicious code enabling XSS attacks 539 req.send_header('Content-Disposition', 540 'attachment;filename=%s' % attachment.filename) 541 if not mimecontent.type.is_known or \ 542 (format == 'txt' and can_render_as_txt): 543 # Force the content to be displayed as text 544 type = MimeType('text/plain', mimecontent.encoding) 545 else: 546 type = mimecontent.type 547 req.send_file(attachment.path, type.mimetype_charset) 551 548 552 # add ''Plain Text'' alternate link if needed 553 if self.render_unsafe_content and not binary and \ 554 mime_type and not mime_type.startswith('text/plain'): 555 plaintext_href = attachment.href(req, format='txt') 556 add_link(req, 'alternate', plaintext_href, 'Plain Text', 557 mime_type) 549 mimetype = mimecontent.type.mimetype 558 550 559 # add ''Original Format'' alternate link (always) 560 raw_href = attachment.href(req, format='raw') 561 add_link(req, 'alternate', raw_href, 'Original Format', mime_type) 551 # add ''Plain Text'' alternate link if needed 552 if can_render_as_txt and mimecontent.type.is_known and \ 553 mimetype != 'text/plain': 554 add_link(req, 'alternate', attachment.href(req, format='txt'), 555 'Plain Text', 'text/plain') 562 556 563 self.log.debug("Rendering preview of file %s with mime-type %s"564 % (attachment.filename, mime_type))557 # add ''Original Format'' alternate link (always) 558 add_link(req, 'alternate', raw_href, 'Original Format', mimetype) 565 559 566 req.hdf['attachment'] = mimeview.preview_to_hdf( 567 req, fd, os.fstat(fd.fileno()).st_size, mime_type, 568 attachment.filename, raw_href, annotations=['lineno']) 569 finally: 570 fd.close() 560 self.log.debug("Rendering preview of file %s with mime-type %s" % \ 561 (attachment.filename, mimetype)) 571 562 563 req.hdf['attachment'] = Mimeview(self.env).preview_to_hdf( 564 req, mimecontent, annotations=['lineno']) 565 572 566 def _render_list(self, req, p_type, p_id): 573 567 self._parent_to_hdf(req, p_type, p_id) 574 568 req.hdf['attachment'] = { -
trac/mimeview/rst.py
28 28 import re 29 29 30 30 from trac.core import * 31 from trac.mimeview.api import IHTMLPreviewRenderer , content_to_unicode31 from trac.mimeview.api import IHTMLPreviewRenderer 32 32 from trac.web.href import Href 33 33 from trac.wiki.formatter import WikiProcessor 34 34 from trac.wiki import WikiSystem … … 223 223 224 224 _inliner = rst.states.Inliner() 225 225 _parser = rst.Parser(inliner=_inliner) 226 content = content_to_unicode(self.env, content, mimetype)226 content = unicode(content) 227 227 content = content.encode('utf-8') 228 228 html = publish_string(content, writer_name='html', parser=_parser, 229 229 settings_overrides={'halt_level': 6}) -
trac/mimeview/api.py
19 19 # Christian Boos <cboos@neuf.fr> 20 20 21 21 """ 22 The `trac.mimeview` module centralize the intelligence related to23 file metadata, principally concerning the `type` (MIME type) of the content24 and, if relevant, concerning the text encoding (charset) used by the content.22 The `trac.mimeview` module centralizes the intelligence related to file 23 metadata, principally concerning the `type` (MIME type) and the text 24 encoding (charset) used by the content, if the latter one is relevant. 25 25 26 26 There are primarily two approaches for getting the MIME type of a given file: 27 27 * taking advantage of existing conventions for the file name 28 28 * examining the file content and applying various heuristics 29 29 30 The module also knows how to convert the file content from one type31 to another type.30 The module also knows about conversions from one data type to another type, 31 like conversions to text/html (this is no more a special case). 32 32 33 In some cases, only the `url` pointing to the file's content is actually 34 needed, that's why we avoid to read the file's content when it's not needed. 35 36 The actual `content` to be converted might be a `unicode` object, 37 but it can also be the raw byte string (`str`) object, or simply 38 an object that can be `read()`. 33 In order to keep the API simple, we deal with a few classes, 34 each encapsulating a part of the knowledge related to content. 35 * the `MimeType`, for storing the mime type string, the charset, 36 but also eventually the name and the commonly used file extension 37 for that type 38 * the `MimeContent` and `FileMimeContent`, which provide a flexible 39 API for handling string and file content, respectively. 40 Those classes inherit from the abstract `MimeContentBase`, which 41 can be used to make new wrapper classes for any kind of content. 42 * the `Conversion` class, which is use to specify conversion from 43 one `MimeType` to another 39 44 """ 40 45 46 import os 41 47 import re 42 48 from StringIO import StringIO 43 49 44 50 from trac.config import IntOption, ListOption, Option 45 51 from trac.core import * 46 52 from trac.util import sorted 47 from trac.util.text import to_utf8, to_unicode 53 from trac.util.text import to_utf8, to_unicode, IDENTITY_CHARSET 48 54 from trac.util.markup import escape, Markup, Fragment, html 49 55 50 56 51 __all__ = ['get_mimetype', 'is_binary', 'detect_unicode', 'Mimeview', 52 'content_to_unicode'] 57 __all__ = ['get_mimetype', 'is_binary', 'detect_unicode', 58 'Mimeview', 'MimeType', 'Conversion', 59 'MimeContentBase', 'MimeContent', 'FileMimeContent', 60 'TEXT_PLAIN', 'TEXT_HTML', 'APPLICATION_OCTET_STREAM'] 53 61 54 62 55 63 # Some common MIME types and their associated keywords and/or file extensions 56 64 65 APPLICATION_OCTET_STREAM_STR = 'application/octet-stream' 66 57 67 KNOWN_MIME_TYPES = { 58 68 'application/pdf': ['pdf'], 59 69 'application/postscript': ['ps'], … … 114 124 for e in exts: 115 125 MIME_MAP[e] = t 116 126 117 # Simple builtin autodetection from the content using a regexp118 MODE_RE = re.compile(119 r"#!(?:[/\w.-_]+/)?(\w+)|" # look for shebang120 r"-\*-\s*(?:mode:\s*)?([\w+-]+)\s*-\*-|" # look for Emacs' -*- mode -*-121 r"vim:.*?syntax=(\w+)" # look for VIM's syntax=<n>122 )123 127 124 def get_mimetype(filename, content=None, mime_map=MIME_MAP): 125 """Guess the most probable MIME type of a file with the given name. 128 # -- a few functions for dealing with MIME types / binary / text content 129 # in a simple way (get_mimetype, is_binary, detect_unicode) 126 130 131 def get_mimetype_from_filename(filename, mime_map=MIME_MAP): 132 """Guess the most probable MIME type of file with the given `filename`. 133 127 134 `filename` is either a filename (the lookup will then use the suffix) 128 135 or some arbitrary keyword. 129 130 `content` is either a `str` or an `unicode` string. 136 `mime_map` maps keywords to MIME types. 137 138 Return the MIME type as a string, or `None` if not detected. 131 139 """ 132 140 suffix = filename.split('.')[-1] 133 141 if suffix in mime_map: … … 141 149 mimetype = mimetypes.guess_type(filename)[0] 142 150 except: 143 151 pass 144 if not mimetype and content:145 match = re.search(MODE_RE, content[:1000])146 if match:147 mode = match.group(1) or match.group(3) or \148 match.group(2).lower()149 if mode in mime_map:150 # 3) mimetype from the content, using the `MODE_RE`151 return mime_map[mode]152 else:153 if is_binary(content):154 # 4) mimetype from the content, using`is_binary`155 return 'application/octet-stream'156 152 return mimetype 157 153 154 # Simple builtin autodetection from the content using a regexp 155 MODE_RE = re.compile( 156 r"#!(?:[/\w.-_]+/)?(\w+)|" # look for shebang 157 r"-\*-\s*(?:mode:\s*)?([\w+-]+)\s*-\*-|" # look for Emacs' -*- mode -*- 158 r"vim:.*?syntax=(\w+)" # look for VIM's syntax=<n> 159 ) 160 161 def get_mimetype_from_content(content): 162 """Guess the most probable MIME type of file with the given `filename`. 163 164 `content` is either a `str` or an `unicode` string 165 166 Return the MIME type as a string, or `None` if not detected. 167 """ 168 match = re.search(MODE_RE, content[:1000]) 169 if match: 170 mode = match.group(1) or match.group(3) or \ 171 match.group(2).lower() 172 if mode in mime_map: 173 # 3) mimetype from the content, using the `MODE_RE` 174 return mime_map[mode] 175 else: 176 if is_binary(content): 177 # 4) mimetype from the content, using `is_binary` 178 return APPLICATION_OCTET_STREAM_STR 179 180 def get_mimetype(filename, content=None, mime_map=MIME_MAP): 181 """Auto-detect MIME type either from the `filename` or from the `content`. 182 """ 183 mimetype = get_mimetype_from_filename(filename, mime_map) 184 if not mimetype and content: 185 mimetype = get_mimetype_from_content(content) 186 return mimetype 187 158 188 def is_binary(data): 159 189 """Detect binary content by checking the first thousand bytes for zeroes. 160 190 … … 165 195 return '\0' in data[:1000] 166 196 167 197 def detect_unicode(data): 168 """Detect different unicode charsets by looking for B OMs (Byte Order Marks).198 """Detect different unicode charsets by looking for Byte Order Marks. 169 199 170 200 Operate obviously only on `str` objects. 171 201 """ … … 178 208 else: 179 209 return None 180 210 181 def content_to_unicode(env, content, mimetype):182 """Retrieve an `unicode` object from a `content` to be previewed"""183 mimeview = Mimeview(env)184 if hasattr(content, 'read'):185 content = content.read(mimeview.max_preview_size)186 return mimeview.to_unicode(content, mimetype)187 211 212 # -- Classes for mimetype, content and conversion 188 213 214 class MimeType(object): 215 """Representation of a MIME-Type. 216 217 If the MIME type correspond to text content, the object can also 218 store a `charset` information. 219 220 A MIME type has a `name` and has an `extension` 221 that can be used for storing the converted data in a file. 222 223 All the properties of this class are read-only. 224 """ 225 226 def __init__(self, mimetype, charset=None, name=None, extension=None): 227 """The `mimetype` string can eventually embed the `charset`.""" 228 self._mimetype = mimetype 229 # determine charset 230 self._charset = charset 231 if not self._charset and self._mimetype: 232 sep_idx = mimetype.find(';') 233 if sep_idx >= 0: 234 self._mimetype = mimetype[:sep_idx].strip() 235 charset_idx = mimetype.find('charset=', sep_idx) 236 if charset_idx >= 0: 237 self._charset = mimetype[charset_idx+8:].strip() 238 self._extension = extension 239 self._name = name 240 241 def __repr__(self): 242 return self.mimetype_charset 243 244 def _get_extension(self): 245 if not self._extension: 246 self._extension = KNOWN_MIME_TYPES.get(self.mimetype) 247 if not self._extension: 248 detail = self.mimetype.split('/', 1)[1] 249 if detail.startswith('x-'): 250 self._extension = detail[2:] 251 return self._extension 252 253 def _get_mimetype_charset(self): 254 """Combine the MIME type and charset information in a single string. 255 """ 256 if self._mimetype and self._charset: 257 return '%s; charset=%s' % (self.mimetype, self.charset) 258 else: 259 return self.mimetype 260 261 is_known = property(lambda x: x._mimetype is not None) 262 mimetype = property(lambda x: x._mimetype or APPLICATION_OCTET_STREAM_STR, 263 doc="MIME Type string (without charset information)") 264 charset = property(lambda x: x._charset, 265 doc="Eventual charset information") 266 name = property(lambda x: x._name or x._extension) 267 extension = property(_get_extension) 268 mimetype_charset = property(_get_mimetype_charset) 269 270 def match(self, other, regexp=False): 271 """Compare MIME type string only. 272 273 If `regexp` is set, `self.mimetype` is used as a regexp. 274 """ 275 if not isinstance(other, MimeType): 276 return False 277 if regexp: 278 return re.match(self.mimetype, other.mimetype) 279 else: 280 return self.mimetype == other.mimetype 281 282 283 TEXT_PLAIN = MimeType('text/plain', 'utf-8', 'Plain Text', 'txt') 284 TEXT_HTML = MimeType('text/html', 'utf-8', 'HTML', 'html') 285 APPLICATION_OCTET_STREAM = MimeType(APPLICATION_OCTET_STREAM_STR, 286 IDENTITY_CHARSET, 287 'Undefined (binary)', 'bin') 288 289 290 class MimeContentBase(object): 291 """An abstract MIME content, with an associated MimeType. 292 293 Such an object has means to auto-detect both the MIME Type and 294 the `encoding` of its content. 295 296 That `encoding` is more reliable than the `type.charset` information. 297 There are additional consistency checks that are performed, and it 298 can be `None` if the content is an `unicode` object. 299 300 The content itself can be accessed in various ways: through 301 the iterator protocol, the len() and unicode() operators... 302 """ 303 304 def __init__(self, env, mimetype=None, filename=None, url=None): 305 """ 306 `mimetype` can be specified as a `MimeType` object, 307 or as string, which will then be a hint about the content. 308 309 If the mimetype is not specified or equal to 310 "application/octet-stream", then it will be auto-detected when needed. 311 312 In case auto-detection fails, APPLICATION_OCTET_STREAM will be the 313 corresponding MIME type. 314 315 The `filename` is simply a suggested basename for that content. 316 317 The `url` is a link for retrieving the raw content directly 318 from the server. This can be useful for converters that can 319 provide links to objects, instead of having to expand the 320 content inline. 321 """ 322 self.env = env 323 if isinstance(mimetype, basestring): 324 mimetype = MimeType(mimetype) 325 self._type = mimetype 326 self._filename = filename 327 self._url = url 328 self._binary = None 329 self._encoding = False 330 331 def __repr__(self): 332 return '<%s %s "%s">' % (self.__class__.__name__, self._type, 333 self._filename or self._url) 334 335 def __unicode__(self): 336 """Return the `unicode` object corresponding to the content.""" 337 return to_unicode(self.content, self.encoding) 338 # Note: this does the right thing if the content is already `unicode` 339 340 341 def _is_binary(self): 342 """An heuristic to guess whether the content is binary data or not. 343 344 This will trigger the retrieval of an `excerpt` of the content. 345 """ 346 if self._binary is None: 347 mimetype = self.type.mimetype 348 self._binary = is_binary(self.excerpt) or \ 349 (self.type.is_known and 350 mimetype == APPLICATION_OCTET_STREAM_STR) or \ 351 mimetype in Mimeview(self.env).treat_as_binary 352 return self._binary 353 354 def _get_type(self): 355 """Get or determine the MimeType corresponding to this content. 356 357 An `excerpt` of the content will be examined if needed. 358 """ 359 if self._type is None: # not set 360 mimetype = None 361 if self.filename: 362 mimemap = Mimeview(self.env).mimemap 363 mimetype = get_mimetype_from_filename(self.filename, mimemap) 364 if not mimetype: 365 mimetype = get_mimetype_from_content(self.excerpt) 366 if not mimetype: 367 pass # TODO 0.11: go through IMimeTypeDetectors 368 self._type = MimeType(mimetype) 369 return self._type 370 371 def _set_type(self, type): 372 """Simply replace the existing `type` by the given `MimeType` object. 373 374 If `None` is given, this will force auto-detection the next time 375 `type` will be accessed. 376 377 Can be used for in-place conversion (e.g. ''any'' to text/plain). 378 """ 379 self._type = type 380 381 def _get_encoding(self): 382 """Get or determine the current encoding of that `content`. 383 384 The encoding will be determined using this order: 385 * from the charset information present in the mimetype information 386 * auto-detection of the charset from the `content` 387 * if nothing else worked, use the configured `default_charset` 388 389 If the `content` happens to be a genuine `unicode` object, then 390 this returns `None`. 391 If the `content` is binary, then the encoding will be the identity 392 charset (ISO Latin 1). 393 """ 394 if self._encoding is False: 395 charset = self.type.charset 396 if charset: 397 self._encoding = charset 398 elif isinstance(self.excerpt, str): 399 utf_encoding = detect_unicode(self.excerpt) 400 if utf_encoding is not None: 401 self._encoding = utf_encoding 402 elif self.is_binary: 403 self._encoding = IDENTITY_CHARSET 404 elif isinstance(self.excerpt, unicode): 405 self._encoding = None 406 if self._encoding is False: 407 pass # TODO 0.11: go through ICharsetDetectors here 408 if self._encoding is False: 409 self._encoding = Mimeview(self.env).default_charset 410 return self._encoding 411 412 def _get_content(self): 413 """Retrieve all the content. 414 415 Default implementation based on iterator. If the iterator itself 416 is implemented based on the content... reimplement this one! 417 """ 418 return "".join(self.__iter__()) 419 420 def read(self): # TODO: remove in 0.11 421 return self.content # (compatibility with IHTMLPreviewRenderer) 422 423 # Methods that need to be reimplemented by subclasses: 424 425 def __iter__(self): 426 """Iterate on chunks of raw content.""" 427 raise NotImplementedError 428 429 def __len__(self): 430 """Length of the raw content, in bytes.""" 431 raise NotImplementedError 432 433 def _get_excerpt(self, len=1000): 434 """Extracts the first `len` characters from the content.""" 435 raise NotImplementedError 436 437 type = property(fget=lambda x: x._get_type(), 438 fset=lambda x, y: x._set_type(y)) 439 is_binary = property(lambda x: x._is_binary()) 440 encoding = property(lambda x: x._get_encoding()) 441 excerpt = property(lambda x: x._get_excerpt()) 442 content = property(lambda x: x._get_content()) 443 filename = property(lambda x: x._filename) 444 url = property(lambda x: x._url) 445 446 447 class MimeContent(MimeContentBase): 448 """MIME-typed content wrapper for a basestring.""" 449 450 def __init__(self, env, content, mimetype, filename='file', url=None): 451 MimeContentBase.__init__(self, env, mimetype, filename, url) 452 self._content = content 453 454 # Reimplemented methods 455 456 def _get_content(self): 457 """Retrieve the wrapped content. 458 459 Note: therefore this *might* be an `unicode` object. 460 Remember that in this case, `encoding` will be `None`. 461 """ 462 return self._content 463 464 def __iter__(self): 465 """Iterate on chunks of content. 466 467 If the content `is_binary` property is `False`, those chunks will 468 be lines, with the line endings kept. 469 """ 470 if self.is_binary: 471 buf = StringIO(self.content) 472 chunk = buf.read(1000) 473 while chunk: 474 yield chunk 475 chunk = buf.read(1000) 476 else: 477 for line in self.content.splitlines(True): 478 yield line 479 480 def __len__(self): 481 """Length of the content, in characters.""" 482 return len(self.content) 483 484 def _get_excerpt(self, len=1000): 485 """Extracts the first `len` characters from the content.""" 486 return self._content[:len] 487 488 489 class FileMimeContent(MimeContentBase): 490 """MIME-typed content wrapper for a file.""" 491 492 def __init__(self, env, path, url=None, kind='File', mimetype=None): 493 self._fd = None 494 self._path = path 495 self._kind = kind 496 self._excerpt = None 497 MimeContentBase.__init__(self, env, mimetype, os.path.basename(path), 498 url) 499 def __del__(self): 500 if self._fd: 501 self._fd.close() 502 503 def _ensure_open(self): 504 if not self._fd: 505 try: 506 self._fd = open(self._path) 507 except IOError: 508 raise TracError('%s "%s" not found' % (self._kind, 509 self._filename)) 510 # Reimplemented methods 511 512 def __iter__(self): 513 """Iterate on chunks of raw content.""" 514 chunk = self.excerpt 515 while chunk: 516 yield chunk 517 chunk = self._fd.read(1000) 518 519 def __len__(self): 520 """Length of the raw content, in bytes.""" 521 if self._fd: 522 stat = os.fstat(self._fd.fileno()) 523 else: 524 stat = os.stat(self._path) 525 return stat.st_size 526 527 def _get_excerpt(self, len=1000): 528 """Extracts the `len` first bytes from the content.""" 529 if self._excerpt is None: 530 self._ensure_open() 531 self._excerpt = self._fd.read(1000) 532 return self._excerpt 533 534 535 class NoConversion(TracError): 536 def __init__(self, msg, from_, to): 537 self.msg = msg 538 self.from_ = from_ 539 self.to = to 540 541 def message(self): 542 return '%s, from %s to %s' % \ 543 (self.msg, self.from_.mimetype, self.to.mimetype) 544 545 class Conversion(object): 546 """A specification for performing a data conversion. 547 548 Each conversion is identified by a `key` and targets an output `mimetype`. 549 550 A conversion also specifies a `quality` ranking, which is a number 551 in the range 0 to 9, where 0 means no support and 9 means "perfect" 552 support (try to keep 9 available for user defined conversions, 553 though nothing will prevent them from using 10 or 100...) 554 555 Finally, `expand_tabs` indicates whether a tab expansion should precede 556 the conversion attempt. 557 558 e.g. Conversion(key='latex', quality=8, mimetype=MimeType('text/x-tex')) 559 """ 560 561 def __init__(self, key, quality=1, mimetype=TEXT_HTML, expand_tabs=False): 562 self.key = key 563 self.quality = quality 564 self.mimetype = mimetype 565 self.expand_tabs = expand_tabs 566 567 def __repr__(self): 568 return '<Conversion "%s" to %s>' % (self.key, self.mimetype) 569 570 571 # -- Deprecated (TODO: remove in 0.11) 572 189 573 class IHTMLPreviewRenderer(Interface): 190 574 """Extension point interface for components that add HTML renderers of 191 575 specific content types to the `Mimeview` component. 192 576 193 (Deprecated)577 Deprecated in 0.10. Implement `IContentConverter` instead. 194 578 """ 195 579 196 580 # implementing classes should set this property to True if they … … 198 582 expand_tabs = False 199 583 200 584 def get_quality_ratio(mimetype): 201 """Return the level of support this renderer provides for the `content` 202 of the specified MIME type. The return value must be a number between 203 0 and 9, where 0 means no support and 9 means "perfect" support. 204 """ 585 """Return the level of support this renderer provides""" 205 586 206 587 def render(req, mimetype, content, filename=None, url=None): 207 """Render an XHTML preview of the raw `content`. 588 """Render an XHTML preview of the raw `content`.""" 208 589 209 The `content` might be:210 * a `str` object211 * an `unicode` stri
