File: Synopsis/Formatters/HTML/View.py 1
2
3
4
5
6
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)
86 f.close()
87
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
94 self.__title_index = text.find(self.__title_tag)
95 if self.__title_index != -1:
96
97 header = header[:self.__title_index] +header[self.__title_index+len(self.__title_tag):]
98
99 mo = self.__re_closehead.search(header)
100 if mo: self.__headextra_index = mo.start()
101 else: self.__headextra_index = -1
102
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
113 self.__header = header
114 footer = text[content_index+len(self.__content_tag):]
115
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
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)