Создание и редактирование 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/.
Маршрутизация
| Задача | Скрипт |
|---|---|
| Правка фразы/реквизита в .docx | edit_document.csx + edits.json |
| Объединить акт+заключение, склеить несколько .docx | merge_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.docx → document_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→#load→using→ код. 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, Heading2Before=120/After=60 - Переносы:
EnableAutoHyphenation(mainPart)приJustificationValues.Both - Нумерация:
AddGostPageNumberHeader+GostPageSetupWithNumbers; убрать →GostPageSetup()
Документ чёрно-белый. Русский текст кириллицей.
Пары шрифтов:
| Назначение | Вариант 1 | Вариант 2 |
|---|---|---|
| Основной текст | Times New Roman | PT Serif |
| Заголовки | Arial | PT Sans |
| Моноширинный | Courier New | JetBrains 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.0SpacingBetweenLines.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
| Родитель | Дети |
|---|---|
Paragraph | ParagraphProperties → Runs |
Run | RunProperties → Text/Break/TabChar |
Table | TableProperties → TableGrid → Rows |
TableRow | TableRowProperties → Cells |
TableCell | TableCellProperties → ≥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). Тогда:
TableProperties → TableGrid (число 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
Открой превью: поля на месте, без «рек» из пробелов, межстрочный соответствует, нумерация на месте.
Чек-лист
- Output ≠ input по имени файла.
- Правка существующего →
edit_document.csx. - Объединение →
merge_document.csx. - Сломанный input →
repair_document.csxпервым. EnsureWordReadyParts(doc)— последняя строка в using-блоке;StripPackageBom(outputPath)— сразу после закрытия блока.EnableAutoHyphenation(mainPart)при justify.Line="240"если не просили 1.5.After="120"или меньше.- Нумерация:
AddGostPageNumberHeader+GostPageSetupWithNumbers; без неё →GostPageSetup(). - Нумерация разделов —
SectionCounter(объявиvar sec = new SectionCounter();); стилевой заголовок для оглавления —Heading1/Heading2. - Чекбоксы —
CheckBox(bool). - Таблицы —
CreateTableили ручнойTableсtblGrid(колонок = max ширине строки); полноширинная строка —SpanCell(span = число колонок). - PDF-превью открыто и проверено.
Сохранение
Файл в /home/user/output/. После сохранения → present_files.
Попробуйте этот навык
Зарегистрируйтесь и используйте навык «Создание и редактирование Word-документов (OpenXML)» бесплатно.