daswer123 commited on
Commit
0ebae9a
·
verified ·
1 Parent(s): 078908e

Upload 7 files

Browse files
Files changed (2) hide show
  1. app.py +40 -15
  2. converter.py +49 -34
app.py CHANGED
@@ -2,20 +2,39 @@ import gradio as gr
2
  from converter import MarkdownToDocxConverter
3
  from datetime import datetime
4
  import os
 
5
 
6
  converter = MarkdownToDocxConverter()
7
 
8
- def convert_markdown_to_docx(markdown_text: str, file_input: str):
9
- if file_input:
10
- with open(file_input, "r") as file:
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  markdown_text = file.read()
12
 
13
-
 
 
14
  output_dir = "output"
15
  os.makedirs(output_dir, exist_ok=True)
16
  output_filename = f"output_{datetime.now().strftime('%Y%m%d_%H%M%S')}.docx"
17
- converter.convert(markdown_text, os.path.join(output_dir, output_filename))
18
- return os.path.join(output_dir, output_filename)
 
 
 
19
 
20
  demo = gr.Blocks(title="Markdown to DOCX Converter")
21
 
@@ -24,15 +43,21 @@ with demo:
24
  with gr.Row():
25
  with gr.Column():
26
  with gr.Tab("Текст"):
27
- markdown_input = gr.TextArea(label="Markdown Input", value="")
28
- with gr.Tab("Файл (в приоритете)"):
29
- file_input = gr.File(label="Markdown Input")
 
 
30
  with gr.Column():
31
- gr.Markdown("Output:")
32
- docx_output = gr.File(label="DOCX Output")
33
- convert_button = gr.Button("Convert")
34
- convert_button.click(convert_markdown_to_docx, inputs=[markdown_input, file_input], outputs=docx_output)
35
-
 
 
 
 
36
 
37
  if __name__ == "__main__":
38
- demo.launch()
 
2
  from converter import MarkdownToDocxConverter
3
  from datetime import datetime
4
  import os
5
+ import requests
6
 
7
  converter = MarkdownToDocxConverter()
8
 
9
+ def convert_markdown_to_docx(markdown_text: str, file_input: str, url_input: str):
10
+ # Приоритет: URL > Файл > Текст
11
+ if url_input:
12
+ try:
13
+ response = requests.get(url_input)
14
+ response.raise_for_status()
15
+ # Пытаемся получить "сырое" содержимое для таких сайтов, как GitHub Gist
16
+ if "gist.github.com" in url_input and not url_input.endswith("/raw"):
17
+ raw_url = url_input + "/raw"
18
+ response = requests.get(raw_url)
19
+ response.raise_for_status()
20
+ markdown_text = response.text
21
+ except requests.exceptions.RequestException as e:
22
+ raise gr.Error(f"Ошибка при скачивании файла по URL: {e}")
23
+ elif file_input:
24
+ with open(file_input.name, "r", encoding='utf-8') as file:
25
  markdown_text = file.read()
26
 
27
+ if not markdown_text.strip():
28
+ raise gr.Error("Нет входных данных для конвертации. Введите текст, загрузите файл или укажите URL.")
29
+
30
  output_dir = "output"
31
  os.makedirs(output_dir, exist_ok=True)
32
  output_filename = f"output_{datetime.now().strftime('%Y%m%d_%H%M%S')}.docx"
33
+ output_path = os.path.join(output_dir, output_filename)
34
+
35
+ converter.convert(markdown_text, output_path)
36
+
37
+ return output_path
38
 
39
  demo = gr.Blocks(title="Markdown to DOCX Converter")
40
 
 
43
  with gr.Row():
44
  with gr.Column():
45
  with gr.Tab("Текст"):
46
+ markdown_input = gr.TextArea(label="Markdown Input", value="", lines=15)
47
+ with gr.Tab("Файл"):
48
+ file_input = gr.File(label="Загрузите Markdown файл")
49
+ with gr.Tab("URL"):
50
+ url_input = gr.Textbox(label="Введите URL Markdown файла", placeholder="https://gist.github.com/...")
51
  with gr.Column():
52
+ gr.Markdown("### Результат")
53
+ docx_output = gr.File(label="Скачать DOCX")
54
+
55
+ convert_button = gr.Button("Конвертировать", variant="primary")
56
+ convert_button.click(
57
+ convert_markdown_to_docx,
58
+ inputs=[markdown_input, file_input, url_input],
59
+ outputs=docx_output
60
+ )
61
 
62
  if __name__ == "__main__":
63
+ demo.launch(server_name="0.0.0.0", server_port=8010)
converter.py CHANGED
@@ -1,8 +1,8 @@
1
  import re
2
  import json
3
  import io
4
- import requests
5
  import base64
 
6
  from pathlib import Path
7
  from typing import List, Dict, Any, Optional, Tuple
8
  import markdown
@@ -21,7 +21,6 @@ class MarkdownToDocxConverter:
21
 
22
  def __init__(self):
23
  self.doc = None
24
- self.styles_created = False
25
 
26
  # Цветовая схема для подсветки кода
27
  self.code_colors = {
@@ -35,9 +34,6 @@ class MarkdownToDocxConverter:
35
 
36
  def create_styles(self):
37
  """Создание пользовательских стилей для документа"""
38
- if self.styles_created:
39
- return
40
-
41
  # Стиль для блоков кода
42
  code_style = self.doc.styles.add_style('CodeBlock', WD_STYLE_TYPE.PARAGRAPH)
43
  code_style.font.name = 'Consolas'
@@ -65,8 +61,6 @@ class MarkdownToDocxConverter:
65
  heading_style.font.color.rgb = RGBColor(0, 0, 0)
66
  heading_style.font.bold = True
67
  heading_style.font.size = Pt(26 - i * 3)
68
-
69
- self.styles_created = True
70
 
71
  def parse_code_block(self, code_text: str, language: str = '') -> List[Tuple[str, str]]:
72
  """Простая подсветка синтаксиса для кода"""
@@ -198,17 +192,19 @@ class MarkdownToDocxConverter:
198
  elif element.name == 'pre':
199
  code_element = element.find('code')
200
  if code_element:
201
- # Извлекаем язык из класса
202
  classes = code_element.get('class', [])
203
- language = ''
204
- for cls in classes:
205
- if cls.startswith('language-'):
206
- language = cls.replace('language-', '')
207
- break
208
  code_text = code_element.get_text()
209
- if language == 'mermaid':
 
 
210
  self.add_mermaid_diagram(code_text)
211
  else:
 
 
 
 
 
 
212
  self.add_code_block(code_text, language)
213
  else:
214
  self.add_code_block(element.get_text())
@@ -248,11 +244,11 @@ class MarkdownToDocxConverter:
248
  self.process_html_element(child, parent_paragraph)
249
 
250
  def add_mermaid_diagram(self, code: str):
251
- """Рендеринг и вставка диаграммы Mermaid"""
252
  try:
253
- # Кодируем код диаграммы в base64
254
- graphbytes = code.encode("ascii")
255
- base64_bytes = base64.b64encode(graphbytes)
256
  base64_string = base64_bytes.decode("ascii")
257
 
258
  # Формируем URL для запроса
@@ -327,30 +323,49 @@ class MarkdownToDocxConverter:
327
 
328
  def convert(self, markdown_text: str, output_path: str):
329
  """Конвертация Markdown текста в DOCX файл"""
330
- # Создаем новый документ
 
 
 
 
 
 
 
 
 
 
 
 
331
  self.doc = Document()
332
  self.create_styles()
333
-
334
- # Конвертируем Markdown в HTML
335
  md = markdown.Markdown(extensions=[
336
- 'fenced_code',
337
- 'codehilite',
338
- 'tables',
339
- 'toc',
340
- 'nl2br',
341
- 'sane_lists'
342
  ])
343
-
344
  html = md.convert(markdown_text)
345
-
346
- # Парсим HTML с BeautifulSoup
347
  soup = BeautifulSoup(html, 'html.parser')
348
-
349
- # Обрабатываем каждый элемент
350
  for element in soup.children:
351
- self.process_html_element(element)
 
 
 
 
352
 
353
- # Сохраняем документ
 
 
 
 
 
 
 
 
 
 
 
354
  self.doc.save(output_path)
355
  print(f"Документ успешно сохранен: {output_path}")
356
 
 
1
  import re
2
  import json
3
  import io
 
4
  import base64
5
+ import requests
6
  from pathlib import Path
7
  from typing import List, Dict, Any, Optional, Tuple
8
  import markdown
 
21
 
22
  def __init__(self):
23
  self.doc = None
 
24
 
25
  # Цветовая схема для подсветки кода
26
  self.code_colors = {
 
34
 
35
  def create_styles(self):
36
  """Создание пользовательских стилей для документа"""
 
 
 
37
  # Стиль для блоков кода
38
  code_style = self.doc.styles.add_style('CodeBlock', WD_STYLE_TYPE.PARAGRAPH)
39
  code_style.font.name = 'Consolas'
 
61
  heading_style.font.color.rgb = RGBColor(0, 0, 0)
62
  heading_style.font.bold = True
63
  heading_style.font.size = Pt(26 - i * 3)
 
 
64
 
65
  def parse_code_block(self, code_text: str, language: str = '') -> List[Tuple[str, str]]:
66
  """Простая подсветка синтаксиса для кода"""
 
192
  elif element.name == 'pre':
193
  code_element = element.find('code')
194
  if code_element:
 
195
  classes = code_element.get('class', [])
 
 
 
 
 
196
  code_text = code_element.get_text()
197
+
198
+ # Check if it's a mermaid diagram
199
+ if 'mermaid' in classes or 'language-mermaid' in classes:
200
  self.add_mermaid_diagram(code_text)
201
  else:
202
+ # It's a regular code block, find the language
203
+ language = ''
204
+ for cls in classes:
205
+ if cls.startswith('language-'):
206
+ language = cls.replace('language-', '')
207
+ break
208
  self.add_code_block(code_text, language)
209
  else:
210
  self.add_code_block(element.get_text())
 
244
  self.process_html_element(child, parent_paragraph)
245
 
246
  def add_mermaid_diagram(self, code: str):
247
+ """Рендеринг и вставка диаграммы Mermaid через mermaid.ink"""
248
  try:
249
+ # Кодируем код диаграммы в URL-безопасный base64
250
+ graphbytes = code.encode("utf-8")
251
+ base64_bytes = base64.urlsafe_b64encode(graphbytes)
252
  base64_string = base64_bytes.decode("ascii")
253
 
254
  # Формируем URL для запроса
 
323
 
324
  def convert(self, markdown_text: str, output_path: str):
325
  """Конвертация Markdown текста в DOCX файл"""
326
+ # 1. Извлекаем диаграммы Mermaid и заменяем их плейсхолдерами
327
+ mermaid_diagrams = {}
328
+ def replace_mermaid(match):
329
+ key = f"%%MERMAID_DIAGRAM_{len(mermaid_diagrams)}%%"
330
+ # Сохраняем только код диаграммы
331
+ mermaid_diagrams[key] = match.group(1).strip()
332
+ # Возвращаем плейсхолдер в виде параграфа, чтобы он не был удален
333
+ return f"\n<p>{key}</p>\n"
334
+
335
+ # Регулярное выражение для поиска блоков ```mermaid ... ```
336
+ markdown_text = re.sub(r'```mermaid\n(.*?)\n```', replace_mermaid, markdown_text, flags=re.DOTALL)
337
+
338
+ # 2. Создаем новый документ и стили
339
  self.doc = Document()
340
  self.create_styles()
341
+
342
+ # 3. Конвертируем оставшийся Markdown в HTML
343
  md = markdown.Markdown(extensions=[
344
+ 'fenced_code', 'codehilite', 'tables', 'toc', 'nl2br', 'sane_lists'
 
 
 
 
 
345
  ])
 
346
  html = md.convert(markdown_text)
347
+
348
+ # 4. Парсим HTML и обрабатываем элементы
349
  soup = BeautifulSoup(html, 'html.parser')
 
 
350
  for element in soup.children:
351
+ # Проверяем, содержит ли элемент плейсхолдер Mermaid
352
+ if isinstance(element, NavigableString):
353
+ # Пропускаем пустые строки
354
+ if not str(element).strip():
355
+ continue
356
 
357
+ text_content = element.get_text().strip()
358
+ if "%%MERMAID_DIAGRAM_" in text_content:
359
+ key = text_content
360
+ if key in mermaid_diagrams:
361
+ self.add_mermaid_diagram(mermaid_diagrams[key])
362
+ else:
363
+ # Если плейсхолдер найден, но нет диаграммы, просто пропускаем
364
+ continue
365
+ else:
366
+ self.process_html_element(element)
367
+
368
+ # 5. Сохраняем документ
369
  self.doc.save(output_path)
370
  print(f"Документ успешно сохранен: {output_path}")
371