Synopsis - Synopsis/Formatters/HTML/View.py

File: Synopsis/Formatters/HTML/View.py

      1#
      2# Copyright (C) 2000 Stephen Davies
      3# Copyright (C) 2000 Stefan Seefeld
      4# All rights reserved.
      5# Licensed to the public under the terms of the GNU LGPL (>= 2),
      6# see the file COPYING for details.
      7#
      8
      9"""
     10View base class, contains base functionality and common interface for all Views.
     11"""
     12
     13from Synopsis.Processor import Parametrized, Parameter
     14from Synopsis.Formatters import open_file
     15from Tags import *
     16
     17import re, os
     18
     19class Format(Parametrized):
     20    """Default and base class for formatting a view layout. The Format
     21    class basically defines the HTML used at the start and end of the view.
     22    The default creates an XHTML compliant header and footer with a proper
     23    title, and link to the stylesheet."""
     24
     25    def init(self, processor, prefix):
     26
     27        self.prefix = prefix
     28
     29    def view_header(self, os, title, body, headextra, view):
     30        """Called to output the view header to the given output stream.
     31        @param os a file-like object (use os.write())
     32        @param title the title of this view
     33        @param body the body tag, which may contain extra parameters such as
     34        onLoad scripts, and may also be empty eg: for the frames index
     35        @param headextra extra html to put in the head section, such as
     36        scripts
     37        """
     38
     39        os.write('<?xml version="1.0" encoding="iso-8859-1"?>\n')
     40        os.write('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"\n')
     41        os.write('    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n')
     42        os.write('<html xmlns="http://www.w3.org/1999/xhtml" lang="en">\n')
     43        os.write('<!-- ' + view.filename() + ' -->\n')
     44        os.write('<!-- this view was generated by ' + view.__class__.__name__ + ' -->\n')
     45        os.write("<head>\n")
     46        os.write('<meta content="text/html; charset=iso-8859-1" http-equiv="Content-Type"/>\n')
     47        os.write(element('title','Synopsis - '+ title) + '\n')
     48        css = self.prefix + 'style.css'
     49        os.write(element('link', type='text/css', rel='stylesheet', href=css) + '\n')
     50        os.write(headextra)
     51        os.write("</head>\n%s\n"%body)
     52
     53    def view_footer(self, os, body):
     54        """Called to output the view footer to the given output stream.
     55        @param os a file-like object (use os.write())
     56        @param body the close body tag, which may be empty eg: for the frames
     57        index
     58        """
     59
     60        os.write("\n%s\n</html>\n"%body)
     61
     62class Template(Format):
     63    """Format subclass that uses a template file to define the HTML header
     64    and footer for each view."""
     65
     66    template = Parameter('', 'the html template file')
     67    copy_files = Parameter([], 'a list of files to be copied into the output dir')
     68
     69    def init(self, processor, prefix):
     70
     71        Format.init(self, processor, prefix)
     72        self.__re_body = re.compile('<body(?P<params>([ \t\n]+[-a-zA-Z0-9]+=("[^"]*"|\'[^\']*\'|[^ \t\n>]*))*)>', re.I)
     73        self.__re_closebody = re.compile('</body>', re.I)
     74        self.__re_closehead = re.compile('</head>', re.I)
     75        self.__title_tag = '@TITLE@'
     76        self.__content_tag = '@CONTENT@'
     77        for file in self.copy_files:
     78            processor.file_layout.copy_file(file, file)
     79        self.load_file()
     80
     81    def load_file(self):
     82        """Loads and parses the template file"""
     83
     84        f = open(self.template, 'rt')
     85        text = f.read(1024*64) # arbitrary max limit of 64kb
     86        f.close()
     87        # Find the content tag
     88        content_index = text.find(self.__content_tag)
     89        if content_index == -1:
     90            print "Fatal: content tag '%s' not found in template file!"%self.__content_tag
     91            raise SystemError, "Content tag not found"
     92        header = text[:content_index]
     93        # Find the title (doesn't matter if not found)
     94        self.__title_index = text.find(self.__title_tag)
     95        if self.__title_index != -1:
     96            # Remove the title tag
     97            header = header[:self.__title_index] +header[self.__title_index+len(self.__title_tag):]
     98        # Find the close head tag
     99        mo = self.__re_closehead.search(header)
    100        if mo: self.__headextra_index = mo.start()
    101        else: self.__headextra_index = -1
    102        # Find the body tag
    103        mo = self.__re_body.search(header)
    104        if not mo:
    105            print "Fatal: body tag not found in template file!"
    106            print "(if you are sure there is one, this may be a bug in Synopsis)"
    107            raise SystemError, "Body tag not found"
    108        if mo.group('params'): self.__body_params = mo.group('params')
    109        else: self.__body_params = ''
    110        self.__body_index = mo.start()
    111        header = header[:mo.start()] + header[mo.end():]
    112        # Store the header
    113        self.__header = header
    114        footer = text[content_index+len(self.__content_tag):]
    115        # Find the close body tag
    116        mo = self.__re_closebody.search(footer)
    117        if not mo:
    118            print "Fatal: close body tag not found in template file"
    119            raise SystemError, "Close body tag not found"
    120        self.__closebody_index = mo.start()
    121        footer = footer[:mo.start()] + footer[mo.end():]
    122        self.__footer = footer
    123
    124    def write(self, os, text):
    125        """Writes the text to the output stream, replaceing @PREFIX@ with the
    126        prefix for this file"""
    127
    128        sections = text.split('@PREFIX@')
    129        os.write(self.prefix.join(sections))
    130
    131    def view_header(self, os, title, body, headextra, view):
    132        """Formats the header using the template file"""
    133
    134        if not body: return Format.view_header(self, os, title, body, headextra)
    135        header = self.__header
    136        index = 0
    137        if self.__title_index != -1:
    138            self.write(os, header[:self.__title_index])
    139            self.write(os, title)
    140            index = self.__title_index
    141        if self.__headextra_index != -1:
    142            self.write(os, header[index:self.__headextra_index])
    143            self.write(os, headextra)
    144            index = self.__headextra_index
    145        self.write(os, header[index:self.__body_index])
    146        if body:
    147            if body[-1] == '>':
    148                self.write(os, body[:-1]+self.__body_params+body[-1])
    149            else:
    150                # Hmmmm... Should not happen, perhaps use regex?
    151                self.write(os, body)
    152        self.write(os, header[self.__body_index:])
    153
    154    def view_footer(self, os, body):
    155        """Formats the footer using the template file"""
    156
    157        if not body: return Format.view_footer(self, os, body)
    158        footer = self.__footer
    159        self.write(os, footer[:self.__closebody_index])
    160        self.write(os, body)
    161        self.write(os, footer[self.__closebody_index:])
    162
    163class View(Parametrized):
    164    """Base class for Views. The base class provides a common interface, and
    165    also handles common operations such as opening the file, and delegating
    166    the view formatting to a strategy class."""
    167
    168    template = Parameter(Format(), 'the object that provides the html template for the view')
    169
    170    def __init__(self, **kwds):
    171
    172        super(View, self).__init__(**kwds)
    173        self.main = False
    174
    175    def register(self, frame):
    176        """Registers this View class with its frame."""
    177
    178        self.frame = frame
    179        self.directory_layout = self.frame.processor.directory_layout
    180        self.processor = frame.processor
    181        self.__os = None
    182
    183    def filename(self):
    184        """Return the filename (currently) associated with the view."""
    185
    186        return ''
    187
    188    def title(self):
    189        """Return the title (currently) associated with the view."""
    190
    191        return ''
    192
    193    def root(self):
    194        """Return a pair of (url, label) to link to the entry point of this view."""
    195
    196        return None, None
    197
    198    def write_navigation_bar(self):
    199        """Generate a navigation bar for this view."""
    200
    201        self.write(self.frame.navigation_bar(self) + '\n')
    202
    203    def os(self):
    204        "Returns the output stream opened with start_file"
    205
    206        return self.__os
    207
    208    def write(self, str):
    209        """Writes the given string to the currently opened file"""
    210
    211        self.__os.write(str)
    212
    213    def register_filenames(self):
    214        """Register filenames for each file this View will generate."""
    215
    216        pass
    217
    218    def toc(self):
    219        """Retrieves the TOC for this view. This method assumes that the view
    220        generates info for the the whole ASG, which could be the Scope,
    221        the Source (source code) or the XRef (cross reference info).
    222        The default implementation returns None."""
    223
    224        pass
    225
    226    def process(self):
    227        """Process the ASG, creating view-specific html pages."""
    228
    229        pass
    230
    231    def open_file(self):
    232        """Returns a new output stream. This template method is for internal
    233        use only, but may be overriden in derived classes.
    234        The default joins output dir and self.filename()"""
    235
    236        path = os.path.join(self.processor.output, self.filename())
    237        return open_file(path)
    238
    239    def close_file(self):
    240        """Closes the internal output stream. This template method is for
    241        internal use only, but may be overriden in derived classes."""
    242
    243        self.__os.close()
    244        self.__os = None
    245
    246    def start_file(self, body='', headextra=''):
    247        """Start a new file with given filename, title and body. This method
    248        opens a file for writing, and writes the html header crap at the top.
    249        You must specify a title, which is prepended with the project name.
    250        The body argument is optional, and it is preferred to use stylesheets
    251        for that sort of stuff. You may want to put an onLoad handler in it
    252        though in which case that's the place to do it. The opened file is
    253        stored and can be accessed using the os() method."""
    254
    255        self.__os = self.open_file()
    256        prefix = rel(self.filename(), '')
    257        self.template.init(self.processor, prefix)
    258        if not body:
    259            body = '<body class="%s">'%self.__class__.__name__
    260        self.template.view_header(self.__os, self.title(), body, headextra, self)
    261
    262    def end_file(self, body='</body>'):
    263        """Close the file using given close body tag. The default is
    264        just a close body tag, but if you specify '' then nothing will be
    265        written (useful for a frames view)"""
    266
    267        self.template.view_footer(self.__os, body)
    268        self.close_file()
    269
    270    def reference(self, name, scope, label=None, **keys):
    271        """Returns a reference to the given name. The name is a scoped name,
    272        and the optional label is an alternative name to use as the link text.
    273        The name is looked up in the TOC so the link may not be local. The
    274        optional keys are appended as attributes to the A tag."""
    275
    276        if not label: label = escape(str(scope.prune(name)))
    277        entry = self.processor.toc[name]
    278        if entry: return href(rel(self.filename(), entry.link), label, **keys)
    279        return label or ''
    280

Generated on Thu Apr 16 16:27:16 2009 by
synopsis (version devel)