summaryrefslogtreecommitdiff
path: root/sandbox/rtf-writer/RTFWriter.py
blob: 15cd8d64726a08c905b199886226c188d1797dec (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# -*- coding: utf-8 -*-
""" This is a docutils RTF Writer

   We just need a few features so it shouldn't be too difficult

   This was made possible by:

      http://search.cpan.org/~sburke/RTF-Writer/lib/RTF/Cookbook.pod

   from:

      Sean M. Burke, sburke@cpan.org
      
   Author:    BenoƮt Allard
   Contact:   benoit@aeteurope.nl
   Copyright: This module has been placed in the public domain.
"""

from docutils import writers, nodes

class Writer(writers.Writer):

    supported = ('rtf',)
    output = None

    def __init__(self):
        writers.Writer.__init__(self)
        self._translator_class = RTFTranslator

    def translate(self):
        visitor = self._translator_class(self.document)
        self.document.walkabout(visitor)
        self.output = visitor.astext()

def toansi(text):
    """ Encode special characters """
    trans = {'{': r'\{',
             '}': r'\}',
             '\\': r'\\',
             }
    out = ''
    for char in text:
        if char in trans:
            out += trans[char]
        elif ord(char) < 127:
            out += char
        else:
            out += r"\'%x" % ord(char)
    return out

FONT_NAME = 'Tahoma'

# Unit is half point
FONT_SIZE = {
    -1: 18, # text
     0: 24, # title
     1: 28, # first heading
     2: 24, # second heading
}

PAR_INDENT = 480

def indent(fn):
    def wrapper(self, *args, **kwargs):
        self.par_level += 1
        return fn(self, *args, **kwargs)
    return wrapper


def dedent(fn):
    def wrapper(self, *args, **kwargs):
        self.par_level -= 1
        return fn(self, *args, **kwargs)
    return wrapper

class RTFTranslator(nodes.NodeVisitor):
    
    def __init__(self, document):
        nodes.NodeVisitor.__init__(self, document)
        self.body = []
        self.section_level = 0
        self.par_level = -1
        self.bullets = [] # stack of the currents bullets
        self.bullet = None # next one to add to the next paragraph

    def astext(self):
        return '\n'.join(self.body)

    def visit_document(self, node):
        self.body.append(r'{\rtf1\ansi\deff0{\fonttbl{\f0 %s;}}' % FONT_NAME)
        self.body.append(r'\deflang1033\widowctrl\fs%d' % FONT_SIZE[-1])
    
    def depart_document(self, node):
        self.body.append('}')

    def visit_title(self, node):
        if isinstance(node.parent, nodes.document):
            """ doc title """
            self.body.append(r'{\pard\par\qc\f0\fs%d\b' % FONT_SIZE[0])
        elif isinstance(node.parent, nodes.section):
            level = self.section_level
            self.body.append(r'{\pard\par\f0\fs%d\b' % FONT_SIZE[level])

    def depart_title(self, node):
        self.body.append(r'\par}')
            
    def visit_Text(self, node):
        if self.bullet is not None:
            self.body.append(self.bullet+'\~')
            self.bullet = None
        self.body.append(toansi(node.astext()))

    def depart_Text(self, node):
        pass

    def visit_strong(self, node):
        self.body.append(r'{\b')

    def depart_strong(self, node):
        self.body.append('}')

    def visit_section(self, node):
        self.section_level += 1
        
    def depart_section(self, node):
        self.section_level -= 1

    @indent
    def visit_paragraph(self, node):
        self.body.append(r'{\pard\par\f0\qj\li%d' % (PAR_INDENT * self.par_level))
        if self.bullet is not None:
            self.body.append(r'\fi-%d' % (PAR_INDENT / 2))

    @dedent
    def depart_paragraph(self, node):
        self.body.append('\par}')

    @indent
    def visit_bullet_list(self, node):
        self.bullets.append(node['bullet'])

    @dedent
    def depart_bullet_list(self, node):
        self.bullets.pop()

    def visit_list_item(self, node):
        self.bullet = self.bullets[-1]
        
    def depart_list_item(self, node):
        pass

    @indent
    def visit_block_quote(self, node):
        pass

    @dedent
    def depart_block_quote(self, node):
        pass

    def visit_reference(self, node):
        if 'refuri' in node:
            self.body.append(r'{\field{\*\fldinst{HYPERLINK "%s"}}{\fldrslt{\ul' % node['refuri'])
        else:
            # to balance parenthesis
            self.body.append(r'{{{')

    def depart_reference(self, node):
        self.body.append('}}}')

    def visit_docinfo(self, node):
        self.body.append(r'{\info')

    def depart_docinfo(self, node):
        self.body.append('}')

    def visit_author(self, node):
        self.body.append(r'{\author')

    def depart_author(self, node):
        self.body.append(r'}')

if __name__ == "__main__":
    """ To test the writer """
    from docutils.core import publish_string
    f_in = open('test.rtf', 'rb')
    rtf = publish_string(f_in.read(), writer=Writer())
    f_in.close()

    print rtf

    f_out = open('a.out', 'wb')
    f_out.write(rtf)
    f_out.close()