Извлечение содержимого документа Word на основе стилей

В предыдущих записях, таких как Импорт таблицы из программы Word в Excel, я показал, как можно извлекать содержимое из конкретных элементов управления содержимым. В этих записях элементы управления содержимым использовались для добавления семантической структуры в документ. Подобная структура оказывается очень полезной при извлечении и вставке содержимого. Но как работать с другими типами содержимого? Одной из наиболее востребованных задач является извлечение содержимого на основе его стиля, например стиля абзаца, последовательности знаков или даже таблицы. Иными словами, стили также можно использовать для добавления семантической структуры и смыслового значения в документ. В сегодняшней записи я собираюсь рассказать о двух вещах.

  1. Как извлекать содержимое на основе стилей.
  2. Как добавить методы расширений в пакет Open XML SDK.

Сегодня вас ожидает сюрприз: я попробую создать видеоролик для своих записей блога. Дайте мне знать, окажутся ли эти видеоролики для вас полезными.

Решение

Для поиска содержимого документа Word на основе стилей потребуется выполнить следующие действия:

  1. Открыть документ Word с помощью пакета Open XML SDK.
  2. Получить доступ к главному разделу документа.
  3. Найти идентификатор стиля, соответствующий имени стиля. Ссылки на идентификатор стиля содержатся в абзацах, последовательностях знаков и таблицах.
  4. Просмотреть все абзацы, последовательности знаков с общим начертанием и таблицы в главном разделе документа.
  5. Выполнить фильтрацию списка абзацев, последовательностей знаков и таблиц на основе наличия в этих объектах или элементах ссылки на определенное имя стиля.
  6. Вернуть результирующий список абзацев, последовательностей знаков и таблиц.

Чтобы более наглядно представить изложенный здесь материал, начнем работать со следующим документом Word, содержащим стили абзацев, последовательностей знаков и таблиц:

В этом документе используются следующие стили:

  • Стиль абзаца — Heading 1.
  • Стиль последовательности знаков — Intense Emphasis.
  • Стиль таблицы — Light List Accent 1.

Если вы хотите отслеживать все этапы работы непосредственно по коду, наше решение можно без труда загрузить здесь.

Код

Разрабатывая это решение, я подумал, что было бы действительно здорово воспользоваться возможностями методов расширений для языка C#. Методы расширений позволяют "добавлять" методы в существующие типы без создания производных типов, перекомпиляции или изменения исходных типов каким-либо другим способом. В данном случае я добавляю три метода расширений из класса MainDocumentPart (не забудьте, что этот класс представляет главный раздел document.xml моего документа Word).

  1. ParagraphsByStyleName — этот метод извлекает список абзацев в главном разделе документа, которым присвоено определенное имя стиля.
  2. RunsByStyleName — этот метод извлекает список последовательностей знаков в главном разделе документа, которым присвоено определенное имя стиля.
  3. TablesByStyleName — этот метод извлекает список таблиц в главном разделе документа, которым присвоено определенное имя стиля.

Все три метода очень похожи, за одним существенным исключением: для запроса данных в них используются разные строго типизированные классы. Я поместил созданные методы расширения в класс под названием WordStyleExtensions. Вы можете и в дальнейшем использовать или даже расширять этот класс в своих собственных целях.

Ссылки на стили в абзацах, последовательностях знаков или таблицах реализуются с помощью идентификаторов стиля, поэтому нам необходим способ поиска идентификатора в имени стиля. В следующем фрагменте кода эта задача реализована для любого стиля:

private static string GetStyleIdFromStyleName(MainDocumentPart mainPart, string styleName) { StyleDefinitionsPart stylePart = mainPart.StyleDefinitionsPart; string styleId = stylePart.Styles.Descendants<StyleName>() .Where(s => s.Val.Value.Equals(styleName)) .Select(n => ((Style)n.Parent).StyleId).FirstOrDefault(); return styleId ?? styleName; }

В этом коде выполняется простой поиск идентификатора стиля в имени стиля. Если идентификатор не найден, возвращается имя стиля.

Рассмотрим подробнее код для извлечения абзацев на основе имени стиля. Как указано выше в разделе "Решение", эта задача разбивается на два этапа. Сначала необходимо извлечь все абзацы в главном разделе документа, воспользовавшись следующим кодом:

public static IEnumerable<Paragraph> ParagraphsByStyleName(this MainDocumentPart mainPart, string styleName) { string styleId = GetStyleIdFromStyleName(mainPart, styleName); IEnumerable<Paragraph> paraList = mainPart.Document.Descendants<Paragraph>() .Where(p => IsParagraphInStyle(p, styleId)); return paraList; }

Следующий этап состоит в фильтрации абзацев на основе использования в этих параграфах определенного имени стиля. Эту задачу можно реализовать с помощью следующего кода:

private static bool IsParagraphInStyle(Paragraph p, string styleId) { ParagraphProperties pPr = p.GetFirstChild<ParagraphProperties>(); if (pPr != null) { ParagraphStyleId paraStyle = pPr.ParagraphStyleId; if (paraStyle != null) { return paraStyle.Val.Value.Equals(styleId); } } return false; }

Не правда ли, просто?! Самым замечательным здесь является то, что эти методы можно легко изменить для работы с последовательностями знаков или таблицами. Взгляните на методы извлечения содержимого на основе стилей последовательностей знаков и таблиц.

public static IEnumerable<Run> RunsByStyleName(this MainDocumentPart mainPart, string styleName) { string styleId = GetStyleIdFromStyleName(mainPart, styleName); IEnumerable<Run> runList = mainPart.Document.Descendants<Run>() .Where(r => IsRunInStyle(r, styleId)); return runList; } private static bool IsRunInStyle(Run r, string styleId) { RunProperties rPr = r.GetFirstChild<RunProperties>(); if (rPr != null) { RunStyle runStyle = rPr.RunStyle; if (runStyle != null) { return runStyle.Val.Value.Equals(styleId); } } return false; } public static IEnumerable<Table> TablesByStyleName(this MainDocumentPart mainPart, string styleName) { string styleId = GetStyleIdFromStyleName(mainPart, styleName); IEnumerable<Table> tableList = mainPart.Document.Descendants<Table>() .Where(t => IsTableInStyle(t, styleId)); return tableList; } private static bool IsTableInStyle(Table tbl, string styleId) { TableProperties tblPr = tbl.GetFirstChild<TableProperties>(); if (tblPr != null) { TableStyle tblStyle = tblPr.TableStyle; if (tblStyle != null) { return tblStyle.Val.Value.Equals(styleId); } } return false; }

Теперь, когда мы создали методы расширений, нам осталось только вызвать эти методы.

static void Main(string[] args) { string paraStyle = "Heading1"; string runStyle = "IntenseEmphasis"; string tableStyle = "LightList-Accent1"; using (WordprocessingDocument myDoc = WordprocessingDocument.Open("input.docx", true)) { MainDocumentPart mainPart = myDoc.MainDocumentPart; Console.WriteLine("Number of paragraphs with " + paraStyle + " styles: " + mainPart.ParagraphsByStyleName(paraStyle).Count()); Console.WriteLine("Number of runs with " + runStyle + " styles: " + mainPart.RunsByStyleName(runStyle).Count()); Console.WriteLine("Number of tables with " + tableStyle + " styles: " + mainPart.TablesByStyleName(tableStyle).Count()); } Console.ReadKey(); }

Заключение

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

После выполнения кода на экран выводится следующий результат:

Зияд Раджаби (Zeyad Rajabi)

Это локализованная запись блога. Исходную статью можно найти по адресу https://blogs.msdn.com/brian_jones/archive/2009/05/05/retrieving-word-content-based-on-styles.