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 + 'synopsis.css'
49 os.write(element('link', type='text/css', rel='stylesheet', href=css) + '\n')
50 js = self.prefix + 'synopsis.js'
51 os.write(element('script', '', type='text/javascript', src=js) + '\n')
52 os.write(headextra)
53 os.write("</head>\n%s\n"%body)
54
55 def view_footer(self, os, body):
56 """Called to output the view footer to the given output stream.
57 @param os a file-like object (use os.write())
58 @param body the close body tag, which may be empty eg: for the frames
59 index
60 """
61
62 os.write("\n%s\n</html>\n"%body)
63
64class Template(Format):
65 """Format subclass that uses a template file to define the HTML header
66 and footer for each view."""
67
68 template = Parameter('', 'the html template file')
69 copy_files = Parameter([], 'a list of files to be copied into the output dir')
70
71 def init(self, processor, prefix):
72
73 Format.init(self, processor, prefix)
74 self.__re_body = re.compile('<body(?P<params>([ \t\n]+[-a-zA-Z0-9]+=("[^"]*"|\'[^\']*\'|[^ \t\n>]*))*)>', re.I)
75 self.__re_closebody = re.compile('</body>', re.I)
76 self.__re_closehead = re.compile('</head>', re.I)
77 self.__title_tag = '@TITLE@'
78 self.__content_tag = '@CONTENT@'
79 for file in self.copy_files:
80 processor.file_layout.copy_file(file, file)
81 self.load_file()
82
83 def load_file(self):
84 """Loads and parses the template file"""
85
86 f = open(self.template, 'rt')
87 text = f.read(1024*64)
88 f.close()
89
90 content_index = text.find(self.__content_tag)
91 if content_index == -1:
92 print "Fatal: content tag '%s' not found in template file!"%self.__content_tag
93 raise SystemError, "Content tag not found"
94 header = text[:content_index]
95
96 self.__title_index = text.find(self.__title_tag)
97 if self.__title_index != -1:
98
99 header = header[:self.__title_index] +header[self.__title_index+len(self.__title_tag):]
100
101 mo = self.__re_closehead.search(header)
102 if mo: self.__headextra_index = mo.start()
103 else: self.__headextra_index = -1
104
105 mo = self.__re_body.search(header)
106 if not mo:
107 print "Fatal: body tag not found in template file!"
108 print "(if you are sure there is one, this may be a bug in Synopsis)"
109 raise SystemError, "Body tag not found"
110 if mo.group('params'): self.__body_params = mo.group('params')
111 else: self.__body_params = ''
112 self.__body_index = mo.start()
113 header = header[:mo.start()] + header[mo.end():]
114
115 self.__header = header
116 footer = text[content_index+len(self.__content_tag):]
117
118 mo = self.__re_closebody.search(footer)
119 if not mo:
120 print "Fatal: close body tag not found in template file"
121 raise SystemError, "Close body tag not found"
122 self.__closebody_index = mo.start()
123 footer = footer[:mo.start()] + footer[mo.end():]
124 self.__footer = footer
125
126 def write(self, os, text):
127 """Writes the text to the output stream, replaceing @PREFIX@ with the
128 prefix for this file"""
129
130 sections = text.split('@PREFIX@')
131 os.write(self.prefix.join(sections))
132
133 def view_header(self, os, title, body, headextra, view):
134 """Formats the header using the template file"""
135
136 if not body: return Format.view_header(self, os, title, body, headextra)
137 header = self.__header
138 index = 0
139 if self.__title_index != -1:
140 self.write(os, header[:self.__title_index])
141 self.write(os, title)
142 index = self.__title_index
143 if self.__headextra_index != -1:
144 self.write(os, header[index:self.__headextra_index])
145 self.write(os, headextra)
146 index = self.__headextra_index
147 self.write(os, header[index:self.__body_index])
148 if body:
149 if body[-1] == '>':
150 self.write(os, body[:-1]+self.__body_params+body[-1])
151 else:
152
153 self.write(os, body)
154 self.write(os, header[self.__body_index:])
155
156 def view_footer(self, os, body):
157 """Formats the footer using the template file"""
158
159 if not body: return Format.view_footer(self, os, body)
160 footer = self.__footer
161 self.write(os, footer[:self.__closebody_index])
162 self.write(os, body)
163 self.write(os, footer[self.__closebody_index:])
164
165class View(Parametrized):
166 """Base class for Views. The base class provides a common interface, and
167 also handles common operations such as opening the file, and delegating
168 the view formatting to a strategy class."""
169
170 template = Parameter(Format(), 'the object that provides the html template for the view')
171
172 def __init__(self, **kwds):
173
174 super(View, self).__init__(**kwds)
175 self.main = False
176 self._id_counter = 0
177
178
179 def generate_id(self):
180 """Generate an id that is (at least) unique on a particular view,
181 and thus, html document."""
182
183 c = self._id_counter
184 self._id_counter += 1
185 return c
186
187 def register(self, frame):
188 """Registers this View class with its frame."""
189
190 self.frame = frame
191 self.directory_layout = self.frame.processor.directory_layout
192 self.processor = frame.processor
193 self.__os = None
194
195 def filename(self):
196 """Return the filename (currently) associated with the view."""
197
198 return ''
199
200 def title(self):
201 """Return the title (currently) associated with the view."""
202
203 return ''
204
205 def root(self):
206 """Return a pair of (url, label) to link to the entry point of this view."""
207
208 return None, None
209
210 def write_navigation_bar(self):
211 """Generate a navigation bar for this view."""
212
213 self.write(self.frame.navigation_bar(self) + '\n')
214
215 def os(self):
216 "Returns the output stream opened with start_file"
217
218 return self.__os
219
220 def write(self, str):
221 """Writes the given string to the currently opened file"""
222
223 self.__os.write(str)
224
225 def register_filenames(self):
226 """Register filenames for each file this View will generate."""
227
228 pass
229
230 def toc(self):
231 """Retrieves the TOC for this view. This method assumes that the view
232 generates info for the the whole ASG, which could be the Scope,
233 the Source (source code) or the XRef (cross reference info).
234 The default implementation returns None."""
235
236 pass
237
238 def process(self):
239 """Process the ASG, creating view-specific html pages."""
240
241 pass
242
243 def open_file(self):
244 """Returns a new output stream. This template method is for internal
245 use only, but may be overriden in derived classes.
246 The default joins output dir and self.filename()"""
247
248 path = os.path.join(self.processor.output, self.filename())
249 return open_file(path)
250
251 def close_file(self):
252 """Closes the internal output stream. This template method is for
253 internal use only, but may be overriden in derived classes."""
254
255 self.__os.close()
256 self.__os = None
257
258 def start_file(self, body='', headextra=''):
259 """Start a new file with given filename, title and body. This method
260 opens a file for writing, and writes the html header crap at the top.
261 You must specify a title, which is prepended with the project name.
262 The body argument is optional, and it is preferred to use stylesheets
263 for that sort of stuff. You may want to put an onLoad handler in it
264 though in which case that's the place to do it. The opened file is
265 stored and can be accessed using the os() method."""
266
267 self.__os = self.open_file()
268 prefix = rel(self.filename(), '')
269 self.template.init(self.processor, prefix)
270 if not body:
271 body = '<body class="%s" onload="load()">'%self.__class__.__name__
272 self.template.view_header(self.__os, self.title(), body, headextra, self)
273
274 def end_file(self, body='</body>'):
275 """Close the file using given close body tag. The default is
276 just a close body tag, but if you specify '' then nothing will be
277 written (useful for a frames view)"""
278
279 self.template.view_footer(self.__os, body)
280 self.close_file()
281
282 def reference(self, name, scope, label=None, **keys):
283 """Returns a reference to the given name. The name is a scoped name,
284 and the optional label is an alternative name to use as the link text.
285 The name is looked up in the TOC so the link may not be local. The
286 optional keys are appended as attributes to the A tag."""
287
288 if not label: label = escape(str(scope.prune(name)))
289 entry = self.processor.toc[name]
290 if entry: return href(rel(self.filename(), entry.link), label, **keys)
291 return label or ''
292
Generated on Tue Jul 20 09:07:14 2010 by
synopsis (version devel)