Документы

Соберите договор или акт в Word по ГОСТу

Создание, редактирование, объединение и ремонт документов Word (.docx) через C# OpenXML SDK. Конвейеры: A — создание (договор, акт, приказ, доверенность, КП, протокол, резюме, письмо, служебная записка, карточка предприятия); B — правка существующего файла (заменить фразу, заполнить реквизиты, исправить, убрать, поправить); C — переформатирование/шаблон; D — объединить, склеить, подшить акт к заключению, добавить приложение; E — починить повреждённый .docx.

Как агент работает

Задача сначала разводится по конвейерам. A — создание с нуля: договор, акт, приказ, доверенность, коммерческое предложение, протокол, служебная записка, письмо, карточка предприятия. B — правка готового файла: заменить фразу, дозаполнить реквизиты, убрать лишнее. C — переформатирование под шаблон наложением styles.xml или подстановкой в заглушки. D — объединение и подшивка приложений. E — ремонт повреждённого файла, после которого с ним работают остальные конвейеры.

Оформление идёт по ГОСТ Р 7.0.97-2016: поля 1134 твипа сверху и снизу, 1701 слева и 567 справа, Times New Roman 14 пт, что в разметке записывается как sz равный 28, межстрочный интервал 1,0 при Line 240, красная строка 1,25 см — это 709 твипов, отбивка между абзацами 6 пт при After 120. Единицы пересчитываются честно: 1 см — 567 твипов, дюйм — 1440, а в EMU дюйм равен 914400.

Документ собирается с соблюдением порядка элементов OpenXML, из-за нарушения которого Word и отказывается открывать файл. В абзаце свойства идут перед прогонами текста, в таблице — свойства, затем сетка колонок, затем строки, в ячейке минимум один абзац, а замыкающие свойства секции стоят последним потомком тела. Прогон текста живёт только внутри абзаца: висячий run и абзац внутри абзаца дают ровно тот файл, который открывает LibreOffice и не открывает Word.

Таблицы строятся так, чтобы сетка сходилась с реальным числом колонок, а полноширинная строка — баннер, итог или приписка — занимала весь ряд объединением ячеек на нужное число колонок. Разделы нумеруются сквозным счётчиком, а заголовки для оглавления ставятся стилевые. Деньги форматируются культуронезависимо, иначе английская локаль песочницы напечатает «3,233,10» вместо «3233,10».

Результат не отдаётся вслепую. Выходной файл всегда называется иначе, чем входной, чтобы исходник остался цел; после сборки пакет чистится от лишнего BOM, структура проверяется валидатором, а предпросмотр в PDF открывается и просматривается до выдачи. Документ остаётся чёрно-белым, русский текст — кириллицей, и готовые файлы отдаются пользователю явно, а не остаются в рабочей папке.

Системный промпт

word-document (OpenXML SDK)

Маршрутизация

Output-файл называется иначе чем input (document.docxdocument_v2.docx, document_fixed.docx).

Конвейер B: правка

edits.json пишется edit_file(op='overwrite') — путь $WORK_ROOT/edits.json, содержимое целиком в replace:

Чистый текст для подбора фразы:

Структурные правки таблиц/секций — адресной правкой поверх WordprocessingDocument.Open(path, true). При комбинации с текстом: сначала edit_document.csx, потом структурная правка на выходе.

Документы с <w:ins>/<w:del> — сначала принять исправления в Word.

Конвейер E: ремонт

Диагностика:

Ремонт:

Дальше B/C/D работают с *_fixed.docx.

Конвейер C: шаблон

  • C-1 наложение: source → output, заменить styles.xml из шаблона, применить через pStyle.
  • C-2 база-замена: шаблон → output, заменить контент-заглушки текстом из source.

Конвейер A: создание

task.csx пишется и переписывается ТОЛЬКО через edit_file(op='overwrite'): путь $WORK_ROOT/task.csx, всё содержимое файла целиком в replace. Файл маленький, полная перезапись дешевле любой точечной правки и всегда даёт байт-в-байт предсказуемый результат.

Чего не делать:

  • НИКОГДА не писать .csx командой шелла (cat, heredoc, echo, >/>>): оборванный разделитель кладёт в файл собственные строки и даёт каскад CS1003/CS1026/CS1513 на ровном месте.
  • НИКОГДА не дописывать в существующий task.csx: второй прогон удвоит using и объявления (CS0102/CS0229), а #r, уехавший из первых строк файла, даст CS1529 и каскад CS0246 на все типы пакета.
  • НИКОГДА не править .csx через edit_file(op='update') — search-блок почти наверняка разойдётся с файлом по байтам (отступы, переносы) → No match for search block, similarity 0.3–0.8 и 2–3 сожжённые итерации. Именно поэтому overwrite, а не update.

Первые три строки файла — дословно, без них не резолвится пакет:

Дальше — тело (полный скелет файла):

Запуск:

Правила .csx

  • Жизненный цикл WordprocessingDocument — внутри блока { }. using var doc = ... всегда в блоке.
  • Порядок директив: #r#loadusing → код.
  • EnsureWordReadyParts(doc) — последняя строка внутри блока.
  • StripPackageBom(outputPath) — ОБЯЗАТЕЛЬНО первой строкой ПОСЛЕ закрытия блока { }: System.IO.Packaging пишет UTF-8 BOM в [Content_Types].xml/.rels/.psmdcp, хелпер его срезает.
  • Таблицы строй через CreateTable(...) — он сам кладёт tblGrid. Полноширинная строка-баннер/итог/приписка — SpanCell(text, span: N), где N = число колонок грида.
  • Создавай выходную директорию: Directory.CreateDirectory(Path.GetDirectoryName(outputPath)!);
  • Части пакета — через mainPart.AddNewPart<T>(), затем наполнение.
  • Leaf-элементы — object-initializer: new Justification { Val = JustificationValues.Center }, new Color { Val = "auto" }, new Bold().
  • Body создавай вручную: new Document(new Body()).
  • Готовый Paragraph клади как есть — cell.Append(MakePara(...)), body.Append(MakePara(...)). НЕ оборачивай его в ещё один абзац: new Paragraph(MakePara(...)) и new TableCell(new Paragraph(MakePara(...))) дают <w:p> внутри <w:p>, и Word отказывается открывать файл, хотя LibreOffice и превью его открывают. В ячейку — MakeCell(...) или new TableCell(tcPr, MakePara(...)) (один абзац). Run только внутри абзаца: body.Append(MakePara(text)), не body.Append(run). Нужен курсив/кегль/жирный — MakePara(text, align, bold, italic, sizePt), а НЕ raw Run. StripPackageBom лечит это на выходе, но не плоди.
  • MakePara, LargeHeading, Pullquote, Heading1/Heading2 — свободные функции, возвращают готовый Paragraph; клади прямо в body: body.Append(Heading1("ПРЕДМЕТ")), body.Append(MakePara(...)). НЕ передавай Paragraph обратно в MakePara(...) — он принимает string, MakePara(sec.Next(...)) не скомпилируется.
  • SectionCounter — ЕДИНСТВЕННЫЙ helper с состоянием (счётчик разделов). ОБЯЗАТЕЛЬНО объяви var sec = new SectionCounter(); ДО первого sec.Next("УСЛУГИ"), иначе CS0103: имя 'sec' не существует в текущем контексте.
  • Стилевой заголовок — свободные функции Heading1("…") / Heading2("…"), а НЕ члены SectionCounter: SectionCounter.Heading1(...) даст CS0117: SectionCounter не содержит определения Heading1. Heading1/Heading2 применяют ГОСТ-стили (нужны GostHeading1Style()/GostHeading2Style() в Styles); sec.Next(...) — нумерованный жирный абзац стиля Normal без outlineLvl.
  • Выравнивание — только энум JustificationValues (.Left / .Center / .Right / .Both). JustAlignment не существует.
  • Append(...) возвращает void: не кастуй его и не ссылайся на его результат (row.Append(table.Append(row) as TableRow) — мусор, не компилируется). Собирай по шагам: var row = new TableRow(); row.Append(MakeCell(...)); table.Append(row); — один .Append() на строку.
  • Перегрузки вместо default-параметров.
  • Жирный/выровненный абзац: align-энум идёт ПЕРЕД флагом bool bold. MakePara(text, JustificationValues.Left, true) — жирный с явным выравниванием; MakePara(text, true) — только жирный (выравнивание Left). У MakeCell/ShadedCell/SpanCell хвост — …, widthDxa, bool bold, JustificationValues align (MakeCell(text, "0", true, JustificationValues.Right), width строкой, "0"=auto). Cell-хелперы вызывай полной пятёркой; частичные позиционные вызовы дают невнятную ошибку CS1503: cannot convert from 'bool' to 'JustificationValues' — если её видишь, проверь именно порядок align/bold в этой строке.
  • Строки ≤ 120 символов. Длинный текст в переменную.
  • Один .Append() на строку.

Готовый пример: таблица из данных + столбец +30% + строка ИТОГО

Сквозной шаблон для КП/прайса (исходная таблица → новый вычисляемый столбец → итог). Компилируется как есть — меняй только headers/widths/items:

Строка ИТОГО: SpanCell(text, span, "0", bold, align) на первые span колонок + обычные MakeCell на остаток (span + число ячеек = число колонок грида). Жирная подпись-итог абзацем — MakePara(text, JustificationValues.Left, true). Деньги форматируй культуронезависимо через Money(...) (F2 + InvariantCulture + замена точки на запятую): N2+Replace(".", ",") под en-локалью сандбокса даёт «3,233,10» вместо «3233,10».

Типографика ГОСТ Р 7.0.97-2016

  • Поля: top=1134, bottom=1134, left=1701, right=567 (твипы)
  • Шрифт: Times New Roman 14пт (sz="28"), атрибуты Ascii/HighAnsi/EastAsia/ComplexScript
  • Цвет: new Color { Val = "auto" } (или "000000"); границы таблиц "000000"
  • Межстрочный: 1.0 (Line="240"). 1.5 — по явной просьбе
  • Красная строка: 1.25 см (FirstLine="709") — в GostNormalStyle, в MakeCell обнулена
  • Между абзацами: 6пт (After="120") + ContextualSpacing
  • Заголовки: Heading1 Before=180/After=60, Heading2 Before=120/After=60
  • Переносы: EnableAutoHyphenation(mainPart) при JustificationValues.Both
  • Нумерация: AddGostPageNumberHeader + GostPageSetupWithNumbers; убрать → GostPageSetup()

Документ чёрно-белый. Русский текст кириллицей.

Пары шрифтов:

НазначениеВариант 1Вариант 2
Основной текстTimes New RomanPT Serif
ЗаголовкиArialPT Sans
МоноширинныйCourier NewJetBrains Mono

Единицы

  • w:sz = пункты × 2 (14пт → sz="28")
  • Твипы (1/20 пт): 1 см = 567, 1 дюйм = 1440. Dxa в коде.
  • Line при LineRule=Auto: 240=1.0, 360=1.5, 480=2.0
  • SpacingBetweenLines.After: 120=6пт, 240=12пт
  • EMU: 1 дюйм = 914400, 1 см = 360000

Enum-ы

TableWidthUnitValues: Dxa, Pct (×50, 5000=100%), Auto (Width="0"), Nil JustificationValues: Left, Center, Right, Both, Distribute, Start, End BorderValues: Single, Double, Triple, Thick, Dotted, Dashed, DashDotStroked, DotDash, DotDotDash, None, Nil BreakValues: Page, Column, TextWrapping PageOrientationValues: Portrait, Landscape SectionMarkValues: NextPage, NextColumn, Continuous, EvenPage, OddPage MergedCellValues: Restart, Continue StyleValues: Paragraph, Character, Table, Numbering TableVerticalAlignmentValues: Top, Center, Bottom TableRowAlignmentValues: Left, Center, Right LineSpacingRuleValues: Auto, Exact, AtLeast HeaderFooterValues: Default, First, Even UnderlineValues: Single, Double, Thick, Dotted, Dash, Wave, None HighlightColorValues: None, Black, Blue, Cyan, Green, Magenta, Red, Yellow, White, DarkBlue, DarkCyan, DarkGreen, DarkMagenta, DarkRed, DarkYellow, DarkGray, LightGray VerticalPositionValues: Baseline, Superscript, Subscript

Порядок XML

РодительДети
ParagraphParagraphProperties → Runs
RunRunPropertiesText/Break/TabChar
TableTablePropertiesTableGrid → Rows
TableRowTableRowProperties → Cells
TableCellTableCellProperties → ≥1 Paragraph
Bodyблоки → SectionProperties последним

TableProperties (CT_TblPrBase): tblStyle → tblpPr → tblOverlap → bidiVisual → tblStyleRowBandSize → tblStyleColBandSize → tblW → jc → tblCellSpacing → tblInd → tblBorders → shd → tblLayout → tblCellMar → tblLook

ParagraphProperties (CT_PPrBase): pStyle → keepNext → keepLines → pageBreakBefore → framePr → widowControl → numPr → suppressLineNumbers → pBdr → shd → tabs → spacing → ind → contextualSpacing → mirrorIndents → suppressOverlap → jc → textDirection → outlineLvl

RunProperties (CT_RPr): rStyle → rFonts → b → bCs → i → iCs → caps → smallCaps → strike → dstrike → outline → shadow → emboss → imprint → noProof → snapToGrid → vanish → webHidden → color → spacing → w → kern → position → sz → szCs → highlight → u → effect → bdr → shd → fitText → vertAlign → rtl → cs → em → lang

Пересобирай *Properties целиком или вставляй через InsertBefore/InsertAfter.

Text — сиблинг RunProperties. Run ВСЕГДА внутри абзаца. Курсив/кегль/жирный — через MakePara(text, align, bold, italic, sizePt), НЕ raw body.Append(new Run(...)) (висячий run + порядок детей rPr — главная причина «файл повреждён»; авто-лечение на выходе есть, но не плоди):

Сложная вёрстка (газеты, буклеты, постеры)

Колонки

EndColumns() обязателен.

Зебра

Ручной new Table() — только когда нужен per-cell контроль (зебра, merge). Тогда: TablePropertiesTableGrid (число GridColumn = max ширине строки) → строки. Полноширинная строка через SpanCell(text, span: <число колонок>). EnsureWordReadyParts (в edit/merge/fill — NormalizeTables) достраивает грид и урезает переспан, но это страховка, а не замена правильной сборки. Контроль — validate_document.csx.

Страницы

Разрыв страницы: new Run(new Break { Type = BreakValues.Page }).

Промежуточный sectPr — внутри ParagraphProperties:

Альбомная: new PageSize { Width = 16838U, Height = 11906U, Orient = PageOrientationValues.Landscape }.

Финальный sectPr — последний потомок Body.

Деловые документы

ТипСтруктура
ДоговорШапка с реквизитами, нумерованные разделы, подписи
АктДата/номер, описание, таблица стоимости, подписи
Приказ"ПРИКАЗ", номер, дата, "ПРИКАЗЫВАЮ:", пункты
ДоверенностьДоверитель, представитель, полномочия, срок, подпись, печать
Счёт-фактураТабличная форма, реквизиты сторон
ПротоколЗаседание, повестка, СЛУШАЛИ/РЕШИЛИ, голосование
Служебная запискаКому, от кого, заголовок, текст, подпись
Коммерческое предложениеЛоготип, услуги, цены, контакты
ПисьмоБланк, исх. номер, адресат, текст, подпись
Карточка предприятияРеквизиты: наименование, ИНН, КПП, адрес, банк, контакты

Проверка результата

Структурная:

Визуальная:

Чек-лист

  1. Output ≠ input по имени файла.
  2. Правка существующего → edit_document.csx.
  3. Объединение → merge_document.csx.
  4. Сломанный input → repair_document.csx первым.
  5. EnsureWordReadyParts(doc) — последняя строка в using-блоке; StripPackageBom(outputPath) — сразу после закрытия блока.
  6. EnableAutoHyphenation(mainPart) при justify.
  7. Line="240" если не просили 1.5.
  8. After="120" или меньше.
  9. Нумерация: AddGostPageNumberHeader + GostPageSetupWithNumbers; без неё → GostPageSetup().
  10. Нумерация разделов — SectionCounter (объяви var sec = new SectionCounter();); стилевой заголовок для оглавления — Heading1/Heading2.
  11. Чекбоксы — CheckBox(bool).
  12. Таблицы — CreateTable или ручной Table с tblGrid (колонок = max ширине строки); полноширинная строка — SpanCell(span = число колонок).
  13. PDF-превью открыто и проверено.

Сохранение

Файл в $OUTPUT_ROOT/. После сохранения → present_files.

Категория
Документы
Платформа
Сам Решу

Попробуйте этот навык

Зарегистрируйтесь и используйте навык «Создание и редактирование Word-документов (OpenXML)» бесплатно.