Skip to content

Added MoveFrom/MoveTo code #332

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 12, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,81 @@ a revision.

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

## Move From Element

The following information from the [!include[ISO/IEC 29500 URL](../includes/iso-iec-29500-link.md)] specification
introduces the Move From element (`moveFrom`).

### moveFrom (Move Source Paragraph)

This element indicates that the parent paragraph has been relocated
from this position and marked as a revision. This does not affect the revision
status of the paragraph's content and pertains solely to the paragraph's
existence as a distinct entity.

Consider a WordprocessingML document where a paragraph of text is moved down
within the document.This relocated paragraph would be represented using the
following WordprocessingML markup:

```xml
<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 (Move Source Location Container - Start)

This element marks the beginning of a region where the move source contents are part of a single named move.
The following information from the [!include[ISO/IEC 29500 URL](../includes/iso-iec-29500-link.md)] specification
introduces the Move From Range Star element (`moveFromRangeStart`).

### moveFromRangeEnd (Move Source Location Container - End)

This element marks the end of a region where the move source contents are part of a single named move.
The following information from the [!include[ISO/IEC 29500 URL](../includes/iso-iec-29500-link.md)] specification
introduces the Move From Range Star element (`moveFromRangeEnd`).

## The Moved To Element
The following information from the [!include[ISO/IEC 29500 URL](../includes/iso-iec-29500-link.md)] specification
introduces the MoveTo element (`moveTo`).

### moveTo (Move Destination Paragraph)
This element specifies that the parent paragraph has been moved to this location and tracked as a revision.
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.

Consider a WordprocessingML document in which a paragraph of text is moved down in the document.
This moved paragraph would be represented using the following WordprocessingML markup:

```xml
<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 (Move Destination Location Container - Start)

This element specifies the start of the region whose move destination contents are part of a single named move.
The following information from the [!include[ISO/IEC 29500 URL](../includes/iso-iec-29500-link.md)] specification
introduces the Move To Range Start element (`moveToRangeStart`).

### moveToRangeEnd (Move Destination Location Container - End)

This element specifies the end of a region whose move destination contents are part of a single named move.
The following information from the [!include[ISO/IEC 29500 URL](../includes/iso-iec-29500-link.md)] specification
introduces the Move To Range Start element (`moveToRangeEnd`).

## Sample Code

The following code example shows how to accept the entire revisions in a
Expand Down
113 changes: 69 additions & 44 deletions samples/word/accept_all_revisions/cs/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,62 +19,87 @@ static void AcceptAllRevisions(string fileName, string authorName)
Body body = wdDoc.MainDocumentPart.Document.Body;

// Handle the formatting changes.
List<OpenXmlElement> changes = body.Descendants<ParagraphPropertiesChange>()
.Where(c => c.Author is not null && c.Author.Value == authorName).Cast<OpenXmlElement>().ToList();

foreach (OpenXmlElement change in changes)
{
change.Remove();
}
RemoveElements(body.Descendants<ParagraphPropertiesChange>().Where(c => c.Author?.Value == authorName));

// Handle the deletions.
List<OpenXmlElement> deletions = body
.Descendants<Deleted>()
.Where(c => c.Author is not null && c.Author.Value == authorName)
.Cast<OpenXmlElement>().ToList();
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));

deletions.AddRange(body.Descendants<DeletedRun>()
.Where(c => c.Author is not null && c.Author.Value == authorName).Cast<OpenXmlElement>().ToList());
// Handle the insertions.
HandleInsertions(body, authorName);

deletions.AddRange(body.Descendants<DeletedMathControl>()
.Where(c => c.Author is not null && c.Author.Value == authorName).Cast<OpenXmlElement>().ToList());
// Handle move from elements.
RemoveElements(body.Descendants<Paragraph>()
.Where(p => p.Descendants<MoveFrom>()
.Any(m => m.Author?.Value == authorName)));
RemoveElements(body.Descendants<MoveFromRangeEnd>());

foreach (OpenXmlElement deletion in deletions)
{
deletion.Remove();
}

// Handle the insertions.
List<OpenXmlElement> insertions =
body.Descendants<Inserted>()
.Where(c => c.Author is not null && c.Author.Value == authorName).Cast<OpenXmlElement>().ToList();
// Handle move to elements.
HandleMoveToElements(body, authorName);
}
}

insertions.AddRange(body.Descendants<InsertedRun>()
.Where(c => c.Author is not null && c.Author.Value == authorName).Cast<OpenXmlElement>().ToList());
// Method to remove elements from the document body
static void RemoveElements(IEnumerable<OpenXmlElement> elements)
{
foreach (var element in elements.ToList())
{
element.Remove();
}
}

insertions.AddRange(body.Descendants<InsertedMathControl>()
.Where(c => c.Author is not null && c.Author.Value == authorName).Cast<OpenXmlElement>().ToList());
// 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();
Copy link
Preview

Copilot AI Mar 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inserted elements are not filtered by author; consider filtering by authorName to maintain consistency with the handling of other insertion elements.

Suggested change
var insertions = body.Descendants<Inserted>().Cast<OpenXmlElement>().ToList();
var insertions = body.Descendants<Inserted>().Where(c => c.Author?.Value == authorName).Cast<OpenXmlElement>().ToList();

Copilot uses AI. Check for mistakes.

insertions.AddRange(body.Descendants<InsertedRun>().Where(c => c.Author?.Value == authorName));
insertions.AddRange(body.Descendants<InsertedMathControl>().Where(c => c.Author?.Value == authorName));

foreach (OpenXmlElement insertion in insertions)
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>())
{
// Found new content.
// Promote them to the same level as node, and then delete the node.
foreach (var run in insertion.Elements<Run>())

if (run == insertion.FirstChild)
{
insertion.InsertAfterSelf(new Run(run.OuterXml));
}
else
{
if (run == insertion.FirstChild)
{
insertion.InsertAfterSelf(new Run(run.OuterXml));
}
else
{
OpenXmlElement nextSibling = insertion.NextSibling()!;
nextSibling.InsertAfterSelf(new Run(run.OuterXml));
}
OpenXmlElement nextSibling = insertion.NextSibling()!;
nextSibling.InsertAfterSelf(new Run(run.OuterXml));
}
}

insertion.RemoveAttribute("rsidR", "https://schemas.openxmlformats.org/wordprocessingml/2006/main");
insertion.RemoveAttribute("rsidRPr", "https://schemas.openxmlformats.org/wordprocessingml/2006/main");
insertion.Remove();
// 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 moveToElements = body.Descendants<MoveToRun>().Cast<OpenXmlElement>().ToList();
moveToElements.AddRange(body.Descendants<Paragraph>()
.Where(p => p.Descendants<MoveFrom>()
.Any(m => m.Author?.Value == authorName)));
moveToElements.AddRange(body.Descendants<MoveToRangeEnd>());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var moveToElements = body.Descendants<MoveToRun>().Cast<OpenXmlElement>().ToList();
moveToElements.AddRange(body.Descendants<Paragraph>()
.Where(p => p.Descendants<MoveFrom>()
.Any(m => m.Author?.Value == authorName)));
moveToElements.AddRange(body.Descendants<MoveToRangeEnd>());
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>();
var moveToElements = [.. paragraphs, .. moveToRun, .. moveToRangeEnd];

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AddRange feels out of place with all the LINQ stuff - better to stay in one camp or the other

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the VB, could do the equivalent of

var moveToElements = paragraphs.Concat(moveToRun).Concat(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();
}
}
}
22 changes: 22 additions & 0 deletions samples/word/accept_all_revisions/cs/accept_all_revisions_cs.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.12.35728.132 d17.12
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "accept_all_revisions_cs", "accept_all_revisions_cs.csproj", "{FDDDBEF7-7D8F-4623-95E9-5E30BB031414}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{FDDDBEF7-7D8F-4623-95E9-5E30BB031414}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FDDDBEF7-7D8F-4623-95E9-5E30BB031414}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FDDDBEF7-7D8F-4623-95E9-5E30BB031414}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FDDDBEF7-7D8F-4623-95E9-5E30BB031414}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
111 changes: 67 additions & 44 deletions samples/word/accept_all_revisions/vb/Program.vb
Original file line number Diff line number Diff line change
@@ -1,66 +1,89 @@
Imports DocumentFormat.OpenXml
Imports DocumentFormat.OpenXml.Packaging
Imports DocumentFormat.OpenXml.Wordprocessing
Imports System
Imports System.Collections.Generic
Imports System.Linq

Module Program
Sub Main(args As String())
Dim fileName = args(0)
Dim authorName = args(1)
AcceptAllRevisions(args(0), args(1))
End Sub

'Public Sub AcceptRevisions(ByVal fileName As String, ByVal authorName As String)
' Given a document name and an author name, accept revisions.
Sub AcceptAllRevisions(fileName As String, authorName As String)
Using wdDoc As WordprocessingDocument = WordprocessingDocument.Open(fileName, True)
If wdDoc.MainDocumentPart Is Nothing OrElse wdDoc.MainDocumentPart.Document.Body Is Nothing Then
Throw New ArgumentNullException("MainDocumentPart and/or Body is null.")
End If

Dim body As Body = wdDoc.MainDocumentPart.Document.Body

' Handle the formatting changes.
Dim changes As List(Of OpenXmlElement) =
body.Descendants(Of ParagraphPropertiesChange)() _
.Where(Function(c) c.Author.Value = authorName).Cast(Of OpenXmlElement)().ToList()

For Each change In changes
change.Remove()
Next
RemoveElements(body.Descendants(Of ParagraphPropertiesChange)().Where(Function(c) c.Author?.Value = authorName))

' Handle the deletions.
Dim deletions As List(Of OpenXmlElement) =
body.Descendants(Of Deleted)() _
.Where(Function(c) c.Author.Value = authorName).Cast(Of OpenXmlElement)().ToList()
RemoveElements(body.Descendants(Of Deleted)().Where(Function(c) c.Author?.Value = authorName))
RemoveElements(body.Descendants(Of DeletedRun)().Where(Function(c) c.Author?.Value = authorName))
RemoveElements(body.Descendants(Of DeletedMathControl)().Where(Function(c) c.Author?.Value = authorName))

deletions.AddRange(body.Descendants(Of DeletedRun)() _
.Where(Function(c) c.Author.Value = authorName).Cast(Of OpenXmlElement)().ToList())
' Handle the insertions.
HandleInsertions(body, authorName)

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

For Each deletion In deletions
deletion.Remove()
Next
' Handle move to elements.
HandleMoveToElements(body, authorName)
End Using
End Sub

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

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

insertions.AddRange(body.Descendants(Of InsertedMathControl)() _
.Where(Function(c) c.Author.Value = authorName).Cast(Of OpenXmlElement)().ToList())
For Each insertion In insertions
' Promote new content to the same level as the node and then delete the node
For Each run In insertion.Elements(Of Run)()
If run Is insertion.FirstChild Then
insertion.InsertAfterSelf(New Run(run.OuterXml))
Else
Dim nextSibling As OpenXmlElement = insertion.NextSibling()
nextSibling.InsertAfterSelf(New Run(run.OuterXml))
End If
Next

For Each insertion In insertions
' Found new content. Promote them to the same level as node, and then
' delete the node.
For Each run In insertion.Elements(Of Run)()
If run Is insertion.FirstChild Then
insertion.InsertAfterSelf(New Run(run.OuterXml))
Else
insertion.NextSibling().InsertAfterSelf(New Run(run.OuterXml))
End If
Next
insertion.RemoveAttribute("rsidR", "https://schemas.openxmlformats.org/wordprocessingml/2006/main")
insertion.RemoveAttribute("rsidRPr", "https://schemas.openxmlformats.org/wordprocessingml/2006/main")
insertion.Remove()
' 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()
Next
End Sub

' Method to handle move-to elements in the document body
Sub HandleMoveToElements(body As Body, authorName As String)
' Collect all move-to elements by the specified author
Dim moveToElements As List(Of OpenXmlElement) = body.Descendants(Of MoveToRun)().Cast(Of OpenXmlElement)().ToList()
moveToElements.AddRange(body.Descendants(Of Paragraph)().Where(Function(p) p.Descendants(Of MoveFrom)().Any(Function(m) m.Author?.Value = authorName)))
moveToElements.AddRange(body.Descendants(Of MoveToRangeEnd)())

For Each toElement In moveToElements
' Promote new content to the same level as the node and then delete the node
For Each run In toElement.Elements(Of Run)()
toElement.InsertBeforeSelf(New Run(run.OuterXml))
Next
End Using
' Remove the move-to element itself
toElement.Remove()
Next
End Sub
End Module
End Module
22 changes: 22 additions & 0 deletions samples/word/accept_all_revisions/vb/accept_all_revisions_vb.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.12.35728.132 d17.12
MinimumVisualStudioVersion = 10.0.40219.1
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "accept_all_revisions_vb", "accept_all_revisions_vb.vbproj", "{9B669D97-F249-4437-9314-6A7ABAC50451}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{9B669D97-F249-4437-9314-6A7ABAC50451}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9B669D97-F249-4437-9314-6A7ABAC50451}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9B669D97-F249-4437-9314-6A7ABAC50451}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9B669D97-F249-4437-9314-6A7ABAC50451}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal