Edgewall Software

ChristianBoos: mimeview_conversion.diff

File mimeview_conversion.diff, 50.0 kB (added by cboos, 2 years ago)

Work in progress -- IContentConverter and IHTMLPreviewRenderer merge

  • trac/attachment.py

     
    432432            fd.seek(0) 
    433433             
    434434            binary = is_binary(str_data) 
    435             mime_type = mimeview.get_mimetype(attachment.filename, str_data) 
     435            mimetype = mimeview.get_mimetype(attachment.filename, str_data) 
    436436 
    437437            # Eventually send the file directly 
    438438            format = req.args.get('format') 
     
    442442                    # contain malicious code enabling XSS attacks 
    443443                    req.send_header('Content-Disposition', 'attachment;' + 
    444444                                    'filename=' + attachment.filename) 
    445                 if not mime_type or (self.render_unsafe_content and \ 
     445                if not mimetype or (self.render_unsafe_content and \ 
    446446                                     not binary and format == 'txt'): 
    447                     mime_type = 'text/plain' 
    448                 if 'charset=' not in mime_type: 
    449                     charset = mimeview.get_charset(str_data, mime_type) 
    450                     mime_type = mime_type + '; charset=' + charset 
    451                 req.send_file(attachment.path, mime_type) 
     447                    mimetype = 'text/plain' 
     448                full_mimetype = mimeview.get_mimetype_charset( 
     449                    attachment.filename, str_data, mimetype) 
     450                req.send_file(attachment.path, full_mimetype) 
    452451 
    453452            # add ''Plain Text'' alternate link if needed 
    454453            if self.render_unsafe_content and not binary and \ 
    455                not mime_type.startswith('text/plain'): 
     454               not mimetype.startswith('text/plain'): 
    456455                plaintext_href = attachment.href(req, format='txt') 
    457456                add_link(req, 'alternate', plaintext_href, 'Plain Text', 
    458                          mime_type) 
     457                         mimetype) 
    459458 
    460459            # add ''Original Format'' alternate link (always) 
    461460            raw_href = attachment.href(req, format='raw') 
    462             add_link(req, 'alternate', raw_href, 'Original Format', mime_type) 
     461            add_link(req, 'alternate', raw_href, 'Original Format', mimetype) 
    463462 
    464463            self.log.debug("Rendering preview of file %s with mime-type %s" 
    465                            % (attachment.filename, mime_type)) 
     464                           % (attachment.filename, mimetype)) 
    466465 
    467466            req.hdf['attachment'] = mimeview.preview_to_hdf( 
    468                 req, fd, os.fstat(fd.fileno()).st_size, mime_type, 
     467                req, fd, os.fstat(fd.fileno()).st_size, mimetype, 
    469468                attachment.filename, raw_href, annotations=['lineno']) 
    470469        finally: 
    471470            fd.close() 
  • trac/mimeview/api.py

    <
     
    2727 * taking advantage of existing conventions for the file name 
    2828 * examining the file content and applying various heuristics 
    2929 
    30 The module also knows how to convert the file content from one type 
    31 to another type. 
     30The module also knows about conversions from one data type to another type, 
     31like conversions to text/html (this is no more a special case). 
    3232 
    3333In some cases, only the `url` pointing to the file's content is actually 
    3434needed, that's why we avoid to read the file's content when it's not needed. 
     
    4949 
    5050 
    5151__all__ = ['get_mimetype', 'is_binary', 'detect_unicode', 'Mimeview', 
    52            'content_to_unicode'] 
     52           'content_to_unicode', 
     53           'combine_mimetype_charset', 'split_mimetype_charset'] 
    5354 
    5455 
    5556# Some common MIME types and their associated keywords and/or file extensions 
     
    153154                    return 'application/octet-stream' 
    154155        return mimetype 
    155156 
     157def combine_mimetype_charset(mimetype, charset): 
     158    """Combine the MIME type and charset information in a single string.""" 
     159    if mimetype and charset and not 'charset' in mimetype: 
     160        return '%s; charset=%s' % (mimetype, charset) 
     161    else: 
     162        return mimetype 
     163 
     164def split_mimetype_charset(full_mimetype): 
     165    """Return (mimetype, charset) from the combined information""" 
     166    mimetype = full_mimetype 
     167    charset = None 
     168    idx = full_mimetype.find(';') 
     169    if idx >= 0: 
     170        mimetype = full_mimetype[:idx].strip() 
     171        idx = full_mimetype.find('charset=', idx) 
     172        if idx >= -1: 
     173            charset = full_mimetype[idx+8:].strip() 
     174    return mimetype, charset 
     175 
    156176def is_binary(data): 
    157177    """Detect binary content by checking the first thousand bytes for zeroes. 
    158178 
     
    176196    else: 
    177197        return None 
    178198 
     199# Deprecated (TODO: remove in 0.11) 
     200 
    179201def content_to_unicode(env, content, mimetype): 
    180     """Retrieve an `unicode` object from a `content` to be previewed""" 
    181     mimeview = Mimeview(env) 
    182     if hasattr(content, 'read'): 
    183         content = content.read(mimeview.max_preview_size) 
    184     return mimeview.to_unicode(content, mimetype) 
     202    """Retrieve an `unicode` object from a `content` to be previewed. 
     203    ''Deprecated in 0.10.'' 
     204    """ 
     205    return Mimeview(env).fetch_content(content, mimetype) 
    185206 
    186207 
    187208class IHTMLPreviewRenderer(Interface): 
    188209    """Extension point interface for components that add HTML renderers of 
    189210    specific content types to the `Mimeview` component. 
    190211 
    191     (Deprecated) 
     212    Deprecated in 0.10. Implement `IContentConverter` instead. 
    192213    """ 
    193214 
    194215    # implementing classes should set this property to True if they 
     
    196217    expand_tabs = False 
    197218 
    198219    def get_quality_ratio(mimetype): 
    199         """Return the level of support this renderer provides for the `content` 
    200         of the specified MIME type. The return value must be a number between 
    201         0 and 9, where 0 means no support and 9 means "perfect" support. 
    202         """ 
     220        """Return the level of support this renderer provides""" 
    203221 
    204222    def render(req, mimetype, content, filename=None, url=None): 
    205         """Render an XHTML preview of the raw `content`. 
     223        """Render an XHTML preview of the raw `content`.""" 
    206224 
    207         The `content` might be: 
    208          * a `str` object 
    209          * an `unicode` string 
    210          * any object with a `read` method, returning one of the above 
    211225 
    212         It is assumed that the content will correspond to the given `mimetype`. 
    213  
    214         Besides the `content` value, the same content may eventually 
    215         be available through the `filename` or `url` parameters. 
    216         This is useful for renderers that embed objects, using <object> or 
    217         <img> instead of including the content inline. 
    218          
    219         Can return the generated XHTML text as a single string or as an 
    220         iterable that yields strings. In the latter case, the list will 
    221         be considered to correspond to lines of text in the original content. 
    222         """ 
    223  
    224226class IHTMLPreviewAnnotator(Interface): 
    225227    """Extension point interface for components that can annotate an XHTML 
    226228    representation of file contents with additional information.""" 
     
    238240        annotation data.""" 
    239241 
    240242 
     243class Conversion(object): 
     244    """A data conversion specification. 
     245 
     246    The conversion goes from an `in_type` to an `out_type`. 
     247    A conversion is identified by a `key`, has a `name` and proposes 
     248    an `extension` that can be used for storing the converted data in a file. 
     249 
     250    The `quality` ratio of the conversion is a number in the range 0 to 9, 
     251    where 0 means no support and 9 means "perfect" support. 
     252 
     253    Finally, `expand_tabs` indicates whether a tab expansion should precede 
     254    the conversion attempt. 
     255 
     256    e.g. Conversion(key='latex', name='LaTeX', extension='tex', 
     257                    in_type='text/x-trac-wiki', out_type='text/x-tex', 
     258                    quality=8) 
     259    """ 
     260 
     261    def __init__(self, key, name=None, extension='', 
     262                 in_type=None, out_type='text/html', 
     263                 quality=1, expand_tabs=False): 
     264        self.key = key 
     265        self.name = name or key 
     266        self.extension = extension 
     267        self.in_type = in_type 
     268        self.out_type = out_type 
     269        self.quality = quality 
     270        self.expand_tabs = expand_tabs 
     271 
     272    def __repr__(self): 
     273        return '<Conversion "%s" %s -> %s>' % \ 
     274               (self.key, self.in_type, self.out_type)  
     275 
     276 
    241277class IContentConverter(Interface): 
    242278    """An extension point interface for generic MIME based content 
    243279    conversion.""" 
    244280 
    245     def get_supported_conversions(): 
    246         """Return an iterable of tuples in the form (key, name, extension, 
    247         in_mimetype, out_mimetype, quality) representing the MIME conversions 
    248         supported and 
    249         the quality ratio of the conversion in the range 0 to 9, where 0 means 
    250         no support and 9 means "perfect" support. eg. ('latex', 'LaTeX', 'tex', 
    251         'text/x-trac-wiki', 'text/plain', 8)""" 
     281    def get_supported_conversions(mimetype): 
     282        """Check if conversion of `mimetype` is supported by this converter. 
    252283 
    253     def convert_content(req, mimetype, content, key): 
    254         """Convert the given content from mimetype to the output MIME type 
    255         represented by key. Returns a tuple in the form (content, 
    256         output_mime_type).""" 
     284        Return an iterable of `Conversion` objects if this is the case. 
     285        """ 
    257286 
     287    def convert_content(req, conversion, content, filename, url): 
     288        """Convert the given `content` using the specified `conversion` object. 
    258289 
     290        If not directly available through the `content` value, 
     291        the content may be available through the `filename` or `url` 
     292        arguments. 
     293        This can be useful for converters that can provide links to objects, 
     294        instead of having to inline the content. 
     295 
     296        Return the converted content. 
     297        """ 
     298 
     299 
    259300class Mimeview(Component): 
    260301    """A generic class to prettify data, typically source code.""" 
    261302 
    262     renderers = ExtensionPoint(IHTMLPreviewRenderer) 
     303    renderers = ExtensionPoint(IHTMLPreviewRenderer) # TODO: remove in 0.11 
    263304    annotators = ExtensionPoint(IHTMLPreviewAnnotator) 
    264305    converters = ExtensionPoint(IContentConverter) 
    265306 
     
    282323    def __init__(self): 
    283324        self._mime_map = None 
    284325         
    285     # Public API 
     326    # -- MIME type conversion 
     327     
     328    def get_supported_conversions(self, mimetype, content=None, filename=None): 
     329        """Return a list of possible conversions for the given `content`. 
    286330 
    287     def get_supported_conversions(self, mimetype): 
    288         """Return a list of target MIME types in same form as 
    289         `IContentConverter.get_supported_conversions()`, but with the converter 
    290         component appended. Output is ordered from best to worst quality.""" 
     331        The input `mimetype` is inferred from the `content` and/or the 
     332        `filename`, if not given. 
     333 
     334        Return a list of (conversion,converter), ordered from best 
     335        to worst quality. 
     336        """ 
     337        # Ensure we have a mimetype and only the mimetype, without the charset 
     338        if mimetype: 
     339            mimetype, charset = split_mimetype_charset(mimetype) 
     340        else: 
     341            mimetype = self.get_mimetype(filename, content) or 'text/plain' 
     342 
     343        # Build list of possible conversions, with their associated converters 
    291344        converters = [] 
    292345        for converter in self.converters: 
    293             for k, n, e, im, om, q in converter.get_supported_conversions(): 
    294                 if im == mimetype and q > 0: 
    295                     converters.append((k, n, e, im, om, q, converter)) 
    296         converters = sorted(converters, key=lambda i: i[-1], reverse=True) 
    297         return converters 
     346            print converter 
     347            for conversion in converter.get_supported_conversions(mimetype): 
     348                if conversion.quality > 0: 
     349                    converters.append((conversion, converter)) 
    298350 
    299     def convert_content(self, req, mimetype, content, key, filename=None, 
    300                         url=None): 
    301         """Convert the given content to the target MIME type represented by 
    302         `key`, which can be either a MIME type or a key. Returns a tuple of 
    303         (content, output_mime_type, extension).""" 
     351        # ---- Backward compatibility support for IHTMLPreviewRenderer 
     352        class RendererWrapper(object): 
     353            def __init__(self, renderer): 
     354                self.renderer = renderer 
     355            def convert_content(self, req, conversion, content, 
     356                                filename=None, url=None): 
     357                return self.renderer.render(req, conversion.in_type, 
     358                                            content, filename, url) 
     359        for renderer in self.renderers: 
     360            qr = renderer.get_quality_ratio(mimetype) 
     361            if qr > 0: 
     362                expand_tabs = getattr(renderer, 'expand_tabs', False) 
     363                converters.append( 
     364                    (Conversion(key='', name='', extension=None, 
     365                                in_type=mimetype, out_type='text/html', 
     366                                quality=8, expand_tabs=expand_tabs), 
     367                     RendererWrapper(renderer))) 
     368        # ---- (to be removed in 0.11) 
     369 
     370        return sorted(converters, key=lambda c: c[0].quality, reverse=True) 
     371 
     372    def convert_content(self, req, content, mimetype, selector, 
     373                        filename=None, url=None): 
     374        """Convert the `content` to targeted MIME type specified by 'selector'. 
     375 
     376        The content has the MIME type `mimetype` and the target MIME type 
     377        is determined by `selector`, which can be either directly the 
     378        output MIME type or a key identifying the Conversion. 
     379 
     380        Returns a tuple of (content, output_mime_type, extension). 
     381        """ 
    304382        if not content: 
    305             return ('', 'text/plain;charset=utf-8') 
     383            return ('', 'text/plain; charset=utf-8', '') 
    306384 
    307         # Ensure we have a MIME type for this content 
    308         full_mimetype = mimetype 
    309         if not full_mimetype: 
    310             if hasattr(content, 'read'): 
    311                 content = content.read(self.max_preview_size) 
    312             full_mimetype = self.get_mimetype(filename, content) 
    313         if full_mimetype: 
    314             mimetype = full_mimetype.split(';')[0].strip() # split off charset 
    315         else: 
    316             mimetype = full_mimetype = 'text/plain' # fallback if not binary 
     385        # Ensure we have the mimetype and the charset information 
     386        print `('cc', filename, content, mimetype)` 
     387        full_mimetype = self.get_mimetype_charset(filename, content, mimetype) 
     388        mimetype, charset = split_mimetype_charset(full_mimetype) 
    317389 
    318         # Choose best converter 
    319         candidates = self.get_supported_conversions(mimetype) 
    320         candidates = [c for c in candidates if key in (c[0], c[4])] 
     390        # Filter the converters of `mimetype` that are matching `selector` 
     391        candidates = self.get_supported_conversions(mimetype, content, filename) 
     392        candidates = [c for c in candidates 
     393                      if selector in (c[0].key, c[0].out_type)] 
    321394        if not candidates: 
    322395            raise TracError('No available MIME conversions from %s to %s' % 
    323                             (mimetype, key)) 
     396                            (mimetype, selector)) 
    324397 
     398        tab_expanded = False # we don't want to expand tabs more than once. 
     399 
    325400        # First candidate which converts successfully wins. 
    326         for ck, name, ext, input_mimettype, output_mimetype, quality, \ 
    327                 converter in candidates: 
     401        for conversion, converter in candidates: 
     402            if conversion.expand_tabs and not tab_expanded: 
     403                content = self.fetch_content(content, full_mimetype) 
     404                content = content.expandtabs(self.tab_width) 
     405                tab_expanded = True 
    328406            try: 
    329                 output = converter.convert_content(req, mimetype, content, ck) 
     407                output = converter.convert_content(req, conversion, content, 
     408                                                   filename, url) 
    330409                if not output: 
    331410                    continue 
    332                 return (output[0], output[1], ext) 
     411                return (output[0], output[1], conversion.extension) 
    333412            except Exception, e: 
    334413                self.log.warning('MIME conversion using %s failed (%s)' 
    335414                                 % (converter, e), exc_info=True) 
    336         raise TracError('No available MIME conversions from %s to %s' % 
    337                         (mimetype, key)) 
     415        raise TracError('No MIME conversions from %s to %s succeeded' % 
     416                        (mimetype, selector)) 
    338417 
     418    # -- XHTML rendering and annotations (based on the conversion API) 
     419     
    339420    def get_annotation_types(self): 
    340421        """Generator that returns all available annotation types.""" 
    341422        for annotator in self.annotators: 
     
    343424 
    344425    def render(self, req, mimetype, content, filename=None, url=None, 
    345426               annotations=None): 
    346         """Render an XHTML preview of the given `content`. 
    347  
    348         `content` is the same as an `IHTMLPreviewRenderer.render`'s 
    349         `content` argument. 
    350  
    351         The specified `mimetype` will be used to select the most appropriate 
    352         `IHTMLPreviewRenderer` implementation available for this MIME type. 
    353         If not given, the MIME type will be infered from the filename or the 
    354         content. 
    355  
    356         Return a string containing the XHTML text. 
     427        """Render an XHTML preview of the given `content`, with `annotations`. 
    357428        """ 
    358         if not content: 
    359             return '' 
    360  
    361         # Ensure we have a MIME type for this content 
    362         full_mimetype = mimetype 
    363         if not full_mimetype: 
    364             if hasattr(content, 'read'): 
    365                 content = content.read(self.max_preview_size) 
    366             full_mimetype = self.get_mimetype(filename, content) 
    367         if full_mimetype: 
    368             mimetype = full_mimetype.split(';')[0].strip() # split off charset 
     429        result, output_type, ext = self.convert_content( 
     430            req, content, mimetype, 'text/html', filename, url) 
     431        print `result` 
     432        if isinstance(result, Fragment): 
     433            return result 
     434        elif isinstance(result, basestring): 
     435            return Markup(to_unicode(result)) 
     436        elif annotations: 
     437            return Markup(self._annotate(result, annotations)) 
    369438        else: 
    370             mimetype = full_mimetype = 'text/plain' # fallback if not binary 
     439            buf = StringIO() 
     440            buf.write('<div class="code"><pre>') 
     441            for line in result: 
     442                buf.write(line + '\n') 
     443            buf.write('</pre></div>') 
     444            return Markup(buf.getvalue()) 
    371445 
    372         # Determine candidate `IHTMLPreviewRenderer`s 
    373         candidates = [] 
    374         for renderer in self.renderers: 
    375             qr = renderer.get_quality_ratio(mimetype) 
    376             if qr > 0: 
    377                 candidates.append((qr, renderer)) 
    378         candidates.sort(lambda x,y: cmp(y[0], x[0])) 
    379  
    380         # First candidate which renders successfully wins. 
    381         # Also, we don't want to expand tabs more than once. 
    382         expanded_content = None 
    383         for qr, renderer in candidates: 
    384             try: 
    385                 self.log.debug('Trying to render HTML preview using %s' 
    386                                % renderer.__class__.__name__) 
    387                 # check if we need to perform a tab expansion 
    388                 rendered_content = content 
    389                 if getattr(renderer, 'expand_tabs', False): 
    390                     if expanded_content is None: 
    391                         content = content_to_unicode(self.env, content, 
    392                                                      full_mimetype) 
    393                         expanded_content = content.expandtabs(self.tab_width) 
    394                     rendered_content = expanded_content 
    395                 result = renderer.render(req, full_mimetype, rendered_content, 
    396                                          filename, url) 
    397                 if not result: 
    398                     continue 
    399                 elif isinstance(result, Fragment): 
    400                     return result 
    401                 elif isinstance(result, basestring): 
    402                     return Markup(to_unicode(result)) 
    403                 elif annotations: 
    404                     return Markup(self._annotate(result, annotations)) 
    405                 else: 
    406                     buf = StringIO() 
    407                     buf.write('<div class="code"><pre>') 
    408                     for line in result: 
    409                         buf.write(line + '\n') 
    410                     buf.write('</pre></div>') 
    411                     return Markup(buf.getvalue()) 
    412             except Exception, e: 
    413                 self.log.warning('HTML preview using %s failed (%s)' 
    414                                  % (renderer, e), exc_info=True) 
    415  
    416446    def _annotate(self, lines, annotations): 
    417447        buf = StringIO() 
    418448        buf.write('<table class="code"><thead><tr>') 
     
    447477        buf.write('</tbody></table>') 
    448478        return buf.getvalue() 
    449479 
    450     def get_max_preview_size(self): 
    451         """Deprecated: use `max_preview_size` attribute directly.""" 
    452         return self.max_preview_size 
    453  
     480    # -- MIME type and charset detection 
     481     
    454482    def get_charset(self, content='', mimetype=None): 
    455483        """Infer the character encoding from the `content` or the `mimetype`. 
    456484 
     
    459487        The charset will be determined using this order: 
    460488         * from the charset information present in the `mimetype` argument 
    461489         * auto-detection of the charset from the `content` 
    462          * the configured `default_charset`  
     490         * the configured `default_charset` 
    463491        """ 
    464492        if mimetype: 
    465             ctpos = mimetype.find('charset=') 
    466             if ctpos >= 0: 
    467                 return mimetype[ctpos + 8:].strip() 
     493            mimetype, charset = split_mimetype_charset(mimetype) 
     494            if charset: 
     495                return charset 
    468496        if isinstance(content, str): 
    469497            utf = detect_unicode(content) 
    470498            if utf is not None: 
    471499                return utf 
     500        # TODO: ICharsetDetector 
    472501        return self.default_charset 
    473502 
    474503    def get_mimetype(self, filename, content=None): 
    475504        """Infer the MIME type from the `filename` or the `content`. 
    476505 
    477         `content` is either a `str` or an `unicode` object. 
     506        `content` is either a `str` or an `unicode` object, 
     507        or something that can be `read`. 
    478508 
    479         Return the detected MIME type, augmented by the 
    480         charset information (i.e. "<mimetype>; charset=..."), 
    481         or `None` if detection failed. 
     509        Return the detected MIME type or `None` if detection failed. 
    482510        """ 
    483511        # Extend default extension to MIME type mappings with configured ones 
    484512        if not self._mime_map: 
     
    489517                    for keyword in assocations: # Note: [0] kept on purpose 
    490518                        self._mime_map[keyword] = assocations[0] 
    491519 
    492         mimetype = get_mimetype(filename, content, self._mime_map) 
     520        # read the content only if there's no other way to get the mimetype 
     521        if hasattr(content, 'read'): 
     522            # first try to get the mimetype from the filename only 
     523            mimetype = get_mimetype(filename, None, self._mime_map) 
     524            if mimetype: 
     525                return mimetype 
     526            content = self.fetch_content(content, mimetype) 
     527        return get_mimetype(filename, content, self._mime_map) 
     528 
     529    def get_mimetype_charset(self, filename, content=None, mimetype=None): 
     530        """Retrieve combined mimetype and charset information. 
     531 
     532        If `mimetype` is given, we check if it provides the needed information, 
     533        otherwise we try to detect the mimetype and/or the charset. 
     534        """ 
     535        print `('gmc', filename, content, mimetype)` 
    493536        charset = None 
     537        if not mimetype: 
     538            mimetype = self.get_mimetype(filename, content) 
     539        print `('gmc2', mimetype)` 
    494540        if mimetype: 
     541            if 'charset=' in mimetype: 
     542                return mimetype 
    495543            charset = self.get_charset(content, mimetype) 
    496         if mimetype and charset and not 'charset' in mimetype: 
    497             mimetype += '; charset=' + charset 
    498         return mimetype 
     544        return combine_mimetype_charset(mimetype, charset) 
    499545 
    500     def to_utf8(self, content, mimetype=None): 
    501         """Convert an encoded `content` to utf-8. 
    502  
    503         ''Deprecated in 0.10. You should use `unicode` strings only.'' 
    504         """ 
    505         return to_utf8(content, self.get_charset(content, mimetype)) 
    506  
     546    # -- Charset conversion 
     547     
    507548    def to_unicode(self, content, mimetype=None, charset=None): 
    508549        """Convert `content` (an encoded `str` object) to an `unicode` object. 
    509550 
     
    514555            charset = self.get_charset(content, mimetype) 
    515556        return to_unicode(content, charset) 
    516557 
     558    def fetch_content(self, content, mimetype): 
     559        if hasattr(content, 'read'): 
     560            content = content.read(self.max_preview_size) 
     561        return self.to_unicode(content, mimetype) 
     562 
     563    # -- Deprecated API (TODO: remove in 0.11) 
     564 
     565    def get_max_preview_size(self): 
     566        """Deprecated: use `max_preview_size` attribute directly.""" 
     567        return self.max_preview_size 
     568 
     569    def to_utf8(self, content, mimetype=None): 
     570        """Convert an encoded `content` to utf-8. 
     571 
     572        ''Deprecated in 0.10. You should use `unicode` strings only.'' 
     573        """ 
     574        return to_utf8(content, self.get_charset(content, mimetype)) 
     575 
     576    # -- Utilities 
     577 
    517578    def configured_modes_mapping(self, renderer): 
    518         """Return a MIME type to `(mode,quality)` mapping for given `option`""" 
     579        """Utility for configurable custom converters 
     580 
     581        Return a MIME type to `(mode,quality)` mapping for given `option`, 
     582        assuming a format of comma-separated <mimetype>:<mode>:<quality> 
     583        associations. 
     584 
     585        See EnscriptConverter and SilverCityConverter. 
     586        """ 
    519587        types, option = {}, '%s_modes' % renderer 
    520588        for mapping in self.config['mimeviewer'].getlist(option): 
    521589            if not mapping: 
     
    543611                                           url, annotations), 
    544612                    'raw_href': url} 
    545613 
    546     def send_converted(self, req, in_type, content, selector, filename='file'): 
     614    def send_converted(self, req, content, mimetype, selector, 
     615                       filename='file'): 
    547616        """Helper method for converting `content` and sending it directly. 
    548617 
    549         `selector` can be either a key or a MIME Type.""" 
     618        `mimetype` is the type of the content. 
     619        `selector` can be either a key or the expected output MIME Type. 
     620        """ 
    550621        from trac.web import RequestDone 
    551         content, output_type, ext = self.convert_content(req, in_type, 
    552                                                          content, selector) 
     622        content, output_type, ext = self.convert_content( 
     623            req, content, mimetype, selector, filename) 
    553624        req.send_response(200) 
    554625        req.send_header('Content-Type', output_type) 
    555         req.send_header('Content-Disposition', 'filename=%s.%s' % (filename, 
    556                                                                   ext)) 
     626        req.send_header('Content-Disposition', 'filename=%s.%s' %  
     627                        (filename, ext)) 
    557628        req.end_headers() 
    558629        req.write(content) 
    559630        raise RequestDone         
    560631         
    561632 
     633# utility for Mimeview._annotate 
    562634def _html_splitlines(lines):