# md_to_docx_gost_final.py from docx import Document from docx.shared import Pt, Mm, RGBColor, Inches from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_LINE_SPACING from docx.oxml.ns import qn from docx.oxml import OxmlElement from docx.enum.table import WD_TABLE_ALIGNMENT import re import requests from io import BytesIO # Счётчик для листингов listing_counter = 0 def setup_gost_style(doc): style = doc.styles['Normal'] font = style.font font.name = 'Times New Roman' font.size = Pt(14) font.color.rgb = RGBColor(0, 0, 0) # Чёрный цвет rFonts = style.element.rPr.get_or_add_rFonts() rFonts.set(qn('w:eastAsia'), 'Times New Roman') rFonts.set(qn('w:cs'), 'Times New Roman') pf = style.paragraph_format pf.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY pf.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE pf.first_line_indent = Mm(1.25) pf.space_before = Pt(0) pf.space_after = Pt(0) section = doc.sections[0] section.top_margin = Mm(20) section.bottom_margin = Mm(20) section.left_margin = Mm(30) section.right_margin = Mm(15) def add_heading(doc, text, level=1): heading = doc.add_heading(text, level=level) if level <= 3: heading.alignment = WD_ALIGN_PARAGRAPH.CENTER for run in heading.runs: run.font.bold = True run.font.size = Pt(16) if level == 1 else Pt(14) run.font.color.rgb = RGBColor(0, 0, 0) def add_listing_caption(doc, language="C++"): global listing_counter listing_counter += 1 p = doc.add_paragraph(f"Листинг {listing_counter} — Код на {language}", style='Caption') p.alignment = WD_ALIGN_PARAGRAPH.CENTER p.style.font.color.rgb = RGBColor(0, 0, 0) for run in p.runs: run.font.color.rgb = RGBColor(0, 0, 0) def add_code_block_as_table(doc, code_lines, language=""): global listing_counter add_listing_caption(doc, language) # Подпись ДО таблицы table = doc.add_table(rows=1, cols=1) table.style = 'Table Grid' table.alignment = WD_TABLE_ALIGNMENT.CENTER cell = table.cell(0, 0) # Серый фон shading_elm = parse_xml(f'') cell._tc.get_or_add_tcPr().append(shading_elm) paragraph = cell.paragraphs[0] run = paragraph.add_run() run.font.name = 'Courier New' run._element.rPr.rFonts.set(qn('w:eastAsia'), 'Courier New') run.font.size = Pt(12) run.font.color.rgb = RGBColor(0, 0, 0) run.text = "\n".join(code_lines).strip() # Запрещаем разрыв строки внутри первой строки таблицы tr = table.rows[0]._tr trPr = tr.get_or_add_trPr() cantSplit = OxmlElement('w:cantSplit') cantSplit.set(qn('w:val'), 'true') trPr.append(cantSplit) def add_image_from_url(doc, url, caption_text): try: response = requests.get(url.strip(), timeout=5) image_stream = BytesIO(response.content) p = doc.add_picture(image_stream, width=Inches(5)) p.alignment = WD_ALIGN_PARAGRAPH.CENTER # Подпись к рисунку caption = doc.add_paragraph(f"Рисунок — {caption_text}", style='Caption') caption.alignment = WD_ALIGN_PARAGRAPH.CENTER for run in caption.runs: run.font.color.rgb = RGBColor(0, 0, 0) except Exception as e: print(f"⚠️ Ошибка загрузки изображения: {url} — {e}") doc.add_paragraph(f"[Изображение не загружено: {caption_text}]").alignment = WD_ALIGN_PARAGRAPH.CENTER def parse_xml(xml): from docx.oxml import parse_xml as ox_parse return ox_parse(xml) def nsdecls(*namespaces): return ' '.join(f'xmlns:{ns}="http://schemas.openxmlformats.org/wordprocessingml/2006/main"' for ns in namespaces) def add_formatted_paragraph(doc, text): p = doc.add_paragraph() p.style = 'Normal' parts = re.split(r'(\*\*[^*]+\*\*)', text) for part in parts: if part.startswith('**') and part.endswith('**'): clean_text = part[2:-2] run = p.add_run(clean_text) run.bold = True run.font.color.rgb = RGBColor(0, 0, 0) elif part.strip(): run = p.add_run(part) run.font.color.rgb = RGBColor(0, 0, 0) def parse_markdown_and_fill(doc, md_text): global listing_counter lines = md_text.split('\n') i = 0 in_code_block = False code_buffer = [] code_language = "" while i < len(lines): line = lines[i].rstrip() # === Заголовки 1–4 === if line.startswith('#### '): if in_code_block: add_code_block_as_table(doc, code_buffer, code_language) in_code_block = False code_buffer = [] add_heading(doc, line[5:].strip(), level=4) elif line.startswith('### '): if in_code_block: add_code_block_as_table(doc, code_buffer, code_language) in_code_block = False code_buffer = [] add_heading(doc, line[4:].strip(), level=3) elif line.startswith('## '): if in_code_block: add_code_block_as_table(doc, code_buffer, code_language) in_code_block = False code_buffer = [] add_heading(doc, line[3:].strip(), level=2) elif line.startswith('# '): if in_code_block: add_code_block_as_table(doc, code_buffer, code_language) in_code_block = False code_buffer = [] add_heading(doc, line[2:].strip(), level=1) # === Горизонтальная линия === elif line.strip() == '---': doc.add_paragraph().add_run().add_break() # === Блок кода ``` === elif line.startswith('```'): if not in_code_block: in_code_block = True code_language = line[3:].strip() or "C++" code_buffer = [] else: in_code_block = False add_code_block_as_table(doc, code_buffer, code_language) code_buffer = [] elif in_code_block: code_buffer.append(line) # === Изображения: ![Описание](url) === elif re.match(r'^!\[.*?\]\(.*?\)$', line.strip()): match = re.match(r'^!\[(.*?)\]\((.*?)\)$', line.strip()) if match: caption = match.group(1) url = match.group(2) add_image_from_url(doc, url, caption) # === Маркированный список === elif line.startswith('- '): p = doc.add_paragraph(line[2:], style='List Bullet') for run in p.runs: run.font.color.rgb = RGBColor(0, 0, 0) # === Обычный абзац с жирным === else: if line.strip(): if '**' in line: add_formatted_paragraph(doc, line) else: p = doc.add_paragraph(line) p.style = 'Normal' for run in p.runs: run.font.color.rgb = RGBColor(0, 0, 0) i += 1 # Остаток кода if in_code_block and code_buffer: add_code_block_as_table(doc, code_buffer, code_language) def main(): try: with open(r"C:\Users\hlebushek\Desktop\Пример.txt", "r", encoding="utf-8") as f: md_content = f.read() except FileNotFoundError: print("Файл 'Пример.txt' не найден.") return doc = Document() setup_gost_style(doc) parse_markdown_and_fill(doc, md_content) output_filename = "Практическая_работа_6.docx" doc.save(output_filename) print(f"✅ Готово! Файл сохранён как '{output_filename}'") print(f"📎 Включены: изображения, чёрный текст, листинги перед кодом, нумерация.") if __name__ == '__main__': main()