1
2# Copyright Aleksey Gurtovoy 2004-2009
3#
4# Distributed under the Boost Software License, Version 1.0.
5# (See accompanying file LICENSE_1_0.txt or copy at
6# http://www.boost.org/LICENSE_1_0.txt)
7
8from docutils.writers import html4_frames
9from docutils.writers import html4css1
10from docutils import nodes
11
12import re
13import string
14
15
16class Writer(html4_frames.Writer):
17
18    def __init__(self):
19        self.__super = html4_frames.Writer
20        self.__super.__init__(self)
21        self.translator = refdoc_translator
22
23
24class refdoc_translator(html4_frames.frame_pages_translator):
25
26    tocframe_width      = 25
27    re_include          = re.compile(r'(\s*#include\s+<)(.*?\.hpp)?(>\s*)?')
28    re_identifier       = re.compile(r'(.*?\W*)(\w+)(\W.*?)?')
29    re_modtime          = re.compile(r'\s*modtime:\s*(.*)')
30    re_auto_id          = re.compile(r'^id(\d+)$')
31    in_literal_block    = 0
32    in_reference        = 0
33
34    def __init__(self, document, index_page, page_files_dir, extension):
35        self.docframe += ' refmanual'
36        self.__super = html4_frames.frame_pages_translator
37        self.__super.__init__(self, document, index_page, page_files_dir, extension)
38
39
40    def visit_section( self, node ):
41        base = self
42        self = self.active_visitor()
43        if self.section_level == 1:
44            self.section_level = 2
45        base.__super.visit_section( base, node )
46
47    def depart_section( self, node ):
48        self.__super.depart_section( self, node )
49        self = self.active_visitor()
50        if self.section_level == 2:
51            self.section_level = 1
52
53
54    def visit_title( self, node ):
55        self.__super.visit_title( self, node )
56        if self.re_auto_id.match( self._node_id( node.parent ) ):
57            name = nodes.make_id( node.astext() )
58            self = self.active_visitor()
59            self.body.append( self.starttag(
60                  {}, 'a', '', name=name, href='#%s' % name, CLASS='subsection-title'
61                ) )
62
63    def depart_title( self, node ):
64        base = self
65        if self.re_auto_id.match( self._node_id( node.parent ) ):
66            self = self.active_visitor()
67            self.body.append( '</a>')
68
69        base.__super.depart_title( base, node )
70
71
72    def visit_table(self, node):
73        self = self.active_visitor()
74        self.body.append(
75            self.starttag(node, 'table', CLASS='docutils table', border="1"))
76
77
78    def visit_reference(self, node):
79        self.in_reference = 1
80        if len(node) == 1 and isinstance(node[0], nodes.literal) and node[0].has_key('class'):
81            if node.has_key('class') and node['class'].find(node[0]['class']) == -1:
82                node['class'] += ' %s' % node[0]['class']
83            else:
84                node['class'] = node[0]['class']
85
86        self.__super.visit_reference(self, node)
87
88
89    def depart_reference(self, node):
90        self.__super.depart_reference(self, node)
91        self.in_reference = 0
92
93
94    def visit_literal(self, node):
95        if self.in_reference:
96            self.__super.visit_literal(self, node)
97
98        base = self
99        self = self.active_visitor()
100
101        self.body.append(self.starttag(node, 'tt', '', CLASS='literal'))
102        text = node.astext()
103
104        if base.re_include.search(text):
105            text = base.re_include.sub(lambda m: base._handle_include_sub(self, m), text)
106            self.body.append('<span class="pre">%s</span>' % text)
107        else:
108            for token in self.words_and_spaces.findall(text):
109                if token.strip():
110                    if base.re_identifier.search(token):
111                        token = base.re_identifier.sub(lambda m: base._handle_id_sub(self, m), token)
112                    else:
113                        token = self.encode(token)
114
115                    self.body.append('<span class="pre">%s</span>' % token)
116                elif token in ('\n', ' '):
117                    # Allow breaks at whitespace:
118                    self.body.append(token)
119                else:
120                    # Protect runs of multiple spaces; the last space can wrap:
121                    self.body.append('&nbsp;' * (len(token) - 1) + ' ')
122
123        self.body.append('</tt>')
124        # Content already processed:
125        raise nodes.SkipNode
126
127
128    def visit_literal_block(self, node):
129        self.__super.visit_literal_block(self, node)
130        self.in_literal_block = True
131
132    def depart_literal_block(self, node):
133        self.__super.depart_literal_block(self, node)
134        self.in_literal_block = False
135
136
137    def visit_license_and_copyright(self, node):
138        self = self.active_visitor()
139        self.context.append( len( self.body ) )
140
141    def depart_license_and_copyright(self, node):
142        self = self.active_visitor()
143        start = self.context.pop()
144        self.footer = self.body[start:]
145        del self.body[start:]
146
147
148    def visit_Text(self, node):
149        if not self.in_literal_block:
150            self.__super.visit_Text(self, node)
151        else:
152            base = self
153            self = self.active_visitor()
154
155            text = node.astext()
156            if base.re_include.search(text):
157                text = base.re_include.sub(lambda m: base._handle_include_sub(self, m), text)
158            elif base.re_identifier.search(text):
159                text = base.re_identifier.sub(lambda m: base._handle_id_sub(self, m), text)
160            else:
161                text = self.encode(text)
162
163            self.body.append(text)
164
165
166    def depart_Text(self, node):
167        pass
168
169
170    def visit_substitution_reference(self, node):
171        # debug help
172        print 'Unresolved substitution_reference:', node.astext()
173        raise nodes.SkipNode
174
175
176    def _footer_content(self):
177        self = self.active_visitor()
178        parts = ''.join( self.footer ).split( '\n' )
179        parts = [ '<div class="copyright">%s</div>' % x if x.startswith( 'Copyright' ) else x for x in parts ]
180        return '<td><div class="copyright-footer">%s</div></td>' % '\n'.join( parts ) if len( parts ) else ''
181
182
183    def _toc_as_text( self, visitor ):
184        footer_end = visitor.body.pop()
185        visitor.body.append( self._footer_content() )
186        visitor.body.append( footer_end )
187        return visitor.astext()
188
189
190    def _handle_include_sub(base, self, match):
191        if not match.group(2) or not match.group():
192            return self.encode(match.group(0))
193
194        header = match.group(2)
195        result = self.encode(match.group(1))
196        result += '<a href="%s" class="header">%s</a>' \
197                        % ( '../../../../%s' % header
198                          , self.encode(header)
199                          )
200
201        result += self.encode(match.group(3))
202        return result
203
204
205    def _handle_id_sub(base, self, match):
206        identifier = match.group(2)
207
208        if not base.document.has_name( identifier.lower() ):
209            return self.encode(match.group(0))
210
211        def get_section_id( id ):
212            node = base.document.ids[ id ]
213            if isinstance( node, nodes.section ):
214                return id
215
216            if isinstance( node, nodes.target ):
217                return get_section_id( node.get( 'refid' ) )
218
219            return None
220
221        id = get_section_id( base.document.nameids[ identifier.lower() ] )
222        if not id:
223            return self.encode(match.group(0))
224
225        if id == 'inserter':
226            id = 'inserter-class'
227
228        result = self.encode(match.group(1))
229        result += '<a href="%s" class="identifier">%s</a>' \
230                        % ( base._chunk_ref( base._active_chunk_id(), base._make_chunk_id( id ) )
231                          , self.encode(identifier)
232                          )
233
234        if match.group(3):
235            result += self.encode(match.group(3))
236
237        return result
238
239
240    def _make_chunk_id( self, node_id ):
241        if self.re_auto_id.match( node_id ):
242            node = self.document.ids[ node_id ]
243            return '%s-%s' % ( self._node_id( node.parent ), node['dupnames'][0] )
244
245        if node_id.startswith( 'boost-mpl-' ):
246            return node_id[ len( 'boost-mpl-' ): ]
247
248        return node_id
249
250