daswer123 commited on
Commit
e8c87b8
·
verified ·
1 Parent(s): 4ed55f0

Upload 7 files

Browse files
Files changed (7) hide show
  1. .gitattributes +2 -35
  2. .gitignore +2 -0
  3. Dockerfile +20 -0
  4. app.py +38 -0
  5. converter.py +390 -0
  6. docker-compose.yml +8 -0
  7. requirements.txt +4 -0
.gitattributes CHANGED
@@ -1,35 +1,2 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ # Auto detect text files and perform LF normalization
2
+ * text=auto
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ /output
2
+ *.pyc
Dockerfile ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use an official Python runtime as a parent image
2
+ FROM python:3.9-slim
3
+
4
+ # Set the working directory in the container
5
+ WORKDIR /app
6
+
7
+ # Copy the dependencies file to the working directory
8
+ COPY requirements.txt .
9
+
10
+ # Install any needed packages specified in requirements.txt
11
+ RUN pip install --no-cache-dir -r requirements.txt
12
+
13
+ # Copy the rest of the application's code to the working directory
14
+ COPY . .
15
+
16
+ # Make port 8010 available to the world outside this container
17
+ EXPOSE 8010
18
+
19
+ # Run app.py when the container launches
20
+ CMD ["python", "app.py"]
app.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 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
+
22
+ with demo:
23
+ gr.Markdown("# Markdown to DOCX Converter")
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(server_name="0.0.0.0", server_port=8010)
converter.py ADDED
@@ -0,0 +1,390 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ import json
3
+ from pathlib import Path
4
+ from typing import List, Dict, Any, Optional, Tuple
5
+ import markdown
6
+ from markdown.extensions import codehilite, fenced_code, tables, toc
7
+ from docx import Document
8
+ from docx.shared import Pt, RGBColor, Inches
9
+ from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
10
+ from docx.enum.style import WD_STYLE_TYPE
11
+ from docx.oxml import OxmlElement
12
+ from docx.oxml.ns import qn
13
+ from bs4 import BeautifulSoup, NavigableString
14
+
15
+
16
+ class MarkdownToDocxConverter:
17
+ """Конвертер Markdown в DOCX с сохранением структуры и форматирования"""
18
+
19
+ def __init__(self):
20
+ self.doc = None
21
+ self.styles_created = False
22
+
23
+ # Цветовая схема для подсветки кода
24
+ self.code_colors = {
25
+ 'keyword': RGBColor(0, 0, 255), # Синий для ключевых слов
26
+ 'string': RGBColor(0, 128, 0), # Зеленый для строк
27
+ 'comment': RGBColor(128, 128, 128), # Серый для комментариев
28
+ 'number': RGBColor(255, 0, 0), # Красный для чисел
29
+ 'function': RGBColor(128, 0, 128), # Фиолетовый для функций
30
+ 'default': RGBColor(0, 0, 0) # Черный по умолчанию
31
+ }
32
+
33
+ def create_styles(self):
34
+ """Создание пользовательских стилей для документа"""
35
+ if self.styles_created:
36
+ return
37
+
38
+ # Стиль для блоков кода
39
+ code_style = self.doc.styles.add_style('CodeBlock', WD_STYLE_TYPE.PARAGRAPH)
40
+ code_style.font.name = 'Consolas'
41
+ code_style.font.size = Pt(9)
42
+ code_style.paragraph_format.space_before = Pt(6)
43
+ code_style.paragraph_format.space_after = Pt(6)
44
+ code_style.paragraph_format.left_indent = Inches(0.3)
45
+
46
+ # Добавляем серый фон для блоков кода
47
+ shading = OxmlElement('w:shd')
48
+ shading.set(qn('w:val'), 'clear')
49
+ shading.set(qn('w:color'), 'auto')
50
+ shading.set(qn('w:fill'), 'F0F0F0')
51
+ code_style.element.get_or_add_pPr().append(shading)
52
+
53
+ # Стиль для inline кода
54
+ inline_code_style = self.doc.styles.add_style('InlineCode', WD_STYLE_TYPE.CHARACTER)
55
+ inline_code_style.font.name = 'Consolas'
56
+ inline_code_style.font.size = Pt(9)
57
+ inline_code_style.font.color.rgb = RGBColor(255, 0, 0)
58
+
59
+ # Стили для заголовков
60
+ for i in range(1, 7):
61
+ heading_style = self.doc.styles[f'Heading {i}']
62
+ heading_style.font.color.rgb = RGBColor(0, 0, 0)
63
+ heading_style.font.bold = True
64
+ heading_style.font.size = Pt(26 - i * 3)
65
+
66
+ self.styles_created = True
67
+
68
+ def parse_code_block(self, code_text: str, language: str = '') -> List[Tuple[str, str]]:
69
+ """Простая подсветка синтаксиса для кода"""
70
+ tokens = []
71
+
72
+ if language.lower() in ['json', 'javascript', 'js']:
73
+ # Простая токенизация для JSON/JavaScript
74
+ lines = code_text.split('\n')
75
+ for line in lines:
76
+ # Комментарии
77
+ if line.strip().startswith('//'):
78
+ tokens.append((line + '\n', 'comment'))
79
+ continue
80
+
81
+ # Строки в кавычках
82
+ parts = re.split(r'("(?:[^"\\]|\\.)*")', line)
83
+ for i, part in enumerate(parts):
84
+ if i % 2 == 1: # Строка в кавычках
85
+ tokens.append((part, 'string'))
86
+ else:
87
+ # Числа
88
+ sub_parts = re.split(r'(\b\d+\.?\d*\b)', part)
89
+ for j, sub_part in enumerate(sub_parts):
90
+ if j % 2 == 1: # Число
91
+ tokens.append((sub_part, 'number'))
92
+ else:
93
+ # Ключевые слова
94
+ keywords = ['GET', 'POST', 'PUT', 'DELETE', 'Bearer', 'Authorization']
95
+ for keyword in keywords:
96
+ if keyword in sub_part:
97
+ sub_part = sub_part.replace(keyword, f'\x00{keyword}\x01')
98
+
99
+ final_parts = re.split(r'\x00(.*?)\x01', sub_part)
100
+ for k, final_part in enumerate(final_parts):
101
+ if k % 2 == 1: # Ключевое слово
102
+ tokens.append((final_part, 'keyword'))
103
+ else:
104
+ tokens.append((final_part, 'default'))
105
+ tokens.append(('\n', 'default'))
106
+
107
+ elif language.lower() == 'http':
108
+ lines = code_text.split('\n')
109
+ for line in lines:
110
+ if line.startswith(('GET', 'POST', 'PUT', 'DELETE', 'PATCH')):
111
+ parts = line.split(' ', 2)
112
+ if len(parts) >= 1:
113
+ tokens.append((parts[0] + ' ', 'keyword'))
114
+ if len(parts) >= 2:
115
+ tokens.append((parts[1], 'string'))
116
+ if len(parts) >= 3:
117
+ tokens.append((' ' + parts[2], 'default'))
118
+ elif ':' in line:
119
+ key, value = line.split(':', 1)
120
+ tokens.append((key + ':', 'function'))
121
+ tokens.append((value, 'string'))
122
+ else:
123
+ tokens.append((line, 'default'))
124
+ tokens.append(('\n', 'default'))
125
+ else:
126
+ # Для других языков просто возвращаем текст как есть
127
+ tokens.append((code_text, 'default'))
128
+
129
+ return tokens
130
+
131
+ def add_code_block(self, code_text: str, language: str = ''):
132
+ """Добавление блока кода с подсветкой синтаксиса"""
133
+ # Создаем параграф с кодом
134
+ p = self.doc.add_paragraph()
135
+ p.style = 'CodeBlock'
136
+
137
+ # Парсим и добавляем токены с цветами
138
+ tokens = self.parse_code_block(code_text.strip(), language)
139
+
140
+ for token_text, token_type in tokens:
141
+ if token_text:
142
+ run = p.add_run(token_text)
143
+ run.font.name = 'Consolas'
144
+ run.font.size = Pt(9)
145
+ run.font.color.rgb = self.code_colors.get(token_type, self.code_colors['default'])
146
+
147
+ def process_inline_code(self, paragraph, text: str):
148
+ """Обработка inline кода в тексте"""
149
+ parts = re.split(r'`([^`]+)`', text)
150
+ for i, part in enumerate(parts):
151
+ if i % 2 == 0: # Обычный текст
152
+ if part:
153
+ paragraph.add_run(part)
154
+ else: # Код
155
+ run = paragraph.add_run(part)
156
+ run.font.name = 'Consolas'
157
+ run.font.size = Pt(9)
158
+ run.font.color.rgb = RGBColor(255, 0, 0)
159
+ # Добавляем серый фон
160
+ shading = OxmlElement('w:shd')
161
+ shading.set(qn('w:val'), 'clear')
162
+ shading.set(qn('w:color'), 'auto')
163
+ shading.set(qn('w:fill'), 'E0E0E0')
164
+ run._element.get_or_add_rPr().append(shading)
165
+
166
+ def process_html_element(self, element, parent_paragraph=None):
167
+ """Рекурсивная обработка HTML элементов"""
168
+ if isinstance(element, NavigableString):
169
+ text = str(element)
170
+ if parent_paragraph and text.strip():
171
+ if '`' in text:
172
+ self.process_inline_code(parent_paragraph, text)
173
+ else:
174
+ parent_paragraph.add_run(text)
175
+ return
176
+
177
+ if element.name == 'h1':
178
+ p = self.doc.add_heading(element.get_text(), level=1)
179
+ elif element.name == 'h2':
180
+ p = self.doc.add_heading(element.get_text(), level=2)
181
+ elif element.name == 'h3':
182
+ p = self.doc.add_heading(element.get_text(), level=3)
183
+ elif element.name == 'h4':
184
+ p = self.doc.add_heading(element.get_text(), level=4)
185
+ elif element.name == 'h5':
186
+ p = self.doc.add_heading(element.get_text(), level=5)
187
+ elif element.name == 'h6':
188
+ p = self.doc.add_heading(element.get_text(), level=6)
189
+ elif element.name == 'p':
190
+ p = self.doc.add_paragraph()
191
+ for child in element.children:
192
+ self.process_html_element(child, p)
193
+ elif element.name == 'pre':
194
+ code_element = element.find('code')
195
+ if code_element:
196
+ # Извлекаем язык из класса
197
+ classes = code_element.get('class', [])
198
+ language = ''
199
+ for cls in classes:
200
+ if cls.startswith('language-'):
201
+ language = cls.replace('language-', '')
202
+ break
203
+ code_text = code_element.get_text()
204
+ self.add_code_block(code_text, language)
205
+ else:
206
+ self.add_code_block(element.get_text())
207
+ elif element.name == 'code' and parent_paragraph:
208
+ # Inline код
209
+ run = parent_paragraph.add_run(element.get_text())
210
+ run.font.name = 'Consolas'
211
+ run.font.size = Pt(9)
212
+ run.font.color.rgb = RGBColor(255, 0, 0)
213
+ elif element.name == 'ul':
214
+ for li in element.find_all('li', recursive=False):
215
+ p = self.doc.add_paragraph(style='List Bullet')
216
+ for child in li.children:
217
+ self.process_html_element(child, p)
218
+ elif element.name == 'ol':
219
+ for i, li in enumerate(element.find_all('li', recursive=False), 1):
220
+ p = self.doc.add_paragraph(style='List Number')
221
+ for child in li.children:
222
+ self.process_html_element(child, p)
223
+ elif element.name == 'strong' or element.name == 'b':
224
+ if parent_paragraph:
225
+ run = parent_paragraph.add_run(element.get_text())
226
+ run.bold = True
227
+ elif element.name == 'em' or element.name == 'i':
228
+ if parent_paragraph:
229
+ run = parent_paragraph.add_run(element.get_text())
230
+ run.italic = True
231
+ elif element.name == 'table':
232
+ self.add_table(element)
233
+ elif element.name == 'hr':
234
+ # Горизонтальная линия
235
+ p = self.doc.add_paragraph()
236
+ p.add_run('_' * 50).font.color.rgb = RGBColor(128, 128, 128)
237
+ else:
238
+ # Для остальных элементов рекурсивно обрабатываем детей
239
+ for child in element.children:
240
+ self.process_html_element(child, parent_paragraph)
241
+
242
+ def add_table(self, table_element):
243
+ """Добавление таблицы в документ"""
244
+ rows = table_element.find_all('tr')
245
+ if not rows:
246
+ return
247
+
248
+ # Считаем количество столбцов
249
+ cols = max(len(row.find_all(['td', 'th'])) for row in rows)
250
+
251
+ # Создаем таблицу
252
+ table = self.doc.add_table(rows=0, cols=cols)
253
+ table.style = 'Table Grid'
254
+
255
+ # Добавляем строки
256
+ for row_element in rows:
257
+ cells = row_element.find_all(['td', 'th'])
258
+ row = table.add_row()
259
+
260
+ for i, cell_element in enumerate(cells):
261
+ if i < cols:
262
+ cell = row.cells[i]
263
+ # Очищаем ячейку
264
+ cell.text = cell_element.get_text().strip()
265
+
266
+ # Если это заголовок, делаем жирным
267
+ if cell_element.name == 'th':
268
+ for paragraph in cell.paragraphs:
269
+ for run in paragraph.runs:
270
+ run.bold = True
271
+
272
+ def convert(self, markdown_text: str, output_path: str):
273
+ """Конвертация Markdown текста в DOCX файл"""
274
+ # Создаем новый документ
275
+ self.doc = Document()
276
+ self.create_styles()
277
+
278
+ # Конвертируем Markdown в HTML
279
+ md = markdown.Markdown(extensions=[
280
+ 'fenced_code',
281
+ 'codehilite',
282
+ 'tables',
283
+ 'toc',
284
+ 'nl2br',
285
+ 'sane_lists'
286
+ ])
287
+
288
+ html = md.convert(markdown_text)
289
+
290
+ # Парсим HTML с BeautifulSoup
291
+ soup = BeautifulSoup(html, 'html.parser')
292
+
293
+ # Обрабатываем каждый элемент
294
+ for element in soup.children:
295
+ self.process_html_element(element)
296
+
297
+ # Сохраняем документ
298
+ self.doc.save(output_path)
299
+ print(f"Документ успешно сохранен: {output_path}")
300
+
301
+
302
+ # Пример использования
303
+ if __name__ == "__main__":
304
+ # Пример Markdown текста с API документацией
305
+ sample_markdown = """# API Documentation
306
+
307
+ ## Ресурсы
308
+
309
+ ### GET /api/resources/presets
310
+ Получить список доступных пресетов для создания проектов.
311
+
312
+ **Запрос:**
313
+ ```http
314
+ GET /api/resources/presets
315
+ Authorization: Bearer YOUR_API_KEY
316
+ ```
317
+
318
+ **Ответ:**
319
+ ```json
320
+ {
321
+ "status": "ok",
322
+ "data": [
323
+ "news_template.json",
324
+ "education_template.json",
325
+ "entertainment_template.json"
326
+ ]
327
+ }
328
+ ```
329
+
330
+ ### POST /api/resources/create
331
+ Создать новый ресурс.
332
+
333
+ **Параметры запроса:**
334
+ - `name` (string, обязательный) - название ресурса
335
+ - `type` (string, обязательный) - тип ресурса
336
+ - `preset` (string, опциональный) - имя пресета
337
+
338
+ **Пример запроса:**
339
+ ```javascript
340
+ const response = await fetch('/api/resources/create', {
341
+ method: 'POST',
342
+ headers: {
343
+ 'Authorization': 'Bearer YOUR_API_KEY',
344
+ 'Content-Type': 'application/json'
345
+ },
346
+ body: JSON.stringify({
347
+ name: 'My Resource',
348
+ type: 'document',
349
+ preset: 'news_template.json'
350
+ })
351
+ });
352
+ ```
353
+
354
+ ## ��оды ответов
355
+
356
+ | Код | Описание |
357
+ |-----|----------|
358
+ | 200 | Успешный запрос |
359
+ | 401 | Неавторизован |
360
+ | 404 | Ресурс не найден |
361
+ | 500 | Внутренняя ошибка сервера |
362
+
363
+ ## Примечания
364
+
365
+ - Все запросы должны содержать заголовок `Authorization`
366
+ - Ответы возвращаются в формате JSON
367
+ - Используйте `UTF-8` кодировку для всех запросов
368
+ """
369
+
370
+ # Создаем конвертер и конвертируем
371
+ converter = MarkdownToDocxConverter()
372
+ # converter.convert(sample_markdown, "USER_DOCUMENTATION.md")
373
+
374
+ # Можно также конвертировать из файла
375
+ try:
376
+ with open('USER_DOCUMENTATION.md', 'r', encoding='utf-8') as f:
377
+ markdown_content = f.read()
378
+ converter.convert(markdown_content, 'output.docx')
379
+ except UnicodeDecodeError:
380
+ print("Error: Unable to decode file with UTF-8 encoding. Trying with different encoding...")
381
+ try:
382
+ with open('USER_DOCUMENTATION.md', 'r', encoding='latin-1') as f:
383
+ markdown_content = f.read()
384
+ converter.convert(markdown_content, 'output.docx')
385
+ except Exception as e:
386
+ print(f"Error reading file: {e}")
387
+ except FileNotFoundError:
388
+ print("Error: USER_DOCUMENTATION.md file not found")
389
+ except Exception as e:
390
+ print(f"Error: {e}")
docker-compose.yml ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+ services:
3
+ markdown-converter:
4
+ build: .
5
+ ports:
6
+ - "8010:8010"
7
+ volumes:
8
+ - ./output:/app/output
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ python-docx
2
+ gradio
3
+ markdown
4
+ beautifulsoup4