本主题演示如何使用 Open XML SDK for Office 以编程方式接受字处理文档中的所有修订。
WordProcessingML 文档的结构
文档的基本文档结构WordProcessingML由 和 body 元素组成document,后跟一个或多个块级元素(例如 p表示段落)。 段落包含一个或多个 r 元素。
r代表 run,它是具有一组通用属性(如格式设置)的文本区域。 运行包含一个或多个 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>
使用 Open XML SDK,可以使用与元素对应的 WordprocessingML 强类型类创建文档结构和内容。 可以在 命名空间中找到 DocumentFormat.OpenXml.Wordprocessing 这些类。 下表列出了对应于 、、body、 rp和 t 元素的类的document类名。
| WordprocessingML 元素 | Open XML SDK 类 | 说明 |
|---|---|---|
<document/> |
Document | 主文档部件的根元素。 |
<body/> |
Body | 块级结构(如段落、表格、批注和 ISO/IEC 29500 规范中指定的其他项)的容器。 |
<p/> |
Paragraph | 段落。 |
<r/> |
Run | 一段连续文本。 |
<t/> |
Text | 文本范围。 |
有关 WordprocessingML 文档的各个部分和元素的整体结构的详细信息,请参阅 WordprocessingML 文档的结构。
文档的基本文档结构WordProcessingML由 和 body 元素组成document,后跟一个或多个块级元素(例如 p表示段落)。 段落包含一个或多个 r 元素。
r代表 run,它是具有一组通用属性(如格式设置)的文本区域。 运行包含一个或多个 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>
使用 Open XML SDK,可以使用与元素对应的 WordprocessingML 强类型类创建文档结构和内容。 可以在 命名空间中找到 DocumentFormat.OpenXml.Wordprocessing 这些类。 下表列出了对应于 、、body、 rp和 t 元素的类的document类名。
| WordprocessingML 元素 | Open XML SDK 类 | 说明 |
|---|---|---|
| document | Document | 主文档部件的根元素。 |
| body | Body | 块级结构(如段落、表格、批注和 ISO/IEC 29500 规范中指定的其他项)的容器。 |
| p | Paragraph | 段落。 |
| r | Run | 一段连续文本。 |
| t | Text | 一个文本范围。 |
ParagraphPropertiesChange 元素
接受修订标记时,可以通过删除现有文本或插入新文本来更改段落的属性。 以下部分介绍了代码中用于更改段落内容的三个元素,主要 <w: pPrChange> (段落属性) 的修订信息、 <w:del> (Deleted Paragraph) 和 <w:ins> (Inserted Table Row) 元素。
ISO/IEC 29500 规范中的以下信息介绍了ParagraphPropertiesChange元素 (pPrChange) 。
*pPrChange (段落属性的修订信息)
此元素指定 WordprocessingML 文档中一组段落属性的单处修订的详细信息。
此元素按如下方式存储此修订:
此元素的子元素包含此修订之前应用于此段落的完整段落属性集。
此元素的属性包含有关此修订发生时间(换句话说,这些段落属性何时成为"先前的"段落属性集)的信息。
假设对 WordprocessingML 文档中的一个段落设置了居中格式,并且将此段落属性更改作为修订进行跟踪。 那么,将使用以下 WordprocessingML 标记指定此修订。
<w:pPr>
<w:jc w:val="center"/>
<w:pPrChange w:id="0" w:date="01-01-2006T12:00:00" w:author="Samantha Smith">
<w:pPr/>
</w:pPrChange>
</w:pPr>
元素指定 Samantha Smith 在 01-01-2006 对段落属性进行了修订,而该段落的上一组段落属性是 null 集 (换句话说,元素) 下没有显式存在的段落属性。
pPr
pPrChange
© ISO/IEC 29500:2016
Deleted 元素
ISO/IEC 29500 规范中的以下信息介绍了 Deleted 元素 (del) 。
del(删除的段落标记)
此元素指定,在 WordprocessingML 文档中分隔段落末尾的段落标记应被视为已删除 (换句话说,此段落的内容不再由此段落标记分隔,并与下一段结合使用,但这些内容不应自动标记为已删除) 作为修订的一部分。
假设存在一个由两个段落组成(每个段落由一个段落标记 ¶ 界定)的文档:
如果删除界定第一个段落结束位置的有形字符并将此更改作为修订进行跟踪,则将产生以下结果:
此修订使用以下 WordprocessingML 表示:
<w:p>
<w:pPr>
<w:rPr>
<w:del w:id="0" … />
</w:rPr>
</w:pPr>
<w:r>
<w:t>This is paragraph one.</w:t>
</w:r>
</w:p>
<w:p>
<w:r>
<w:t>This is paragraph two.</w:t>
</w:r>
</w:p>
del第一个段落标记的运行属性上的 元素指定删除此段落标记,并将此删除作为修订进行跟踪。
© ISO/IEC 29500:2016
Inserted 元素
ISO/IEC 29500 规范中的以下信息介绍了 Inserted 元素 (ins) 。
ins(插入的表格行)
此元素指定应将父表格行视为插入的行,并且已将该插入操作作为修订进行跟踪。 此设置不应表示有关此行中的表格单元格或其内容的任何修订状态(它们必须通过单独的修订标记表示),并且应该仅影响表格行本身。
假设存在一个包含两行两列的表格,并且已使用修订将第二行标记为已插入。 此要求将使用以下 WordprocessingML 指定:
<w:tbl>
<w:tr>
<w:tc>
<w:p/>
</w:tc>
<w:tc>
<w:p/>
</w:tc>
</w:tr>
<w:tr>
<w:trPr>
<w:ins w:id="0" … />
</w:trPr>
<w:tc>
<w:p/>
</w:tc>
<w:tc>
<w:p/>
</w:tc>
</w:tr>
</w:tbl>
第 ins 二个表行的表行属性上的 元素指定插入此行,并且此插入作为修订进行跟踪。
© ISO/IEC 29500:2016
从元素移动
ISO/IEC 29500 规范中的以下信息介绍了 Move From 元素 (moveFrom) 。
moveFrom (移动源段落)
此元素指示父段落已从此位置重新定位并标记为修订。 这不会影响段落内容的修订状态,仅涉及段落作为一个不同实体的存在。
请考虑 WordprocessingML 文档,其中文本段落在文档中向下移动。 此重定位段落将使用以下 WordprocessingML 标记表示:
<w:moveFromRangeStart w:id="0" w:name="aMove"/>
<w:p>
<w:pPr>
<w:rPr>
<w:moveFrom w:id="1" … />
</w:rPr>
</w:pPr>
…</w:p>
</w:moveFromRangeEnd w:id="0"/>
moveFromRangeStart (移动源位置容器 - 启动)
此元素标记一个区域的开头,其中移动源内容是单个命名移动的一部分。
ISO/IEC 29500 规范中的以下信息介绍了“从范围开始移动”元素 (moveFromRangeStart) 。
moveFromRangeEnd (移动源位置容器 - 结束)
此元素标记一个区域的末尾,其中移动源内容是单个命名移动的一部分。
ISO/IEC 29500 规范中的以下信息介绍了 () moveFromRangeEnd 的 Move From Range End 元素。
已移动到元素
ISO/IEC 29500 规范中的以下信息介绍了 MoveTo 元素 (moveTo) 。
moveTo (Move Destination Paragraph)
此元素指定父段落已移动到此位置并作为修订进行跟踪。 这并不暗示有关段落内容的修订状态,仅适用于段落作为其自己的唯一段落的存在。
请考虑 WordprocessingML 文档,其中文本段落在文档中向下移动。 移动的段落将使用以下 WordprocessingML 标记表示:
<w:moveToRangeStart w:id="0" w:name="aMove"/>
<w:p>
<w:pPr>
<w:rPr>
<w:moveTo w:id="1" … />
</w:rPr>
</w:pPr>
…</w:p>
</w:moveToRangeEnd w:id="0"/>
moveToRangeStart (移动目标位置容器 - 启动)
此元素指定其移动目标内容属于单个命名移动的一部分的区域的起点。
ISO/IEC 29500 规范中的以下信息介绍了“移动到范围开始”元素 (moveToRangeStart) 。
moveToRangeEnd (移动目标位置容器 - end)
此元素指定其移动目标内容属于单个命名移动的一部分的区域的末尾。
ISO/IEC 29500 规范中的以下信息介绍了“移动到范围结束”元素 (moveToRangeEnd) 。
示例代码
以下代码示例演示如何接受字处理文档中的全部修订。
运行程序后,打开字处理文档以确保已接受所有修订标记。
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using System;
using System.Collections.Generic;
using System.Linq;
AcceptAllRevisions(args[0], args[1]);
static void AcceptAllRevisions(string fileName, string authorName)
{
using (WordprocessingDocument wdDoc = WordprocessingDocument.Open(fileName, true))
{
if (wdDoc.MainDocumentPart is null || wdDoc.MainDocumentPart.Document.Body is null)
{
throw new ArgumentNullException("MainDocumentPart and/or Body is null.");
}
Body body = wdDoc.MainDocumentPart.Document.Body;
// Handle the formatting changes.
RemoveElements(body.Descendants<ParagraphPropertiesChange>().Where(c => c.Author?.Value == authorName));
// Handle the deletions.
RemoveElements(body.Descendants<Deleted>().Where(c => c.Author?.Value == authorName));
RemoveElements(body.Descendants<DeletedRun>().Where(c => c.Author?.Value == authorName));
RemoveElements(body.Descendants<DeletedMathControl>().Where(c => c.Author?.Value == authorName));
// Handle the insertions.
HandleInsertions(body, authorName);
// Handle move from elements.
RemoveElements(body.Descendants<Paragraph>()
.Where(p => p.Descendants<MoveFrom>()
.Any(m => m.Author?.Value == authorName)));
RemoveElements(body.Descendants<MoveFromRangeEnd>());
// Handle move to elements.
HandleMoveToElements(body, authorName);
}
}
// Method to remove elements from the document body
static void RemoveElements(IEnumerable<OpenXmlElement> elements)
{
foreach (var element in elements.ToList())
{
element.Remove();
}
}
// Method to handle insertions in the document body
static void HandleInsertions(Body body, string authorName)
{
// Collect all insertion elements by the specified author
var insertions = body.Descendants<Inserted>().Cast<OpenXmlElement>().ToList();
insertions.AddRange(body.Descendants<InsertedRun>().Where(c => c.Author?.Value == authorName));
insertions.AddRange(body.Descendants<InsertedMathControl>().Where(c => c.Author?.Value == authorName));
foreach (var insertion in insertions)
{
// Promote new content to the same level as the node and then delete the node
foreach (var run in insertion.Elements<Run>())
{
if (run == insertion.FirstChild)
{
insertion.InsertAfterSelf(new Run(run.OuterXml));
}
else
{
OpenXmlElement nextSibling = insertion.NextSibling()!;
nextSibling.InsertAfterSelf(new Run(run.OuterXml));
}
}
// Remove specific attributes and the insertion element itself
insertion.RemoveAttribute("rsidR", "https://schemas.openxmlformats.org/wordprocessingml/2006/main");
insertion.RemoveAttribute("rsidRPr", "https://schemas.openxmlformats.org/wordprocessingml/2006/main");
insertion.Remove();
}
}
// Method to handle move-to elements in the document body
static void HandleMoveToElements(Body body, string authorName)
{
// Collect all move-to elements by the specified author
var paragraphs = body.Descendants<Paragraph>()
.Where(p => p.Descendants<MoveFrom>()
.Any(m => m.Author?.Value == authorName));
var moveToRun = body.Descendants<MoveToRun>();
var moveToRangeEnd = body.Descendants<MoveToRangeEnd>();
List<OpenXmlElement> moveToElements = [.. paragraphs, .. moveToRun, .. moveToRangeEnd];
foreach (var toElement in moveToElements)
{
// Promote new content to the same level as the node and then delete the node
foreach (var run in toElement.Elements<Run>())
{
toElement.InsertBeforeSelf(new Run(run.OuterXml));
}
// Remove the move-to element itself
toElement.Remove();
}
}