Разработка
NTA

Рецензирование DOCX на C#

В этом году я учувствовал в конкурсе по реализации сервиса, который должен проверять формат оформления документов и вносить изменения в режиме правки. Существующие библиотеки либо не решали эту задачу вовсе, либо оказались платными. Было принято решение погрузиться в формат документа MS Word (Office Open XML) и написать свою библиотеку на .net Framework.

Как устроен DOCX

.docx файл – это zip архив. Он содержит в себе разметку и содержание документа в виде xml и других файлов. Его можно распаковать с помощью архиватора:

Document.xml – файл, который содержит в себе разметку параграфов и таблиц документ, за исключением колонтитулов и сносок. Эти блоки вынесены в отдельные файлы.

Содержимое файла:

<w:document ... > <w:body><!-- в body последовательно, будут перечислены абзацы и таблицы --> <!-- абзац. может содержать несколько Run'ов. У каждого Run может быть свой стиль оформления --> <w:p w:rsidR="00B93B17" w:rsidRPr="009D49F6" w:rsidRDefault="00162675" w:rsidP="00C83C69"> <w:pPr> <!-- свойства абзаца --> <w:rPr> <!-- свойства Run (w:r) --> <w:lang w:val="en-US"/> </w:rPr> </w:pPr> <w:r w:rsidRPr="00C83C69"> <!--Run. может содержать текст, картинки и тп --> <w:rPr><!-- свойства Run. Здесь хранится инфо о формате текста. Шрифт, размре, цвет, ссылка на стиль и тп --> <w:rStyle w:val="ad"/> <w:rFonts w:eastAsiaTheme="majorEastAsia"/> <w:i w:val="0"/> </w:rPr> <w:t>1</w:t> <!-- текст Run'а--> </w:r> <w:r> ... </w:r> </w:p> <w:sectPr w:rsidR="00B93B17" w:rsidRPr="009D49F6" w:rsidSect="00E142D1"> <w:headerReference w:type="even" r:id="rId8"/> <!--ссылка на файл заголовка по ID можно вычислить путь к файлу в _rels\document.xml.rels --> <!--и другие ссылки --> <w:pgSz w:w="11906" w:h="16838"/> <w:pgMar w:top="1134" w:right="1134" w:bottom="1134" w:left="1134" w:header="709" w:footer="709" w:gutter="0"/> <w:cols w:space="708"/> <w:docGrid w:linePitch="360"/> </w:sectPr> </w:body> </w:document>

Мы можем изменить document.xml и\или другие файлы, запаковать все в zip архив, переименовать его в .docx и открыть с помощью MS Word. Если правила разметки не нарушены MS Word сможет его отобразить. На этом принципе основана работа библиотеки TDV.Docx.

Библиотека не охватывает все возможности изменения документа, а лишь основную часть. Добавление параграфов, таблиц, изображений, колонтитулы и рецензирование.

Правки документа

Первым делом нужно подключить библиотеку:

using TDV.Docx;

Открытие и сохранение документа:

using (FileStream fs = new FileStream("1.docx", FileMode.Open)) { DocxDocument doc = new DocxDocument(fs); /* change code */ doc.document.Apply(); // Метод Apply() применяет изменения к файлу (в данном случае к document.xml) //Если вы изменяете другие файлы, например верхний колонтитул, для них так же нужно вызывать метод Apply() //cохранинение файла using (FileStream sw = new FileStream("1_fixed.docx", FileMode.OpenOrCreate)) { byte[] b = doc.ToBytes(); sw.Write(doc.ToBytes(), 0, b.Length); } }

Далее предполагается, что doc — экземпляр DocxDocument.

Тело документа содержит в себе последовательность параграфов и таблиц.Все эти классы унаследованы от базового Node. Перебирая ноды и ориентируясь на их содержимое можно эффективно осуществлять навигацию по документу.

foreach(Node node in doc.document.body.childNodes) { if (node is Table) { Table tbl = (Table)node; Tc cell = tbl.GetCell(0, 0); foreach (Paragraph p in cell.Paragraphs) { if (p.Text == "") p.CorrectDel("Дядя Вася"); //Удаление в режиме правки } } if (node is Paragraph) { Paragraph p = (Paragraph)node; p.Text = "это параграф"; } }

Каждый параграф содержит параметры стиля. Если параграф содержит ссылку на стиль, сначала применяются параметры стиля, затем параметры параграфа.Например, если в стиле указано выравнивание по центру, а в свойствах параграфа справа в итоге будет выравнивание по правому краю.Аналогично устроены все параметры.

Свойства параграфа содержат в себе свойства Run (w:rPr). Каждый Run так же содержит раздел свойств. Свойства Run более приоритетны чем свойства родительского параграфа.

Получить/установить параметры параграфа несложно:

Paragraph p = (Paragraph)node; p.pPr.HorizontalAlign = HORIZONTAL_ALIGN.BOTH; p.pPr.ind.firstLine = 1.25f; //отступ первой строки p.pPr.pBdr.Bottom = new Border(LINE_TYPE.SINGLE, 4);//Нижняя граница линия, толщина 4 p.pPr.pBdr.Between = new Border(); //Граница между параграфами - нет p.pPr.rPr.IsBold=false; // Обращение к дефолтным свойствам Run p.pPr.spacing.after = 0; // отсутп после абзаца p.pPr.spacing.before = 0; // отсутп перед абзацем p.pPr.spacing.line = 1; // Межстрочный интервал

Класс PStyle содержит в себе все свойства параграфа.

PStyle pStyle = new PStyle(HORIZONTAL_ALIGN.LEFT, new Border(), new Border(), new Border(), new Border(), new Border(), new Border(), 0, 0, 0, 0, 0, 0, 0); p.pPr.SetStyle(pStyle); //Применить стиль pStyle к параграфу p

Аналогичным образом устроены стили Run.

Paragraph p = (Paragraph)node; foreach (R r in p.rNodes) { RProp runProp = r.rPr; runProp.border.border = new Border(); //Нет границы Run runProp.Color = "#ffffff"; //Белый цвет runProp.Highlight = "#000000"; //черная заливка runProp.IsBold = false; //не жирный runProp.IsItalic = true; //курсив runProp.IsStrike = false; //не зачеркнутый runProp.Underline = LINE_TYPE.DOTTED; //подчеркнутый. линия из точек runProp.Font = "Times New Roman"; //шрифт runProp.FontSize = 10.5f; //размер шрифта } RStyle rStyle = new RStyle(true, "Times New Roman", 22, false, false, LINE_TYPE.NONE, "", "",new Border()); Paragraph p = (Paragraph)node; foreach (R r in p.rNodes) r.rPr.SetStyle(rStyle);

Сравнение в режиме правки (рецензирование)

Для редактирования документа в режиме правки придуманы отдельные методы. Их название начинается с «Correct..» или «Compare..».

Исходный документ выглядит так:

Изменение текста параграфа:

Paragraph p = (Paragraph)node; p.CorrectSetText("новый текст", rStyle, "Имя автора");

Результат:

Изменение стиля параграфа:

PStyle pStyle = new PStyle(HORIZONTAL_ALIGN.LEFT, new Border(), new Border(), new Border(), new Border(LINE_TYPE.SINGLE,4,0,"#f5f111"), new Border(), new Border(), 0, 0, 1, 2, 0, 0, 0); Paragraph p = (Paragraph)node; p.ComparePStyle(pStyle, "Имя автора");

Результат:

Описывать весь функционал здесь я не буду, в репозитории есть инструкция с примерами. Библиотека так же позволяет осуществлять рецензирование стилей Run, таблиц, колонтитулов, сносок, вставка и удаление параграфов, работу с изображениями. Подробную информацию можно найти в репозитории на GitHub.

0
2 комментария
ИгорьOK

Написание названий публичных свойств зависит от погоды за окном? 

Ответить
Развернуть ветку
NTA
Автор

Нет, скорее наоборот :)
ЗЫ: исправим в следующем релизе

Ответить
Развернуть ветку
Читать все 2 комментария
null