~ubuntu-branches/ubuntu/karmic/python-docutils/karmic

« back to all changes in this revision

Viewing changes to docutils/writers/s5_html/__init__.py

  • Committer: Bazaar Package Importer
  • Author(s): martin f. krafft
  • Date: 2006-07-10 11:45:05 UTC
  • mfrom: (2.1.4 edgy)
  • Revision ID: james.westby@ubuntu.com-20060710114505-otkhqcslevewxmz5
Tags: 0.4-3
Added build dependency on python-central (closes: #377580).

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Author: Chris Liechti
 
2
# Contact: cliechti@gmx.net
 
3
# Author: David Goodger
 
4
# Contact: goodger@python.org
 
5
# Revision: $Revision: 4223 $
 
6
# Date: $Date: 2005-12-18 00:52:30 +0100 (Sun, 18 Dec 2005) $
 
7
# Copyright: This module has been placed in the public domain.
 
8
 
 
9
"""
 
10
S5/HTML Slideshow Writer.
 
11
"""
 
12
 
 
13
__docformat__ = 'reStructuredText'
 
14
 
 
15
 
 
16
import sys
 
17
import os
 
18
import re
 
19
import docutils
 
20
from docutils import frontend, nodes, utils
 
21
from docutils.writers import html4css1
 
22
from docutils.parsers.rst import directives
 
23
 
 
24
themes_dir_path = utils.relative_path(
 
25
    os.path.join(os.getcwd(), 'dummy'),
 
26
    os.path.join(os.path.dirname(__file__), 'themes'))
 
27
 
 
28
def find_theme(name):
 
29
    # Where else to look for a theme?
 
30
    # Check working dir?  Destination dir?  Config dir?  Plugins dir?
 
31
    path = os.path.join(themes_dir_path, name)
 
32
    if not os.path.isdir(path):
 
33
        raise docutils.ApplicationError(
 
34
            'Theme directory not found: %r (path: %r)' % (name, path))
 
35
    return path
 
36
 
 
37
 
 
38
class Writer(html4css1.Writer):
 
39
 
 
40
    settings_spec = html4css1.Writer.settings_spec + (
 
41
        'S5 Slideshow Specific Options',
 
42
        'For the S5/HTML writer, the --no-toc-backlinks option '
 
43
        '(defined in General Docutils Options above) is the default, '
 
44
        'and should not be changed.',
 
45
        (('Specify an installed S5 theme by name.  Overrides --theme-url.  '
 
46
          'The default theme name is "default".  The theme files will be '
 
47
          'copied into a "ui/<theme>" directory, in the same directory as the '
 
48
          'destination file (output HTML).  Note that existing theme files '
 
49
          'will not be overwritten (unless --overwrite-theme-files is used).',
 
50
          ['--theme'],
 
51
          {'default': 'default', 'metavar': '<name>',
 
52
           'overrides': 'theme_url'}),
 
53
         ('Specify an S5 theme URL.  The destination file (output HTML) will '
 
54
          'link to this theme; nothing will be copied.  Overrides --theme.',
 
55
          ['--theme-url'],
 
56
          {'metavar': '<URL>', 'overrides': 'theme'}),
 
57
         ('Allow existing theme files in the ``ui/<theme>`` directory to be '
 
58
          'overwritten.  The default is not to overwrite theme files.',
 
59
          ['--overwrite-theme-files'],
 
60
          {'action': 'store_true'}),
 
61
         ('Keep existing theme files in the ``ui/<theme>`` directory; do not '
 
62
          'overwrite any.  This is the default.',
 
63
          ['--keep-theme-files'],
 
64
          {'dest': 'overwrite_theme_files', 'action': 'store_false'}),
 
65
         ('Enable the current slide indicator ("1 / 15").  '
 
66
          'The default is to disable it.',
 
67
          ['--current-slide'],
 
68
          {'action': 'store_true'}),
 
69
         ('Disable the current slide indicator.  This is the default.',
 
70
          ['--no-current-slide'],
 
71
          {'dest': 'current_slide', 'action': 'store_false'}),))
 
72
 
 
73
    settings_default_overrides = {'toc_backlinks': 0}
 
74
 
 
75
    config_section = 's5_html writer'
 
76
    config_section_dependencies = ('writers', 'html4css1 writer')
 
77
 
 
78
    def __init__(self):
 
79
        html4css1.Writer.__init__(self)
 
80
        self.translator_class = S5HTMLTranslator
 
81
 
 
82
 
 
83
class S5HTMLTranslator(html4css1.HTMLTranslator):
 
84
 
 
85
    doctype = (
 
86
        '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"'
 
87
        ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n')
 
88
 
 
89
    s5_stylesheet_template = """\
 
90
<!-- configuration parameters -->
 
91
<meta name="defaultView" content="slideshow" />
 
92
<meta name="controlVis" content="hidden" />
 
93
<!-- style sheet links -->
 
94
<script src="%(path)s/slides.js" type="text/javascript"></script>
 
95
<link rel="stylesheet" href="%(path)s/slides.css"
 
96
      type="text/css" media="projection" id="slideProj" />
 
97
<link rel="stylesheet" href="%(path)s/outline.css"
 
98
      type="text/css" media="screen" id="outlineStyle" />
 
99
<link rel="stylesheet" href="%(path)s/print.css"
 
100
      type="text/css" media="print" id="slidePrint" />
 
101
<link rel="stylesheet" href="%(path)s/opera.css"
 
102
      type="text/css" media="projection" id="operaFix" />\n"""
 
103
    # The script element must go in front of the link elements to
 
104
    # avoid a flash of unstyled content (FOUC), reproducible with
 
105
    # Firefox.
 
106
 
 
107
    disable_current_slide = """
 
108
<style type="text/css">
 
109
#currentSlide {display: none;}
 
110
</style>\n"""
 
111
 
 
112
    layout_template = """\
 
113
<div class="layout">
 
114
<div id="controls"></div>
 
115
<div id="currentSlide"></div>
 
116
<div id="header">
 
117
%(header)s
 
118
</div>
 
119
<div id="footer">
 
120
%(title)s%(footer)s
 
121
</div>
 
122
</div>\n"""
 
123
# <div class="topleft"></div>
 
124
# <div class="topright"></div>
 
125
# <div class="bottomleft"></div>
 
126
# <div class="bottomright"></div>
 
127
 
 
128
    default_theme = 'default'
 
129
    """Name of the default theme."""
 
130
 
 
131
    base_theme_file = '__base__'
 
132
    """Name of the file containing the name of the base theme."""
 
133
 
 
134
    direct_theme_files = (
 
135
        'slides.css', 'outline.css', 'print.css', 'opera.css', 'slides.js')
 
136
    """Names of theme files directly linked to in the output HTML"""
 
137
 
 
138
    indirect_theme_files = (
 
139
        's5-core.css', 'framing.css', 'pretty.css', 'blank.gif', 'iepngfix.htc')
 
140
    """Names of files used indirectly; imported or used by files in
 
141
    `direct_theme_files`."""
 
142
 
 
143
    required_theme_files = indirect_theme_files + direct_theme_files
 
144
    """Names of mandatory theme files."""
 
145
 
 
146
    def __init__(self, *args):
 
147
        html4css1.HTMLTranslator.__init__(self, *args)
 
148
        #insert S5-specific stylesheet and script stuff:
 
149
        self.theme_file_path = None
 
150
        self.setup_theme()
 
151
        self.stylesheet.append(self.s5_stylesheet_template
 
152
                               % {'path': self.theme_file_path})
 
153
        if not self.document.settings.current_slide:
 
154
            self.stylesheet.append(self.disable_current_slide)
 
155
        self.add_meta('<meta name="version" content="S5 1.1" />\n')
 
156
        self.s5_footer = []
 
157
        self.s5_header = []
 
158
        self.section_count = 0
 
159
        self.theme_files_copied = None
 
160
 
 
161
    def setup_theme(self):
 
162
        if self.document.settings.theme:
 
163
            self.copy_theme()
 
164
        elif self.document.settings.theme_url:
 
165
            self.theme_file_path = self.document.settings.theme_url
 
166
        else:
 
167
            raise docutils.ApplicationError(
 
168
                'No theme specified for S5/HTML writer.')
 
169
 
 
170
    def copy_theme(self):
 
171
        """
 
172
        Locate & copy theme files.
 
173
 
 
174
        A theme may be explicitly based on another theme via a '__base__'
 
175
        file.  The default base theme is 'default'.  Files are accumulated
 
176
        from the specified theme, any base themes, and 'default'.
 
177
        """
 
178
        settings = self.document.settings
 
179
        path = find_theme(settings.theme)
 
180
        theme_paths = [path]
 
181
        self.theme_files_copied = {}
 
182
        required_files_copied = {}
 
183
        # This is a link (URL) in HTML, so we use "/", not os.sep:
 
184
        self.theme_file_path = '%s/%s' % ('ui', settings.theme)
 
185
        if settings._destination:
 
186
            dest = os.path.join(
 
187
                os.path.dirname(settings._destination), 'ui', settings.theme)
 
188
            if not os.path.isdir(dest):
 
189
                os.makedirs(dest)
 
190
        else:
 
191
            # no destination, so we can't copy the theme
 
192
            return
 
193
        default = 0
 
194
        while path:
 
195
            for f in os.listdir(path):  # copy all files from each theme
 
196
                if f == self.base_theme_file:
 
197
                    continue            # ... except the "__base__" file
 
198
                if ( self.copy_file(f, path, dest)
 
199
                     and f in self.required_theme_files):
 
200
                    required_files_copied[f] = 1
 
201
            if default:
 
202
                break                   # "default" theme has no base theme
 
203
            # Find the "__base__" file in theme directory:
 
204
            base_theme_file = os.path.join(path, self.base_theme_file)
 
205
            # If it exists, read it and record the theme path:
 
206
            if os.path.isfile(base_theme_file):
 
207
                lines = open(base_theme_file).readlines()
 
208
                for line in lines:
 
209
                    line = line.strip()
 
210
                    if line and not line.startswith('#'):
 
211
                        path = find_theme(line)
 
212
                        if path in theme_paths: # check for duplicates (cycles)
 
213
                            path = None         # if found, use default base
 
214
                        else:
 
215
                            theme_paths.append(path)
 
216
                        break
 
217
                else:                   # no theme name found
 
218
                    path = None         # use default base
 
219
            else:                       # no base theme file found
 
220
                path = None             # use default base
 
221
            if not path:
 
222
                path = find_theme(self.default_theme)
 
223
                theme_paths.append(path)
 
224
                default = 1
 
225
        if len(required_files_copied) != len(self.required_theme_files):
 
226
            # Some required files weren't found & couldn't be copied.
 
227
            required = list(self.required_theme_files)
 
228
            for f in required_files_copied.keys():
 
229
                required.remove(f)
 
230
            raise docutils.ApplicationError(
 
231
                'Theme files not found: %s'
 
232
                % ', '.join(['%r' % f for f in required]))
 
233
 
 
234
    files_to_skip_pattern = re.compile(r'~$|\.bak$|#$|\.cvsignore$')
 
235
 
 
236
    def copy_file(self, name, source_dir, dest_dir):
 
237
        """
 
238
        Copy file `name` from `source_dir` to `dest_dir`.
 
239
        Return 1 if the file exists in either `source_dir` or `dest_dir`.
 
240
        """
 
241
        source = os.path.join(source_dir, name)
 
242
        dest = os.path.join(dest_dir, name)
 
243
        if self.theme_files_copied.has_key(dest):
 
244
            return 1
 
245
        else:
 
246
            self.theme_files_copied[dest] = 1
 
247
        if os.path.isfile(source):
 
248
            if self.files_to_skip_pattern.search(source):
 
249
                return None
 
250
            settings = self.document.settings
 
251
            if os.path.exists(dest) and not settings.overwrite_theme_files:
 
252
                settings.record_dependencies.add(dest)
 
253
            else:
 
254
                src_file = open(source, 'rb')
 
255
                src_data = src_file.read()
 
256
                src_file.close()
 
257
                dest_file = open(dest, 'wb')
 
258
                dest_dir = dest_dir.replace(os.sep, '/')
 
259
                dest_file.write(src_data.replace(
 
260
                    'ui/default', dest_dir[dest_dir.rfind('ui/'):]))
 
261
                dest_file.close()
 
262
                settings.record_dependencies.add(source)
 
263
            return 1
 
264
        if os.path.isfile(dest):
 
265
            return 1
 
266
 
 
267
    def depart_document(self, node):
 
268
        header = ''.join(self.s5_header)
 
269
        footer = ''.join(self.s5_footer)
 
270
        title = ''.join(self.html_title).replace('<h1 class="title">', '<h1>')
 
271
        layout = self.layout_template % {'header': header,
 
272
                                         'title': title,
 
273
                                         'footer': footer}
 
274
        self.fragment.extend(self.body)
 
275
        self.body_prefix.extend(layout)
 
276
        self.body_prefix.append('<div class="presentation">\n')
 
277
        self.body_prefix.append(
 
278
            self.starttag({'classes': ['slide'], 'ids': ['slide0']}, 'div'))
 
279
        if not self.section_count:
 
280
            self.body.append('</div>\n')
 
281
        self.body_suffix.insert(0, '</div>\n')
 
282
        # skip content-type meta tag with interpolated charset value:
 
283
        self.html_head.extend(self.head[1:])
 
284
        self.html_body.extend(self.body_prefix[1:] + self.body_pre_docinfo
 
285
                              + self.docinfo + self.body
 
286
                              + self.body_suffix[:-1])
 
287
 
 
288
    def depart_footer(self, node):
 
289
        start = self.context.pop()
 
290
        self.s5_footer.append('<h2>')
 
291
        self.s5_footer.extend(self.body[start:])
 
292
        self.s5_footer.append('</h2>')
 
293
        del self.body[start:]
 
294
 
 
295
    def depart_header(self, node):
 
296
        start = self.context.pop()
 
297
        header = ['<div id="header">\n']
 
298
        header.extend(self.body[start:])
 
299
        header.append('\n</div>\n')
 
300
        del self.body[start:]
 
301
        self.s5_header.extend(header)
 
302
 
 
303
    def visit_section(self, node):
 
304
        if not self.section_count:
 
305
            self.body.append('\n</div>\n')
 
306
        self.section_count += 1
 
307
        self.section_level += 1
 
308
        if self.section_level > 1:
 
309
            # dummy for matching div's
 
310
            self.body.append(self.starttag(node, 'div', CLASS='section'))
 
311
        else:
 
312
            self.body.append(self.starttag(node, 'div', CLASS='slide'))
 
313
 
 
314
    def visit_subtitle(self, node):
 
315
        if isinstance(node.parent, nodes.section):
 
316
            level = self.section_level + self.initial_header_level - 1
 
317
            if level == 1:
 
318
                level = 2
 
319
            tag = 'h%s' % level
 
320
            self.body.append(self.starttag(node, tag, ''))
 
321
            self.context.append('</%s>\n' % tag)
 
322
        else:
 
323
            html4css1.HTMLTranslator.visit_subtitle(self, node)
 
324
 
 
325
    def visit_title(self, node, move_ids=0):
 
326
        html4css1.HTMLTranslator.visit_title(self, node, move_ids=move_ids)