forked from Trust-Code/python-boleto
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhtml.py
More file actions
276 lines (220 loc) · 9.8 KB
/
html.py
File metadata and controls
276 lines (220 loc) · 9.8 KB
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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# -*- coding: utf-8 -*-
"""
pyboleto.html
~~~~~~~~~~~~~
Classe Responsável por fazer o output do boleto em html.
:copyright: © 2012 by Artur Felipe de Sousa
:license: BSD, see LICENSE for more details.
"""
from builtins import zip
from builtins import object
import os
import string
import sys
import codecs
import base64
from itertools import chain
if sys.version_info < (3,):
from itertools import zip_longest as zip_longest
zip_longest # chamando para evitar erro de nao uso do zip_longest
else:
from itertools import zip_longest
DIGITS = [
['n', 'n', 'w', 'w', 'n'],
['w', 'n', 'n', 'n', 'w'],
['n', 'w', 'n', 'n', 'w'],
['w', 'w', 'n', 'n', 'n'],
['n', 'n', 'w', 'n', 'w'],
['w', 'n', 'w', 'n', 'n'],
['n', 'w', 'w', 'n', 'n'],
['n', 'n', 'n', 'w', 'w'],
['w', 'n', 'n', 'w', 'n'],
['n', 'w', 'n', 'w', 'n'],
]
class BoletoHTML(object):
"""Geração do Boleto em HTML
Esta classe é responsável por imprimir o boleto em HTML.
Outras classes podem ser implementadas no futuro com a mesma interface,
para fazer output em LaTeX, etc ...
Esta classe pode imprimir boletos em formato de carnê (2 boletos por
página) ou em formato de folha cheia.
:param file_descr: Um arquivo ou *file-like* class.
:param landscape: Formato da folha. Usar ``True`` para boleto
tipo carnê.
"""
def __init__(self, file_descr, landscape=False):
# Tamanhos em px
self.width = 750
self.widthCanhoto = 0
self.fontSizeTitle = 9
self.heightLine = 27
self.fontSizeValue = 12
self.title = 'Boleto bancário'
self.fileDescr = file_descr
if landscape:
raise NotImplementedError('Em desenvolvimento...')
else:
tpl = string.Template(self._load_template('head.html'))
self.html = tpl.substitute(title=self.title, width=self.width,
font_size_value=self.fontSizeValue,
height_line=self.heightLine,
font_size_title=self.fontSizeTitle)
def _load_template(self, template):
pyboleto_dir = os.path.dirname(os.path.abspath(__file__))
template_path = os.path.join(pyboleto_dir, 'templates', template)
with open(template_path, 'r') as tpl:
template_content = tpl.read()
return template_content
def _load_image(self, logo_image):
pyboleto_dir = os.path.dirname(os.path.abspath(__file__))
image_path = os.path.join(pyboleto_dir, 'media', logo_image)
return image_path
def _drawReciboSacado(self, boletoDados):
"""Imprime o Recibo do Sacado para modelo de página inteira
:param boletoDados: Objeto com os dados do boleto a ser preenchido.
Deve ser subclasse de :class:`pyboleto.data.BoletoData`
:type boletoDados: :class:`pyboleto.data.BoletoData`
"""
tpl = string.Template(self._load_template('recibo_sacado.html'))
tpl_data = {}
# Cabeçalho
tpl_data['logo_img'] = ''
if boletoDados.logo_image:
img = codecs.open(self._load_image(boletoDados.logo_image))
aux = img.read()
aux = base64.b64encode(aux)
img_base64 = 'data:image/jpeg;base64,{0}'.format(aux)
tpl_data['logo_img'] = img_base64
tpl_data['codigo_dv_banco'] = boletoDados.codigo_dv_banco
# Corpo
tpl_data['cedente'] = boletoDados.cedente
tpl_data['agencia_conta_cedente'] = boletoDados.agencia_conta_cedente
tpl_data['cedente_documento'] = boletoDados.cedente_documento
data_vencimento = boletoDados.data_vencimento
tpl_data['data_vencimento'] = data_vencimento.strftime('%d/%m/%Y')
tpl_data['sacado'] = boletoDados.sacado[0]
tpl_data['nosso_numero_format'] = boletoDados.format_nosso_numero()
tpl_data['numero_documento'] = boletoDados.numero_documento
data_documento = boletoDados.data_documento
tpl_data['data_documento'] = data_documento.strftime('%d/%m/%Y')
tpl_data['cedente_endereco'] = boletoDados.cedente_endereco
valor_doc = self._formataValorParaExibir(boletoDados.valor_documento)
tpl_data['valor_documento'] = valor_doc
# Demonstrativo
tpl_data['demonstrativo'] = ''
for dm in boletoDados.demonstrativo:
tpl_data['demonstrativo'] += '<p>{0}</p>'.format(dm)
self.html += tpl.substitute(tpl_data)
def _drawHorizontalCorteLine(self):
self.html += '<hr />'
def _drawReciboCaixa(self, boletoDados):
"""Imprime o Recibo do Caixa
:param boletoDados: Objeto com os dados do boleto a ser preenchido.
Deve ser subclasse de :class:`pyboleto.data.BoletoData`
:type boletoDados: :class:`pyboleto.data.BoletoData`
"""
tpl = string.Template(self._load_template('recibo_caixa.html'))
tpl_data = {}
# Cabeçalho
tpl_data['logo_img'] = ''
if boletoDados.logo_image:
tpl_data['logo_img'] = self._load_image(boletoDados.logo_image)
tpl_data['codigo_dv_banco'] = boletoDados.codigo_dv_banco
tpl_data['linha_digitavel'] = boletoDados.linha_digitavel
# Corpo
data_vencimento = boletoDados.data_vencimento
tpl_data['data_vencimento'] = data_vencimento.strftime('%d/%m/%Y')
# value em unicode em data.py
if isinstance(boletoDados.local_pagamento, str):
tpl_data['local_pagamento'] = boletoDados.local_pagamento.encode
('utf-8')
else:
tpl_data['local_pagamento'] = boletoDados.local_pagamento
tpl_data['cedente'] = boletoDados.cedente
tpl_data['agencia_conta_cedente'] = boletoDados.agencia_conta_cedente
data_documento = boletoDados.data_documento
tpl_data['data_documento'] = data_documento.strftime('%d/%m/%Y')
tpl_data['numero_documento'] = boletoDados.numero_documento
tpl_data['especie_documento'] = boletoDados.especie_documento
tpl_data['aceite'] = boletoDados.aceite
data_process = boletoDados.data_processamento
tpl_data['data_processamento'] = data_process.strftime('%d/%m/%Y')
tpl_data['nosso_numero_format'] = boletoDados.format_nosso_numero()
tpl_data['carteira'] = boletoDados.carteira
tpl_data['especie'] = boletoDados.especie
tpl_data['quantidade'] = boletoDados.quantidade
valor = self._formataValorParaExibir(boletoDados.valor)
tpl_data['valor'] = valor
valor_doc = self._formataValorParaExibir(boletoDados.valor_documento)
tpl_data['valor_documento'] = valor_doc
# Instruções
tpl_data['instrucoes'] = ''
for instrucao in boletoDados.instrucoes:
tpl_data['instrucoes'] += '<p>{0}</p>'.format(instrucao)
# Rodapé
tpl_data['sacado_info'] = ''
for linha_sacado in boletoDados.sacado:
tpl_data['sacado_info'] += '<p>{0}</p>'.format(linha_sacado)
# Código de barras
tpl_data['barcode'] = self._codigoBarraI25(boletoDados.barcode)
self.html += tpl.substitute(tpl_data)
def drawBoletoCarneDuplo(self, boletoDados1, boletoDados2=None):
"""Imprime um boleto tipo carnê com 2 boletos por página.
:param boletoDados1: Objeto com os dados do boleto a ser preenchido.
Deve ser subclasse de :class:`pyboleto.data.BoletoData`
:param boletoDados2: Objeto com os dados do boleto a ser preenchido.
Deve ser subclasse de :class:`pyboleto.data.BoletoData`
:type boletoDados1: :class:`pyboleto.data.BoletoData`
:type boletoDados2: :class:`pyboleto.data.BoletoData`
"""
raise NotImplementedError('Em desenvolvimento')
def drawBoleto(self, boletoDados):
"""Imprime Boleto Convencional
Você pode chamar este método diversas vezes para criar um arquivo com
várias páginas, uma por boleto.
:param boletoDados: Objeto com os dados do boleto a ser preenchido.
Deve ser subclasse de :class:`pyboleto.data.BoletoData`
:type boletoDados: :class:`pyboleto.data.BoletoData`
"""
self._drawReciboSacado(boletoDados)
self._drawHorizontalCorteLine()
self._drawReciboCaixa(boletoDados)
self._drawHorizontalCorteLine()
def nextPage(self):
"""Força início de nova página"""
self.html += '</div><div class="pagina">'
def save(self):
"""Fecha boleto e constroi o arquivo"""
self.html += '</div></body></html>'
if hasattr(self.fileDescr, 'write'):
self.fileDescr.write(self.html)
else:
with open(self.fileDescr, 'w') as fd:
fd.write(self.html)
def _formataValorParaExibir(self, nfloat):
if nfloat:
txt = nfloat
txt = txt.replace('.', ',')
else:
txt = ""
return txt
def _codigoBarraI25(self, code):
"""Imprime Código de barras otimizado para boletos
http://en.wikipedia.org/wiki/Interleaved_2_of_5
"""
digits = ['n', 'n s', 'n', 'n s']
if len(code) % 2 != 0:
code = '0' + code
for digt1, digt2 in self._grouper(2, code):
digt1_repr = DIGITS[int(digt1)]
digt2_repr = [x + ' s' for x in DIGITS[int(digt2)]]
digits.extend(chain(*list(zip(digt1_repr, digt2_repr))))
digits.extend(['w', 'n s', 'n'])
result = []
for digit in digits:
result.append('<span class="{0}"></span>'.format(digit))
return ''.join(result)
def _grouper(self, n, iterable, fillvalue=None):
"""grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"""
args = [iter(iterable)] * n
return zip_longest(fillvalue=fillvalue, *args)