📁

Создание и редактирование Word-документов (OpenXML)

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

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

word-document (OpenXML SDK)

DOCX через C# OpenXML SDK (.NET, DocumentFormat.OpenXml 3.2.0). Готовые скрипты в /mnt/skills/word_document_ru/scripts/.

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

ЗадачаСкрипт
Правка фразы/реквизита в .docxedit_document.csx + edits.json
Объединить акт+заключение, склеить несколько .docxmerge_document.csx
XMLSyntaxError: w:t/w:r, python-docx падает на Document(path)repair_document.csx → работать с *_fixed.docx
Создать .docx с нулякопия create_document.csx
Применить шаблон форматированияКонвейер C
Структурная проверкаvalidate_document.csx

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

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

mkdir -p /tmp/work
cat > /tmp/work/edits.json <<'JSON_EOF'
[
  { "find": "точная фраза из документа", "replace": "новый текст" }
]
JSON_EOF

dotnet-script /mnt/skills/word_document_ru/scripts/edit_document.csx \
  /tmp/input/document.docx /home/user/output/document_v2.docx /tmp/work/edits.json

find — длинный уникальный фрагмент дословно из документа. 0 совпадений → скрипт падает с ненулевым exit, формулировку править и повторять.

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

mkdir -p /tmp/work/unpacked && unzip -o /tmp/input/document.docx -d /tmp/work/unpacked
python3 -c "import re; x=open('/tmp/work/unpacked/word/document.xml',encoding='utf-8').read(); print(re.sub(r'<[^>]+>','',x))"

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

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

Конвейер D: объединение

dotnet-script /mnt/skills/word_document_ru/scripts/merge_document.csx \
  /tmp/input/base.docx /home/user/output/merged.docx \
  /tmp/input/extra1.docx /tmp/input/extra2.docx

Первый аргумент — база (её колонтитулы и нумерация). Колонтитулы приложенных игнорируются. Картинки и гиперссылки приложений не переносятся — скрипт печатает .

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

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

dotnet-script /mnt/skills/word_document_ru/scripts/repair_document.csx /tmp/input/document.docx

Ремонт:

dotnet-script /mnt/skills/word_document_ru/scripts/repair_document.csx \
  /tmp/input/document.docx /home/user/output/document_fixed.docx

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

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

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

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

Старт-шаблон:

mkdir -p /tmp/work
cp /mnt/skills/word_document_ru/scripts/create_document.csx /tmp/work/task.csx

Правка скрипта — ТОЛЬКО полной перезаписью ВСЕГО файла через quoted-heredoc (cat > task.csx <<'CSX_EOF' … CSX_EOF). НИКОГДА не правь .csx через edit_file или точечный search/replace: твой search-блок почти наверняка разойдётся с файлом по байтам (отступы, переносы) → No match for search block, similarity 0.3–0.8 и 2–3 сожжённые итерации. Файл маленький — перезапиши его целиком:

cat > /tmp/work/task.csx <<'CSX_EOF'
#r "nuget: DocumentFormat.OpenXml, 3.2.0"
#load "/mnt/skills/word_document_ru/scripts/table_styles.csx"
#load "/mnt/skills/word_document_ru/scripts/russian_typography.csx"

using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;

string outputPath = "/home/user/output/document.docx";

{
    System.IO.Directory.CreateDirectory(
        System.IO.Path.GetDirectoryName(outputPath)!);

    using var doc = WordprocessingDocument.Create(
        outputPath, WordprocessingDocumentType.Document);
    var mainPart = doc.AddMainDocumentPart();
    mainPart.Document = new Document(new Body());
    var body = mainPart.Document.Body!;

    EnableAutoHyphenation(mainPart);

    var stylesPart = mainPart.AddNewPart<StyleDefinitionsPart>();
    stylesPart.Styles = new Styles(
        GostDocDefaults(), GostNormalStyle(),
        GostHeading1Style(), GostHeading2Style());

    // --- логика документа ---

    EnsureWordReadyParts(doc);
}

StripPackageBom(outputPath);
Console.WriteLine($"Документ создан: {outputPath}");
CSX_EOF

dotnet-script /tmp/work/task.csx

Правила .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() на строку.

Хелперы (после #load)

// Параграфы — '\n' → Text+Break внутри одного Run
Paragraph MakePara(string text)
Paragraph MakePara(string text, JustificationValues align)
Paragraph MakePara(string text, JustificationValues align, bool bold)
Paragraph MakePara(string text, bool bold)
// Курсив и нестандартный кегль — ТОЛЬКО через эти перегрузки, не raw new Run.
// sizePt в пунктах (16, 13, 11…); italic для сносок-источников; bold+sizePt для
// заголовка раздела. rPr собирается в каноническом порядке автоматически.
Paragraph MakePara(string text, JustificationValues align, bool bold, bool italic)
Paragraph MakePara(string text, JustificationValues align, bool bold, bool italic, int sizePt)

// Ячейки — width в твипах, "0" = auto-fit; '\n' → Break
TableCell MakeCell(string text)
TableCell MakeCell(string text, string widthDxa)
TableCell MakeCell(string text, string widthDxa, bool bold)
TableCell MakeCell(string text, string widthDxa, bool bold, JustificationValues align)

// Строки
TableRow MakeRow(string label, string value)
TableRow MakeRow(string label, string value, bool boldLabel)
TableRow MakeRow(params string[] cells)

// Таблицы
TableProperties WithFullBorders(string widthPct = "5000")
TableProperties WithNoBorders(string widthPct = "5000")
Table CreateTable(string[][] data)
Table CreateTable(string[][] data, bool headerBold, int[] columnWidthsDxa)
TableRow CreateHeaderRow(string[] headers)

// Сложные ячейки
TableCell ShadedCell(string text, string fillHex)
TableCell ShadedCell(string text, string fillHex, string widthDxa)
TableCell ShadedCell(string text, string fillHex, string widthDxa, bool bold, JustificationValues align)
TableCell SpanCell(string text, int span)
TableCell SpanCell(string text, int span, bool bold)
TableCell SpanCell(string text, int span, string widthDxa, bool bold, JustificationValues align)
TableCell HMergeStart(TableCell cell)
TableCell HMergeContinue()
TableCell VMergeStart(TableCell cell)
TableCell VMergeContinue()

// Колонки
Paragraph BeginColumns(int columnCount)
Paragraph EndColumns()

// Плакатные/масштабные заголовки (мастхеды, обложки, 24–48pt, center+caps).
// Заголовок раздела делового документа (13–16pt жирный) — это НЕ LargeHeading:
// бери MakePara(text, align, true, false, sizePt) или Heading1/Heading2.
Paragraph LargeHeading(string text, int sizePt)
Paragraph LargeHeading(string text, int sizePt, JustificationValues align, bool caps, int letterSpacingTwips)

// Pullquote
Paragraph Pullquote(string text)
Paragraph Pullquote(string text, JustificationValues align)

// Заголовки со стилями Heading1/Heading2 (outlineLvl → оглавление/навигация Word)
// Свободные функции, без new. Нужны GostHeading1Style()/GostHeading2Style() в Styles.
Paragraph Heading1(string text)
Paragraph Heading2(string text)

// Нумерация разделов (договор, акт, протокол) — КЛАСС С СОСТОЯНИЕМ
class SectionCounter
//   var sec = new SectionCounter();          // ОБЯЗАТЕЛЬНО объявить до первого Next()
//   body.Append(sec.Next("ПРЕДМЕТ"));        // "1. ПРЕДМЕТ" — жирный абзац стиля Normal

// Чекбокс
string CheckBox(bool isChecked)   // ☒ или ☐
string CheckBox()                 // ☐

// ГОСТ-стили
DocDefaults GostDocDefaults()      // Line=240, After=120
Style GostNormalStyle()            // justify + ContextualSpacing
Style GostHeading1Style()          // Before=180, After=60
Style GostHeading2Style()          // Before=120, After=60

// Межстрочный
SpacingBetweenLines SingleSpacing()       // 1.0
SpacingBetweenLines OneAndHalfSpacing()   // 1.5

// Переносы (при justify русский)
DocumentSettingsPart EnableAutoHyphenation(MainDocumentPart mainPart)

// Word-обязательные части — последней строкой в using-блоке
void EnsureWordReadyParts(WordprocessingDocument doc)

// Страница
SectionProperties GostPageSetup()  // без номеров

// ГОСТ-нумерация (верх, центр, без титула)
(HeaderPart main, HeaderPart title) AddGostPageNumberHeader(MainDocumentPart mainPart)
SectionProperties GostPageSetupWithNumbers(
    MainDocumentPart mainPart, HeaderPart mainHeader, HeaderPart titleHeader)

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

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

string Money(decimal v) =>
    v.ToString("F2", System.Globalization.CultureInfo.InvariantCulture).Replace(".", ",");

string[] headers = { "Наименование", "Цена, руб.", "Цена +30%, руб." };
int[] widths = { 6000, 1800, 1800 };

var table = new Table();
table.Append(WithFullBorders());
var grid = new TableGrid();
foreach (var w in widths) grid.Append(new GridColumn { Width = w.ToString() });
table.Append(grid);

var head = new TableRow(new TableRowProperties(new TableHeader()));
for (int i = 0; i < headers.Length; i++)
    head.Append(MakeCell(headers[i], widths[i].ToString(), true, JustificationValues.Center));
table.Append(head);

string[][] items = {
    new[]{ "Материал V-Loc 180", "2487,00" },
    new[]{ "Материал V-Loc 90",  "2673,00" },
};

decimal total = 0m;
foreach (var it in items)
{
    decimal price = decimal.Parse(it[1].Replace(" ", "").Replace(",", "."));
    decimal priceUp = Math.Round(price * 1.30m, 2);
    total += priceUp;

    var row = new TableRow();
    row.Append(MakeCell(it[0], widths[0].ToString(), false, JustificationValues.Left));
    row.Append(MakeCell(it[1], widths[1].ToString(), false, JustificationValues.Right));
    row.Append(MakeCell(Money(priceUp), widths[2].ToString(), false, JustificationValues.Right));
    table.Append(row);
}

var totalRow = new TableRow();
totalRow.Append(SpanCell("ИТОГО:", 2, "0", true, JustificationValues.Right));
totalRow.Append(MakeCell(Money(total), widths[2].ToString(), true, JustificationValues.Right));
table.Append(totalRow);

body.Append(table);
body.Append(MakePara($"Итого с наценкой 30%: {Money(total)} руб.", JustificationValues.Left, true));

Строка ИТОГО: 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 — главная причина «файл повреждён»; авто-лечение на выходе есть, но не плоди):

body.Append(MakePara("Источник: …", JustificationValues.Left, false, true, 11));
// raw — только если хелпер не покрывает кейс; тогда run ОБЯЗАТЕЛЬНО в Paragraph:
body.Append(new Paragraph(new Run(new RunProperties(new Bold()), new Text("текст"))));

Заголовок со своим стилем

var headingStyle = new Style(
    new StyleName { Val = "heading 1" },
    new StyleParagraphProperties(
        new OutlineLevel { Val = 0 },
        new SpacingBetweenLines { Before = "180", After = "60", Line = "240" }
    ),
    new StyleRunProperties(new Bold(), new FontSize { Val = "28" }, new Color { Val = "auto" })
) { Type = StyleValues.Paragraph, StyleId = "Heading1" };

Merge ячеек

// Горизонтальный
row.Append(HMergeStart(MakeCell("Заголовок", "0", true, JustificationValues.Center)));
row.Append(HMergeContinue());
row.Append(HMergeContinue());

// Вертикальный
row1.Append(VMergeStart(MakeCell("По данным ООО А", "4000", true, JustificationValues.Center)));
row2.Append(VMergeContinue());

// GridSpan
row.Append(SpanCell("ПЛАН ПРОДАЖ — 2026 год", span: 7, bold: true));

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

Колонки

body.Append(LargeHeading("THE NEW YORK TIMES", 48,
    JustificationValues.Center, caps: true, letterSpacingTwips: 20));
body.Append(MakePara("ВТОРНИК, 24 МАЯ 2026", JustificationValues.Center));

body.Append(BeginColumns(3));
body.Append(MakePara("LEAD STORY", JustificationValues.Left, bold: true));
body.Append(MakePara("Первая колонка..."));
body.Append(MakePara("Вторая колонка..."));
body.Append(EndColumns());

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

Зебра

var table = new Table();
table.Append(WithFullBorders());
table.Append(new TableGrid(
    new GridColumn { Width = "3000" },
    new GridColumn { Width = "3000" },
    new GridColumn { Width = "3000" }));

var header = new TableRow(new TableRowProperties(new TableHeader()));
header.Append(ShadedCell("Регион", "E8E8E8", "3000", true, JustificationValues.Center));
header.Append(ShadedCell("План", "E8E8E8", "3000", true, JustificationValues.Center));
header.Append(ShadedCell("Факт", "E8E8E8", "3000", true, JustificationValues.Center));
table.Append(header);

string[][] rows = { new[]{"Москва","100","112"}, new[]{"СПб","80","75"} };
for (int i = 0; i < rows.Length; i++)
{
    var r = new TableRow();
    var fill = i % 2 == 0 ? "F8F8F8" : "FFFFFF";
    foreach (var v in rows[i]) r.Append(ShadedCell(v, fill, "3000"));
    table.Append(r);
}
body.Append(table);

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

Многострочная ячейка

sigRow.Append(MakeCell("ООО «Альфа»\nИНН 7707123456\nКПП 770701001\nг. Москва, ул. Примерная, д. 1",
    "4500", false, JustificationValues.Left));

Страницы

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

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

new Paragraph(
    new ParagraphProperties(
        new SectionProperties(
            new SectionType { Val = SectionMarkValues.NextPage },
            new PageSize { Width = 11906U, Height = 16838U },
            new PageMargin { Top = 1134, Bottom = 1134, Left = 1701U, Right = 567U }
        )
    )
)

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

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

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

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

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

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

dotnet-script /mnt/skills/word_document_ru/scripts/validate_document.csx /home/user/output/document.docx

Визуальная:

soffice --headless --convert-to pdf /home/user/output/document.docx --outdir /tmp/work/
pdftoppm -jpeg -r 150 /tmp/work/document.pdf /tmp/work/page

Открой превью: поля на месте, без «рек» из пробелов, межстрочный соответствует, нумерация на месте.

Чек-лист

  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-превью открыто и проверено.

Сохранение

Файл в /home/user/output/. После сохранения → present_files.

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

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

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