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