Skip to content

Commit 66a1466

Browse files
Added MoveFrom/MoveTo code (OfficeDev#332)
* Added MoveFrom/MoveTo code --------- Co-authored-by: Alfred Hellstern <[email protected]>
1 parent ba3dd72 commit 66a1466

File tree

5 files changed

+260
-92
lines changed

5 files changed

+260
-92
lines changed

docs/word/how-to-accept-all-revisions-in-a-word-processing-document.md

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ Using the Open XML SDK, you can create document structure and content using stro
4646

4747
## ParagraphPropertiesChange Element
4848

49-
When you accept a revision mark, you change the properties of a paragraph either by deleting an existing text or inserting a new text. In the following sections, you read about three elements that are used in the code to change the paragraph contents, mainly, `<w: pPrChange>` (Revision Information for Paragraph Properties), `<w:del>` (Deleted Paragraph), and `<w:ins>` (Inserted Table Row) elements.
49+
When you accept a revision mark, you change the properties of a paragraph either by deleting existing text or inserting new text. In the following sections, you read about three elements that are used in the code to change the paragraph contents, mainly, `<w: pPrChange>` (Revision Information for Paragraph Properties), `<w:del>` (Deleted Paragraph), and `<w:ins>` (Inserted Table Row) elements.
5050

5151
The following information from the [!include[ISO/IEC 29500 URL](../includes/iso-iec-29500-link.md)] specification introduces the `ParagraphPropertiesChange` element (`pPrChange`).
5252

@@ -86,9 +86,12 @@ This element specifies that the paragraph mark delimiting the end of a paragraph
8686

8787
Consider a document consisting of two paragraphs (with each paragraph delimited by a pilcrow ¶):
8888

89-
![Two paragraphs each delimited by a pilcrow](../media/w-delparagraphs01.gif) If the physical character delimiting the end of the first paragraph is deleted and this change is tracked as a revision, the following will result:
89+
![Two paragraphs each delimited by a pilcrow](../media/w-delparagraphs01.gif)
90+
91+
If the physical character delimiting the end of the first paragraph is deleted and this change is tracked as a revision, the following will result:
9092

9193
![Two paragraphs delimited by a single pilcrow](../media/w-delparagraphs02.gif)
94+
9295
This revision is represented using the following WordprocessingML:
9396

9497
```xml
@@ -162,12 +165,87 @@ a revision.
162165

163166
© [!include[ISO/IEC 29500 version](../includes/iso-iec-29500-version.md)]
164167

168+
## Move From Element
169+
170+
The following information from the [!include[ISO/IEC 29500 URL](../includes/iso-iec-29500-link.md)] specification
171+
introduces the Move From element (`moveFrom`).
172+
173+
### moveFrom (Move Source Paragraph)
174+
175+
This element indicates that the parent paragraph has been relocated
176+
from this position and marked as a revision. This does not affect the revision
177+
status of the paragraph's content and pertains solely to the paragraph's
178+
existence as a distinct entity.
179+
180+
Consider a WordprocessingML document where a paragraph of text is moved down
181+
within the document. This relocated paragraph would be represented using the
182+
following WordprocessingML markup:
183+
184+
```xml
185+
<w:moveFromRangeStart w:id="0" w:name="aMove"/>
186+
<w:p>
187+
<w:pPr>
188+
<w:rPr>
189+
<w:moveFrom w:id="1" … />
190+
</w:rPr>
191+
</w:pPr>
192+
…</w:p>
193+
</w:moveFromRangeEnd w:id="0"/>
194+
```
195+
196+
### moveFromRangeStart (Move Source Location Container - Start)
197+
198+
This element marks the beginning of a region where the move source contents are part of a single named move.
199+
The following information from the [!include[ISO/IEC 29500 URL](../includes/iso-iec-29500-link.md)] specification
200+
introduces the Move From Range Start element (`moveFromRangeStart`).
201+
202+
### moveFromRangeEnd (Move Source Location Container - End)
203+
204+
This element marks the end of a region where the move source contents are part of a single named move.
205+
The following information from the [!include[ISO/IEC 29500 URL](../includes/iso-iec-29500-link.md)] specification
206+
introduces the Move From Range End element (`moveFromRangeEnd`).
207+
208+
## The Moved To Element
209+
The following information from the [!include[ISO/IEC 29500 URL](../includes/iso-iec-29500-link.md)] specification
210+
introduces the MoveTo element (`moveTo`).
211+
212+
### moveTo (Move Destination Paragraph)
213+
This element specifies that the parent paragraph has been moved to this location and tracked as a revision.
214+
This does not imply anything about the revision state of the contents of the paragraph, and applies only to the existence of the paragraph as its own unique paragraph.
215+
216+
Consider a WordprocessingML document in which a paragraph of text is moved down in the document.
217+
This moved paragraph would be represented using the following WordprocessingML markup:
218+
219+
```xml
220+
<w:moveToRangeStart w:id="0" w:name="aMove"/>
221+
<w:p>
222+
<w:pPr>
223+
<w:rPr>
224+
<w:moveTo w:id="1" … />
225+
</w:rPr>
226+
</w:pPr>
227+
…</w:p>
228+
</w:moveToRangeEnd w:id="0"/>
229+
```
230+
231+
### moveToRangeStart (Move Destination Location Container - Start)
232+
233+
This element specifies the start of the region whose move destination contents are part of a single named move.
234+
The following information from the [!include[ISO/IEC 29500 URL](../includes/iso-iec-29500-link.md)] specification
235+
introduces the Move To Range Start element (`moveToRangeStart`).
236+
237+
### moveToRangeEnd (Move Destination Location Container - End)
238+
239+
This element specifies the end of a region whose move destination contents are part of a single named move.
240+
The following information from the [!include[ISO/IEC 29500 URL](../includes/iso-iec-29500-link.md)] specification
241+
introduces the Move To Range End element (`moveToRangeEnd`).
242+
165243
## Sample Code
166244

167245
The following code example shows how to accept the entire revisions in a
168246
word processing document.
169247

170-
After you have run the program, open the word processing file to make
248+
After you have run the program, open the word processing document to make
171249
sure that all revision marks have been accepted.
172250

173251
### [C#](#tab/cs)

samples/word/accept_all_revisions/cs/Program.cs

Lines changed: 71 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -19,62 +19,88 @@ static void AcceptAllRevisions(string fileName, string authorName)
1919
Body body = wdDoc.MainDocumentPart.Document.Body;
2020

2121
// Handle the formatting changes.
22-
List<OpenXmlElement> changes = body.Descendants<ParagraphPropertiesChange>()
23-
.Where(c => c.Author is not null && c.Author.Value == authorName).Cast<OpenXmlElement>().ToList();
24-
25-
foreach (OpenXmlElement change in changes)
26-
{
27-
change.Remove();
28-
}
22+
RemoveElements(body.Descendants<ParagraphPropertiesChange>().Where(c => c.Author?.Value == authorName));
2923

3024
// Handle the deletions.
31-
List<OpenXmlElement> deletions = body
32-
.Descendants<Deleted>()
33-
.Where(c => c.Author is not null && c.Author.Value == authorName)
34-
.Cast<OpenXmlElement>().ToList();
35-
36-
deletions.AddRange(body.Descendants<DeletedRun>()
37-
.Where(c => c.Author is not null && c.Author.Value == authorName).Cast<OpenXmlElement>().ToList());
25+
RemoveElements(body.Descendants<Deleted>().Where(c => c.Author?.Value == authorName));
26+
RemoveElements(body.Descendants<DeletedRun>().Where(c => c.Author?.Value == authorName));
27+
RemoveElements(body.Descendants<DeletedMathControl>().Where(c => c.Author?.Value == authorName));
3828

39-
deletions.AddRange(body.Descendants<DeletedMathControl>()
40-
.Where(c => c.Author is not null && c.Author.Value == authorName).Cast<OpenXmlElement>().ToList());
29+
// Handle the insertions.
30+
HandleInsertions(body, authorName);
4131

42-
foreach (OpenXmlElement deletion in deletions)
43-
{
44-
deletion.Remove();
45-
}
32+
// Handle move from elements.
33+
RemoveElements(body.Descendants<Paragraph>()
34+
.Where(p => p.Descendants<MoveFrom>()
35+
.Any(m => m.Author?.Value == authorName)));
36+
RemoveElements(body.Descendants<MoveFromRangeEnd>());
4637

47-
// Handle the insertions.
48-
List<OpenXmlElement> insertions =
49-
body.Descendants<Inserted>()
50-
.Where(c => c.Author is not null && c.Author.Value == authorName).Cast<OpenXmlElement>().ToList();
38+
// Handle move to elements.
39+
HandleMoveToElements(body, authorName);
40+
}
41+
}
5142

52-
insertions.AddRange(body.Descendants<InsertedRun>()
53-
.Where(c => c.Author is not null && c.Author.Value == authorName).Cast<OpenXmlElement>().ToList());
43+
// Method to remove elements from the document body
44+
static void RemoveElements(IEnumerable<OpenXmlElement> elements)
45+
{
46+
foreach (var element in elements.ToList())
47+
{
48+
element.Remove();
49+
}
50+
}
5451

55-
insertions.AddRange(body.Descendants<InsertedMathControl>()
56-
.Where(c => c.Author is not null && c.Author.Value == authorName).Cast<OpenXmlElement>().ToList());
52+
// Method to handle insertions in the document body
53+
static void HandleInsertions(Body body, string authorName)
54+
{
55+
// Collect all insertion elements by the specified author
56+
var insertions = body.Descendants<Inserted>().Cast<OpenXmlElement>().ToList();
57+
insertions.AddRange(body.Descendants<InsertedRun>().Where(c => c.Author?.Value == authorName));
58+
insertions.AddRange(body.Descendants<InsertedMathControl>().Where(c => c.Author?.Value == authorName));
5759

58-
foreach (OpenXmlElement insertion in insertions)
60+
foreach (var insertion in insertions)
61+
{
62+
// Promote new content to the same level as the node and then delete the node
63+
foreach (var run in insertion.Elements<Run>())
5964
{
60-
// Found new content.
61-
// Promote them to the same level as node, and then delete the node.
62-
foreach (var run in insertion.Elements<Run>())
65+
66+
if (run == insertion.FirstChild)
67+
{
68+
insertion.InsertAfterSelf(new Run(run.OuterXml));
69+
}
70+
else
6371
{
64-
if (run == insertion.FirstChild)
65-
{
66-
insertion.InsertAfterSelf(new Run(run.OuterXml));
67-
}
68-
else
69-
{
70-
OpenXmlElement nextSibling = insertion.NextSibling()!;
71-
nextSibling.InsertAfterSelf(new Run(run.OuterXml));
72-
}
72+
OpenXmlElement nextSibling = insertion.NextSibling()!;
73+
nextSibling.InsertAfterSelf(new Run(run.OuterXml));
7374
}
75+
}
76+
77+
// Remove specific attributes and the insertion element itself
78+
insertion.RemoveAttribute("rsidR", "https://schemas.openxmlformats.org/wordprocessingml/2006/main");
79+
insertion.RemoveAttribute("rsidRPr", "https://schemas.openxmlformats.org/wordprocessingml/2006/main");
80+
insertion.Remove();
81+
}
82+
}
83+
84+
// Method to handle move-to elements in the document body
85+
static void HandleMoveToElements(Body body, string authorName)
86+
{
87+
// Collect all move-to elements by the specified author
88+
var paragraphs = body.Descendants<Paragraph>()
89+
.Where(p => p.Descendants<MoveFrom>()
90+
.Any(m => m.Author?.Value == authorName));
91+
var moveToRun = body.Descendants<MoveToRun>();
92+
var moveToRangeEnd = body.Descendants<MoveToRangeEnd>();
7493

75-
insertion.RemoveAttribute("rsidR", "https://schemas.openxmlformats.org/wordprocessingml/2006/main");
76-
insertion.RemoveAttribute("rsidRPr", "https://schemas.openxmlformats.org/wordprocessingml/2006/main");
77-
insertion.Remove();
94+
List<OpenXmlElement> moveToElements = [.. paragraphs, .. moveToRun, .. moveToRangeEnd];
95+
96+
foreach (var toElement in moveToElements)
97+
{
98+
// Promote new content to the same level as the node and then delete the node
99+
foreach (var run in toElement.Elements<Run>())
100+
{
101+
toElement.InsertBeforeSelf(new Run(run.OuterXml));
78102
}
103+
// Remove the move-to element itself
104+
toElement.Remove();
79105
}
80-
}
106+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.12.35728.132 d17.12
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "accept_all_revisions_cs", "accept_all_revisions_cs.csproj", "{FDDDBEF7-7D8F-4623-95E9-5E30BB031414}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|Any CPU = Debug|Any CPU
11+
Release|Any CPU = Release|Any CPU
12+
EndGlobalSection
13+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
14+
{FDDDBEF7-7D8F-4623-95E9-5E30BB031414}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15+
{FDDDBEF7-7D8F-4623-95E9-5E30BB031414}.Debug|Any CPU.Build.0 = Debug|Any CPU
16+
{FDDDBEF7-7D8F-4623-95E9-5E30BB031414}.Release|Any CPU.ActiveCfg = Release|Any CPU
17+
{FDDDBEF7-7D8F-4623-95E9-5E30BB031414}.Release|Any CPU.Build.0 = Release|Any CPU
18+
EndGlobalSection
19+
GlobalSection(SolutionProperties) = preSolution
20+
HideSolutionNode = FALSE
21+
EndGlobalSection
22+
EndGlobal

samples/word/accept_all_revisions/vb/Program.vb

Lines changed: 64 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -4,63 +4,83 @@ Imports DocumentFormat.OpenXml.Wordprocessing
44

55
Module Program
66
Sub Main(args As String())
7-
Dim fileName = args(0)
8-
Dim authorName = args(1)
7+
AcceptAllRevisions(args(0), args(1))
8+
End Sub
99

10-
'Public Sub AcceptRevisions(ByVal fileName As String, ByVal authorName As String)
11-
' Given a document name and an author name, accept revisions.
10+
Sub AcceptAllRevisions(fileName As String, authorName As String)
1211
Using wdDoc As WordprocessingDocument = WordprocessingDocument.Open(fileName, True)
12+
If wdDoc.MainDocumentPart Is Nothing OrElse wdDoc.MainDocumentPart.Document.Body Is Nothing Then
13+
Throw New ArgumentNullException("MainDocumentPart and/or Body is null.")
14+
End If
15+
1316
Dim body As Body = wdDoc.MainDocumentPart.Document.Body
1417

1518
' Handle the formatting changes.
16-
Dim changes As List(Of OpenXmlElement) =
17-
body.Descendants(Of ParagraphPropertiesChange)() _
18-
.Where(Function(c) c.Author.Value = authorName).Cast(Of OpenXmlElement)().ToList()
19-
20-
For Each change In changes
21-
change.Remove()
22-
Next
19+
RemoveElements(body.Descendants(Of ParagraphPropertiesChange)().Where(Function(c) c.Author?.Value = authorName))
2320

2421
' Handle the deletions.
25-
Dim deletions As List(Of OpenXmlElement) =
26-
body.Descendants(Of Deleted)() _
27-
.Where(Function(c) c.Author.Value = authorName).Cast(Of OpenXmlElement)().ToList()
22+
RemoveElements(body.Descendants(Of Deleted)().Where(Function(c) c.Author?.Value = authorName))
23+
RemoveElements(body.Descendants(Of DeletedRun)().Where(Function(c) c.Author?.Value = authorName))
24+
RemoveElements(body.Descendants(Of DeletedMathControl)().Where(Function(c) c.Author?.Value = authorName))
2825

29-
deletions.AddRange(body.Descendants(Of DeletedRun)() _
30-
.Where(Function(c) c.Author.Value = authorName).Cast(Of OpenXmlElement)().ToList())
26+
' Handle the insertions.
27+
HandleInsertions(body, authorName)
3128

32-
deletions.AddRange(body.Descendants(Of DeletedMathControl)() _
33-
.Where(Function(c) c.Author.Value = authorName).Cast(Of OpenXmlElement)().ToList())
29+
' Handle move from elements.
30+
RemoveElements(body.Descendants(Of Paragraph)().Where(Function(p) p.Descendants(Of MoveFrom)().Any(Function(m) m.Author?.Value = authorName)))
31+
RemoveElements(body.Descendants(Of MoveFromRangeEnd)())
3432

35-
For Each deletion In deletions
36-
deletion.Remove()
37-
Next
33+
' Handle move to elements.
34+
HandleMoveToElements(body, authorName)
35+
End Using
36+
End Sub
3837

39-
' Handle the insertions.
40-
Dim insertions As List(Of OpenXmlElement) =
41-
body.Descendants(Of Inserted)() _
42-
.Where(Function(c) c.Author.Value = authorName).Cast(Of OpenXmlElement)().ToList()
38+
' Method to remove elements from the document body
39+
Sub RemoveElements(elements As IEnumerable(Of OpenXmlElement))
40+
For Each element In elements.ToList()
41+
element.Remove()
42+
Next
43+
End Sub
4344

44-
insertions.AddRange(body.Descendants(Of InsertedRun)() _
45-
.Where(Function(c) c.Author.Value = authorName).Cast(Of OpenXmlElement)().ToList())
45+
' Method to handle insertions in the document body
46+
Sub HandleInsertions(body As Body, authorName As String)
47+
' Collect all insertion elements by the specified author
48+
Dim insertions As List(Of OpenXmlElement) = body.Descendants(Of Inserted)().Cast(Of OpenXmlElement)().ToList()
49+
insertions.AddRange(body.Descendants(Of InsertedRun)().Where(Function(c) c.Author?.Value = authorName))
50+
insertions.AddRange(body.Descendants(Of InsertedMathControl)().Where(Function(c) c.Author?.Value = authorName))
4651

47-
insertions.AddRange(body.Descendants(Of InsertedMathControl)() _
48-
.Where(Function(c) c.Author.Value = authorName).Cast(Of OpenXmlElement)().ToList())
52+
For Each insertion In insertions
53+
' Promote new content to the same level as the node and then delete the node
54+
For Each run In insertion.Elements(Of Run)()
55+
If run Is insertion.FirstChild Then
56+
insertion.InsertAfterSelf(New Run(run.OuterXml))
57+
Else
58+
Dim nextSibling As OpenXmlElement = insertion.NextSibling()
59+
nextSibling.InsertAfterSelf(New Run(run.OuterXml))
60+
End If
61+
Next
4962

50-
For Each insertion In insertions
51-
' Found new content. Promote them to the same level as node, and then
52-
' delete the node.
53-
For Each run In insertion.Elements(Of Run)()
54-
If run Is insertion.FirstChild Then
55-
insertion.InsertAfterSelf(New Run(run.OuterXml))
56-
Else
57-
insertion.NextSibling().InsertAfterSelf(New Run(run.OuterXml))
58-
End If
59-
Next
60-
insertion.RemoveAttribute("rsidR", "https://schemas.openxmlformats.org/wordprocessingml/2006/main")
61-
insertion.RemoveAttribute("rsidRPr", "https://schemas.openxmlformats.org/wordprocessingml/2006/main")
62-
insertion.Remove()
63+
' Remove specific attributes and the insertion element itself
64+
insertion.RemoveAttribute("rsidR", "https://schemas.openxmlformats.org/wordprocessingml/2006/main")
65+
insertion.RemoveAttribute("rsidRPr", "https://schemas.openxmlformats.org/wordprocessingml/2006/main")
66+
insertion.Remove()
67+
Next
68+
End Sub
69+
70+
' Method to handle move-to elements in the document body
71+
Sub HandleMoveToElements(body As Body, authorName As String)
72+
' Collect all move-to elements by the specified author
73+
Dim moveToElements As List(Of OpenXmlElement) = body.Descendants(Of MoveToRun)().Cast(Of OpenXmlElement)().ToList()
74+
moveToElements.AddRange(body.Descendants(Of Paragraph)().Where(Function(p) p.Descendants(Of MoveFrom)().Any(Function(m) m.Author?.Value = authorName)))
75+
moveToElements.AddRange(body.Descendants(Of MoveToRangeEnd)())
76+
77+
For Each toElement In moveToElements
78+
' Promote new content to the same level as the node and then delete the node
79+
For Each run In toElement.Elements(Of Run)()
80+
toElement.InsertBeforeSelf(New Run(run.OuterXml))
6381
Next
64-
End Using
82+
' Remove the move-to element itself
83+
toElement.Remove()
84+
Next
6585
End Sub
66-
End Module
86+
End Module

0 commit comments

Comments
 (0)