Changeset 2312

Show
Ignore:
Timestamp:
06/17/07 04:51:25 (2 years ago)
Author:
roadrunner
Message:

XsltMacro:

Fix #1531. Added an entity-loader to handle relative links inside the
stylesheet and/or xml-doc. As part of this did some refactoring and
created a TransformSource? class and appropriate subclasses to
encapsulate and simplify all the code related to the differing sources.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • xsltmacro/0.9/xslt/Xslt.py

    r2311 r2312  
    6060import os 
    6161import inspect 
     62import threading 
     63import re 
     64import libxml2 
    6265 
    6366from trac.core import Component, implements 
     
    6972 
    7073MY_URL = '/extras/xslt' 
     74tl     = threading.local() 
     75 
     76def resolver(URL, ID, ctxt): 
     77    scheme = URL.split(':', 2)[0] 
     78    if scheme not in ['wiki', 'ticket', 'browser', 'file']: 
     79        return None 
     80 
     81    auth, path = re.match('[^:]*://([^/]*)/(.*)', URL).group(1, 2) 
     82    obj = _get_src(tl.env, tl.hdf, scheme, auth.replace('%2F', '/'), path) 
     83    return obj.getStream() 
     84 
     85libxml2.setEntityLoader(resolver) 
     86 
    7187 
    7288def execute(hdf, args, env): 
     
    120136 
    121137    else: 
    122         style_obj = _get_obj(env, hdf, *stylespec) 
    123         doc_obj   = _get_obj(env, hdf, *docspec) 
     138        style_obj = _get_src(env, hdf, *stylespec) 
     139        doc_obj   = _get_src(env, hdf, *docspec) 
    124140        params    = dict([(_to_str(k[3:]), _to_str(v)) for k, v in opts.iteritems() if k.startswith('xp_')]) 
    125141 
    126         try: 
    127             page, ct  = _transform(style_obj, doc_obj, params) 
    128         finally: 
    129             _close_obj(style_obj) 
    130             _close_obj(doc_obj) 
     142        page, ct  = _transform(style_obj, doc_obj, params, env, hdf) 
    131143 
    132144        return page 
     
    224236    return module, id, file 
    225237 
    226 def _get_obj(env, hdf, module, id, file): 
    227     """Returns a filename (str or unicode), a trac browser Node, or a urllib  
    228        addinfourl. 
    229     """ 
    230  
     238def _transform(style_obj, doc_obj, params, env, hdf): 
     239    import libxslt 
     240 
     241    tl.env = env 
     242    tl.hdf = hdf 
     243 
     244    doc    = None 
     245    style  = None; 
     246    result = None; 
     247 
     248    try: 
     249        try: 
     250            doc = _parse_xml(doc_obj) 
     251        except Exception, e: 
     252            raise Exception("Error parsing %s: %s" % (doc_obj, e)) 
     253 
     254        try: 
     255            styledoc = _parse_xml(style_obj) 
     256        except Exception, e: 
     257            raise Exception("Error parsing %s: %s" % (style_obj, e)) 
     258 
     259        style = libxslt.parseStylesheetDoc(styledoc) 
     260        if not style: 
     261            styledoc.freeDoc() 
     262            raise Exception("%s is not a valid stylesheet" % style_obj) 
     263 
     264        result = style.applyStylesheet(doc, params) 
     265        try: 
     266            output = style.saveResultToString(result) 
     267        except Exception, e: 
     268            # detect empty result doc 
     269            if str(e) != 'error return without exception set': 
     270                raise e 
     271            output = '' 
     272 
     273        if result.get_type() == 'document_xml': 
     274            ct = 'text/xml' 
     275        elif result.get_type() == 'document_html': 
     276            ct = 'text/html' 
     277        elif result.get_type() == 'document_text': 
     278            ct = 'text/plain' 
     279        else: 
     280            ct = 'application/octet-stream' 
     281 
     282    finally: 
     283        if doc:    doc.freeDoc() 
     284        if style:  style.freeStylesheet() 
     285        if result: result.freeDoc() 
     286        tl.env = None 
     287        tl.hdf = None 
     288 
     289    return output, ct 
     290 
     291def _parse_xml(obj): 
     292    if obj.isFile(): 
     293        return libxml2.parseFile(obj.getFile()) 
     294    else: 
     295        return libxml2.readDoc(obj.getStream().read(), obj.getUrl(), None, 0) 
     296 
     297def _get_src(env, hdf, module, id, file): 
    231298    # check permissions first 
    232299    if module == 'wiki'    and not hdf.has_key('trac.acl.WIKI_VIEW')   or \ 
     
    236303        raise Exception('Permission denied: %s' % module) 
    237304 
    238     obj = None 
    239  
    240305    if module == 'browser': 
     306        return BrowserSource(env, hdf, file) 
     307    if module == 'file': 
     308        return FileSource(env, id, file) 
     309    if module == 'wiki' or module == 'ticket': 
     310        return AttachmentSource(env, module, id, file) 
     311    if module == 'url': 
     312        return UrlSource(file) 
     313 
     314    raise Exception("unsupported module '%s'" % module) 
     315 
     316class TransformSource(object): 
     317    """Represents the source of an input (stylesheet or xml-doc) to the transformer""" 
     318 
     319    def __init__(self, module, id, file, obj): 
     320        self.module = module 
     321        self.id     = id 
     322        self.file   = file 
     323        self.obj    = obj 
     324 
     325    def isFile(self): 
     326        return False 
     327 
     328    def getFile(self): 
     329        return None 
     330 
     331    def getUrl(self): 
     332        return "%s://%s/%s" % (self.module, self.id.replace("/", "%2F"), self.file) 
     333 
     334    def get_last_modified(self): 
     335        import time 
     336        return time.time() 
     337 
     338    def __str__(self): 
     339        return str(self.obj) 
     340 
     341    def __del__(self): 
     342        if hasattr(self.obj, 'close') and callable(self.obj.close): 
     343            self.obj.close() 
     344 
     345    class CloseableStream(object): 
     346        """Implement close even if underlying stream doesn't""" 
     347 
     348        def __init__(self, stream): 
     349            self.stream = stream 
     350 
     351        def read(self, len=None): 
     352            return self.stream.read(len) 
     353 
     354        def close(self): 
     355            if hasattr(self.stream, 'close') and callable(self.stream.close): 
     356                self.stream.close() 
     357 
     358class BrowserSource(TransformSource): 
     359    def __init__(self, env, hdf, file): 
    241360        from trac.versioncontrol.web_ui import get_existing_node 
    242361        repos = env.get_repository(hdf.get('trac.authname')) 
    243362        obj   = get_existing_node(env, repos, file, None) 
    244363 
    245     elif module == 'file': 
     364        TransformSource.__init__(self, "browser", "source", file, obj) 
     365 
     366    def getStream(self): 
     367        return self.CloseableStream(self.obj.get_content()) 
     368 
     369    def __str__(self): 
     370        return self.obj.path 
     371 
     372    def get_last_modified(self): 
     373        return self.obj.get_last_modified() 
     374 
     375class FileSource(TransformSource): 
     376    def __init__(self, env, id, file): 
    246377        import re 
    247378        file = re.sub('[^a-zA-Z0-9._/-]', '', file)     # remove forbidden chars 
     
    256387        obj = os.path.join(env.get_htdocs_dir(), file) 
    257388 
    258     elif module == 'wiki' or module == 'ticket': 
     389        TransformSource.__init__(self, "file", id, file, obj) 
     390 
     391    def isFile(self): 
     392        return True 
     393 
     394    def getFile(self): 
     395        return self.obj 
     396 
     397    def getStream(self): 
     398        import urllib 
     399        return urllib.urlopen(self.obj) 
     400 
     401    def get_last_modified(self): 
     402        return os.stat(self.obj).st_mtime 
     403 
     404    def __str__(self): 
     405        return self.obj 
     406 
     407class AttachmentSource(TransformSource): 
     408    def __init__(self, env, module, id, file): 
    259409        from trac.attachment import Attachment 
    260         attachment = Attachment(env, module, id, file) 
    261         obj = attachment.path 
    262  
    263     elif module == 'url': 
     410        obj = Attachment(env, module, id, file) 
     411 
     412        TransformSource.__init__(self, module, id, file, obj) 
     413 
     414    def getStream(self): 
     415        return self.obj.open() 
     416 
     417    def get_last_modified(self): 
     418        return os.stat(self.obj.path).st_mtime 
     419 
     420    def __str__(self): 
     421        return self.obj.path 
     422 
     423class UrlSource(TransformSource): 
     424    def __init__(self, url): 
    264425        import urllib 
    265  
    266         if id: 
    267             raise Exception("unsupported url id '%s'" % id) 
    268  
    269426        try: 
    270            obj = urllib.urlopen(file
     427            obj = urllib.urlopen(url
    271428        except Exception, e: 
    272429            raise Exception('Could not read from url "%s": %s' % (file, e)) 
    273430 
    274     else: 
    275         raise Exception("unsupported module '%s'" % module) 
    276  
    277     return obj 
    278  
    279 def _close_obj(obj): 
    280     if hasattr(obj, 'close') and callable(obj.close): 
    281         obj.close() 
    282  
    283 def _obj_tostr(obj): 
    284     if isinstance(obj, str) or isinstance(obj, unicode): 
    285         return obj 
    286     if isinstance(obj, Node): 
    287         return obj.path 
    288     if hasattr(obj, 'url'): 
    289         return obj.url 
    290     return str(obj) 
    291  
    292 def _transform(style_obj, doc_obj, params): 
    293     import libxslt 
    294  
    295     try: 
    296         styledoc = _parse_xml(style_obj) 
    297     except Exception, e: 
    298         raise Exception("Error parsing %s: %s" % (_obj_tostr(style_obj), e)) 
    299  
    300     try: 
    301         doc = _parse_xml(doc_obj) 
    302     except Exception, e: 
    303         styledoc.freeDoc() 
    304         raise Exception("Error parsing %s: %s" % (_obj_tostr(doc_obj), e)) 
    305  
    306     style = libxslt.parseStylesheetDoc(styledoc) 
    307     if not style: 
    308         styledoc.freeDoc() 
    309         doc.freeDoc() 
    310         raise Exception("%s is not a valid stylesheet" % _obj_tostr(style_obj)) 
    311  
    312     result = style.applyStylesheet(doc, params) 
    313     try: 
    314         output = style.saveResultToString(result) 
    315     except Exception, e: 
    316         # detect empty result doc 
    317         if str(e) != 'error return without exception set': 
    318             raise e 
    319         output = '' 
    320  
    321     if result.get_type() == 'document_xml': 
    322         ct = 'text/xml' 
    323     elif result.get_type() == 'document_html': 
    324         ct = 'text/html' 
    325     elif result.get_type() == 'document_text': 
    326         ct = 'text/plain' 
    327     else: 
    328         ct = 'application/octet-stream' 
    329  
    330     style.freeStylesheet() 
    331     doc.freeDoc() 
    332     result.freeDoc() 
    333  
    334     return output, ct 
    335  
    336 def _parse_xml(obj): 
    337     import libxml2 
    338  
    339     if isinstance(obj, str) or isinstance(obj, unicode): 
    340         return libxml2.parseFile(obj) 
    341     if isinstance(obj, Node): 
    342         return libxml2.parseDoc(obj.get_content().read()) 
    343     if hasattr(obj, 'read') and callable(obj.read): 
    344         return libxml2.parseDoc(obj.read()) 
    345  
    346     raise Exception("unsupported object type '%s'" % type(obj)) 
     431        TransformSource.__init__(self, "url", None, url, obj) 
     432 
     433    def getStream(self): 
     434        return self.obj 
     435 
     436    def getUrl(self): 
     437        return self.file 
     438 
     439    def get_last_modified(self): 
     440        import time 
     441 
     442        lm = self.obj.info().getdate('Last-modified') 
     443        if lm: 
     444            return time.mktime(lm) 
     445        return time.time() 
     446 
     447    def __str__(self): 
     448        return self.obj.url 
    347449 
    348450class XsltProcessor(Component): 
     
    372474            raise TracError('Bad request') 
    373475 
    374         style_obj = _get_obj(self.env, req.hdf, *stylespec) 
    375         doc_obj   = _get_obj(self.env, req.hdf, *docspec) 
     476        style_obj = _get_src(self.env, req.hdf, *stylespec) 
     477        doc_obj   = _get_src(self.env, req.hdf, *docspec) 
    376478        params    = dict([(k[3:], req.args.get(k)) for k in req.args.keys() if k.startswith('xp_')]) 
    377479 
    378         lastmod = max(self._get_last_modified(style_obj), 
    379                       self._get_last_modified(doc_obj)) 
     480        lastmod = max(style_obj.get_last_modified(), 
     481                      doc_obj.get_last_modified()) 
    380482 
    381483        req.check_modified(lastmod) 
     
    384486                req.send_response(304) 
    385487                req.end_headers() 
    386                 _close_obj(style_obj) 
    387                 _close_obj(doc_obj) 
    388488                raise RequestDone 
    389489        if hasattr(req, '_headers'):    # 0.9 compatibility 
     
    392492            req.send_header('Last-Modified', http_date(lastmod)) 
    393493 
    394         try: 
    395             page, content_type = _transform(style_obj, doc_obj, params) 
    396         finally: 
    397             _close_obj(style_obj) 
    398             _close_obj(doc_obj) 
     494        page, content_type = _transform(style_obj, doc_obj, params, env, hdf) 
    399495 
    400496        req.send_response(200) 
     
    412508        raise RequestDone 
    413509 
    414     def _get_last_modified(self, obj): 
    415         import time 
    416  
    417         if isinstance(obj, str) or isinstance(obj, unicode): 
    418             return os.stat(obj).st_mtime 
    419         if isinstance(obj, Node): 
    420             return obj.get_last_modified() 
    421         if hasattr(obj, 'info') and callable(obj.info): 
    422             lm = obj.info().getdate('Last-modified') 
    423             if lm: 
    424                 return time.mktime(lm) 
    425         return time.time() 
    426