First Commit
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
.idea
|
||||||
|
.venv
|
||||||
|
*.docx
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
# GhostEditor
|
||||||
|
Программа для конвертации из формата `md` в формат `docx`(x это без макросов, m с макросами) (`Office Open XML`),`odt`(`OpenDocument`)
|
||||||
|
|
||||||
|
#### **1. python-docx**
|
||||||
|
**Назначение**: Создание и модификация Word-документов.
|
||||||
|
**Ключевые возможности**:
|
||||||
|
- Добавление/редактирование текста, таблиц, изображений.
|
||||||
|
- Управление стилями, колонтитулами, сносками.
|
||||||
|
- Работа с XML через абстракции (Paragraph, Run, Table).
|
||||||
|
|
||||||
|
**Пример**:
|
||||||
|
```python
|
||||||
|
from docx import Document
|
||||||
|
from docx.shared import Pt
|
||||||
|
|
||||||
|
doc = Document()
|
||||||
|
paragraph = doc.add_paragraph("Hello ")
|
||||||
|
run = paragraph.add_run("OOXML!")
|
||||||
|
run.bold = True
|
||||||
|
run.font.size = Pt(14)
|
||||||
|
|
||||||
|
# Таблица
|
||||||
|
table = doc.add_table(rows=2, cols=2)
|
||||||
|
table.cell(0, 0).text = "Cell A1"
|
||||||
|
|
||||||
|
doc.save("report.docx")
|
||||||
|
```
|
||||||
|
|
||||||
|
**Плюсы**:
|
||||||
|
- Интуитивный API, близкий к логике Word.
|
||||||
|
- Поддержка всех ключевых частей пакета (headers, footers, styles).
|
||||||
|
|
||||||
|
**Минусы**:
|
||||||
|
- Нет поддержки макросов.
|
||||||
|
- Ограниченная работа с графикой (изображения добавляются, но редактирование сложно).
|
||||||
|
|
||||||
|
**Ссылка**: [python-docx.readthedocs.io](https://python-docx.readthedocs.io/)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### **2. docxtpl**
|
||||||
|
**Назначение**: Генерация документов по шаблонам (Jinja2).
|
||||||
|
**Особенности**:
|
||||||
|
- Шаблоны .docx с переменными (`{{ variable }}`) и логикой (`{% if %}`).
|
||||||
|
- Поддержка таблиц, изображений, HTML-фрагментов.
|
||||||
|
|
||||||
|
**Пример**:
|
||||||
|
```python
|
||||||
|
from docxtpl import DocxTemplate
|
||||||
|
|
||||||
|
doc = DocxTemplate("template.docx")
|
||||||
|
context = {
|
||||||
|
"title": "Отчет за 2023",
|
||||||
|
"items": [{"name": "Item 1"}, {"name": "Item 2"}]
|
||||||
|
}
|
||||||
|
doc.render(context)
|
||||||
|
doc.save("output.docx")
|
||||||
|
```
|
||||||
|
|
||||||
|
**Плюсы**:
|
||||||
|
- Идеален для шаблонизированных отчетов (договоры, счета).
|
||||||
|
- Интеграция с Django/Flask.
|
||||||
|
|
||||||
|
**Минусы**:
|
||||||
|
- Требует предварительной настройки шаблонов в Word.
|
||||||
|
|
||||||
|
**Ссылка**: [github.com/elapouya/python-docx-template](https://github.com/elapouya/python-docx-template)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### **3. mammoth**
|
||||||
|
**Назначение**: Конвертация .docx → HTML с сохранением структуры.
|
||||||
|
**Особенности**:
|
||||||
|
- Преобразует стили Word в CSS-классы.
|
||||||
|
- Подходит для извлечения контента из сложных документов.
|
||||||
|
|
||||||
|
**Пример**:
|
||||||
|
```python
|
||||||
|
import mammoth
|
||||||
|
|
||||||
|
with open("document.docx", "rb") as docx_file:
|
||||||
|
result = mammoth.convert_to_html(docx_file)
|
||||||
|
html = result.value # HTML-строка
|
||||||
|
warnings = result.messages # Предупреждения
|
||||||
|
```
|
||||||
|
|
||||||
|
**Плюсы**:
|
||||||
|
- Сохраняет семантику (заголовки, списки, таблицы).
|
||||||
|
- Легко интегрируется с парсерами (BeautifulSoup).
|
||||||
|
|
||||||
|
**Минусы**:
|
||||||
|
- Не подходит для обратной конвертации (HTML → docx).
|
||||||
|
|
||||||
|
**Ссылка**: [github.com/mwilliamson/mammoth.py](https://github.com/mwilliamson/mammoth.py)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **Низкоуровневые инструменты (для кастомных задач)**
|
||||||
|
|
||||||
|
#### **1. zipfile + lxml**
|
||||||
|
**Назначение**: Прямой доступ к XML-структуре OOXML.
|
||||||
|
**Когда использовать**:
|
||||||
|
- При работе с нестандартными частями пакета (напр., кастомные XML-маппинги в Excel).
|
||||||
|
- Для восстановления поврежденных файлов.
|
||||||
|
|
||||||
|
**Пример для Excel**:
|
||||||
|
```python
|
||||||
|
import zipfile
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
|
# Распаковываем workbook.xml
|
||||||
|
with zipfile.ZipFile("report.xlsx") as xlsx:
|
||||||
|
xml_data = xlsx.read("xl/workbook.xml")
|
||||||
|
|
||||||
|
# Парсим XML
|
||||||
|
workbook = etree.fromstring(xml_data)
|
||||||
|
namespaces = {"ns": "http://schemas.../spreadsheetml/2006/main"}
|
||||||
|
sheets = workbook.findall(".//ns:sheet", namespaces)
|
||||||
|
|
||||||
|
# Меняем имя листа
|
||||||
|
sheets[0].set("{http://.../officeDocument/2006/relationships}name", "New Sheet")
|
||||||
|
|
||||||
|
# Сохраняем изменения
|
||||||
|
with zipfile.ZipFile("fixed.xlsx", "w") as new_xlsx:
|
||||||
|
for file in xlsx.namelist():
|
||||||
|
if file != "xl/workbook.xml":
|
||||||
|
new_xlsx.writestr(file, xlsx.read(file))
|
||||||
|
new_xlsx.writestr("xl/workbook.xml", etree.tostring(workbook))
|
||||||
|
```
|
||||||
|
|
||||||
|
**Плюсы**:
|
||||||
|
- Полный контроль над структурой.
|
||||||
|
- Работает с любыми частями пакета.
|
||||||
|
|
||||||
|
**Минусы**:
|
||||||
|
- Требует знания XML-схем OOXML.
|
||||||
|
- Риск повреждения файла при ошибках.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### **2. officedoc**
|
||||||
|
**Назначение**: Универсальный доступ к форматам Office (docx, xlsx, pptx).
|
||||||
|
**Особенности**:
|
||||||
|
- Объединяет функционал `python-docx`, `openpyxl`, `python-pptx` в едином API.
|
||||||
|
- Упрощает обработку мультимедийных вложений.
|
||||||
|
|
||||||
|
**Пример**:
|
||||||
|
```python
|
||||||
|
from officedoc import Document
|
||||||
|
|
||||||
|
doc = Document("mixed.docx")
|
||||||
|
for image in doc.images:
|
||||||
|
image.save("extracted_" + image.filename)
|
||||||
|
|
||||||
|
for table in doc.tables:
|
||||||
|
print(table.to_dataframe()) # Конвертация в pandas DataFrame
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ссылка**: [github.com/mikem1701/officedoc](https://github.com/mikem1701/officedoc)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **Сравнение библиотек**
|
||||||
|
|
||||||
|
| **Библиотека** | **Скорость** | **Сложность** | **Особенности** |
|
||||||
|
|----------------------|-------------|---------------|----------------------------|
|
||||||
|
| **python-docx** | Средняя | Средняя | Стандарт для работы с Word |
|
||||||
|
| **docxtpl** | Средняя | Низкая | Шаблонизация через Jinja2 |
|
||||||
|
| **zipfile + lxml** | Высокая | Высокая | Полный контроль над XML |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Для Word (.docx)
|
||||||
|
- **Простое создание документов**: `python-docx`.
|
||||||
|
- **Шаблонные документы** (договоры, счета): `docxtpl`.
|
||||||
|
- **Извлечение контента**: `mammoth` → обработка HTML через BeautifulSoup.
|
||||||
|
- **Редактирование структуры**: `python-docx` + `zipfile` для кастомных частей.
|
||||||
|
|
||||||
|
#### Для кастомных задач
|
||||||
|
- **Восстановление файлов**: `zipfile` + `lxml` (ручное исправление XML).
|
||||||
|
- **Интеграция с внешними системами**: `openpyxl`/`python-docx` + REST API (напр., выгрузка данных из БД в Excel).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Типичные проблемы и решения
|
||||||
|
|
||||||
|
2. Стили не применяются в Word
|
||||||
|
- Проблема: `python-docx` использует стили из `styles.xml`, но не создает новые автоматически.
|
||||||
|
- Решение:
|
||||||
|
```python
|
||||||
|
from docx.shared import Pt
|
||||||
|
style = doc.styles.add_style("Heading3", WD_STYLE_TYPE.PARAGRAPH)
|
||||||
|
font = style.font
|
||||||
|
font.name = "Arial"
|
||||||
|
font.size = Pt(12)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### **Заключение**
|
||||||
|
Для 90% задач достаточно:
|
||||||
|
- **Excel**: `openpyxl` или `pandas`.
|
||||||
|
- **Word**: `python-docx` + `docxtpl`.
|
||||||
|
|
||||||
|
**Используйте низкоуровневые методы (zipfile/lxml) только когда**:
|
||||||
|
|
||||||
|
*Для углубленного изучения:*
|
||||||
|
- [Open XML SDK 2.5](https://learn.microsoft.com/ru-ru/office/open-xml/open-xml-sdk) (официальная документация Microsoft).
|
||||||
|
- [ECMA-376 стандарт](https://www.ecma-international.org/publications-and-standards/standards/ecma-376/) (полная спецификация).
|
||||||
@@ -0,0 +1,317 @@
|
|||||||
|
from docx import Document
|
||||||
|
from docx.shared import Pt, Cm, RGBColor
|
||||||
|
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_LINE_SPACING
|
||||||
|
from docx.enum.style import WD_STYLE_TYPE
|
||||||
|
from docx.oxml.ns import qn
|
||||||
|
from docx.oxml import OxmlElement
|
||||||
|
|
||||||
|
|
||||||
|
def create_gost_document(title="Документ", author="Автор"):
|
||||||
|
"""
|
||||||
|
Создает новый документ Word с оформлением по ГОСТ
|
||||||
|
|
||||||
|
Args:
|
||||||
|
title (str): Заголовок документа
|
||||||
|
author (str): Автор документа
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Document: Объект документа с настроенными стилями ГОСТ
|
||||||
|
"""
|
||||||
|
doc = Document()
|
||||||
|
|
||||||
|
# Настройка полей страницы согласно ГОСТ
|
||||||
|
sections = doc.sections
|
||||||
|
for section in sections:
|
||||||
|
section.top_margin = Cm(2) # Верхнее поле 2 см
|
||||||
|
section.bottom_margin = Cm(2) # Нижнее поле 2 см
|
||||||
|
section.left_margin = Cm(3) # Левое поле 3 см
|
||||||
|
section.right_margin = Cm(1.5) # Правое поле 1.5 см
|
||||||
|
|
||||||
|
# Установка стандартного стиля для всего документа
|
||||||
|
style = doc.styles['Normal']
|
||||||
|
font = style.font
|
||||||
|
font.name = 'Times New Roman'
|
||||||
|
font.size = Pt(14)
|
||||||
|
# Установка кириллического шрифта для документов на русском языке
|
||||||
|
font.element.rPr.rFonts.set(qn('w:eastAsia'), 'Times New Roman')
|
||||||
|
|
||||||
|
# Настройка формата абзаца
|
||||||
|
paragraph_format = style.paragraph_format
|
||||||
|
paragraph_format.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE # Полуторный интервал
|
||||||
|
paragraph_format.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY # Выравнивание по ширине
|
||||||
|
paragraph_format.first_line_indent = Cm(1.25) # Красная строка 1.25 см
|
||||||
|
|
||||||
|
# Создаем заголовок документа
|
||||||
|
title_paragraph = doc.add_heading(level=0)
|
||||||
|
title_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||||
|
title_run = title_paragraph.add_run(title)
|
||||||
|
title_run.font.size = Pt(16)
|
||||||
|
title_run.font.bold = True
|
||||||
|
title_run.font.name = 'Times New Roman'
|
||||||
|
|
||||||
|
# Добавляем имя автора
|
||||||
|
author_paragraph = doc.add_paragraph()
|
||||||
|
author_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||||
|
author_run = author_paragraph.add_run(f"\n{author}")
|
||||||
|
author_run.font.size = Pt(14)
|
||||||
|
author_run.font.name = 'Times New Roman'
|
||||||
|
|
||||||
|
# Добавляем отступ после заголовка
|
||||||
|
doc.add_paragraph()
|
||||||
|
|
||||||
|
# Создаем стили заголовков в соответствии с ГОСТ
|
||||||
|
create_gost_heading_styles(doc)
|
||||||
|
|
||||||
|
return doc
|
||||||
|
|
||||||
|
|
||||||
|
def create_gost_heading_styles(doc):
|
||||||
|
"""
|
||||||
|
Создает стили заголовков в соответствии с требованиями ГОСТ
|
||||||
|
|
||||||
|
Args:
|
||||||
|
doc (Document): Объект документа
|
||||||
|
"""
|
||||||
|
# Заголовок 1 уровня
|
||||||
|
h1_style = doc.styles.add_style('Heading 1 GOST', WD_STYLE_TYPE.PARAGRAPH)
|
||||||
|
h1_style.base_style = doc.styles['Heading 1']
|
||||||
|
h1_style.font.name = 'Times New Roman'
|
||||||
|
h1_style.font.size = Pt(16)
|
||||||
|
h1_style.font.bold = True
|
||||||
|
h1_style.font.all_caps = True # В ГОСТ заголовки часто пишутся прописными
|
||||||
|
h1_style.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||||
|
h1_style.paragraph_format.space_before = Pt(24) # Отступ перед заголовком
|
||||||
|
h1_style.paragraph_format.space_after = Pt(12) # Отступ после заголовка
|
||||||
|
h1_style.paragraph_format.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE
|
||||||
|
|
||||||
|
# Заголовок 2 уровня
|
||||||
|
h2_style = doc.styles.add_style('Heading 2 GOST', WD_STYLE_TYPE.PARAGRAPH)
|
||||||
|
h2_style.base_style = doc.styles['Heading 2']
|
||||||
|
h2_style.font.name = 'Times New Roman'
|
||||||
|
h2_style.font.size = Pt(14)
|
||||||
|
h2_style.font.bold = True
|
||||||
|
h2_style.paragraph_format.space_before = Pt(24)
|
||||||
|
h2_style.paragraph_format.space_after = Pt(6)
|
||||||
|
h2_style.paragraph_format.first_line_indent = Cm(0) # Без красной строки
|
||||||
|
h2_style.paragraph_format.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE
|
||||||
|
|
||||||
|
# Заголовок 3 уровня
|
||||||
|
h3_style = doc.styles.add_style('Heading 3 GOST', WD_STYLE_TYPE.PARAGRAPH)
|
||||||
|
h3_style.base_style = doc.styles['Heading 3']
|
||||||
|
h3_style.font.name = 'Times New Roman'
|
||||||
|
h3_style.font.size = Pt(14)
|
||||||
|
h3_style.font.bold = True
|
||||||
|
h3_style.font.italic = True
|
||||||
|
h3_style.paragraph_format.space_before = Pt(18)
|
||||||
|
h3_style.paragraph_format.space_after = Pt(6)
|
||||||
|
h3_style.paragraph_format.first_line_indent = Cm(0)
|
||||||
|
h3_style.paragraph_format.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE
|
||||||
|
|
||||||
|
# Стиль для списка
|
||||||
|
list_style = doc.styles.add_style('List GOST', WD_STYLE_TYPE.PARAGRAPH)
|
||||||
|
list_style.base_style = doc.styles['List']
|
||||||
|
list_style.font.name = 'Times New Roman'
|
||||||
|
list_style.font.size = Pt(14)
|
||||||
|
list_style.paragraph_format.left_indent = Cm(1.25)
|
||||||
|
list_style.paragraph_format.space_before = Pt(0)
|
||||||
|
list_style.paragraph_format.space_after = Pt(6)
|
||||||
|
list_style.paragraph_format.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE
|
||||||
|
|
||||||
|
# Стиль для подпись таблиц и рисунков
|
||||||
|
caption_style = doc.styles.add_style('Caption GOST', WD_STYLE_TYPE.PARAGRAPH)
|
||||||
|
caption_style.base_style = doc.styles['Caption']
|
||||||
|
caption_style.font.name = 'Times New Roman'
|
||||||
|
caption_style.font.size = Pt(12)
|
||||||
|
caption_style.font.italic = True
|
||||||
|
caption_style.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||||
|
caption_style.paragraph_format.space_before = Pt(6)
|
||||||
|
caption_style.paragraph_format.space_after = Pt(12)
|
||||||
|
|
||||||
|
|
||||||
|
def add_gost_heading(doc, text, level=1):
|
||||||
|
"""
|
||||||
|
Добавляет заголовок с оформлением по ГОСТ
|
||||||
|
|
||||||
|
Args:
|
||||||
|
doc (Document): Объект документа
|
||||||
|
text (str): Текст заголовка
|
||||||
|
level (int): Уровень заголовка (1, 2 или 3)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Paragraph: Объект параграфа с заголовком
|
||||||
|
"""
|
||||||
|
if level == 1:
|
||||||
|
style_name = 'Heading 1 GOST'
|
||||||
|
elif level == 2:
|
||||||
|
style_name = 'Heading 2 GOST'
|
||||||
|
elif level == 3:
|
||||||
|
style_name = 'Heading 3 GOST'
|
||||||
|
else:
|
||||||
|
raise ValueError("Уровень заголовка должен быть 1, 2 или 3")
|
||||||
|
|
||||||
|
# Проверяем, существует ли стиль, если нет - создаем стили ГОСТ
|
||||||
|
if style_name not in doc.styles:
|
||||||
|
create_gost_heading_styles(doc)
|
||||||
|
|
||||||
|
heading = doc.add_heading(text, level=0)
|
||||||
|
heading.style = style_name
|
||||||
|
return heading
|
||||||
|
|
||||||
|
|
||||||
|
def add_gost_paragraph(doc, text, style=None):
|
||||||
|
"""
|
||||||
|
Добавляет абзац с оформлением по ГОСТ
|
||||||
|
|
||||||
|
Args:
|
||||||
|
doc (Document): Объект документа
|
||||||
|
text (str): Текст абзаца
|
||||||
|
style (str, optional): Название стиля
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Paragraph: Объект параграфа
|
||||||
|
"""
|
||||||
|
p = doc.add_paragraph()
|
||||||
|
|
||||||
|
if style and style in doc.styles:
|
||||||
|
p.style = style
|
||||||
|
|
||||||
|
# Основной текст по ГОСТ
|
||||||
|
p.paragraph_format.line_spacing_rule = WD_LINE_SPACING.ONE_POINT_FIVE
|
||||||
|
p.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
|
||||||
|
p.paragraph_format.first_line_indent = Cm(1.25)
|
||||||
|
|
||||||
|
# Добавляем текст
|
||||||
|
p.add_run(text)
|
||||||
|
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
def add_gost_table(doc, data, headers=None):
|
||||||
|
"""
|
||||||
|
Создает таблицу в формате ГОСТ
|
||||||
|
|
||||||
|
Args:
|
||||||
|
doc (Document): Объект документа
|
||||||
|
data (list): Данные таблицы в виде списка списков
|
||||||
|
headers (list, optional): Заголовки таблицы
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Table: Объект таблицы
|
||||||
|
"""
|
||||||
|
# Добавляем подпись таблицы
|
||||||
|
caption = doc.add_paragraph()
|
||||||
|
caption.style = 'Caption GOST'
|
||||||
|
caption.add_run("Таблица ").bold = True
|
||||||
|
|
||||||
|
# Создаем таблицу
|
||||||
|
rows = len(data) + (1 if headers else 0)
|
||||||
|
cols = len(data[0]) if data else 0
|
||||||
|
table = doc.add_table(rows=rows, cols=cols)
|
||||||
|
|
||||||
|
# Стиль таблицы
|
||||||
|
table.style = 'Table Grid'
|
||||||
|
|
||||||
|
# Заполняем заголовки, если они есть
|
||||||
|
if headers:
|
||||||
|
hdr_cells = table.rows[0].cells
|
||||||
|
for i, header in enumerate(headers):
|
||||||
|
hdr_cells[i].text = header
|
||||||
|
for paragraph in hdr_cells[i].paragraphs:
|
||||||
|
for run in paragraph.runs:
|
||||||
|
run.font.bold = True
|
||||||
|
|
||||||
|
# Заполняем данные
|
||||||
|
start_row = 1 if headers else 0
|
||||||
|
for i, row_data in enumerate(data):
|
||||||
|
row_cells = table.rows[i + start_row].cells
|
||||||
|
for j, cell_data in enumerate(row_data):
|
||||||
|
row_cells[j].text = str(cell_data)
|
||||||
|
|
||||||
|
# Настройка шрифта для всех ячеек
|
||||||
|
for row in table.rows:
|
||||||
|
for cell in row.cells:
|
||||||
|
for paragraph in cell.paragraphs:
|
||||||
|
for run in paragraph.runs:
|
||||||
|
run.font.name = 'Times New Roman'
|
||||||
|
run.font.size = Pt(12)
|
||||||
|
|
||||||
|
return table
|
||||||
|
|
||||||
|
|
||||||
|
def save_gost_document(doc, filename):
|
||||||
|
"""
|
||||||
|
Сохраняет документ с настройками совместимости для MS Word
|
||||||
|
|
||||||
|
Args:
|
||||||
|
doc (Document): Объект документа
|
||||||
|
filename (str): Имя файла для сохранения
|
||||||
|
"""
|
||||||
|
# Добавляем настройки совместимости
|
||||||
|
doc.settings.element.xpath('./w:compat')[0].clear()
|
||||||
|
|
||||||
|
# Сохраняем документ
|
||||||
|
doc.save(filename)
|
||||||
|
|
||||||
|
|
||||||
|
# Пример использования
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Создаем документ по ГОСТ
|
||||||
|
document = create_gost_document("Отчет о научно-исследовательской работе", "Иванов И.И.")
|
||||||
|
|
||||||
|
# Добавляем раздел с заголовком 1 уровня
|
||||||
|
add_gost_heading(document, "ВВЕДЕНИЕ", level=1)
|
||||||
|
|
||||||
|
# Добавляем текст введение
|
||||||
|
intro_text = ("Настоящий отчет посвящен исследованию актуальных проблем в области "
|
||||||
|
"разработки программного обеспечения. Цель работы - анализ и разработка "
|
||||||
|
"методов оптимизации процесса создания программных продуктов. В ходе "
|
||||||
|
"исследования были применены методы системного анализа, математического "
|
||||||
|
"моделирования и экспериментальные методы.")
|
||||||
|
add_gost_paragraph(document, intro_text)
|
||||||
|
|
||||||
|
# Добавляем раздел с заголовком 2 уровня
|
||||||
|
add_gost_heading(document, "1. АНАЛИЗ ПРЕДМЕТНОЙ ОБЛАСТИ", level=2)
|
||||||
|
|
||||||
|
# Добавляем текст раздела
|
||||||
|
analysis_text = ("В данном разделе представлен анализ современных подходов к разработке "
|
||||||
|
"программного обеспечения. Проведенный анализ показал, что основными "
|
||||||
|
"проблемами являются недостаточная модульность архитектуры и слабая "
|
||||||
|
"документация проектов.")
|
||||||
|
add_gost_paragraph(document, analysis_text)
|
||||||
|
|
||||||
|
# Добавляем подраздел с заголовком 3 уровня
|
||||||
|
add_gost_heading(document, "1.1. Методы исследования", level=3)
|
||||||
|
|
||||||
|
# Добавляем нумерованный список
|
||||||
|
methods = [
|
||||||
|
"Анализ существующих решений в предметной области",
|
||||||
|
"Моделирование архитектуры программной системы",
|
||||||
|
"Экспериментальная проверка предложенных методов"
|
||||||
|
]
|
||||||
|
|
||||||
|
for method in methods:
|
||||||
|
p = document.add_paragraph(method, style='List GOST')
|
||||||
|
p.style = 'List GOST'
|
||||||
|
p.paragraph_format.left_indent = Cm(1.25)
|
||||||
|
p.paragraph_format.first_line_indent = Cm(-0.63) # Отрицательный отступ для номера
|
||||||
|
|
||||||
|
# Добавляем таблицу
|
||||||
|
headers = ["Метод", "Достоинства", "Недостатки"]
|
||||||
|
data = [
|
||||||
|
["Метод 1", "Высокая точность", "Сложность реализации"],
|
||||||
|
["Метод 2", "Простота", "Низкая точность"],
|
||||||
|
["Метод 3", "Универсальность", "Высокие требования к ресурсам"]
|
||||||
|
]
|
||||||
|
add_gost_table(document, data, headers)
|
||||||
|
|
||||||
|
# Добавляем заключение
|
||||||
|
add_gost_heading(document, "ЗАКЛЮЧЕНИЕ", level=1)
|
||||||
|
conclusion_text = ("В результате проведенного исследования были выявлены основные проблемы "
|
||||||
|
"в области разработки программного обеспечения. Предложенные методы "
|
||||||
|
"позволяют повысить качество создаваемых программных продуктов и сократить "
|
||||||
|
"время на их разработку.")
|
||||||
|
add_gost_paragraph(document, conclusion_text)
|
||||||
|
|
||||||
|
# Сохраняем документ
|
||||||
|
save_gost_document(document, "otchet_po_GOST.docx")
|
||||||
|
print("Документ успешно создан и сохранен как 'otchet_po_GOST.docx'")
|
||||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 125 KiB |
@@ -0,0 +1,32 @@
|
|||||||
|
# открытие файла
|
||||||
|
# установка нужных шрифтов в документе 1.
|
||||||
|
# работа с текстом
|
||||||
|
# работа с таблицами
|
||||||
|
# -поддержка переноса таблицы
|
||||||
|
# сохранение
|
||||||
|
# Настройка оглавлений
|
||||||
|
|
||||||
|
|
||||||
|
from docx import Document
|
||||||
|
|
||||||
|
# Создаем новый документ
|
||||||
|
document = Document()
|
||||||
|
|
||||||
|
# Добавляем заголовок
|
||||||
|
document.add_heading('Пример документа Word', level=1)
|
||||||
|
|
||||||
|
# Добавляем параграф с текстом
|
||||||
|
document.add_paragraph('Это первый параграф, который был добавлен с помощью Python.')
|
||||||
|
|
||||||
|
# Добавляем еще один параграф
|
||||||
|
document.add_paragraph('Это второй параграф. Можно добавлять новый текст, как вам нужно.')
|
||||||
|
|
||||||
|
# Добавляем текст в уже существующий параграф
|
||||||
|
paragraph = document.add_paragraph('А это текст, добавленный к ')
|
||||||
|
paragraph.add_run('существующему ').italic = True
|
||||||
|
paragraph.add_run('параграфу.')
|
||||||
|
|
||||||
|
# Сохраняем документ
|
||||||
|
document.save('мой_документ.docx')
|
||||||
|
|
||||||
|
print("Документ 'мой_документ.docx' успешно создан и текст добавлен.")
|
||||||
@@ -0,0 +1,915 @@
|
|||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import Dict, List, Any, Optional, Union, Callable
|
||||||
|
from docx import Document
|
||||||
|
from docx.shared import Pt, Cm, RGBColor
|
||||||
|
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_LINE_SPACING
|
||||||
|
from docx.enum.style import WD_STYLE_TYPE
|
||||||
|
from docx.oxml.ns import qn
|
||||||
|
from docx.oxml import OxmlElement
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
class GostDocumentConfig:
|
||||||
|
"""Конфигурация параметров документа по ГОСТ"""
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
# Поля документа (в сантиметрах)
|
||||||
|
self.margins = kwargs.get('margins', {
|
||||||
|
'top': 2.0,
|
||||||
|
'bottom': 2.0,
|
||||||
|
'left': 3.0,
|
||||||
|
'right': 1.5
|
||||||
|
})
|
||||||
|
|
||||||
|
# Шрифты
|
||||||
|
self.font = kwargs.get('font', {
|
||||||
|
'name': 'Times New Roman',
|
||||||
|
'size': 14,
|
||||||
|
'main_language': 'ru-RU'
|
||||||
|
})
|
||||||
|
|
||||||
|
# Межстрочный интервал
|
||||||
|
self.line_spacing = kwargs.get('line_spacing', 1.5)
|
||||||
|
|
||||||
|
# Отступы для заголовков
|
||||||
|
self.heading_spacing = kwargs.get('heading_spacing', {
|
||||||
|
1: {'before': 24, 'after': 12},
|
||||||
|
2: {'before': 24, 'after': 6},
|
||||||
|
3: {'before': 18, 'after': 6}
|
||||||
|
})
|
||||||
|
|
||||||
|
# Стили заголовков
|
||||||
|
self.heading_styles = kwargs.get('heading_styles', {
|
||||||
|
1: {'size': 16, 'bold': True, 'all_caps': True, 'align': 'center'},
|
||||||
|
2: {'size': 14, 'bold': True, 'all_caps': False, 'align': 'left'},
|
||||||
|
3: {'size': 14, 'bold': True, 'italic': True, 'all_caps': False, 'align': 'left'}
|
||||||
|
})
|
||||||
|
|
||||||
|
# Стиль абзаца
|
||||||
|
self.paragraph = kwargs.get('paragraph', {
|
||||||
|
'first_line_indent': 1.25, # в см
|
||||||
|
'alignment': 'justify'
|
||||||
|
})
|
||||||
|
|
||||||
|
# Стиль таблиц
|
||||||
|
self.table = kwargs.get('table', {
|
||||||
|
'font_size': 12,
|
||||||
|
'header_bold': True
|
||||||
|
})
|
||||||
|
|
||||||
|
# Стиль подписей
|
||||||
|
self.caption = kwargs.get('caption', {
|
||||||
|
'font_size': 12,
|
||||||
|
'italic': True,
|
||||||
|
'align': 'center'
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class GostDocumentCore(ABC):
|
||||||
|
"""Базовый класс для создания документов по ГОСТ с возможностью расширения"""
|
||||||
|
|
||||||
|
def __init__(self, config: Optional[GostDocumentConfig] = None,
|
||||||
|
title: str = "", author: str = ""):
|
||||||
|
"""
|
||||||
|
Инициализация документа с настройками ГОСТ
|
||||||
|
|
||||||
|
Args:
|
||||||
|
config: Конфигурация документа (если None - используются стандартные настройки ГОСТ)
|
||||||
|
title: Заголовок документа
|
||||||
|
author: Автор документа
|
||||||
|
"""
|
||||||
|
self.config = config or GostDocumentConfig()
|
||||||
|
self.title = title
|
||||||
|
self.author = author
|
||||||
|
self.document = Document()
|
||||||
|
self._element_processors = {}
|
||||||
|
self._setup_document()
|
||||||
|
self._create_gost_styles()
|
||||||
|
|
||||||
|
# Регистрируем базовые процессоры элементов
|
||||||
|
self.register_processor('heading', self._process_heading)
|
||||||
|
self.register_processor('paragraph', self._process_paragraph)
|
||||||
|
self.register_processor('table', self._process_table)
|
||||||
|
self.register_processor('list', self._process_list)
|
||||||
|
self.register_processor('image', self._process_image)
|
||||||
|
|
||||||
|
def _setup_document(self):
|
||||||
|
"""Настройка базовых параметров документа согласно ГОСТ"""
|
||||||
|
# Настройка полей страницы
|
||||||
|
sections = self.document.sections
|
||||||
|
for section in sections:
|
||||||
|
section.top_margin = Cm(self.config.margins['top'])
|
||||||
|
section.bottom_margin = Cm(self.config.margins['bottom'])
|
||||||
|
section.left_margin = Cm(self.config.margins['left'])
|
||||||
|
section.right_margin = Cm(self.config.margins['right'])
|
||||||
|
|
||||||
|
# Установка стандартного стиля для всего документа
|
||||||
|
style = self.document.styles['Normal']
|
||||||
|
font = style.font
|
||||||
|
font.name = self.config.font['name']
|
||||||
|
font.size = Pt(self.config.font['size'])
|
||||||
|
|
||||||
|
# Установка кириллического шрифта
|
||||||
|
font.element.rPr.rFonts.set(qn('w:eastAsia'), self.config.font['name'])
|
||||||
|
|
||||||
|
# Настройка формата абзаца
|
||||||
|
paragraph_format = style.paragraph_format
|
||||||
|
paragraph_format.line_spacing_rule = (
|
||||||
|
WD_LINE_SPACING.ONE_POINT_FIVE if self.config.line_spacing == 1.5
|
||||||
|
else WD_LINE_SPACING.DOUBLE if self.config.line_spacing == 2.0
|
||||||
|
else WD_LINE_SPACING.EXACTLY
|
||||||
|
)
|
||||||
|
paragraph_format.alignment = (
|
||||||
|
WD_ALIGN_PARAGRAPH.JUSTIFY if self.config.paragraph['alignment'] == 'justify'
|
||||||
|
else WD_ALIGN_PARAGRAPH.LEFT if self.config.paragraph['alignment'] == 'left'
|
||||||
|
else WD_ALIGN_PARAGRAPH.CENTER
|
||||||
|
)
|
||||||
|
paragraph_format.first_line_indent = Cm(self.config.paragraph['first_line_indent'])
|
||||||
|
|
||||||
|
def _create_gost_styles(self):
|
||||||
|
"""Создание стилей заголовков и других элементов в соответствии с ГОСТ"""
|
||||||
|
# Заголовок 1 уровня
|
||||||
|
self._create_heading_style(1)
|
||||||
|
# Заголовок 2 уровня
|
||||||
|
self._create_heading_style(2)
|
||||||
|
# Заголовок 3 уровня
|
||||||
|
self._create_heading_style(3)
|
||||||
|
|
||||||
|
# Стиль для списка
|
||||||
|
if 'List GOST' not in self.document.styles:
|
||||||
|
list_style = self.document.styles.add_style('List GOST', WD_STYLE_TYPE.PARAGRAPH)
|
||||||
|
list_style.base_style = self.document.styles['List']
|
||||||
|
list_style.font.name = self.config.font['name']
|
||||||
|
list_style.font.size = Pt(self.config.font['size'])
|
||||||
|
list_style.paragraph_format.left_indent = Cm(1.25)
|
||||||
|
list_style.paragraph_format.space_before = Pt(0)
|
||||||
|
list_style.paragraph_format.space_after = Pt(6)
|
||||||
|
list_style.paragraph_format.line_spacing_rule = (
|
||||||
|
WD_LINE_SPACING.ONE_POINT_FIVE if self.config.line_spacing == 1.5
|
||||||
|
else WD_LINE_SPACING.DOUBLE
|
||||||
|
)
|
||||||
|
|
||||||
|
# Стиль для подписей таблиц и рисунков
|
||||||
|
if 'Caption GOST' not in self.document.styles:
|
||||||
|
caption_style = self.document.styles.add_style('Caption GOST', WD_STYLE_TYPE.PARAGRAPH)
|
||||||
|
caption_style.base_style = self.document.styles['Caption']
|
||||||
|
caption_style.font.name = self.config.font['name']
|
||||||
|
caption_style.font.size = Pt(self.config.caption['font_size'])
|
||||||
|
caption_style.font.italic = self.config.caption['italic']
|
||||||
|
caption_style.paragraph_format.alignment = (
|
||||||
|
WD_ALIGN_PARAGRAPH.CENTER if self.config.caption['align'] == 'center'
|
||||||
|
else WD_ALIGN_PARAGRAPH.LEFT
|
||||||
|
)
|
||||||
|
caption_style.paragraph_format.space_before = Pt(6)
|
||||||
|
caption_style.paragraph_format.space_after = Pt(12)
|
||||||
|
|
||||||
|
def _create_heading_style(self, level: int):
|
||||||
|
"""Создает стиль заголовка заданного уровня"""
|
||||||
|
style_name = f'Heading {level} GOST'
|
||||||
|
|
||||||
|
if style_name in self.document.styles:
|
||||||
|
return
|
||||||
|
|
||||||
|
style = self.document.styles.add_style(style_name, WD_STYLE_TYPE.PARAGRAPH)
|
||||||
|
|
||||||
|
# Настройка шрифта
|
||||||
|
heading_config = self.config.heading_styles[level]
|
||||||
|
style.font.name = self.config.font['name']
|
||||||
|
style.font.size = Pt(heading_config['size'])
|
||||||
|
style.font.bold = heading_config['bold']
|
||||||
|
style.font.italic = heading_config.get('italic', False)
|
||||||
|
style.font.all_caps = heading_config['all_caps']
|
||||||
|
|
||||||
|
# Настройка абзаца
|
||||||
|
spacing = self.config.heading_spacing[level]
|
||||||
|
style.paragraph_format.space_before = Pt(spacing['before'])
|
||||||
|
style.paragraph_format.space_after = Pt(spacing['after'])
|
||||||
|
style.paragraph_format.line_spacing_rule = (
|
||||||
|
WD_LINE_SPACING.ONE_POINT_FIVE if self.config.line_spacing == 1.5
|
||||||
|
else WD_LINE_SPACING.DOUBLE
|
||||||
|
)
|
||||||
|
|
||||||
|
# Выравнивание
|
||||||
|
if heading_config['align'] == 'center':
|
||||||
|
style.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||||
|
elif heading_config['align'] == 'left':
|
||||||
|
style.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.LEFT
|
||||||
|
style.paragraph_format.first_line_indent = Cm(0) # Без красной строки для заголовков
|
||||||
|
|
||||||
|
def register_processor(self, element_type: str, processor: Callable):
|
||||||
|
"""
|
||||||
|
Регистрирует обработчик для определенного типа элемента
|
||||||
|
|
||||||
|
Args:
|
||||||
|
element_type: Тип элемента (например, 'heading', 'table')
|
||||||
|
processor: Функция-обработчик
|
||||||
|
"""
|
||||||
|
self._element_processors[element_type] = processor
|
||||||
|
|
||||||
|
def get_processor(self, element_type: str) -> Optional[Callable]:
|
||||||
|
"""
|
||||||
|
Возвращает обработчик для указанного типа элемента
|
||||||
|
|
||||||
|
Args:
|
||||||
|
element_type: Тип элемента
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Обработчик или None, если не найден
|
||||||
|
"""
|
||||||
|
return self._element_processors.get(element_type)
|
||||||
|
|
||||||
|
def _process_heading(self, element: Dict[str, Any]) -> None:
|
||||||
|
"""Обрабатывает элемент заголовка"""
|
||||||
|
level = element.get('level', 1)
|
||||||
|
text = element.get('text', '')
|
||||||
|
|
||||||
|
heading = self.document.add_heading(level=0)
|
||||||
|
style_name = f'Heading {level} GOST'
|
||||||
|
|
||||||
|
if style_name in self.document.styles:
|
||||||
|
heading.style = style_name
|
||||||
|
else:
|
||||||
|
# Базовая настройка, если стиль не создан
|
||||||
|
heading.alignment = WD_ALIGN_PARAGRAPH.CENTER if level == 1 else WD_ALIGN_PARAGRAPH.LEFT
|
||||||
|
run = heading.add_run(text)
|
||||||
|
run.font.size = Pt(16 if level == 1 else 14)
|
||||||
|
run.font.bold = True
|
||||||
|
|
||||||
|
# Добавляем текст
|
||||||
|
heading.add_run(text)
|
||||||
|
|
||||||
|
def _process_paragraph(self, element: Dict[str, Any]) -> None:
|
||||||
|
"""Обрабатывает элемент абзаца"""
|
||||||
|
text = element.get('text', '')
|
||||||
|
style = element.get('style')
|
||||||
|
|
||||||
|
p = self.document.add_paragraph()
|
||||||
|
|
||||||
|
if style and style in self.document.styles:
|
||||||
|
p.style = style
|
||||||
|
|
||||||
|
# Основной текст по ГОСТ
|
||||||
|
p.paragraph_format.line_spacing_rule = (
|
||||||
|
WD_LINE_SPACING.ONE_POINT_FIVE if self.config.line_spacing == 1.5
|
||||||
|
else WD_LINE_SPACING.DOUBLE
|
||||||
|
)
|
||||||
|
p.paragraph_format.alignment = (
|
||||||
|
WD_ALIGN_PARAGRAPH.JUSTIFY if self.config.paragraph['alignment'] == 'justify'
|
||||||
|
else WD_ALIGN_PARAGRAPH.LEFT
|
||||||
|
)
|
||||||
|
p.paragraph_format.first_line_indent = Cm(self.config.paragraph['first_line_indent'])
|
||||||
|
print(text)
|
||||||
|
# Добавляем текст
|
||||||
|
p.add_run(text)
|
||||||
|
|
||||||
|
def _process_table(self, element: Dict[str, Any]) -> None:
|
||||||
|
"""Обрабатывает элемент таблицы"""
|
||||||
|
data = element.get('data', [])
|
||||||
|
headers = element.get('headers')
|
||||||
|
|
||||||
|
# Добавляем подпись таблицы
|
||||||
|
caption = self.document.add_paragraph()
|
||||||
|
caption.style = 'Caption GOST'
|
||||||
|
caption.add_run("Таблица ").bold = True
|
||||||
|
|
||||||
|
# Создаем таблицу
|
||||||
|
rows = len(data) + (1 if headers else 0)
|
||||||
|
cols = len(data[0]) if data else 0
|
||||||
|
if cols == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
table = self.document.add_table(rows=rows, cols=cols)
|
||||||
|
table.style = 'Table Grid'
|
||||||
|
|
||||||
|
# Заполняем заголовки, если они есть
|
||||||
|
if headers:
|
||||||
|
hdr_cells = table.rows[0].cells
|
||||||
|
for i, header in enumerate(headers):
|
||||||
|
hdr_cells[i].text = header
|
||||||
|
for paragraph in hdr_cells[i].paragraphs:
|
||||||
|
for run in paragraph.runs:
|
||||||
|
run.font.bold = self.config.table['header_bold']
|
||||||
|
|
||||||
|
# Заполняем данные
|
||||||
|
start_row = 1 if headers else 0
|
||||||
|
for i, row_data in enumerate(data):
|
||||||
|
row_cells = table.rows[i + start_row].cells
|
||||||
|
for j, cell_data in enumerate(row_data):
|
||||||
|
row_cells[j].text = str(cell_data)
|
||||||
|
|
||||||
|
# Настройка шрифта для всех ячеек
|
||||||
|
for row in table.rows:
|
||||||
|
for cell in row.cells:
|
||||||
|
for paragraph in cell.paragraphs:
|
||||||
|
for run in paragraph.runs:
|
||||||
|
run.font.name = self.config.font['name']
|
||||||
|
run.font.size = Pt(self.config.table['font_size'])
|
||||||
|
|
||||||
|
def _process_list(self, element: Dict[str, Any]) -> None:
|
||||||
|
"""Обрабатывает элемент списка"""
|
||||||
|
items = element.get('items', [])
|
||||||
|
list_type = element.get('type', 'unordered') # 'unordered' или 'ordered'
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
p = self.document.add_paragraph(style='List GOST')
|
||||||
|
p.paragraph_format.left_indent = Cm(1.25)
|
||||||
|
p.paragraph_format.first_line_indent = Cm(-0.63) if list_type == 'ordered' else Cm(0)
|
||||||
|
|
||||||
|
if list_type == 'ordered':
|
||||||
|
# Добавляем номер вручную для упорядоченного списка
|
||||||
|
p.add_run(f"{items.index(item) + 1}. ").bold = True
|
||||||
|
|
||||||
|
p.add_run(item)
|
||||||
|
|
||||||
|
def _process_image(self, element: Dict[str, Any]) -> None:
|
||||||
|
"""Обрабатывает элемент изображения (заглушка для будущей реализации)"""
|
||||||
|
# В реальной реализации здесь будет добавление изображения
|
||||||
|
path = element.get('path', '')
|
||||||
|
width = element.get('width', None)
|
||||||
|
|
||||||
|
# Добавляем подпись
|
||||||
|
caption = self.document.add_paragraph()
|
||||||
|
caption.style = 'Caption GOST'
|
||||||
|
caption.add_run("Рисунок ").bold = True
|
||||||
|
|
||||||
|
# В реальной реализации здесь будет добавление изображения
|
||||||
|
# if path and os.path.exists(path):
|
||||||
|
# if width:
|
||||||
|
# self.document.add_picture(path, width=Cm(width))
|
||||||
|
# else:
|
||||||
|
# self.document.add_picture(path)
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def add_element(self, element_type: str, **kwargs) -> None:
|
||||||
|
"""
|
||||||
|
Абстрактный метод для добавления элемента в документ.
|
||||||
|
Должен быть реализован в дочерних классах.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
element_type: Тип элемента (heading, paragraph, table и т.д.)
|
||||||
|
**kwargs: Параметры элемента
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def save(self, filename: str) -> None:
|
||||||
|
"""
|
||||||
|
Сохраняет документ в файл
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filename: Имя файла для сохранения
|
||||||
|
"""
|
||||||
|
# Добавляем настройки совместимости
|
||||||
|
try:
|
||||||
|
self.document.settings.element.xpath('./w:compat')[0].clear()
|
||||||
|
except (IndexError, AttributeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.document.save(filename)
|
||||||
|
|
||||||
|
def create_title_page(self) -> None:
|
||||||
|
"""Создает титульную страницу документа"""
|
||||||
|
# Добавляем отступ сверху
|
||||||
|
for _ in range(5):
|
||||||
|
self.document.add_paragraph()
|
||||||
|
|
||||||
|
# Заголовок
|
||||||
|
title_paragraph = self.document.add_heading(level=0)
|
||||||
|
title_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||||
|
title_run = title_paragraph.add_run(self.title)
|
||||||
|
title_run.font.size = Pt(16)
|
||||||
|
title_run.font.bold = True
|
||||||
|
title_run.font.name = self.config.font['name']
|
||||||
|
|
||||||
|
# Добавляем отступ
|
||||||
|
self.document.add_paragraph()
|
||||||
|
|
||||||
|
# Автор
|
||||||
|
author_paragraph = self.document.add_paragraph()
|
||||||
|
author_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||||
|
author_run = author_paragraph.add_run(self.author)
|
||||||
|
author_run.font.size = Pt(self.config.font['size'])
|
||||||
|
author_run.font.name = self.config.font['name']
|
||||||
|
|
||||||
|
# Добавляем отступ снизу
|
||||||
|
for _ in range(10):
|
||||||
|
self.document.add_paragraph()
|
||||||
|
|
||||||
|
# Дата
|
||||||
|
date_paragraph = self.document.add_paragraph()
|
||||||
|
date_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||||
|
date_run = date_paragraph.add_run("2023")
|
||||||
|
date_run.font.size = Pt(self.config.font['size'])
|
||||||
|
date_run.font.name = self.config.font['name']
|
||||||
|
|
||||||
|
# Устанавливаем титульную страницу как отдельный раздел
|
||||||
|
self.document.sections[0].start_type = 2 # Новая страница
|
||||||
|
|
||||||
|
|
||||||
|
class MarkdownGostDocument(GostDocumentCore):
|
||||||
|
"""
|
||||||
|
Расширенная реализация для обработки Markdown и преобразования в документ по ГОСТ
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, config: Optional[GostDocumentConfig] = None,
|
||||||
|
title: str = "", author: str = ""):
|
||||||
|
super().__init__(config, title, author)
|
||||||
|
self._markdown_processors = {}
|
||||||
|
self._setup_markdown_processors()
|
||||||
|
|
||||||
|
def _setup_markdown_processors(self):
|
||||||
|
"""Настраивает обработчики для различных элементов Markdown"""
|
||||||
|
# Заголовки
|
||||||
|
self.register_markdown_processor(r'#{1,6}\s+(.+)', self._process_md_heading)
|
||||||
|
# Обычный текст
|
||||||
|
self.register_markdown_processor(r'^(?!#{1,6}\s+)(?!-{3,})(?!#{1,6}$)(?!```)(?!!\[.*\]\(.*\)).+',
|
||||||
|
self._process_md_paragraph)
|
||||||
|
# Горизонтальная линия
|
||||||
|
self.register_markdown_processor(r'^-{3,}$', self._process_md_horizontal_rule)
|
||||||
|
# Списки
|
||||||
|
self.register_markdown_processor(r'^(\*|\-|\d+\.)\s+(.+)$', self._process_md_list)
|
||||||
|
# Таблицы (упрощенная обработка)
|
||||||
|
self.register_markdown_processor(r'^\|.*\|$', self._process_md_table)
|
||||||
|
# Изображения
|
||||||
|
self.register_markdown_processor(r'^!\[(.*)\]\((.*)\)$', self._process_md_image)
|
||||||
|
# Код
|
||||||
|
self.register_markdown_processor(r'^```(.*)$', self._process_md_code)
|
||||||
|
|
||||||
|
def register_markdown_processor(self, pattern: str, processor: Callable):
|
||||||
|
"""
|
||||||
|
Регистрирует обработчик для определенного паттерна Markdown
|
||||||
|
|
||||||
|
Args:
|
||||||
|
pattern: Регулярное выражение для паттерна
|
||||||
|
processor: Функция-обработчик
|
||||||
|
"""
|
||||||
|
self._markdown_processors[pattern] = processor
|
||||||
|
|
||||||
|
def _process_md_heading(self, match: re.Match, line: str) -> Dict[str, Any]:
|
||||||
|
"""Обрабатывает заголовок Markdown"""
|
||||||
|
heading_text = match.group(1).strip()
|
||||||
|
level = line.count('#')
|
||||||
|
# Ограничиваем уровень заголовка от 1 до 3 (согласно ГОСТ)
|
||||||
|
level = min(max(level, 1), 3)
|
||||||
|
return {'type': 'heading', 'level': level, 'text': heading_text}
|
||||||
|
|
||||||
|
def _process_md_paragraph(self, match: re.Match, line: str) -> Dict[str, Any]:
|
||||||
|
"""Обрабатывает обычный текст Markdown"""
|
||||||
|
return {'type': 'paragraph', 'text': line.strip()}
|
||||||
|
|
||||||
|
def _process_md_horizontal_rule(self, match: re.Match, line: str) -> Dict[str, Any]:
|
||||||
|
"""Обрабатывает горизонтальную линию Markdown"""
|
||||||
|
# В ГОСТ горизонтальные линии не используются, поэтому просто пропускаем
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _process_md_list(self, match: re.Match, line: str) -> Dict[str, Any]:
|
||||||
|
"""Обрабатывает список Markdown"""
|
||||||
|
items = [line.strip()]
|
||||||
|
list_type = 'ordered' if re.match(r'^\d+\.', line) else 'unordered'
|
||||||
|
return {'type': 'list', 'items': items, 'type': list_type}
|
||||||
|
|
||||||
|
def _process_md_table(self, match: re.Match, line: str) -> Dict[str, Any]:
|
||||||
|
"""Обрабатывает таблицу Markdown (заглушка)"""
|
||||||
|
# Полная обработка таблиц требует анализа нескольких строк
|
||||||
|
return None # Будет обработано в полной реализации
|
||||||
|
|
||||||
|
def _process_md_image(self, match: re.Match, line: str) -> Dict[str, Any]:
|
||||||
|
"""Обрабатывает изображение Markdown"""
|
||||||
|
alt_text = match.group(1)
|
||||||
|
image_path = match.group(2)
|
||||||
|
return {'type': 'image', 'path': image_path, 'alt': alt_text}
|
||||||
|
|
||||||
|
def _process_md_code(self, match: re.Match, line: str) -> Dict[str, Any]:
|
||||||
|
"""Обрабатывает блок кода Markdown"""
|
||||||
|
# Код в ГОСТ обычно оформляется как отдельный раздел или сноска
|
||||||
|
return {'type': 'paragraph', 'text': f"Код: {line}", 'style': 'Code'}
|
||||||
|
|
||||||
|
def add_element(self, element_type: str, **kwargs) -> None:
|
||||||
|
"""
|
||||||
|
Добавляет элемент в документ согласно ГОСТ
|
||||||
|
|
||||||
|
Args:
|
||||||
|
element_type: Тип элемента (heading, paragraph, table и т.д.)
|
||||||
|
**kwargs: Параметры элемента
|
||||||
|
"""
|
||||||
|
processor = self.get_processor(element_type)
|
||||||
|
if processor:
|
||||||
|
element = {'type': element_type, **kwargs}
|
||||||
|
processor(element)
|
||||||
|
|
||||||
|
def process_markdown(self, markdown_text: str) -> None:
|
||||||
|
"""
|
||||||
|
Обрабатывает Markdown текст и добавляет элементы в документ
|
||||||
|
|
||||||
|
Args:
|
||||||
|
markdown_text: Текст в формате Markdown
|
||||||
|
"""
|
||||||
|
lines = markdown_text.split('\n')
|
||||||
|
i = 0
|
||||||
|
while i < len(lines):
|
||||||
|
line = lines[i].strip()
|
||||||
|
if not line:
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
processed = False
|
||||||
|
for pattern, processor in self._markdown_processors.items():
|
||||||
|
match = re.match(pattern, line)
|
||||||
|
if match:
|
||||||
|
element = processor(match, line)
|
||||||
|
if element:
|
||||||
|
self.add_element(element['type'], **{k: v for k, v in element.items() if k != 'type'})
|
||||||
|
processed = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not processed:
|
||||||
|
# Если строка не соответствует ни одному паттерну, обрабатываем как обычный текст
|
||||||
|
self.add_element('paragraph', text=line)
|
||||||
|
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
def process_markdown_file(self, file_path: str) -> None:
|
||||||
|
"""
|
||||||
|
Обрабатывает файл Markdown и добавляет элементы в документ
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file_path: Путь к файлу Markdown
|
||||||
|
"""
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
markdown_text = f.read()
|
||||||
|
self.process_markdown(markdown_text)
|
||||||
|
|
||||||
|
|
||||||
|
# Пример использования
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Создаем кастомную конфигурацию (можно оставить стандартную)
|
||||||
|
custom_config = GostDocumentConfig(
|
||||||
|
margins={
|
||||||
|
'top': 2.0,
|
||||||
|
'bottom': 2.0,
|
||||||
|
'left': 3.0,
|
||||||
|
'right': 1.5
|
||||||
|
},
|
||||||
|
font={
|
||||||
|
'name': 'Times New Roman',
|
||||||
|
'size': 14
|
||||||
|
},
|
||||||
|
line_spacing=1.5,
|
||||||
|
heading_spacing={
|
||||||
|
1: {'before': 24, 'after': 12},
|
||||||
|
2: {'before': 24, 'after': 6},
|
||||||
|
3: {'before': 18, 'after': 6}
|
||||||
|
},
|
||||||
|
heading_styles={
|
||||||
|
1: {'size': 16, 'bold': True, 'all_caps': True, 'align': 'center'},
|
||||||
|
2: {'size': 14, 'bold': True, 'all_caps': False, 'align': 'left'},
|
||||||
|
3: {'size': 14, 'bold': True, 'italic': True, 'all_caps': False, 'align': 'left'}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Создаем документ с поддержкой Markdown
|
||||||
|
doc = MarkdownGostDocument(
|
||||||
|
config=custom_config,
|
||||||
|
title="Отчет о научно-исследовательской работе",
|
||||||
|
author="Иванов И.И., группа 123"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Добавляем титульную страницу
|
||||||
|
doc.create_title_page()
|
||||||
|
|
||||||
|
# Пример обработки Markdown
|
||||||
|
markdown_example = """
|
||||||
|
## Практическая работа 6 (часть 2)
|
||||||
|
**Тема. Нелинейные структуры данных. Бинарное дерево.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1. Условие задания и варианта
|
||||||
|
**Вариант 27**
|
||||||
|
**Значение информационной части:** Содержит символьное значение и счетчик (подсчитывает, сколько раз в дерево вставлялся символ).
|
||||||
|
**Операции варианта:**
|
||||||
|
1. Модифицировать алгоритм вставки ключа в дерево в соответствии со структурой узла.
|
||||||
|
2. Определить символ, который встретился большее количество раз.
|
||||||
|
3. Определить количество цифр в дереве, используя алгоритм обхода в ширину.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Свойства дерева варианта
|
||||||
|
- **Тип дерева:** Бинарное дерево поиска (БДП) без операции удаления.
|
||||||
|
- **Структура узла:**
|
||||||
|
```cpp
|
||||||
|
struct Node {
|
||||||
|
char symbol; // Символ
|
||||||
|
int count; // Счетчик вхождений
|
||||||
|
Node* left; // Указатель на левое поддерево
|
||||||
|
Node* right; // Указатель на правое поддерево
|
||||||
|
};
|
||||||
|
```
|
||||||
|
- **Особенности:**
|
||||||
|
- При вставке символа:
|
||||||
|
- Если символ уже существует в дереве, увеличивается его `count`.
|
||||||
|
- Иначе создается новый узел с `count = 1`.
|
||||||
|
- Упорядочение узлов: По ASCII-коду символов (меньшие символы — слева, большие — справа).
|
||||||
|
- Операция удаления не реализована (требования варианта 24–30).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Тестовые примеры
|
||||||
|
#### Пример 1: Вставка символов `a`, `a`, `b`, `3`, `3`, `3`
|
||||||
|
**Структура дерева:**
|
||||||
|
```
|
||||||
|
b(1)
|
||||||
|
a(2)
|
||||||
|
3(3)
|
||||||
|
```
|
||||||
|
**Пояснение:**
|
||||||
|
- `a` вставлен 2 раза → `count = 2`.
|
||||||
|
- `3` вставлен 3 раза → `count = 3` (самый частый символ).
|
||||||
|
- Количество цифр: 3 (только символ `3`).
|
||||||
|
|
||||||
|
#### Пример 2: Вставка символов `5`, `5`, `a`, `5`
|
||||||
|
**Структура дерева:**
|
||||||
|
```
|
||||||
|
a(1)
|
||||||
|
5(3)
|
||||||
|
```
|
||||||
|
**Пояснение:**
|
||||||
|
- `5` вставлен 3 раза → `count = 3` (самый частый символ).
|
||||||
|
- Количество цифр: 3.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Описание функций
|
||||||
|
|
||||||
|
#### 4.1. Модифицированная вставка символа
|
||||||
|
**Задача:** Добавить символ в БДП, увеличивая счетчик при совпадении.
|
||||||
|
**Предусловие:** Дерево инициализировано.
|
||||||
|
**Постусловие:** Символ добавлен или обновлен в дереве.
|
||||||
|
**Прототип:**
|
||||||
|
```cpp
|
||||||
|
void BinaryTree::insert(char symbol);
|
||||||
|
```
|
||||||
|
**Алгоритм:**
|
||||||
|
1. Начать с корня.
|
||||||
|
2. Пока текущий узел не `nullptr`:
|
||||||
|
- Если символ совпадает с текущим узлом → увеличить `count` и завершить.
|
||||||
|
- Если символ меньше текущего → перейти в левое поддерево.
|
||||||
|
- Иначе → перейти в правое поддерево.
|
||||||
|
3. Если узел не найден → создать новый узел с `count = 1`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 4.2. Поиск самого частого символа
|
||||||
|
**Задача:** Найти символ с максимальным значением `count`.
|
||||||
|
**Предусловие:** Дерево не пустое.
|
||||||
|
**Постусловие:** Возвращен символ и его счетчик.
|
||||||
|
**Прототип:**
|
||||||
|
```cpp
|
||||||
|
std::pair<char, int> BinaryTree::findMostFrequent();
|
||||||
|
```
|
||||||
|
**Алгоритм:**
|
||||||
|
1. Инициализировать `maxCount = 0`, `maxSymbol = '\\0'`.
|
||||||
|
2. Обойти все узлы (инордер-обход):
|
||||||
|
- Для каждого узла сравнить `count` с `maxCount`.
|
||||||
|
- Если `count > maxCount` → обновить `maxSymbol` и `maxCount`.
|
||||||
|
3. Вернуть пару `(maxSymbol, maxCount)`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 4.3. Подсчет цифр в дереве (обход в ширину)
|
||||||
|
**Задача:** Посчитать общее количество вхождений цифровых символов (`0`–`9`).
|
||||||
|
**Предусловие:** Дерево инициализировано.
|
||||||
|
**Постусловие:** Возвращено число — сумма `count` для всех цифровых символов.
|
||||||
|
**Прототип:**
|
||||||
|
```cpp
|
||||||
|
int BinaryTree::countDigits();
|
||||||
|
```
|
||||||
|
**Алгоритм:**
|
||||||
|
1. Инициализировать очередь и счетчик `total = 0`.
|
||||||
|
2. Поместить корень в очередь.
|
||||||
|
3. Пока очередь не пуста:
|
||||||
|
- Извлечь узел из очереди.
|
||||||
|
- Если символ узла — цифра (`'0'`–`'9'`) → добавить `count` к `total`.
|
||||||
|
- Поместить левый и правый дочерние узлы в очередь.
|
||||||
|
4. Вернуть `total`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 4.4. Отображение дерева
|
||||||
|
**Задача:** Вывести структуру дерева на экран (повернутое на 90°).
|
||||||
|
**Предусловие:** Дерево инициализировано.
|
||||||
|
**Постусловие:** Дерево отображено в консоли.
|
||||||
|
**Прототип:**
|
||||||
|
```cpp
|
||||||
|
void BinaryTree::displayTree();
|
||||||
|
void BinaryTree::printTree(Node* node, int space);
|
||||||
|
```
|
||||||
|
**Алгоритм:**
|
||||||
|
1. Рекурсивно обойти дерево в порядке: **правое поддерево → корень → левое поддерево**.
|
||||||
|
2. Для каждого уровня увеличивать отступ (на 10 пробелов).
|
||||||
|
3. Вывести символ и `count` с текущим отступом.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. Код программы
|
||||||
|
```cpp
|
||||||
|
#include <iostream>
|
||||||
|
#include <stack>
|
||||||
|
#include <queue>
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
struct Node {
|
||||||
|
char symbol;
|
||||||
|
int count;
|
||||||
|
Node* left;
|
||||||
|
Node* right;
|
||||||
|
Node(char s) : symbol(s), count(1), left(nullptr), right(nullptr) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class BinaryTree {
|
||||||
|
private:
|
||||||
|
Node* root;
|
||||||
|
|
||||||
|
void printTree(Node* node, int space) {
|
||||||
|
if (node == nullptr) return;
|
||||||
|
space += 10;
|
||||||
|
printTree(node->right, space);
|
||||||
|
cout << endl;
|
||||||
|
for (int i = 10; i < space; i++) cout << " ";
|
||||||
|
cout << node->symbol << "(" << node->count << ")";
|
||||||
|
printTree(node->left, space);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
BinaryTree() : root(nullptr) {}
|
||||||
|
|
||||||
|
void insert(char symbol) {
|
||||||
|
Node** current = &root;
|
||||||
|
while (*current != nullptr) {
|
||||||
|
if (symbol == (*current)->symbol) {
|
||||||
|
(*current)->count++;
|
||||||
|
return;
|
||||||
|
} else if (symbol < (*current)->symbol) {
|
||||||
|
current = &((*current)->left);
|
||||||
|
} else {
|
||||||
|
current = &((*current)->right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*current = new Node(symbol);
|
||||||
|
}
|
||||||
|
|
||||||
|
pair<char, int> findMostFrequent() {
|
||||||
|
if (root == nullptr) return make_pair('\\0', 0);
|
||||||
|
char maxSymbol = '\\0';
|
||||||
|
int maxCount = 0;
|
||||||
|
stack<Node*> s;
|
||||||
|
Node* current = root;
|
||||||
|
while (current != nullptr || !s.empty()) {
|
||||||
|
while (current != nullptr) {
|
||||||
|
s.push(current);
|
||||||
|
current = current->left;
|
||||||
|
}
|
||||||
|
current = s.top();
|
||||||
|
s.pop();
|
||||||
|
if (current->count > maxCount) {
|
||||||
|
maxCount = current->count;
|
||||||
|
maxSymbol = current->symbol;
|
||||||
|
}
|
||||||
|
current = current->right;
|
||||||
|
}
|
||||||
|
return make_pair(maxSymbol, maxCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
int countDigits() {
|
||||||
|
if (root == nullptr) return 0;
|
||||||
|
int total = 0;
|
||||||
|
queue<Node*> q;
|
||||||
|
q.push(root);
|
||||||
|
while (!q.empty()) {
|
||||||
|
Node* current = q.front();
|
||||||
|
q.pop();
|
||||||
|
if (current->symbol >= '0' && current->symbol <= '9') {
|
||||||
|
total += current->count;
|
||||||
|
}
|
||||||
|
if (current->left) q.push(current->left);
|
||||||
|
if (current->right) q.push(current->right);
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
void displayTree() {
|
||||||
|
printTree(root, 0);
|
||||||
|
cout << "\n\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
BinaryTree tree;
|
||||||
|
int choice;
|
||||||
|
char symbol;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
cout << "\n1. Insert symbol\n";
|
||||||
|
cout << "2. Display tree\n";
|
||||||
|
cout << "3. Find most frequent symbol\n";
|
||||||
|
cout << "4. Count digits\n";
|
||||||
|
cout << "5. Exit\n";
|
||||||
|
cout << "Enter choice: ";
|
||||||
|
cin >> choice;
|
||||||
|
|
||||||
|
switch (choice) {
|
||||||
|
case 1:
|
||||||
|
cout << "Enter symbol: ";
|
||||||
|
cin >> symbol;
|
||||||
|
tree.insert(symbol);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
cout << "Tree structure:\n";
|
||||||
|
tree.displayTree();
|
||||||
|
break;
|
||||||
|
case 3: {
|
||||||
|
auto result = tree.findMostFrequent();
|
||||||
|
if (result.second == 0) {
|
||||||
|
cout << "Tree is empty.\n";
|
||||||
|
} else {
|
||||||
|
cout << "Most frequent symbol: " << result.first
|
||||||
|
<< " (count: " << result.second << ")\n";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 4:
|
||||||
|
cout << "Number of digits: " << tree.countDigits() << endl;
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
cout << "Invalid choice.\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. Скриншоты тестирования
|
||||||
|
#### Тест 1: Вставка `a`, `a`, `b`, `3`, `3`, `3`
|
||||||
|

|
||||||
|
- **Вывод дерева:** Корректно отображена структура с `3(3)` как самым частым символом.
|
||||||
|
- **Количество цифр:** 3 (символ `3` вставлен 3 раза).
|
||||||
|
|
||||||
|
#### Тест 2: Вставка `5`, `5`, `a`, `5`
|
||||||
|

|
||||||
|
- **Самый частый символ:** `5` (count=3).
|
||||||
|
- **Количество цифр:** 3.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 7. Ответы на контрольные вопросы
|
||||||
|
1. **Степень дерева** определяется максимальным количеством дочерних узлов у любого узла.
|
||||||
|
2. **Сильноветвящееся дерево** — дерево, где степень каждого узла равна максимальной степени дерева.
|
||||||
|
3. **Путь в дереве** — последовательность узлов от корня к целевому узлу.
|
||||||
|
4. **Длина пути** — количество ребер в пути (на 1 меньше количества узлов).
|
||||||
|
5. **Степень бинарного дерева** равна 2 (максимум 2 дочерних узла на узел).
|
||||||
|
6. **Пустое дерево** допустимо (корень = `nullptr`).
|
||||||
|
7. **Бинарное дерево** — дерево, где каждый узел имеет не более 2 дочерних узлов.
|
||||||
|
8. **Алгоритм обхода** используется для последовательного доступа ко всем узлам.
|
||||||
|
9. **Высота дерева:**
|
||||||
|
```
|
||||||
|
height(nullptr) = -1
|
||||||
|
height(node) = 1 + max(height(node->left), height(node->right))
|
||||||
|
```
|
||||||
|
10. **Дерево по таблице:**
|
||||||
|

|
||||||
|
11. **Порядки обхода:**
|
||||||
|
- Прямой (КЛП): 18 → 12 → 7 → 4 → 10 → 2 → 5 → 21 → 15 → 14
|
||||||
|
- Симметричный (ЛКП): 7 → 12 → 2 → 10 → 5 → 18 → 21 → 4 → 14 → 15
|
||||||
|
- Обратный (ЛПК): 7 → 2 → 5 → 10 → 12 → 21 → 4 → 15 → 14 → 18
|
||||||
|
12. **Обход в ширину** использует **очередь**.
|
||||||
|
13. **Пример обхода в ширину:** 18 → 12 → 14 → 7 → 10 → 15 → 4 → 2 → 5 → 21.
|
||||||
|
14. **Не рекурсивный обход в глубину** использует **стек**.
|
||||||
|
15. **Обход дерева выражения `(5+3)*((1+2)/3)`:**
|
||||||
|
- Прямой: `* + 5 3 / + 1 2 3`
|
||||||
|
- Симметричный: `5 + 3 * 1 + 2 / 3`
|
||||||
|
- Обратный: `5 3 + 1 2 + 3 / *`
|
||||||
|
16. **Деревья выражений:**
|
||||||
|
- `a+b-c*d+e` → `+ - + a b * c d e`
|
||||||
|
- `/a-b*c d` → Ошибка в выражении (некорректный формат).
|
||||||
|
- `a b c d / - *` → `* a - b / c d`
|
||||||
|
- `* - / + a b c d e` → `* - / + a b c d e`
|
||||||
|
17. **Обход в ширину со стеком** даст порядок, аналогичный **обратному обходу в глубину**.
|
||||||
|
18. **БДП для симметричного обхода `40 45 46 50 65 70 75`:**
|
||||||
|

|
||||||
|
19. **Исходное дерево для прямого обхода `50 45 35 15 40 46 65 75 70`:**
|
||||||
|

|
||||||
|
20. **БДП после вставки `1, 48, 75, 100`:**
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Вывод:** Реализованы все операции для бинарного дерева поиска с модифицированной структурой узла. Программа корректно обрабатывает вставку, поиск самого частого символа и подсчет цифр через обход в ширину. Тесты подтверждают работоспособность алгоритмов..
|
||||||
|
"""
|
||||||
|
# Обрабатываем Markdown
|
||||||
|
doc.process_markdown(markdown_example)
|
||||||
|
|
||||||
|
# Сохраняем документ
|
||||||
|
doc.save("./asdotchet_po_GOST.docx")
|
||||||
|
print("Документ успешно создан и сохранен как 'otchet_po_GOST.docx'")
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
# 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'<w:shd {nsdecls("w")} w:fill="D9D9D9"/>')
|
||||||
|
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)
|
||||||
|
|
||||||
|
# === Изображения:  ===
|
||||||
|
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()
|
||||||
@@ -0,0 +1,284 @@
|
|||||||
|
# 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 json
|
||||||
|
import re
|
||||||
|
import requests
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
import parser
|
||||||
|
|
||||||
|
# Счётчик листингов
|
||||||
|
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="", is_continuation=False):
|
||||||
|
global listing_counter
|
||||||
|
p = doc.add_paragraph(style='Caption')
|
||||||
|
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||||
|
run = p.add_run()
|
||||||
|
run.font.color.rgb = RGBColor(0, 0, 0)
|
||||||
|
run.font.name = 'Times New Roman'
|
||||||
|
run._element.rPr.rFonts.set(qn('w:cs'), 'Times New Roman')
|
||||||
|
|
||||||
|
if is_continuation:
|
||||||
|
run.text = f"Продолжение листинга {listing_counter} — Код на {language}"
|
||||||
|
else:
|
||||||
|
listing_counter += 1
|
||||||
|
run.text = f"Листинг {listing_counter} — Код на {language}"
|
||||||
|
|
||||||
|
|
||||||
|
def add_code_block(doc, code_lines, language=""):
|
||||||
|
global listing_counter
|
||||||
|
add_listing_caption(doc, language, is_continuation=False)
|
||||||
|
|
||||||
|
# Создаём таблицу для кода с выравниванием по левому краю
|
||||||
|
table = doc.add_table(rows=0, cols=1)
|
||||||
|
table.style = 'Table Grid'
|
||||||
|
table.alignment = WD_TABLE_ALIGNMENT.LEFT # Важно: выравнивание по левому краю
|
||||||
|
|
||||||
|
# Добавляем каждую строку кода как отдельную строку таблицы
|
||||||
|
for line in code_lines:
|
||||||
|
row = table.add_row()
|
||||||
|
cell = row.cells[0]
|
||||||
|
cell.text = line.strip()
|
||||||
|
|
||||||
|
# Серый фон для ячейки
|
||||||
|
shading_elm = parse_xml(f'<w:shd {nsdecls("w")} w:fill="D9D9D9"/>')
|
||||||
|
cell._tc.get_or_add_tcPr().append(shading_elm)
|
||||||
|
|
||||||
|
# Настройка параграфа внутри ячейки
|
||||||
|
for paragraph in cell.paragraphs:
|
||||||
|
paragraph_format = paragraph.paragraph_format
|
||||||
|
paragraph_format.left_indent = Mm(0) # Убираем левый отступ
|
||||||
|
paragraph_format.right_indent = Mm(0)
|
||||||
|
paragraph_format.first_line_indent = Mm(0)
|
||||||
|
paragraph_format.alignment = WD_ALIGN_PARAGRAPH.LEFT # Выравнивание по левому краю
|
||||||
|
|
||||||
|
for run in paragraph.runs:
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Добавляем подпись о продолжении, если код длинный
|
||||||
|
if len(code_lines) > 25:
|
||||||
|
p = doc.add_paragraph()
|
||||||
|
run = p.add_run(f"Продолжение листинга {listing_counter} — Код на {language}")
|
||||||
|
run.italic = True
|
||||||
|
run.font.size = Pt(12)
|
||||||
|
run.font.color.rgb = RGBColor(80, 80, 80)
|
||||||
|
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||||
|
p.space_before = Pt(6)
|
||||||
|
p.space_after = Pt(6)
|
||||||
|
|
||||||
|
|
||||||
|
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 process_element(doc, element, depth=0):
|
||||||
|
"""Рекурсивная обработка элемента с учётом вложенности"""
|
||||||
|
global listing_counter
|
||||||
|
|
||||||
|
if element['type'] == 'blank_line':
|
||||||
|
return
|
||||||
|
|
||||||
|
elif element['type'] == 'heading':
|
||||||
|
level = element['attrs']['level']
|
||||||
|
text = ''.join(extract_text(element['children']))
|
||||||
|
add_heading(doc, text, level=level)
|
||||||
|
|
||||||
|
elif element['type'] == 'paragraph':
|
||||||
|
p = doc.add_paragraph()
|
||||||
|
p.style = 'Normal'
|
||||||
|
|
||||||
|
# Обработка вложенных элементов в параграфе
|
||||||
|
for child in element['children']:
|
||||||
|
if child['type'] == 'text':
|
||||||
|
p.add_run(child['raw'])
|
||||||
|
elif child['type'] == 'strong':
|
||||||
|
text = ''.join(extract_text(child['children']))
|
||||||
|
run = p.add_run(text)
|
||||||
|
run.bold = True
|
||||||
|
elif child['type'] == 'codespan':
|
||||||
|
run = p.add_run(child['raw'])
|
||||||
|
run.font.name = 'Courier New'
|
||||||
|
run.font.size = Pt(12)
|
||||||
|
elif child['type'] == 'linebreak':
|
||||||
|
p.add_run().add_break()
|
||||||
|
elif child['type'] == 'image':
|
||||||
|
# Изображение обрабатывается отдельно
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Установка отступов в зависимости от глубины
|
||||||
|
pf = p.paragraph_format
|
||||||
|
pf.left_indent = Mm(5 * depth) if depth > 0 else Mm(0)
|
||||||
|
|
||||||
|
elif element['type'] == 'thematic_break':
|
||||||
|
doc.add_paragraph().add_run().add_break()
|
||||||
|
|
||||||
|
elif element['type'] == 'list':
|
||||||
|
# Обработка списка с учётом глубины вложенности
|
||||||
|
for item in element['children']:
|
||||||
|
if item['type'] == 'list_item':
|
||||||
|
# Создаём маркированный пункт
|
||||||
|
p = doc.add_paragraph(style='List Bullet')
|
||||||
|
p.paragraph_format.left_indent = Mm(5 * depth)
|
||||||
|
p.paragraph_format.first_line_indent = Mm(-3.5) # Отступ для маркера
|
||||||
|
|
||||||
|
# Обрабатываем содержимое пункта списка
|
||||||
|
for child in item['children']:
|
||||||
|
if child['type'] == 'block_text':
|
||||||
|
for subchild in child['children']:
|
||||||
|
if subchild['type'] == 'text':
|
||||||
|
p.add_run(subchild['raw'])
|
||||||
|
elif subchild['type'] == 'strong':
|
||||||
|
text = ''.join(extract_text(subchild['children']))
|
||||||
|
run = p.add_run(text)
|
||||||
|
run.bold = True
|
||||||
|
elif subchild['type'] == 'codespan':
|
||||||
|
run = p.add_run(subchild['raw'])
|
||||||
|
run.font.name = 'Courier New'
|
||||||
|
run.font.size = Pt(12)
|
||||||
|
|
||||||
|
# Рекурсивная обработка вложенных списков
|
||||||
|
for child in item['children']:
|
||||||
|
if child['type'] == 'list':
|
||||||
|
process_element(doc, child, depth=depth + 1)
|
||||||
|
|
||||||
|
elif element['type'] == 'block_code':
|
||||||
|
code_lines = element['raw'].split('\n')
|
||||||
|
language = element['attrs'].get('info', 'plain')
|
||||||
|
add_code_block(doc, code_lines, language)
|
||||||
|
|
||||||
|
elif element['type'] == 'image':
|
||||||
|
url = element['attrs']['url']
|
||||||
|
caption = ''.join(extract_text(element['children']))
|
||||||
|
add_image_from_url(doc, url, caption)
|
||||||
|
|
||||||
|
# Обработка других типов элементов
|
||||||
|
elif element['type'] == 'list_item':
|
||||||
|
# Обработка отдельных пунктов списка (для рекурсии)
|
||||||
|
p = doc.add_paragraph(style='List Bullet')
|
||||||
|
p.paragraph_format.left_indent = Mm(5 * depth)
|
||||||
|
p.paragraph_format.first_line_indent = Mm(-3.5)
|
||||||
|
|
||||||
|
for child in element['children']:
|
||||||
|
if child['type'] == 'block_text':
|
||||||
|
for subchild in child['children']:
|
||||||
|
if subchild['type'] == 'text':
|
||||||
|
p.add_run(subchild['raw'])
|
||||||
|
elif subchild['type'] == 'strong':
|
||||||
|
text = ''.join(extract_text(subchild['children']))
|
||||||
|
run = p.add_run(text)
|
||||||
|
run.bold = True
|
||||||
|
elif subchild['type'] == 'codespan':
|
||||||
|
run = p.add_run(subchild['raw'])
|
||||||
|
run.font.name = 'Courier New'
|
||||||
|
run.font.size = Pt(12)
|
||||||
|
|
||||||
|
# Обработка вложенных списков
|
||||||
|
for child in element['children']:
|
||||||
|
if child['type'] == 'list':
|
||||||
|
process_element(doc, child, depth=depth + 1)
|
||||||
|
|
||||||
|
|
||||||
|
def extract_text(elements):
|
||||||
|
"""Извлекает текст из структурированных элементов"""
|
||||||
|
texts = []
|
||||||
|
for el in elements:
|
||||||
|
if el['type'] == 'text':
|
||||||
|
texts.append(el['raw'])
|
||||||
|
elif 'children' in el:
|
||||||
|
texts.extend(extract_text(el['children']))
|
||||||
|
return texts
|
||||||
|
|
||||||
|
|
||||||
|
def parse_structured_data_and_fill(doc, structured_data):
|
||||||
|
"""Основная функция обработки структурированных данных"""
|
||||||
|
global listing_counter
|
||||||
|
listing_counter = 0 # Сброс счётчика листингов
|
||||||
|
|
||||||
|
for element in structured_data:
|
||||||
|
process_element(doc, element)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
structured_data = parser.mdToraw()
|
||||||
|
# Создаём документ
|
||||||
|
doc = Document()
|
||||||
|
setup_gost_style(doc)
|
||||||
|
|
||||||
|
# Обрабатываем структурированные данные
|
||||||
|
parse_structured_data_and_fill(doc, structured_data)
|
||||||
|
|
||||||
|
# Сохраняем результат
|
||||||
|
output_filename = "Практическая_работа_6.docx"
|
||||||
|
doc.save(output_filename)
|
||||||
|
print(f"✅ Документ сохранён как '{output_filename}'")
|
||||||
|
print(f"📎 Обработано {len(structured_data)} элементов структурированных данных")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -0,0 +1,340 @@
|
|||||||
|
import mistune
|
||||||
|
|
||||||
|
text= """
|
||||||
|
## Практическая работа 6 (часть 2)
|
||||||
|
**Тема. Нелинейные структуры данных. Бинарное дерево.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1. Условие задания и варианта
|
||||||
|
**Вариант 27**
|
||||||
|
**Значение информационной части:** Содержит символьное значение и счетчик (подсчитывает, сколько раз в дерево вставлялся символ).
|
||||||
|
**Операции варианта:**
|
||||||
|
1. Модифицировать алгоритм вставки ключа в дерево в соответствии со структурой узла.
|
||||||
|
2. Определить символ, который встретился большее количество раз.
|
||||||
|
3. Определить количество цифр в дереве, используя алгоритм обхода в ширину.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Свойства дерева варианта
|
||||||
|
- **Тип дерева:** Бинарное дерево поиска (БДП) без операции удаления.
|
||||||
|
- **Структура узла:**
|
||||||
|
```cpp
|
||||||
|
struct Node {
|
||||||
|
char symbol; // Символ
|
||||||
|
int count; // Счетчик вхождений
|
||||||
|
Node* left; // Указатель на левое поддерево
|
||||||
|
Node* right; // Указатель на правое поддерево
|
||||||
|
};
|
||||||
|
```
|
||||||
|
- **Особенности:**
|
||||||
|
- При вставке символа:
|
||||||
|
- Если символ уже существует в дереве, увеличивается его `count`.
|
||||||
|
- Иначе создается новый узел с `count = 1`.
|
||||||
|
- Упорядочение узлов: По ASCII-коду символов (меньшие символы — слева, большие — справа).
|
||||||
|
- Операция удаления не реализована (требования варианта 24–30).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Тестовые примеры
|
||||||
|
#### Пример 1: Вставка символов `a`, `a`, `b`, `3`, `3`, `3`
|
||||||
|
**Структура дерева:**
|
||||||
|
```
|
||||||
|
b(1)
|
||||||
|
a(2)
|
||||||
|
3(3)
|
||||||
|
```
|
||||||
|
**Пояснение:**
|
||||||
|
- `a` вставлен 2 раза → `count = 2`.
|
||||||
|
- `3` вставлен 3 раза → `count = 3` (самый частый символ).
|
||||||
|
- Количество цифр: 3 (только символ `3`).
|
||||||
|
|
||||||
|
#### Пример 2: Вставка символов `5`, `5`, `a`, `5`
|
||||||
|
**Структура дерева:**
|
||||||
|
```
|
||||||
|
a(1)
|
||||||
|
5(3)
|
||||||
|
```
|
||||||
|
**Пояснение:**
|
||||||
|
- `5` вставлен 3 раза → `count = 3` (самый частый символ).
|
||||||
|
- Количество цифр: 3.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Описание функций
|
||||||
|
|
||||||
|
#### 4.1. Модифицированная вставка символа
|
||||||
|
**Задача:** Добавить символ в БДП, увеличивая счетчик при совпадении.
|
||||||
|
**Предусловие:** Дерево инициализировано.
|
||||||
|
**Постусловие:** Символ добавлен или обновлен в дереве.
|
||||||
|
**Прототип:**
|
||||||
|
```cpp
|
||||||
|
void BinaryTree::insert(char symbol);
|
||||||
|
```
|
||||||
|
**Алгоритм:**
|
||||||
|
1. Начать с корня.
|
||||||
|
2. Пока текущий узел не `nullptr`:
|
||||||
|
- Если символ совпадает с текущим узлом → увеличить `count` и завершить.
|
||||||
|
- Если символ меньше текущего → перейти в левое поддерево.
|
||||||
|
- Иначе → перейти в правое поддерево.
|
||||||
|
3. Если узел не найден → создать новый узел с `count = 1`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 4.2. Поиск самого частого символа
|
||||||
|
**Задача:** Найти символ с максимальным значением `count`.
|
||||||
|
**Предусловие:** Дерево не пустое.
|
||||||
|
**Постусловие:** Возвращен символ и его счетчик.
|
||||||
|
**Прототип:**
|
||||||
|
```cpp
|
||||||
|
std::pair<char, int> BinaryTree::findMostFrequent();
|
||||||
|
```
|
||||||
|
**Алгоритм:**
|
||||||
|
1. Инициализировать `maxCount = 0`, `maxSymbol = '\\0'`.
|
||||||
|
2. Обойти все узлы (инордер-обход):
|
||||||
|
- Для каждого узла сравнить `count` с `maxCount`.
|
||||||
|
- Если `count > maxCount` → обновить `maxSymbol` и `maxCount`.
|
||||||
|
3. Вернуть пару `(maxSymbol, maxCount)`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 4.3. Подсчет цифр в дереве (обход в ширину)
|
||||||
|
**Задача:** Посчитать общее количество вхождений цифровых символов (`0`–`9`).
|
||||||
|
**Предусловие:** Дерево инициализировано.
|
||||||
|
**Постусловие:** Возвращено число — сумма `count` для всех цифровых символов.
|
||||||
|
**Прототип:**
|
||||||
|
```cpp
|
||||||
|
int BinaryTree::countDigits();
|
||||||
|
```
|
||||||
|
**Алгоритм:**
|
||||||
|
1. Инициализировать очередь и счетчик `total = 0`.
|
||||||
|
2. Поместить корень в очередь.
|
||||||
|
3. Пока очередь не пуста:
|
||||||
|
- Извлечь узел из очереди.
|
||||||
|
- Если символ узла — цифра (`'0'`–`'9'`) → добавить `count` к `total`.
|
||||||
|
- Поместить левый и правый дочерние узлы в очередь.
|
||||||
|
4. Вернуть `total`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 4.4. Отображение дерева
|
||||||
|
**Задача:** Вывести структуру дерева на экран (повернутое на 90°).
|
||||||
|
**Предусловие:** Дерево инициализировано.
|
||||||
|
**Постусловие:** Дерево отображено в консоли.
|
||||||
|
**Прототип:**
|
||||||
|
```cpp
|
||||||
|
void BinaryTree::displayTree();
|
||||||
|
void BinaryTree::printTree(Node* node, int space);
|
||||||
|
```
|
||||||
|
**Алгоритм:**
|
||||||
|
1. Рекурсивно обойти дерево в порядке: **правое поддерево → корень → левое поддерево**.
|
||||||
|
2. Для каждого уровня увеличивать отступ (на 10 пробелов).
|
||||||
|
3. Вывести символ и `count` с текущим отступом.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. Код программы
|
||||||
|
```cpp
|
||||||
|
#include <iostream>
|
||||||
|
#include <stack>
|
||||||
|
#include <queue>
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
struct Node {
|
||||||
|
char symbol;
|
||||||
|
int count;
|
||||||
|
Node* left;
|
||||||
|
Node* right;
|
||||||
|
Node(char s) : symbol(s), count(1), left(nullptr), right(nullptr) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class BinaryTree {
|
||||||
|
private:
|
||||||
|
Node* root;
|
||||||
|
|
||||||
|
void printTree(Node* node, int space) {
|
||||||
|
if (node == nullptr) return;
|
||||||
|
space += 10;
|
||||||
|
printTree(node->right, space);
|
||||||
|
cout << endl;
|
||||||
|
for (int i = 10; i < space; i++) cout << " ";
|
||||||
|
cout << node->symbol << "(" << node->count << ")";
|
||||||
|
printTree(node->left, space);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
BinaryTree() : root(nullptr) {}
|
||||||
|
|
||||||
|
void insert(char symbol) {
|
||||||
|
Node** current = &root;
|
||||||
|
while (*current != nullptr) {
|
||||||
|
if (symbol == (*current)->symbol) {
|
||||||
|
(*current)->count++;
|
||||||
|
return;
|
||||||
|
} else if (symbol < (*current)->symbol) {
|
||||||
|
current = &((*current)->left);
|
||||||
|
} else {
|
||||||
|
current = &((*current)->right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*current = new Node(symbol);
|
||||||
|
}
|
||||||
|
|
||||||
|
pair<char, int> findMostFrequent() {
|
||||||
|
if (root == nullptr) return make_pair('\\0', 0);
|
||||||
|
char maxSymbol = '\\0';
|
||||||
|
int maxCount = 0;
|
||||||
|
stack<Node*> s;
|
||||||
|
Node* current = root;
|
||||||
|
while (current != nullptr || !s.empty()) {
|
||||||
|
while (current != nullptr) {
|
||||||
|
s.push(current);
|
||||||
|
current = current->left;
|
||||||
|
}
|
||||||
|
current = s.top();
|
||||||
|
s.pop();
|
||||||
|
if (current->count > maxCount) {
|
||||||
|
maxCount = current->count;
|
||||||
|
maxSymbol = current->symbol;
|
||||||
|
}
|
||||||
|
current = current->right;
|
||||||
|
}
|
||||||
|
return make_pair(maxSymbol, maxCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
int countDigits() {
|
||||||
|
if (root == nullptr) return 0;
|
||||||
|
int total = 0;
|
||||||
|
queue<Node*> q;
|
||||||
|
q.push(root);
|
||||||
|
while (!q.empty()) {
|
||||||
|
Node* current = q.front();
|
||||||
|
q.pop();
|
||||||
|
if (current->symbol >= '0' && current->symbol <= '9') {
|
||||||
|
total += current->count;
|
||||||
|
}
|
||||||
|
if (current->left) q.push(current->left);
|
||||||
|
if (current->right) q.push(current->right);
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
void displayTree() {
|
||||||
|
printTree(root, 0);
|
||||||
|
cout << "\n\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
BinaryTree tree;
|
||||||
|
int choice;
|
||||||
|
char symbol;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
cout << "\n1. Insert symbol\n";
|
||||||
|
cout << "2. Display tree\n";
|
||||||
|
cout << "3. Find most frequent symbol\n";
|
||||||
|
cout << "4. Count digits\n";
|
||||||
|
cout << "5. Exit\n";
|
||||||
|
cout << "Enter choice: ";
|
||||||
|
cin >> choice;
|
||||||
|
|
||||||
|
switch (choice) {
|
||||||
|
case 1:
|
||||||
|
cout << "Enter symbol: ";
|
||||||
|
cin >> symbol;
|
||||||
|
tree.insert(symbol);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
cout << "Tree structure:\n";
|
||||||
|
tree.displayTree();
|
||||||
|
break;
|
||||||
|
case 3: {
|
||||||
|
auto result = tree.findMostFrequent();
|
||||||
|
if (result.second == 0) {
|
||||||
|
cout << "Tree is empty.\n";
|
||||||
|
} else {
|
||||||
|
cout << "Most frequent symbol: " << result.first
|
||||||
|
<< " (count: " << result.second << ")\n";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 4:
|
||||||
|
cout << "Number of digits: " << tree.countDigits() << endl;
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
cout << "Invalid choice.\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. Скриншоты тестирования
|
||||||
|
#### Тест 1: Вставка `a`, `a`, `b`, `3`, `3`, `3`
|
||||||
|

|
||||||
|
- **Вывод дерева:** Корректно отображена структура с `3(3)` как самым частым символом.
|
||||||
|
- **Количество цифр:** 3 (символ `3` вставлен 3 раза).
|
||||||
|
|
||||||
|
#### Тест 2: Вставка `5`, `5`, `a`, `5`
|
||||||
|

|
||||||
|
- **Самый частый символ:** `5` (count=3).
|
||||||
|
- **Количество цифр:** 3.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 7. Ответы на контрольные вопросы
|
||||||
|
1. **Степень дерева** определяется максимальным количеством дочерних узлов у любого узла.
|
||||||
|
2. **Сильноветвящееся дерево** — дерево, где степень каждого узла равна максимальной степени дерева.
|
||||||
|
3. **Путь в дереве** — последовательность узлов от корня к целевому узлу.
|
||||||
|
4. **Длина пути** — количество ребер в пути (на 1 меньше количества узлов).
|
||||||
|
5. **Степень бинарного дерева** равна 2 (максимум 2 дочерних узла на узел).
|
||||||
|
6. **Пустое дерево** допустимо (корень = `nullptr`).
|
||||||
|
7. **Бинарное дерево** — дерево, где каждый узел имеет не более 2 дочерних узлов.
|
||||||
|
8. **Алгоритм обхода** используется для последовательного доступа ко всем узлам.
|
||||||
|
9. **Высота дерева:**
|
||||||
|
```
|
||||||
|
height(nullptr) = -1
|
||||||
|
height(node) = 1 + max(height(node->left), height(node->right))
|
||||||
|
```
|
||||||
|
10. **Дерево по таблице:**
|
||||||
|

|
||||||
|
11. **Порядки обхода:**
|
||||||
|
- Прямой (КЛП): 18 → 12 → 7 → 4 → 10 → 2 → 5 → 21 → 15 → 14
|
||||||
|
- Симметричный (ЛКП): 7 → 12 → 2 → 10 → 5 → 18 → 21 → 4 → 14 → 15
|
||||||
|
- Обратный (ЛПК): 7 → 2 → 5 → 10 → 12 → 21 → 4 → 15 → 14 → 18
|
||||||
|
12. **Обход в ширину** использует **очередь**.
|
||||||
|
13. **Пример обхода в ширину:** 18 → 12 → 14 → 7 → 10 → 15 → 4 → 2 → 5 → 21.
|
||||||
|
14. **Не рекурсивный обход в глубину** использует **стек**.
|
||||||
|
15. **Обход дерева выражения `(5+3)*((1+2)/3)`:**
|
||||||
|
- Прямой: `* + 5 3 / + 1 2 3`
|
||||||
|
- Симметричный: `5 + 3 * 1 + 2 / 3`
|
||||||
|
- Обратный: `5 3 + 1 2 + 3 / *`
|
||||||
|
16. **Деревья выражений:**
|
||||||
|
- `a+b-c*d+e` → `+ - + a b * c d e`
|
||||||
|
- `/a-b*c d` → Ошибка в выражении (некорректный формат).
|
||||||
|
- `a b c d / - *` → `* a - b / c d`
|
||||||
|
- `* - / + a b c d e` → `* - / + a b c d e`
|
||||||
|
17. **Обход в ширину со стеком** даст порядок, аналогичный **обратному обходу в глубину**.
|
||||||
|
18. **БДП для симметричного обхода `40 45 46 50 65 70 75`:**
|
||||||
|

|
||||||
|
19. **Исходное дерево для прямого обхода `50 45 35 15 40 46 65 75 70`:**
|
||||||
|

|
||||||
|
20. **БДП после вставки `1, 48, 75, 100`:**
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Вывод:** Реализованы все операции для бинарного дерева поиска с модифицированной структурой узла. Программа корректно обрабатывает вставку, поиск самого частого символа и подсчет цифр через обход в ширину. Тесты подтверждают работоспособность алгоритмов.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def mdToraw():
|
||||||
|
markdown = mistune.create_markdown(renderer='ast')
|
||||||
|
#markdown = mistune.create_markdown()
|
||||||
|
return markdown(text)
|
||||||
|
|
||||||
|
print(mdToraw())
|
||||||
Reference in New Issue
Block a user