Время прочтения: 5 мин.
В этом году я учувствовал в конкурсе по реализации сервиса, который должен проверять формат оформления документов и вносить изменения в режиме правки. Существующие библиотеки либо не решали эту задачу вовсе, либо оказались платными. Было принято решение погрузиться в формат документа 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.