Применить стиль к абзацу в текстовом редакторе
В этом разделе показано, как использовать классы в пакете SDK Open XML для Office для программного применения стиля к абзацу в текстовом документе. Он содержит пример ApplyStyleToParagraph
метода для иллюстрации этой задачи, а также несколько дополнительных примеров методов для проверка, существует ли стиль, добавить новый стиль и добавить часть стилей.
Метод ApplyStyleToParagraph
Пример ApplyStyleToParagraph
метода можно использовать для применения стиля к абзацу. Сначала необходимо получить ссылку на документ, а также ссылку на нужный абзаца. Метод принимает четыре параметра, указывающие: путь к открываемому текстовому документу, идентификатор стиля для применения, имя применяемого стиля и ссылку на абзац, к которому применяется стиль.
static void ApplyStyleToParagraph(WordprocessingDocument doc, string styleid, string stylename, Paragraph p)
В следующих разделах описывается реализация этого метода и вспомогательный код, а также способы вызова метод. Полный листинг кода можно найти в разделе Пример кода в конце данной статьи.
Получение объекта WordprocessingDocument
В разделе "Пример кода" также представлен код, необходимый для вызова примера метода. Чтобы применить стиль к абзацу в документе с помощью этого метода, сначала необходимо указать ссылку на открытый документ. В пакете SDK WordprocessingDocument Open XML класс представляет пакет документов Word. Чтобы открыть документ Word и работать с ним, создайте экземпляр WordprocessingDocument класса из документа. После создания экземпляра используйте его для получения доступа к части документа main, содержащей текст документа. Содержимое основной части документа представляется в пакете в виде XML с помощью разметки WordprocessingML.
Чтобы создать экземпляр класса, вызовите одну из перегрузок Open метода . В следующем примере кода показано, как использовать перегрузку DocumentFormat.OpenXml.Packaging.WordprocessingDocument.Open(String, Boolean)
. Первый параметр принимает строку, представляющую полный путь к открываемому документу. Второй параметр принимает значение true
или false
и указывает, следует ли открывать файл для редактирования. В этом примере параметр предназначен true
для включения доступа на чтение и запись к файлу.
using (WordprocessingDocument doc = WordprocessingDocument.Open(args[0], true))
Структура документа WordProcessingML
Базовая структура WordProcessingML
документа состоит из document
элементов и body
, за которыми следует один или несколько элементов уровня блока, таких как p
, который представляет абзац. Абзац содержит один или несколько r
элементов. Представляет r
собой область текста с общим набором свойств, таких как форматирование. Выполнение содержит один или несколько t
элементов. Элемент t
содержит диапазон текста. В следующем примере кода показана разметка WordprocessingML
для документа, содержащего текст "Пример текста".
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:body>
<w:p>
<w:r>
<w:t>Example text.</w:t>
</w:r>
</w:p>
</w:body>
</w:document>
С помощью пакета SDK Open XML можно создавать структуру документа и содержимое с помощью строго типизированных классов, соответствующих WordprocessingML
элементам. Эти классы находятся в пространстве имен. В следующей таблице перечислены имена классов, которые соответствуют document
элементам , body
, p
, r
и t
.
Элемент WordprocessingML | Класс пакета SDK Open XML | Описание |
---|---|---|
<document/> |
Document | Корневой элемент основной части документа. |
<body/> |
Body | Контейнер для структур уровня блокировки, таких как абзацы, таблицы, примечания и других элементов, описанных в спецификации ISO/IEC 29500. |
<p/> |
Paragraph | Абзац. |
<r/> |
Run | Прогон. |
<t/> |
Text | Диапазон текста. |
Дополнительные сведения об общей структуре частей и элементов документа WordprocessingML см. в разделе Структура документа WordprocessingML.
Получение стиля абзаца
После открытия файла пример кода получает ссылку на первый абзац. Поскольку обычный текст документа word-обработки содержит много типов элементов, код фильтрует потомков в тексте документа по типу Paragraph
. Затем ElementAtOrDefault метод используется для получения ссылки на абзац. Так как элементы индексируются начиная с нуля, вы передаете нулевое значение, чтобы получить ссылку на первый абзац, как показано в следующем примере кода.
// Get the first paragraph in the document.
Paragraph? paragraph = doc?.MainDocumentPart?.Document?.Body?.Descendants<Paragraph>().ElementAtOrDefault(0);
Ссылка на найденный абзац хранится в переменной с именем paragraph. Если абзац не найден по указанному индексу ElementAtOrDefault
, метод возвращает значение NULL в качестве значения по умолчанию. Это позволяет проверить, возвращено ли значение NULL, и вызвать ошибку с соответствующим сообщением.
После получения ссылок на документ и абзац можно вызвать ApplyStyleToParagraph
метод примера для выполнения оставшейся работы. Чтобы вызвать метод, необходимо передать ссылку на документ в первом параметре, styleid применяемого стиля во втором параметре, имя стиля в третьем параметре, а ссылку на абзац, к которому нужно применить стиль, в четвертом параметре.
Добавление элемента свойств абзаца
Первый шаг в примере метода состоит в том, чтобы убедиться, что у абзаца есть элемент свойств абзаца. Это дочерний элемент абзаца, содержащий набор свойств, которые позволяют указать форматирование абзаца.
В следующей информации из спецификации ISO/IEC 29500 представлен pPr
элемент (свойства абзаца), используемый для указания форматирования абзаца. Обратите внимание, что номера разделов, которым предшествует символ "§", взяты из спецификации ISO.
В абзаце все форматы на уровне абзаца хранятся в элементе pPr
(§17.3.1.25; §17.3.1.26). [Примечание.
Среди свойств есть элемент, определяющий pStyle
стиль, применяемый к абзацу. Например, в следующей разметке показан элемент pStyle, который задает стиль "OverdueAmount".
<w:p xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:pPr>
<w:pStyle w:val="OverdueAmount" />
</w:pPr>
...
</w:p>
В пакете SDK Open pPr
XML элемент представлен классом ParagraphProperties . Код определяет, существует ли элемент, и создает новый экземпляр класса , ParagraphProperties
если он этого не делает.
Элемент pPr
является дочерним элементом p
элемента (paragraph); следовательно, PrependChild метод используется для добавления экземпляра, как показано в следующем примере кода.
// If the paragraph has no ParagraphProperties object, create one.
if (p.Elements<ParagraphProperties>().Count() == 0)
{
p.PrependChild<ParagraphProperties>(new ParagraphProperties());
}
// Get the paragraph properties element of the paragraph.
ParagraphProperties pPr = p.Elements<ParagraphProperties>().First();
Добавление части "Стили"
После поиска абзаца и проверки элемента свойств абзаца, необходимо убедиться, что требуемые компоненты для применения стиля существуют. Стили в WordprocessingML хранятся в собственной уникальной части. Хотя она и набор базовых стилей создаются автоматически при создании документа с помощью соответствующего приложения, например Microsoft Word, документ считается допустимым и без части стилей. Если вы создаете документ программным способом с помощью Open XML SDK, часть стилей не создается автоматически, ее необходимо создать явно. Поэтому следующий код проверяет, существует ли часть стилей, и создает ее, если это не так.
// Get the Styles part for this document.
StyleDefinitionsPart? part = doc.MainDocumentPart?.StyleDefinitionsPart;
// If the Styles part does not exist, add it and then add the style.
if (part is null)
{
part = AddStylesPartToPackage(doc);
Пример AddStylesPartToPackage
метода выполняет работу по добавлению части стилей. Он создает часть StyleDefinitionsPart
типа, добавляя ее в качестве дочерней части документа main. Затем код добавляет корневой Styles
элемент, который является родительским элементом, содержащим все стили. Элемент Styles
представлен классом в пакете Styles SDK Open XML. Наконец, код сохраняет часть.
// Add a StylesDefinitionsPart to the document. Returns a reference to it.
static StyleDefinitionsPart AddStylesPartToPackage(WordprocessingDocument doc)
{
MainDocumentPart mainDocumentPart = doc.MainDocumentPart ?? doc.AddMainDocumentPart();
StyleDefinitionsPart part = mainDocumentPart.AddNewPart<StyleDefinitionsPart>();
Styles root = new Styles();
return part;
}
Убедитесь, что стиль существует
Применение к абзацу несуществующего стиля не имеет эффекта: исключение не создается, форматирование не меняется. Пример кода проверяет, существует ли стиль, прежде чем применить его. Стили хранятся в части стилей, поэтому если часть стилей не существует, не может существовать и сам стиль.
Если часть стилей существует, код проверяет соответствующий стиль, вызывая IsStyleIdInDocument
метод примера и передавая документ и styleid. Если совпадение в styleid не найдено, код пытается найти его путем вызова GetStyleIdFromStyleName
метода примера и передачи ему имени стиля.
Если стиль не существует, либо потому, что часть стилей не существует, либо потому, что часть стилей существует, но стиль не существует, код вызывает AddNewStyle
метод примера для добавления стиля.
// Get the Styles part for this document.
StyleDefinitionsPart? part = doc.MainDocumentPart?.StyleDefinitionsPart;
// If the Styles part does not exist, add it and then add the style.
if (part is null)
{
part = AddStylesPartToPackage(doc);
AddNewStyle(part, styleid, stylename);
}
else
{
// If the style is not in the document, add it.
if (IsStyleIdInDocument(doc, styleid) != true)
{
// No match on styleid, so let's try style name.
string? styleidFromName = GetStyleIdFromStyleName(doc, stylename);
if (styleidFromName is null)
{
AddNewStyle(part, styleid, stylename);
}
else
styleid = styleidFromName;
}
}
IsStyleInDocument
В примере метода работа начинается с получения Styles
элемента через Styles
свойство StyleDefinitionsPart элемента main части документа, а затем определяет, существуют ли стили в качестве дочерних элементов этого элемента. Все элементы стиля хранятся в виде потомков этого элемента.
Если стили существуют, код ищет совпадение в styleid. Styleid — это атрибут стиля, который используется во многих местах документа для ссылки на стиль и может рассматриваться как его основной идентификатор. Обычно styleid используется для определения стиля в коде. Метод FirstOrDefault по умолчанию имеет значение NULL, если совпадение не найдено, поэтому код проверяет наличие значения NULL, чтобы узнать, соответствует ли стиль, как показано в следующем фрагменте.
// Return true if the style id is in the document, false otherwise.
static bool IsStyleIdInDocument(WordprocessingDocument doc, string styleid)
{
// Get access to the Styles element for this document.
Styles? s = doc.MainDocumentPart?.StyleDefinitionsPart?.Styles;
if (s is null)
{
return false;
}
// Check that there are styles and how many.
int n = s.Elements<Style>().Count();
if (n == 0)
{
return false;
}
// Look for a match on styleid.
Style? style = s.Elements<Style>()
.Where(st => (st.StyleId is not null && st.StyleId == styleid) && (st.Type is not null && st.Type == StyleValues.Paragraph))
.FirstOrDefault();
if (style is null)
{
return false;
}
return true;
}
Если нужный стиль не удается найти по styleid, код пытается найти совпадение по имени стиля. Пример GetStyleIdFromStyleName
метода выполняет эту работу, ища совпадение по имени стиля и возвращая styleid для соответствующего элемента, если он найден, или null, если нет.
Добавление стиля в часть стилей
Метод в AddNewStyle
примере принимает три параметра. Первый параметр принимает ссылку на часть стилей.
Второй параметр принимает styleid стиля, а третий параметр имя стиля. Код AddNewStyle
создает определение именованного стиля в указанной части.
Чтобы создать стиль, код создает Style экземпляр класса и задает определенные свойства, такие как Type стиль (абзац) и StyleId. Как упоминалось выше, styleid используется для ссылки на стиль и может рассматриваться как основной идентификатор. Обычно styleid используется для определения стиля в коде. У стиля также может быть отдельное понятное имя, отображаемое в пользовательском интерфейсе. Часто имя стиля отображается с прописной буквы и с интервалом (например, "Заголовок 1"), а styleid более сжатый (например, heading1) и предназначен для внутреннего использования. В следующем примере кода styleid и имя стиля принимают значения из параметров styleid и stylename.
Следующий шаг указание нескольких дополнительных свойств, таких как стиль, на котором основан новый стиль, и стиль, который автоматически применяются к следующему абзацу. Код указывает их как "Обычный" стиль. Помните, что необходимо указать значение styleid. Код добавляет эти свойства как потомки элемента стиля.
После создания экземпляра стиля и настройки основных свойств необходимо заняться форматированием стиля. Форматирование стиля выполняется в элементах свойств абзаца (pPr
) и свойств выполнения (rPr
). Чтобы задать характеристики шрифта и цвета прогонов в абзаце, используйте свойства прогона.
Чтобы создать rPr
элемент с соответствующими дочерними элементами, код создает экземпляр StyleRunProperties класса , а затем добавляет экземпляры соответствующих классов свойств. В этом пример кода стиль указывает шрифт, Lucida Console, размер шрифта 12, полужирное и курсивное начертание с использованием цвета Accent2 из части темы документа. Размер шрифта в половинах точки, поэтому значение 24 эквивалентно 12 точкам.
После определения стиля код добавляет его в элемент стиля в части стилей, как показано в следующем примере кода.
// Create a new style with the specified styleid and stylename and add it to the specified
// style definitions part.
static void AddNewStyle(StyleDefinitionsPart styleDefinitionsPart, string styleid, string stylename)
{
// Get access to the root element of the styles part.
styleDefinitionsPart.Styles ??= new Styles();
Styles styles = styleDefinitionsPart.Styles;
// Create a new paragraph style and specify some of the properties.
Style style = new Style()
{
Type = StyleValues.Paragraph,
StyleId = styleid,
CustomStyle = true
};
StyleName styleName1 = new StyleName() { Val = stylename };
BasedOn basedOn1 = new BasedOn() { Val = "Normal" };
NextParagraphStyle nextParagraphStyle1 = new NextParagraphStyle() { Val = "Normal" };
style.Append(styleName1);
style.Append(basedOn1);
style.Append(nextParagraphStyle1);
// Create the StyleRunProperties object and specify some of the run properties.
StyleRunProperties styleRunProperties1 = new StyleRunProperties();
Bold bold1 = new Bold();
Color color1 = new Color() { ThemeColor = ThemeColorValues.Accent2 };
RunFonts font1 = new RunFonts() { Ascii = "Lucida Console" };
Italic italic1 = new Italic();
// Specify a 12 point size.
FontSize fontSize1 = new FontSize() { Val = "24" };
styleRunProperties1.Append(bold1);
styleRunProperties1.Append(color1);
styleRunProperties1.Append(font1);
styleRunProperties1.Append(fontSize1);
styleRunProperties1.Append(italic1);
// Add the run properties to the style.
style.Append(styleRunProperties1);
// Add the style to the styles part.
styles.Append(style);
}
Пример кода
Ниже приведен полный пример кода на C# и Visual Basic.
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using System;
using System.Linq;
using (WordprocessingDocument doc = WordprocessingDocument.Open(args[0], true))
{
// Get the first paragraph in the document.
Paragraph? paragraph = doc?.MainDocumentPart?.Document?.Body?.Descendants<Paragraph>().ElementAtOrDefault(0);
if (paragraph is not null)
{
ApplyStyleToParagraph(doc!, "MyStyle", "MyStyleName", paragraph);
}
}
// Apply a style to a paragraph.
static void ApplyStyleToParagraph(WordprocessingDocument doc, string styleid, string stylename, Paragraph p)
{
if (doc is null)
{
throw new ArgumentNullException(nameof(doc));
}
// If the paragraph has no ParagraphProperties object, create one.
if (p.Elements<ParagraphProperties>().Count() == 0)
{
p.PrependChild<ParagraphProperties>(new ParagraphProperties());
}
// Get the paragraph properties element of the paragraph.
ParagraphProperties pPr = p.Elements<ParagraphProperties>().First();
// Get the Styles part for this document.
StyleDefinitionsPart? part = doc.MainDocumentPart?.StyleDefinitionsPart;
// If the Styles part does not exist, add it and then add the style.
if (part is null)
{
part = AddStylesPartToPackage(doc);
AddNewStyle(part, styleid, stylename);
}
else
{
// If the style is not in the document, add it.
if (IsStyleIdInDocument(doc, styleid) != true)
{
// No match on styleid, so let's try style name.
string? styleidFromName = GetStyleIdFromStyleName(doc, stylename);
if (styleidFromName is null)
{
AddNewStyle(part, styleid, stylename);
}
else
styleid = styleidFromName;
}
}
// Set the style of the paragraph.
pPr.ParagraphStyleId = new ParagraphStyleId() { Val = styleid };
}
// Return true if the style id is in the document, false otherwise.
static bool IsStyleIdInDocument(WordprocessingDocument doc, string styleid)
{
// Get access to the Styles element for this document.
Styles? s = doc.MainDocumentPart?.StyleDefinitionsPart?.Styles;
if (s is null)
{
return false;
}
// Check that there are styles and how many.
int n = s.Elements<Style>().Count();
if (n == 0)
{
return false;
}
// Look for a match on styleid.
Style? style = s.Elements<Style>()
.Where(st => (st.StyleId is not null && st.StyleId == styleid) && (st.Type is not null && st.Type == StyleValues.Paragraph))
.FirstOrDefault();
if (style is null)
{
return false;
}
return true;
}
// Return styleid that matches the styleName, or null when there's no match.
static string? GetStyleIdFromStyleName(WordprocessingDocument doc, string styleName)
{
StyleDefinitionsPart? stylePart = doc.MainDocumentPart?.StyleDefinitionsPart;
string? styleId = stylePart?.Styles?.Descendants<StyleName>()
.Where(s =>
{
OpenXmlElement? p = s.Parent;
EnumValue<StyleValues>? styleValue = p is null ? null : ((Style)p).Type;
return s.Val is not null && s.Val.Value is not null && s.Val.Value.Equals(styleName) &&
(styleValue is not null && styleValue == StyleValues.Paragraph);
})
.Select(n =>
{
OpenXmlElement? p = n.Parent;
return p is null ? null : ((Style)p).StyleId;
}).FirstOrDefault();
return styleId;
}
// Create a new style with the specified styleid and stylename and add it to the specified
// style definitions part.
static void AddNewStyle(StyleDefinitionsPart styleDefinitionsPart, string styleid, string stylename)
{
// Get access to the root element of the styles part.
styleDefinitionsPart.Styles ??= new Styles();
Styles styles = styleDefinitionsPart.Styles;
// Create a new paragraph style and specify some of the properties.
Style style = new Style()
{
Type = StyleValues.Paragraph,
StyleId = styleid,
CustomStyle = true
};
StyleName styleName1 = new StyleName() { Val = stylename };
BasedOn basedOn1 = new BasedOn() { Val = "Normal" };
NextParagraphStyle nextParagraphStyle1 = new NextParagraphStyle() { Val = "Normal" };
style.Append(styleName1);
style.Append(basedOn1);
style.Append(nextParagraphStyle1);
// Create the StyleRunProperties object and specify some of the run properties.
StyleRunProperties styleRunProperties1 = new StyleRunProperties();
Bold bold1 = new Bold();
Color color1 = new Color() { ThemeColor = ThemeColorValues.Accent2 };
RunFonts font1 = new RunFonts() { Ascii = "Lucida Console" };
Italic italic1 = new Italic();
// Specify a 12 point size.
FontSize fontSize1 = new FontSize() { Val = "24" };
styleRunProperties1.Append(bold1);
styleRunProperties1.Append(color1);
styleRunProperties1.Append(font1);
styleRunProperties1.Append(fontSize1);
styleRunProperties1.Append(italic1);
// Add the run properties to the style.
style.Append(styleRunProperties1);
// Add the style to the styles part.
styles.Append(style);
}
// Add a StylesDefinitionsPart to the document. Returns a reference to it.
static StyleDefinitionsPart AddStylesPartToPackage(WordprocessingDocument doc)
{
MainDocumentPart mainDocumentPart = doc.MainDocumentPart ?? doc.AddMainDocumentPart();
StyleDefinitionsPart part = mainDocumentPart.AddNewPart<StyleDefinitionsPart>();
Styles root = new Styles();
return part;
}