Modules |
Files |
Inheritance Tree |
Inheritance Graph |
Name Index |
Config
File: Synopsis/Formatter/HTML/Page.py
1| # $Id: Page.py,v 1.16 2003/01/16 12:46:46 chalky Exp $
2| #
3| # This file is a part of Synopsis.
4| # Copyright (C) 2000, 2001 Stephen Davies
5| # Copyright (C) 2000, 2001 Stefan Seefeld
6| #
7| # Synopsis is free software; you can redistribute it and/or modify it
8| # under the terms of the GNU General Public License as published by
9| # the Free Software Foundation; either version 2 of the License, or
10| # (at your option) any later version.
11| #
12| # This program is distributed in the hope that it will be useful,
13| # but WITHOUT ANY WARRANTY; without even the implied warranty of
14| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15| # General Public License for more details.
16| #
17| # You should have received a copy of the GNU General Public License
18| # along with this program; if not, write to the Free Software
19| # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
20| # 02111-1307, USA.
21| #
22| # $Log: Page.py,v $
23| # Revision 1.16 2003/01/16 12:46:46 chalky
24| # Renamed FilePages to FileSource, FileTree to FileListing. Added FileIndexer
25| # (used to be part of FileTree) and FileDetails.
26| #
27| # Revision 1.15 2002/11/16 04:12:33 chalky
28| # Added strategies for page formatting, and added one to allow template HTML
29| # files to be used.
30| #
31| # Revision 1.14 2002/11/01 03:39:21 chalky
32| # Cleaning up HTML after using 'htmltidy'
33| #
34| # Revision 1.13 2002/10/29 12:43:56 chalky
35| # Added flexible TOC support to link to things other than ScopePages
36| #
37| # Revision 1.12 2002/07/19 14:26:33 chalky
38| # Revert prefix in FileLayout but keep relative referencing elsewhere.
39| #
40| # Revision 1.10 2002/01/09 11:43:41 chalky
41| # Inheritance pics
42| #
43| # Revision 1.9 2002/01/09 10:16:35 chalky
44| # Centralized navigation, clicking links in (html) docs works.
45| #
46| # Revision 1.8 2001/11/09 15:35:04 chalky
47| # GUI shows HTML pages. just. Source window also scrolls to correct line.
48| #
49| # Revision 1.7 2001/07/05 05:39:58 stefan
50| # advanced a lot in the refactoring of the HTML module.
51| # Page now is a truely polymorphic (abstract) class. Some derived classes
52| # implement the 'filename()' method as a constant, some return a variable
53| # dependent on what the current scope is...
54| #
55| # Revision 1.6 2001/07/05 02:08:35 uid20151
56| # Changed the registration of pages to be part of a two-phase construction
57| #
58| # Revision 1.5 2001/06/28 07:22:18 stefan
59| # more refactoring/cleanup in the HTML formatter
60| #
61| # Revision 1.4 2001/06/26 04:32:16 stefan
62| # A whole slew of changes mostly to fix the HTML formatter's output generation,
63| # i.e. to make the output more robust towards changes in the layout of files.
64| #
65| # the rpm script now works, i.e. it generates source and binary packages.
66| #
67| # Revision 1.3 2001/02/05 05:26:24 chalky
68| # Graphs are separated. Misc changes
69| #
70| # Revision 1.2 2001/02/01 15:23:24 chalky
71| # Copywritten brown paper bag edition.
72| #
73| #
74|
75| """
76| Page base class, contains base functionality and common interface for all Pages.
77| """
78|
79| import os.path, cStringIO
80| from Synopsis.Core import Util
81|
82| import core
83| from core import config
84| from Tags import *
85|
86| class PageFormat:
87| """Default and base class for formatting a page layout. The PageFormat
88| class basically defines the HTML used at the start and end of the page.
89| The default creates an XHTML compliant header and footer with a proper
90| title, and link to the stylesheet."""
91| def __init__(self):
92| self.__stylesheet = config.stylesheet
93| self.__prefix = ''
94|
95| def set_prefix(self, prefix):
96| """Sets the prefix to use to correctly reference files in the document
97| root directory."""
98| self.__prefix = prefix
99|
100| def stylesheet(self):
101| """Returns the relative filename of the stylesheet to use. The
102| stylesheet specified in the user's config is copied into the output
103| directory. If this page is not in the same directory, the url returned
104| from this function will have the appropriate number of '..'s added."""
105| return self.__prefix + self.__stylesheet
106|
107| def prefix(self):
108| """Returns the prefix to use to correctly reference files in the
109| document root directory. This will only ever not be '' if you are using the
110| NestedFileLayout, in which case it will be '' or '../' or '../../' etc
111| as appropraite."""
112| return self.__prefix
113|
114| def page_header(self, os, title, body, headextra):
115| """Called to output the page header to the given output stream.
116| @param os a file-like object (use os.write())
117| @param title the title of this page
118| @param body the body tag, which may contain extra parameters such as
119| onLoad scripts, and may also be empty eg: for the frames index
120| @param headextra extra html to put in the head section, such as
121| scripts
122| """
123| os.write('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">')
124| os.write("<html>\n<head>\n")
125| os.write(entity('title','Synopsis - '+ title) + '\n')
126| ss = self.stylesheet()
127| if ss: os.write(solotag('link', type='text/css', rel='stylesheet', href=ss) + '\n')
128| os.write(headextra)
129| os.write("</head>\n%s\n"%body)
130|
131| def page_footer(self, os, body):
132| """Called to output the page footer to the given output stream.
133| @param os a file-like object (use os.write())
134| @param body the close body tag, which may be empty eg: for the frames
135| index
136| """
137| os.write("\n%s\n</html>\n"%body)
138|
139| class TemplatePageFormat (PageFormat):
140| """PageFormat subclass that uses a template file to define the HTML header
141| and footer for each page."""
142| def __init__(self):
143| PageFormat.__init__(self)
144| self.__file = ''
145| self.__re_body = re.compile('<body(?P<params>([ \t\n]+[-a-zA-Z0-9]+=("[^"]*"|\'[^\']*\'|[^ \t\n>]*))*)>', re.I)
146| self.__re_closebody = re.compile('</body>', re.I)
147| self.__re_closehead = re.compile('</head>', re.I)
148| self.__title_tag = '@TITLE@'
149| self.__content_tag = '@CONTENT@'
150| if hasattr(config.obj, 'TemplatePageFormat'):
151| myconfig = config.obj.TemplatePageFormat
152| if hasattr(myconfig, 'file'):
153| self.__file = myconfig.file
154| if hasattr(myconfig, 'copy_files'):
155| for file in myconfig.copy_files:
156| dest = os.path.join(config.basename, file[1])
157| config.files.copyFile(file[0], dest)
158| self.load_file()
159|
160| def load_file(self):
161| """Loads and parses the template file"""
162| f = open(self.__file, 'rt')
163| text = f.read(1024*64) # arbitrary max limit of 64kb
164| f.close()
165| # Find the content tag
166| content_index = text.find(self.__content_tag)
167| if content_index == -1:
168| print "Fatal: content tag '%s' not found in template file!"%self.__content_tag
169| raise SystemError, "Content tag not found"
170| header = text[:content_index]
171| # Find the title (doesn't matter if not found)
172| self.__title_index = text.find(self.__title_tag)
173| if self.__title_index:
174| # Remove the title tag
175| header = header[:self.__title_index] + \
176| header[self.__title_index+len(self.__title_tag):]
177| # Find the close head tag
178| mo = self.__re_closehead.search(header)
179| if mo: self.__headextra_index = mo.start()
180| else: self.__headextra_index = -1
181| # Find the body tag
182| mo = self.__re_body.search(header)
183| if not mo:
184| print "Fatal: body tag not found in template file!"
185| print "(if you are sure there is one, this may be a bug in Synopsis)"
186| raise SystemError, "Body tag not found"
187| if mo.group('params'): self.__body_params = mo.group('params')
188| else: self.__body_params = ''
189| self.__body_index = mo.start()
190| header = header[:mo.start()] + header[mo.end():]
191| # Store the header
192| self.__header = header
193| footer = text[content_index+len(self.__content_tag):]
194| # Find the close body tag
195| mo = self.__re_closebody.search(footer)
196| if not mo:
197| print "Fatal: close body tag not found in template file"
198| raise SystemError, "Close body tag not found"
199| self.__closebody_index = mo.start()
200| footer = footer[:mo.start()] + footer[mo.end():]
201| self.__footer = footer
202|
203| def write(self, os, text):
204| """Writes the text to the output stream, replaceing @PREFIX@ with the
205| prefix for this file"""
206| sections = string.split(text, '@PREFIX@')
207| os.write(string.join(sections, self.prefix()))
208|
209| def page_header(self, os, title, body, headextra):
210| """Formats the header using the template file"""
211| if not body: return PageFormat.page_header(self, os, title, body, headextra)
212| header = self.__header
213| index = 0
214| if self.__title_index != -1:
215| self.write(os, header[:self.__title_index])
216| self.write(os, title)
217| index = self.__title_index
218| if self.__headextra_index != -1:
219| self.write(os, header[index:self.__headextra_index])
220| self.write(os, headextra)
221| index = self.__headextra_index
222| self.write(os, header[index:self.__body_index])
223| if body:
224| if body[-1] == '>':
225| self.write(os, body[:-1]+self.__body_params+body[-1])
226| else:
227| # Hmmmm... Should not happen, perhaps use regex?
228| self.write(os, body)
229| self.write(os, header[self.__body_index:])
230|
231| def page_footer(self, os, body):
232| """Formats the footer using the template file"""
233| if not body: return PageFormat.page_footer(self, os, body)
234| footer = self.__footer
235| self.write(os, footer[:self.__closebody_index])
236| self.write(os, body)
237| self.write(os, footer[self.__closebody_index:])
238|
239| class Page:
240| """Base class for a Page. The base class provides a common interface, and
241| also handles common operations such as opening the file, and delegating
242| the page formatting to a strategy class.
243| @see PageFormat"""
244| def __init__(self, manager):
245| """Constructor, loads the formatting class.
246| @see PageFormat"""
247| self.manager = manager
248| self.__os = None
249| format_class = PageFormat
250| if config.page_format:
251| format_class = Util.import_object(config.page_format, basePackage = 'Synopsis.Formatter.HTML.Page.')
252| self.__format = format_class()
253|
254| def filename(self):
255| "Polymorphic method returning the filename associated with the page"
256| return ''
257| def title(self):
258| "Polymorphic method returning the title associated with the page"
259| return ''
260|
261| def os(self):
262| "Returns the output stream opened with start_file"
263| return self.__os
264|
265| def write(self, str):
266| """Writes the given string to the currently opened file"""
267| self.__os.write(str)
268|
269| def register(self):
270| """Registers this Page class with the PageManager. This method is
271| abstract - derived Pages should implement it to call the appropriate
272| methods in PageManager if they need to. This method is called after
273| construction."""
274| pass
275|
276| def register_filenames(self, start):
277| """Registers filenames for each file this Page will generate, given
278| the starting Scope."""
279| pass
280|
281| def get_toc(self, start):
282| """Retrieves the TOC for this page. This method assumes that the page
283| generates info for the the whole AST, which could be the ScopePages,
284| the FilePages (source code) or the XRefPages (cross reference info).
285| The default implementation returns None. Start is the declaration to
286| start processing from, which could be the global namespace."""
287| pass
288|
289| def process(self, start):
290| """Process the given Scope recursively. This is the method which is
291| called to actually create the files, so you probably want to override
292| it ;)"""
293| pass
294|
295| def process_scope(self, scope):
296| """Process just the given scope"""
297| pass
298|
299| def open_file(self):
300| """Returns a new output stream. This template method is for internal
301| use only, but may be overriden in derived classes.
302| The default joins config.basename and self.filename()
303| and uses Util.open()"""
304| return Util.open(os.path.join(config.basename, self.filename()))
305|
306| def close_file(self):
307| """Closes the internal output stream. This template method is for
308| internal use only, but may be overriden in derived classes."""
309| self.__os.close()
310| self.__os = None
311|
312| def start_file(self, body='<body>', headextra=''):
313| """Start a new file with given filename, title and body. This method
314| opens a file for writing, and writes the html header crap at the top.
315| You must specify a title, which is prepended with the project name.
316| The body argument is optional, and it is preferred to use stylesheets
317| for that sort of stuff. You may want to put an onLoad handler in it
318| though in which case that's the place to do it. The opened file is
319| stored and can be accessed using the os() method."""
320| self.__os = self.open_file()
321| prefix = rel(self.filename(), '')
322| self.__format.set_prefix(prefix)
323| self.__format.page_header(self.__os, self.title(), body, headextra)
324|
325| def end_file(self, body='</body>'):
326| """Close the file using given close body tag. The default is
327| just a close body tag, but if you specify '' then nothing will be
328| written (useful for a frames page)"""
329| self.__format.page_footer(self.__os, body)
330| self.close_file()
331|
332| def reference(self, name, scope, label=None, **keys):
333| """Returns a reference to the given name. The name is a scoped name,
334| and the optional label is an alternative name to use as the link text.
335| The name is looked up in the TOC so the link may not be local. The
336| optional keys are appended as attributes to the A tag."""
337| if not label: label = anglebrackets(Util.ccolonName(name, scope))
338| entry = config.toc[name]
339| if entry: return apply(href, (rel(self.filename(), entry.link), label), keys)
340| return label or ''
341|
342|
343| class BufferPage (Page):
344| """A page that writes to a string buffer."""
345| def _take_control(self):
346| self.open_file = lambda s=self: BufferPage.open_file(s)
347| self.close_file = lambda s=self: BufferPage.close_file(s)
348| self.get_buffer = lambda s=self: BufferPage.get_buffer(s)
349|
350| def open_file(self):
351| "Returns a new StringIO"
352| return cStringIO.StringIO()
353|
354| def close_file(self):
355| "Does nothing."
356| pass
357|
358| def get_buffer(self):
359| """Returns the page as a string, then deletes the internal buffer"""
360| page = self.os().getvalue()
361| # NOW we do the close
362| Page.close_file(self)
363| return page