-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmain.py
331 lines (288 loc) · 24.6 KB
/
main.py
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
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# ======================================================================================================================
import xmltodict
import jinja2
import pdfkit
import webbrowser
from tkinter import filedialog
from os import path
from datetime import datetime
from dateutil import parser
from babel.numbers import format_decimal
from format_utils import *
# ======================================================================================================================
class NFSe:
"""
6: <label id="tipo_rps">
7: <label id="numero_nf">
10: <label id="prestador_razao_social">
11: <label id="prestador_nome_fantasia">
12: <label id="prestador_endereco">
13: <label id="prestador_cep">
14: <label id="prestador_telefone">
15: <label id="prestador_email">
16: <label id="prestador_inscricao_municipal">
17: <label id="prestador_cnpj">
18: <div id="data_geracao">
19: <div id="data_competencia">
20: <div id="cod_autencidade">
21: <div id="reponsavel_retencao">
22: <div id="qrcode_container" class="stack">
23: <img id="qrcode" src="" alt="QR CODE">
25: <label id="natureza_operacao">
26: <label id="numero_rps">
27: <label id="serie_rps">
28: <label id="data_emissao_rps">
29: <label id="local_servicos">
30: <label id="municipio_incidencia">
31: <div id="tomador" class="container stack">
32: <label id="tomador_cnpj">
33: <label id="tomador_im">
34: <label id="tomador_razao_social">
35: <label id="tomador_endereco">
36: <label id="tomador_numero">
37: <label id="tomador_complemento">
38: <label id="tomador_bairro">
39: <label id="tomador_cep">
40: <label id="tomador_cidade">
41: <label id="tomador_telefone">
42: <label id="tomador_email">
43: <div id="intermediario" class="container stack">
44: <label id="intermediario_cnpj">
45: <label id="intermediario_inscricao_municipal">
46: <label id="intermediario_razao_social">
47: <div id="servicos" class="container stack">
48: <div id="descricao_servico">
49: <div id="tributos" class="container stack">
50: <div id="atividade_municipio">
51: <div id="aliquota">
52: <div id="item_lc116">
53: <div id="cod_nbs">
54: <div id="cod_cnae">
55: <div id="valor_total">
56: <div id="desconto_incondicionado">
57: <div id="deducao_base_calculo">
58: <div id="base_calculo">
59: <div id="total_issqn">
60: <div id="issqn_retido">
61: <div id="desconto_condicionado">
62: <div id="pis">
63: <div id="cofins">
64: <div id="inss">
65: <div id="irrf">
66: <div id="csll">
67: <div id="outras_retencoes">
68: <div id="valor_issqn">
69: <div id="valor_liquido">
70: <div id="construcao_civil">
71: <div id="cod_obra">
72: <div id="art">
74: <div id="informacoes_adicionais">
"""
def __init__(self, xml_dict: dict):
# Cabeçalho
self.tipo_rps = "Nota Fiscal de Serviço Eletrônica - NFS-e" # TODO:
self.numero_nf = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('Numero', None)
# Prestador:
self.prestador_razao_social = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('PrestadorServico', {}).get('RazaoSocial', None)
self.prestador_nome_fantasia = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('PrestadorServico', {}).get('NomeFantasia', None)
self.prestador_endereco = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('PrestadorServico', {}).get('Endereco', {}).get('Endereco', {}) # TODO: Juntar as outras informações do endereço
self.prestador_cep = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('PrestadorServico', {}).get('Endereco', {}).get('Cep', None)
self.prestador_telefone = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('PrestadorServico', {}).get('Contato', {}).get('Telefone', None)
self.prestador_email = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('PrestadorServico', {}).get('Contato', {}).get('Email', None)
self.prestador_inscricao_municipal = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('Prestador', {}).get('InscricaoMunicipal', None)
self.prestador_cnpj = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('Prestador', {}).get('CpfCnpj', {}).get('Cnpj', None)
self.prestador_cpf = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('Prestador', {}).get('CpfCnpj', {}).get('Cpf', None)
self.data_geracao = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DataEmissao', None)
self.data_competencia = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('Competencia', None)
self.cod_autencidade = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('CodigoVerificacao', None)
self.reponsavel_retencao = None # TODO:
self.qrcode = None # TODO:
# Identificação:
self.natureza_operacao = None # TODO: DeclaracaoPrestacaoServico > InfDeclaracaoPrestacaoServico > Servico > ExigibilidadeISS ?
self.numero_rps = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('Rps', {}).get('IdentificacaoRps', {}).get('Numero', None)
self.serie_rps = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('Rps', {}).get('IdentificacaoRps', {}).get('Serie', None) # TODO: Converter número para "RPS - Recibo Provisórios de Serviços"
self.data_emissao_rps = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('Rps', {}).get('DataEmissao', None)
self.local_servicos = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('Servico', {}).get('CodigoMunicipio', None) # TODO: Tabela IBGE
self.municipio_incidencia = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('Servico', {}).get('MunicipioIncidencia', None) # TODO: Tabela IBGE
# Tomador:
self.tomador_cnpj = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('TomadorServico', {}).get('IdentificacaoTomador', {}).get('CpfCnpj', {}).get('Cnpj', None)
self.tomador_cpf = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('TomadorServico', {}).get('IdentificacaoTomador', {}).get('CpfCnpj', {}).get('Cpf', None)
self.tomador_im = None # TODO:
self.tomador_razao_social = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('TomadorServico', {}).get('RazaoSocial', None)
self.tomador_endereco = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('TomadorServico', {}).get('Endereco', {}).get('Endereco', None)
self.tomador_numero = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('TomadorServico', {}).get('Endereco', {}).get('Numero', None)
self.tomador_complemento = None # TODO:
self.tomador_bairro = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('TomadorServico', {}).get('Endereco', {}).get('Bairro', None)
self.tomador_cep = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('TomadorServico', {}).get('Endereco', {}).get('Cep', None)
self.tomador_cidade = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('TomadorServico', {}).get('Endereco', {}).get('CodigoMunicipio', None) # TODO: Tabela IGBE
self.tomador_telefone = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('TomadorServico', {}).get('Contato', {}).get('Telefone', None)
self.tomador_email = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('TomadorServico', {}).get('Contato', {}).get('Email', None)
# Intermediário:
self.intermediario_cnpj = None # TODO:
self.intermediario_inscricao_municipal = None # TODO:
self.intermediario_razao_social = None # TODO:
# Descrição:
self.descricao_servico = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('Servico', {}).get('Discriminacao', None)
# Tributos:
self.cod_tributacao = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('Servico', {}).get('CodigoTributacaoMunicipio', None)
self.atividade_municipio = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DescricaoCodigoTributacaoMunicípio', None)
self.aliquota = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('ValoresNfse', {}).get('Aliquota', None)
self.item_lc116 = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('Servico', {}).get('CodigoTributacaoMunicipio', None)
self.cod_nbs = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('Servico', {}).get('CodigoNbs', None)
self.cod_cnae = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('Servico', {}).get('CodigoCnae', None)
self.valor_total = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('Servico', {}).get('Valores', {}).get('ValorServicos', None)
self.desconto_incondicionado = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('Servico', {}).get('Valores', {}).get('DescontoIncondicionado', None)
self.deducao_base_calculo = None # TODO:
self.base_calculo = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('ValoresNfse', {}).get('BaseCalculo', None)
self.total_issqn = None # TODO: ValoresNfse > ValorIss ?
self.issqn_retido = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('Servico', {}).get('IssRetido', None)
self.desconto_condicionado = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('Servico', {}).get('Valores', {}).get('DescontoCondicionado', None)
self.pis = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('Servico', {}).get('Valores', {}).get('ValorPis', None)
self.cofins = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('Servico', {}).get('Valores', {}).get('ValorCofins', None)
self.inss = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('Servico', {}).get('Valores', {}).get('ValorInss', None)
self.irrf = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('Servico', {}).get('Valores', {}).get('ValorIr', None)
self.csll = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('Servico', {}).get('Valores', {}).get('ValorCsll', None)
self.outras_retencoes = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('Servico', {}).get('Valores', {}).get('OutrasRetencoes', None)
self.valor_issqn = None # TODO: ValoresNfse > ValorIss ?
self.valor_liquido = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('ValoresNfse', {}).get('ValorLiquidoNfse', None)
self.construcao_civil = None # TODO:
self.cod_obra = None # TODO:
self.art = None # TODO:
# Informações:
self.informacoes_adicionais = xml_dict.get('Nfse', {}).get('InfNfse', {}).get('DeclaracaoPrestacaoServico', {}).get('InfDeclaracaoPrestacaoServico', {}).get('InformacoesComplementares', None)
def get_formated(self):
data = {
"tipo_rps" : self.tipo_rps if self.tipo_rps else "",
"numero_nf" : self.numero_nf if self.numero_nf else "",
# Prestador:
"prestador_razao_social" : self.prestador_razao_social if self.prestador_razao_social else "",
"prestador_nome_fantasia" : self.prestador_nome_fantasia if self.prestador_nome_fantasia else "",
"prestador_endereco" : self.prestador_endereco if self.prestador_endereco else "",
"prestador_cep" : formatar_cep(self.prestador_cep) if self.prestador_cep else "",
"prestador_telefone" : self.prestador_telefone if self.prestador_telefone else "",
"prestador_email" : self.prestador_email if self.prestador_email else "",
"prestador_inscricao_municipal" : self.prestador_inscricao_municipal if self.prestador_inscricao_municipal else "",
"prestador_cnpj" : formatar_cnpj(self.prestador_cnpj) if self.prestador_cnpj else "",
"prestador_cpf" : formatar_cpf(self.prestador_cpf) if self.prestador_cpf else "",
"data_geracao" : parser.parse(self.data_geracao).strftime("%d/%m/%Y %H:%M:%S") if self.data_geracao else "",
"data_competencia" : parser.parse(self.data_competencia).strftime("%d/%m/%Y") if self.data_competencia else "",
"cod_autencidade" : self.cod_autencidade if self.cod_autencidade else "",
"reponsavel_retencao" : self.reponsavel_retencao if self.reponsavel_retencao else "",
"qrcode" : self.qrcode if self.qrcode else "",
# Identificação:
"natureza_operacao" : self.natureza_operacao if self.natureza_operacao else "",
"numero_rps" : self.numero_rps if self.numero_rps else "",
"serie_rps" : self.serie_rps if self.serie_rps else "",
"data_emissao_rps" : parser.parse(self.data_emissao_rps).strftime("%d/%m/%Y") if self.data_emissao_rps else "",
"local_servicos" : self.local_servicos if self.local_servicos else "",
"municipio_incidencia" : self.municipio_incidencia if self.municipio_incidencia else "",
# Tomador:
"tomador_cnpj" : formatar_cnpj(self.tomador_cnpj) if self.tomador_cnpj else "",
"tomador_cpf" : formatar_cpf(self.tomador_cpf) if self.tomador_cpf else "",
"tomador_im" : self.tomador_im if self.tomador_im else "",
"tomador_razao_social" : self.tomador_razao_social if self.tomador_razao_social else "",
"tomador_endereco" : self.tomador_endereco if self.tomador_endereco else "",
"tomador_numero" : self.tomador_numero if self.tomador_numero else "",
"tomador_complemento" : self.tomador_complemento if self.tomador_complemento else "",
"tomador_bairro" : self.tomador_bairro if self.tomador_bairro else "",
"tomador_cep" : formatar_cep(self.tomador_cep) if self.tomador_cep else "",
"tomador_cidade" : self.tomador_cidade if self.tomador_cidade else "",
"tomador_telefone" : self.tomador_telefone if self.tomador_telefone else "",
"tomador_email" : self.tomador_email if self.tomador_email else "",
# Intermediário:
"intermediario_cnpj" : formatar_cnpj(self.intermediario_cnpj) if self.intermediario_cnpj else "",
"intermediario_inscricao_municipal" : self.intermediario_inscricao_municipal if self.intermediario_inscricao_municipal else "",
"intermediario_razao_social" : self.intermediario_razao_social if self.intermediario_razao_social else "",
# Descrição:
"descricao_servico" : self.descricao_servico if self.descricao_servico else "",
# Tributos:
"cod_tributacao" : self.cod_tributacao if self.cod_tributacao else "",
"atividade_municipio" : self.atividade_municipio if self.atividade_municipio else "",
"aliquota" : format_decimal(self.aliquota, format="#0.00", locale="pt_BR")if self.aliquota else "",
"item_lc116" : self.item_lc116 if self.item_lc116 else "",
"cod_nbs" : self.cod_nbs if self.cod_nbs else "",
"cod_cnae" : self.cod_cnae if self.cod_cnae else "",
"valor_total" : formatar_moeda(self.valor_total) if self.valor_total else "",
"desconto_incondicionado" : formatar_moeda(self.desconto_incondicionado) if self.desconto_incondicionado else "",
"deducao_base_calculo" : formatar_moeda(self.deducao_base_calculo) if self.deducao_base_calculo else "",
"base_calculo" : formatar_moeda(self.base_calculo) if self.base_calculo else "",
"total_issqn" : formatar_moeda(self.total_issqn) if self.total_issqn else "",
"issqn_retido" : self.issqn_retido if self.issqn_retido else "",
"desconto_condicionado" : formatar_moeda(self.desconto_condicionado) if self.desconto_condicionado else "",
"pis" : formatar_moeda(self.pis) if self.pis else "",
"cofins" : formatar_moeda(self.cofins) if self.cofins else "",
"inss" : formatar_moeda(self.inss) if self.inss else "",
"irrf" : formatar_moeda(self.irrf) if self.irrf else "",
"csll" : formatar_moeda(self.csll) if self.csll else "",
"outras_retencoes" : formatar_moeda(self.outras_retencoes) if self.outras_retencoes else "",
"valor_issqn" : formatar_moeda(self.valor_issqn) if self.valor_issqn else "",
"valor_liquido" : formatar_moeda(self.valor_liquido) if self.valor_liquido else "",
"construcao_civil" : self.construcao_civil if self.construcao_civil else "",
"cod_obra" : self.cod_obra if self.cod_obra else "",
"art" : self.art if self.art else "",
# Informações Complementares
"informacoes_adicionais" : self.informacoes_adicionais if self.informacoes_adicionais else "",
}
return data
def __str__(self):
return str(self.__dict__)
# ======================================================================================================================
def xml_to_dict(xmlfile: str):
with open(xmlfile, 'r', encoding='utf8', ) as f:
result = xmltodict.parse(f.read())
return result
def render_html(nfse: NFSe, template_filename) -> str:
env = jinja2.Environment(loader = jinja2.FileSystemLoader('./'))
html = env.get_template(template_filename).render(nfse.get_formated())
return html
def save_html(html, filepath):
html_fileroot = path.splitext(filepath)[0]
agora = datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
html_filename = f'{html_fileroot}-{agora}.html'
with open(html_filename, 'w', encoding='utf8') as hf:
hf.write(html)
return html_filename
def save_pdf(html: str, filepath) -> str:
html_fileroot = path.splitext(filepath)[0]
agora = datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
pdf_filename = f'{html_fileroot}-{agora}.pdf'
config = pdfkit.configuration(wkhtmltopdf=r"C:\Program Files\wkhtmltopdf\bin\wkhtmltopdf.exe")
css_path = path.abspath("templates/static/css/nfse_pdf.css")
options = {
"enable-local-file-access" : "",
'encoding' : 'UTF-8',
'margin-left' : '0mm',
'margin-right' : '0mm',
'margin-bottom' : '0mm',
'margin-top' : '0mm',
}
pdfkit.from_string(html, pdf_filename, css=css_path, configuration=config, options=options)
return pdf_filename
def main(save_htmlfile=False):
files = filedialog.askopenfilenames(filetypes=[('XML', '*.xml')])
for filepath in files:
try:
xml_dict = xml_to_dict(filepath)
nfse = NFSe(xml_dict)
except Exception as e:
print(f"Erro ao ler arquivo XML {filepath}: ", repr(e), )
continue
if save_htmlfile:
try:
html = render_html(nfse, 'templates/nfse.html')
html_fiilename = save_html(html, filepath)
webbrowser.open(html_fiilename, new=2)
except Exception as e:
print(f"Erro ao gerar HTML do arquivo {filepath}: ", repr(e), )
continue
try:
html_pdf = render_html(nfse, 'templates/nfse_pdf.html')
pdf_filename = save_pdf(html_pdf, filepath)
webbrowser.open(f'file:///{pdf_filename}', new=2)
except Exception as e:
print(f"Erro ao gerar PDF do arquivo {filepath}: ", repr(e), )
continue
# ======================================================================================================================
if __name__ == "__main__":
main(save_htmlfile=True)
# ======================================================================================================================