13ze commited on
Commit
2442fcd
·
verified ·
1 Parent(s): 477ba05

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +158 -144
app.py CHANGED
@@ -1,17 +1,18 @@
1
  # -*- coding: utf-8 -*-
2
  import gradio as gr
3
  import html2text
4
- from bs4 import BeautifulSoup, Comment # Adicionado Comment para remover comentários HTML
5
  import logging
6
- import re # Para limpeza final do markdown
7
 
8
  logging.basicConfig(level=logging.INFO)
9
 
10
- def extrair_limpar_html_v4(html_bruto):
11
  """
12
- Tenta extrair o conteúdo principal, remove seções finais comuns
13
- (comentários, posts relacionados), limpa tags/atributos e retorna o HTML.
14
- V4: Mantém lógica de limpeza V3, ajusta para melhor formato final.
 
15
 
16
  :param html_bruto: String contendo o código HTML original.
17
  :return: String contendo o HTML limpo e focado no conteúdo principal.
@@ -26,138 +27,154 @@ def extrair_limpar_html_v4(html_bruto):
26
  comment.extract()
27
 
28
  target_element = None
 
29
 
30
- # --- 1. Tentar Extrair o Conteúdo Principal ---
31
- # Seletores reordenados, priorizando article e classes de conteúdo comuns
32
  main_content_selectors = [
33
- 'article', '.entry-content', '.post-content', '.post-body', '.article-body',
34
- '.td-post-content', 'main', '[role="main"]', '.post',
35
- '#content', '#main', '#bodyContent',
 
 
36
  ]
37
- # Nota: A qualidade da extração depende muito da estrutura do site de origem.
38
- # Seletores podem precisar de ajuste para sites específicos.
39
 
40
  for selector in main_content_selectors:
41
- potential_match = soup.select_one(selector)
42
- if potential_match:
43
- # Heurística básica para evitar containers muito pequenos
44
- if len(potential_match.get_text(strip=True)) > 150:
45
- target_element = potential_match
46
- logging.info(f"Conteúdo principal identificado usando o seletor: '{selector}'")
47
- break
48
- else:
49
- logging.info(f"Seletor '{selector}' encontrado, mas conteúdo muito pequeno. Continuando busca.")
50
-
51
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  if not target_element:
 
 
53
  if soup.body:
54
  target_element = soup.body
55
- logging.info("Nenhum contêiner principal específico encontrado. Usando <body> como fallback.")
 
56
  else:
57
- target_element = soup
58
- logging.warning("Nenhum contêiner principal ou <body> encontrado. Processando todo o input.")
59
-
60
- if not target_element:
61
- return ""
62
-
63
- logging.info(f"Trabalhando dentro do elemento: <{target_element.name} id='{target_element.get('id', 'N/A')}' class='{' '.join(target_element.get('class', []))}'>")
64
-
65
- # --- 2. Remover Seções Finais Indesejadas DENTRO do target_element ---
66
- # Esta é a parte mais crítica e dependente do site.
67
- # Adicione mais seletores se necessário para o site específico.
68
- end_content_selectors = [
69
- '#comments', '.comments-area', '#respond', '.comment-respond',
70
- '.related-posts', '.jp-relatedposts', '.yarpp-related', '.wp_rp_content', # Posts relacionados
71
- '.post-navigation', '.nav-links', '.pagination', # Navegação/Paginação
72
- '.author-box', '.author-info', # Biografia do autor
73
- '.share-buttons', '.social-sharing', '.shariff', # Compartilhamento
74
- '#jp-post-flair', '.post-tags', '.entry-tags', # Tags/Flair
75
- '.edit-link', # Links de edição (comum em WP)
76
- '#disqus_thread', # Disqus comments
77
- '.saboxplugin-wrap', # Simple Author Box plugin
78
- # Heurística baseada no texto do seu exemplo: Elementos contendo esses H2/H3
79
- # Isso é mais frágil, use com cuidado. Tenta encontrar o elemento PAI do Hx.
80
- # 'h2:contains("Comentários")', 'h3:contains("Deixe um comentário")', 'h2:contains("Mais posts")',
81
- # -- Se usar os seletores :contains acima, precisaria de lógica extra para pegar o PAI --
82
- # Melhor usar IDs/Classes se disponíveis no HTML original.
83
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
- removed_end_count = 0
86
- for selector in end_content_selectors:
87
- # Usar select para encontrar todos os elementos que batem DENTRO do target_element
88
- elements_to_remove = target_element.select(selector)
89
- if elements_to_remove:
90
- logging.info(f"Tentando remover elementos para o seletor final: '{selector}'")
91
- for element in elements_to_remove:
92
- # Verifica se o elemento ainda está na árvore e pertence ao target_element
93
- # (evita remover algo fora ou já removido)
94
- if element.find_parent(target_element.name,
95
- attrs=target_element.attrs) is not None:
96
- logging.info(f" Removendo: <{element.name} id='{element.get('id', 'N/A')}' class='{' '.join(element.get('class', []))}'>")
97
- element.decompose()
98
- removed_end_count += 1
99
-
100
- if removed_end_count > 0:
101
- logging.info(f"Removidas {removed_end_count} seções finais indesejadas.")
102
- else:
103
- logging.info("Nenhuma seção final indesejada conhecida foi encontrada ou removida.")
104
-
105
- # --- 3. Limpeza Geral DENTRO do que sobrou do target_element ---
106
  tags_para_remover_geral = [
107
  'script', 'style', 'form', 'input', 'button', 'select', 'textarea', 'label',
108
  'footer', 'header', 'nav', 'aside', 'iframe', 'noscript', 'meta', 'link',
109
- 'canvas', 'svg', 'audio', 'video', # Elementos multimídia não textuais
110
- 'figure', # Remover figure se não quiser a imagem + legenda juntas
111
- # Cuidado: Remover 'figure' pode quebrar a associação imagem/legenda se 'figcaption' for permitido.
112
- # Se quiser manter imagens, mas não o container figure, descomente a linha abaixo
113
- # e ajuste as tags permitidas/limpeza de atributos.
114
  ]
115
  removed_general_count = 0
 
116
  for tag_name in tags_para_remover_geral:
117
  for tag in target_element.find_all(tag_name):
118
  tag.decompose()
119
  removed_general_count +=1
120
  if removed_general_count > 0:
121
- logging.info(f"Removidas {removed_general_count} tags gerais indesejadas (script, style, etc.).")
122
-
123
 
124
- # --- 4. Limpar Atributos e Tags Restantes ---
125
  tags_permitidas = {
126
  'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'br', 'a', 'strong', 'b',
127
  'em', 'i', 'u', 's', 'strike', 'del', 'ul', 'ol', 'li', 'img',
128
  'table', 'thead', 'tbody', 'tr', 'th', 'td', 'blockquote', 'pre', 'code',
129
- 'figcaption' # Permitir legenda se 'figure' for removido ou 'img' estiver dentro dela
130
  }
131
  atributos_permitidos = {
132
  'a': ['href', 'title'],
133
- 'img': ['src', 'alt', 'title', 'width', 'height'], # Manter alt é importante
134
  'th': ['colspan', 'rowspan', 'scope'],
135
  'td': ['colspan', 'rowspan'],
136
  'blockquote': ['cite'],
137
  'ol': ['start'],
138
- # Não permitir 'class' ou 'id' por padrão para limpeza máxima
 
139
  }
140
 
141
- # Iterar sobre uma cópia da lista de tags para poder modificar durante a iteração
142
  for tag in list(target_element.find_all(True)):
143
- # Se a tag foi decomposta em uma iteração anterior, pule
144
- if not tag.parent:
145
- continue
146
 
147
  if tag.name not in tags_permitidas:
148
- # Se a tag não é permitida, remove a tag mas mantém o conteúdo
149
- tag.unwrap()
150
  else:
151
- # Se a tag é permitida, limpa os atributos não permitidos
152
  atributos_para_manter = atributos_permitidos.get(tag.name, [])
153
  attrs_mantidos = {}
154
- # Preserva atributos essenciais mesmo que não listados explicitamente (ex: href em 'a')
155
- if tag.name == 'a' and 'href' in tag.attrs:
156
- attrs_mantidos['href'] = tag.attrs['href']
157
- if tag.name == 'img' and 'src' in tag.attrs:
158
- attrs_mantidos['src'] = tag.attrs['src']
159
 
160
- # Adiciona outros atributos permitidos
161
  for attr, value in tag.attrs.items():
162
  if attr in atributos_para_manter:
163
  attrs_mantidos[attr] = value
@@ -165,86 +182,83 @@ def extrair_limpar_html_v4(html_bruto):
165
 
166
  # Retorna o HTML limpo e focado como string
167
  html_final = str(target_element)
168
- # Pequena limpeza final do HTML antes de passar pro html2text
169
- html_final = html_final.replace(' ', ' ') # Substitui non-breaking spaces
 
 
 
 
 
 
 
170
  return html_final
171
 
172
 
173
- def html_para_markdown_final_v4(html_input):
174
  """
175
- Pipeline completo V4: Extrai, limpa (com foco na formatação V2) e converte.
176
  """
177
  if not html_input:
178
  return "Por favor, insira algum código HTML."
179
 
180
  try:
181
- # 1. Extrai, remove seções finais e limpa HTML
182
- logging.info("--- Iniciando Extração e Limpeza do HTML ---")
183
- html_processado = extrair_limpar_html_v4(html_input)
184
- logging.info("--- Extração e Limpeza do HTML Concluída ---")
185
 
186
- # Verifica se o resultado tem conteúdo textual significativo
187
  soup_check = BeautifulSoup(html_processado, 'html.parser')
188
  if not html_processado or not soup_check.get_text(strip=True):
189
- logging.warning("HTML resultante após limpeza está vazio ou sem texto.")
190
- # Opcional: retornar o HTML limpo para depuração
191
- # return f"HTML resultante vazio ou sem texto.\nHTML limpo (para depuração):\n{html_processado}"
192
  return "HTML resultante após extração e limpeza está vazio ou não contém texto."
193
 
194
- # 2. Converte o HTML processado para Markdown
195
- logging.info("--- Iniciando Conversão para Markdown ---")
196
  converter = html2text.HTML2Text()
197
- # Configurações para tentar replicar a formatação "boa" anterior (V2)
198
- converter.body_width = 0 # Sem quebra de linha automática por largura
199
  converter.ignore_links = False
200
  converter.ignore_images = False
201
- converter.ignore_emphasis = False # Manter negrito/itálico
202
- # Remover opções que podem afetar espaçamento/parágrafos (como single_line_break)
203
- # converter.single_line_break = True # Removido - Deixar html2text tratar <p>
204
- converter.use_automatic_links = True # Tentar detectar links
205
- converter.unicode_snob = True # Usar caracteres unicode para listas, etc.
206
- converter.escape_snob = True # Escapar caracteres markdown
207
 
208
  markdown_output = converter.handle(html_processado)
209
- logging.info("--- Conversão para Markdown Concluída ---")
210
 
211
- # 3. Pós-processamento do Markdown (Simplificado - mais próximo da V2)
212
- logging.info("--- Iniciando Pós-processamento do Markdown ---")
213
- # Remover espaços em branco no início/fim de cada linha
214
  linhas = [line.strip() for line in markdown_output.splitlines()]
215
- # Remover linhas completamente vazias
216
  linhas_filtradas = [line for line in linhas if line]
217
- # Juntar com duas quebras de linha para parágrafos (Markdown padrão)
218
  markdown_output = "\n\n".join(linhas_filtradas)
219
 
220
- # Limpeza final extra (opcional): Remover múltiplos espaços dentro das linhas
221
- markdown_output = re.sub(r' +', ' ', markdown_output)
222
- # Remover espaços antes de quebras de linha
223
- markdown_output = re.sub(r' +\n', '\n', markdown_output)
 
 
224
 
225
- logging.info("--- Pós-processamento do Markdown Concluído ---")
226
  return markdown_output.strip()
227
 
228
  except Exception as e:
229
- logging.error(f"Erro durante o processo V4: {e}", exc_info=True)
230
- # Tentar retornar o HTML processado em caso de erro na conversão/pós-processamento
231
- try:
232
- html_on_error = html_processado
233
- except NameError:
234
- html_on_error = "(HTML não disponível)"
235
- return (f"Ocorreu um erro: {str(e)}\n\n"
236
- f"Verifique os logs do Space para detalhes.\n\n"
237
- f"HTML processado antes do erro (para depuração):\n"
238
- f"{html_on_error[:2000]}...") # Limita o tamanho
239
 
240
 
241
  # --- Cria a interface Gradio ---
242
  iface = gr.Interface(
243
- fn=html_para_markdown_final_v4, # Usando a função V4
244
  inputs=gr.Textbox(lines=20, label="Insira o HTML bruto aqui", placeholder="Cole o código-fonte HTML completo da página..."),
245
- outputs=gr.Textbox(lines=20, label="Markdown Resultante (Conteúdo Principal Limpo - V4)", show_copy_button=True),
246
- title="Conversor HTML para Markdown (V4 - Foco Conteúdo + Formatação)",
247
- description="Cole o HTML. O script tenta isolar o artigo principal, remove comentários/relacionados/etc. (melhor resultado com IDs/classes padrão), limpa o HTML restante e converte para Markdown, buscando boa formatação.",
248
  allow_flagging='never'
249
  )
250
 
 
1
  # -*- coding: utf-8 -*-
2
  import gradio as gr
3
  import html2text
4
+ from bs4 import BeautifulSoup, Comment
5
  import logging
6
+ import re
7
 
8
  logging.basicConfig(level=logging.INFO)
9
 
10
+ def extrair_limpar_html_v5(html_bruto):
11
  """
12
+ Extrai o conteúdo principal (priorizando .entry-content), remove
13
+ elementos irmãos indesejados (tags, nav, comments, related), limpa
14
+ o conteúdo principal e retorna o HTML limpo.
15
+ V5: Adaptado para a estrutura HTML fornecida.
16
 
17
  :param html_bruto: String contendo o código HTML original.
18
  :return: String contendo o HTML limpo e focado no conteúdo principal.
 
27
  comment.extract()
28
 
29
  target_element = None
30
+ main_container = None # Guarda o elemento que contém o target_element e os irmãos
31
 
32
+ # --- 1. Encontrar o Contêiner Principal Específico (.entry-content) ---
33
+ # Seletores em ordem de preferência para este site
34
  main_content_selectors = [
35
+ '.entry-content', # O mais provável para o corpo do post neste HTML
36
+ '.wp-block-post-content', # Alternativa
37
+ 'article', # Fallback
38
+ 'main', # Fallback mais amplo
39
+ # '[role="main"]', # Menos provável neste tema
40
  ]
 
 
41
 
42
  for selector in main_content_selectors:
43
+ target_element = soup.select_one(selector)
44
+ if target_element:
45
+ logging.info(f"Conteúdo principal identificado usando o seletor: '{selector}'")
46
+ # Tenta encontrar um pai razoável para procurar irmãos
47
+ # Anda alguns níveis acima se necessário, mas não até o body/html se possível
48
+ potential_main_container = target_element.parent
49
+ levels_up = 0
50
+ while potential_main_container and potential_main_container.name in ['div', 'section'] and levels_up < 3:
51
+ # Verifica se este pai contém os blocos indesejados como irmãos do target
52
+ if potential_main_container.select_one('.wp-block-post-terms, .wp-block-comments, .wp-block-query'):
53
+ main_container = potential_main_container
54
+ logging.info(f"Container principal para busca de irmãos definido como: <{main_container.name}>")
55
+ break
56
+ potential_main_container = potential_main_container.parent
57
+ levels_up += 1
58
+
59
+ # Se não encontrou um container com irmãos indesejados, usa o pai direto
60
+ if not main_container:
61
+ main_container = target_element.parent
62
+ if main_container:
63
+ logging.info(f"Container principal para busca de irmãos definido como pai direto: <{main_container.name}>")
64
+
65
+
66
+ break # Para ao encontrar o primeiro target
67
+
68
+ # Fallback se nenhum seletor específico funcionou
69
  if not target_element:
70
+ logging.warning("Nenhum seletor de conteúdo principal específico (.entry-content, article, main) encontrado.")
71
+ # Tenta usar o body, mas a limpeza de irmãos não será eficaz
72
  if soup.body:
73
  target_element = soup.body
74
+ main_container = soup.body # Define main_container como body
75
+ logging.info("Usando <body> como target_element e main_container.")
76
  else:
77
+ logging.error("Falha crítica: Nenhum elemento de conteúdo ou body encontrado.")
78
+ return "" # Não nada para processar
79
+
80
+ # Se não conseguiu definir um main_container, não pode remover irmãos
81
+ if not main_container:
82
+ logging.warning("Não foi possível determinar um container válido para remover irmãos.")
83
+ # Prossegue limpando apenas o target_element encontrado
84
+
85
+ # --- 2. Remover Elementos Irmãos Indesejados (SE main_container foi definido) ---
86
+ if main_container and target_element is not main_container: # remove irmãos se o target não for o próprio container
87
+ logging.info(f"Procurando irmãos indesejados de <{target_element.name}> dentro de <{main_container.name}>...")
88
+ siblings_to_remove_selectors = [
89
+ '.wp-block-post-terms', # Bloco de Tags
90
+ '.wp-container-core-group-is-layout-9b36172e', # Div que contém a navegação Prev/Next (baseado no HTML)
91
+ '.wp-block-comments', # Bloco de comentários inteiro
92
+ '.wp-block-query', # Bloco "Mais Posts" (Query Loop)
93
+ # Poderíamos ser mais específicos para "Mais Posts", mas .wp-block-query parece ok aqui
94
+ # Exemplo: 'div.wp-block-group:has(> h2:contains("Mais posts"))' # Requer análise mais complexa
95
+ ]
96
+ removed_siblings_count = 0
97
+ # Itera sobre os elementos DENTRO do main_container
98
+ for element in main_container.find_all(recursive=False): # Apenas filhos diretos ou netos? Melhor procurar em todo o container
99
+ # Verifica se o elemento atual NÃO é o target_element ou um de seus pais
100
+ if element is not target_element and not element.find(target_element):
101
+ for selector in siblings_to_remove_selectors:
102
+ # Verifica se o elemento corresponde a um seletor indesejado
103
+ # Usamos select_one para garantir que estamos testando o próprio elemento
104
+ # Ou podemos usar element.matches(selector) se a versão do bs4 suportar bem
105
+ if element.select_one(f':is({selector})'): # :is() para testar o próprio elemento
106
+ logging.info(f" Removendo irmão/elemento indesejado: <{element.name} class='{' '.join(element.get('class',[]))}'> (match com '{selector}')")
107
+ element.decompose()
108
+ removed_siblings_count += 1
109
+ break # Sai do loop de seletores para este elemento
110
+
111
+ # Abordagem alternativa/complementar: Buscar DEPOIS do target_element
112
+ for sibling in target_element.find_next_siblings():
113
+ for selector in siblings_to_remove_selectors:
114
+ # Verifica se o irmão corresponde a um seletor indesejado
115
+ if sibling.select_one(f':is({selector})'): # :is() para testar o próprio irmão
116
+ logging.info(f" Removendo irmão SEGUINTE indesejado: <{sibling.name} class='{' '.join(sibling.get('class',[]))}'> (match com '{selector}')")
117
+ sibling.decompose()
118
+ removed_siblings_count += 1
119
+ break # Vai para o próximo irmão
120
+
121
+
122
+ if removed_siblings_count > 0:
123
+ logging.info(f"Removidos {removed_siblings_count} elementos/irmãos indesejados.")
124
+ else:
125
+ logging.info("Nenhum elemento/irmão indesejado conhecido foi encontrado ou removido após o conteúdo principal.")
126
 
127
+ # --- 3. Limpeza Geral DENTRO do target_element isolado ---
128
+ logging.info(f"Iniciando limpeza geral DENTRO do target_element: <{target_element.name}>")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  tags_para_remover_geral = [
130
  'script', 'style', 'form', 'input', 'button', 'select', 'textarea', 'label',
131
  'footer', 'header', 'nav', 'aside', 'iframe', 'noscript', 'meta', 'link',
132
+ 'canvas', 'svg', 'audio', 'video', 'figure', # Remover figure, manter figcaption permitido
133
+ # '.wp-block-button', # Remover botões? Pode ser útil manter alguns. Avaliar.
 
 
 
134
  ]
135
  removed_general_count = 0
136
+ # Importante: usar find_all DENTRO do target_element
137
  for tag_name in tags_para_remover_geral:
138
  for tag in target_element.find_all(tag_name):
139
  tag.decompose()
140
  removed_general_count +=1
141
  if removed_general_count > 0:
142
+ logging.info(f"Removidas {removed_general_count} tags gerais indesejadas dentro do target_element.")
 
143
 
144
+ # --- 4. Limpar Atributos e Tags Restantes no target_element ---
145
  tags_permitidas = {
146
  'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'br', 'a', 'strong', 'b',
147
  'em', 'i', 'u', 's', 'strike', 'del', 'ul', 'ol', 'li', 'img',
148
  'table', 'thead', 'tbody', 'tr', 'th', 'td', 'blockquote', 'pre', 'code',
149
+ 'figcaption'
150
  }
151
  atributos_permitidos = {
152
  'a': ['href', 'title'],
153
+ 'img': ['src', 'alt', 'title', 'width', 'height'],
154
  'th': ['colspan', 'rowspan', 'scope'],
155
  'td': ['colspan', 'rowspan'],
156
  'blockquote': ['cite'],
157
  'ol': ['start'],
158
+ 'pre': [], # Geralmente não precisa de atributos
159
+ 'code': ['class'], # Permitir classe para syntax highlighting (ex: class="language-python")
160
  }
161
 
162
+ # Iterar sobre uma cópia da lista de tags DENTRO do target_element
163
  for tag in list(target_element.find_all(True)):
164
+ if not tag.parent: continue # Ignora tags removidas
 
 
165
 
166
  if tag.name not in tags_permitidas:
167
+ tag.unwrap() # Remove tag, mantém conteúdo
 
168
  else:
169
+ # Limpa atributos
170
  atributos_para_manter = atributos_permitidos.get(tag.name, [])
171
  attrs_mantidos = {}
172
+ # Mantém atributos essenciais primeiro
173
+ if tag.name == 'a' and 'href' in tag.attrs: attrs_mantidos['href'] = tag.attrs['href']
174
+ if tag.name == 'img' and 'src' in tag.attrs: attrs_mantidos['src'] = tag.attrs['src']
175
+ if tag.name == 'img' and 'alt' in tag.attrs: attrs_mantidos['alt'] = tag.attrs['alt'] # Manter ALT
 
176
 
177
+ # Adiciona outros permitidos
178
  for attr, value in tag.attrs.items():
179
  if attr in atributos_para_manter:
180
  attrs_mantidos[attr] = value
 
182
 
183
  # Retorna o HTML limpo e focado como string
184
  html_final = str(target_element)
185
+ html_final = html_final.replace(' ', ' ')
186
+ # Remover divs vazios que podem sobrar após unwrap
187
+ soup_final = BeautifulSoup(html_final, 'html.parser')
188
+ for div in soup_final.find_all('div'):
189
+ if not div.get_text(strip=True) and not div.find(['img', 'br']): # Se não tem texto nem imagem/br
190
+ div.decompose()
191
+ html_final = str(soup_final)
192
+
193
+ logging.info("Limpeza final do HTML concluída.")
194
  return html_final
195
 
196
 
197
+ def html_para_markdown_final_v5(html_input):
198
  """
199
+ Pipeline completo V5: Extrai .entry-content, remove irmãos, limpa, converte.
200
  """
201
  if not html_input:
202
  return "Por favor, insira algum código HTML."
203
 
204
  try:
205
+ # 1. Extrai, remove irmãos indesejados e limpa HTML
206
+ logging.info("--- Iniciando Extração e Limpeza V5 ---")
207
+ html_processado = extrair_limpar_html_v5(html_input)
208
+ logging.info("--- Extração e Limpeza V5 Concluída ---")
209
 
 
210
  soup_check = BeautifulSoup(html_processado, 'html.parser')
211
  if not html_processado or not soup_check.get_text(strip=True):
212
+ logging.warning("HTML resultante V5 após limpeza está vazio ou sem texto.")
 
 
213
  return "HTML resultante após extração e limpeza está vazio ou não contém texto."
214
 
215
+ # 2. Converte o HTML processado para Markdown (Config V4/V2)
216
+ logging.info("--- Iniciando Conversão para Markdown V5 ---")
217
  converter = html2text.HTML2Text()
218
+ converter.body_width = 0
 
219
  converter.ignore_links = False
220
  converter.ignore_images = False
221
+ converter.ignore_emphasis = False
222
+ converter.use_automatic_links = True
223
+ converter.unicode_snob = True
224
+ converter.escape_snob = True
 
 
225
 
226
  markdown_output = converter.handle(html_processado)
227
+ logging.info("--- Conversão para Markdown V5 Concluída ---")
228
 
229
+ # 3. Pós-processamento do Markdown (Simplificado - V4/V2)
230
+ logging.info("--- Iniciando Pós-processamento do Markdown V5 ---")
 
231
  linhas = [line.strip() for line in markdown_output.splitlines()]
 
232
  linhas_filtradas = [line for line in linhas if line]
 
233
  markdown_output = "\n\n".join(linhas_filtradas)
234
 
235
+ # Limpeza final extra
236
+ markdown_output = re.sub(r' +', ' ', markdown_output) # Múltiplos espaços
237
+ markdown_output = re.sub(r' +\n', '\n', markdown_output) # Espaços antes de \n
238
+ # Remover marcadores de lista vazios ou estranhos que podem sobrar
239
+ markdown_output = re.sub(r'\n\n[-*+]\s*\n\n', '\n\n', markdown_output)
240
+ markdown_output = re.sub(r'^\s*[-*+]\s*\n\n', '', markdown_output) # No início
241
 
242
+ logging.info("--- Pós-processamento do Markdown V5 Concluído ---")
243
  return markdown_output.strip()
244
 
245
  except Exception as e:
246
+ logging.error(f"Erro durante o processo V5: {e}", exc_info=True)
247
+ try: html_on_error = html_processado
248
+ except NameError: html_on_error = "(HTML não disponível)"
249
+ return (f"Ocorreu um erro V5: {str(e)}\n\n"
250
+ f"Verifique os logs do Space.\n\n"
251
+ f"HTML processado antes do erro:\n"
252
+ f"{html_on_error[:2000]}...")
 
 
 
253
 
254
 
255
  # --- Cria a interface Gradio ---
256
  iface = gr.Interface(
257
+ fn=html_para_markdown_final_v5, # Usando a função V5
258
  inputs=gr.Textbox(lines=20, label="Insira o HTML bruto aqui", placeholder="Cole o código-fonte HTML completo da página..."),
259
+ outputs=gr.Textbox(lines=20, label="Markdown Resultante (Conteúdo Principal Limpo - V5)", show_copy_button=True),
260
+ title="Conversor HTML para Markdown (V5 - Específico para Estrutura WP)",
261
+ description="Cole o HTML. O script tenta isolar '.entry-content', remove tags/comentários/relacionados/nav que vêm *depois* dele, limpa o HTML restante e converte para Markdown (formatação V2/V4).",
262
  allow_flagging='never'
263
  )
264