// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Xml.Linq;
using DocumentFormat.OpenXml.Packaging;
namespace OpenXmlPowerTools
{
internal class ReverseRevisionsInfo
{
public bool InInsert;
}
public static class RevisionProcessor
{
public static WmlDocument RejectRevisions(WmlDocument document)
{
using (var streamDoc = new OpenXmlMemoryStreamDocument(document))
{
using (WordprocessingDocument doc = streamDoc.GetWordprocessingDocument())
{
RejectRevisions(doc);
}
return streamDoc.GetModifiedWmlDocument();
}
}
public static void RejectRevisions(WordprocessingDocument doc)
{
// Reject revisions for those revisions that can't be rejected by inverting the sense
// of the revision, and then accepting.
RejectRevisionsForPart(doc.MainDocumentPart);
foreach (HeaderPart part in doc.MainDocumentPart.HeaderParts)
{
RejectRevisionsForPart(part);
}
foreach (FooterPart part in doc.MainDocumentPart.FooterParts)
{
RejectRevisionsForPart(part);
}
if (doc.MainDocumentPart.EndnotesPart is EndnotesPart endnotesPart1)
{
RejectRevisionsForPart(endnotesPart1);
}
if (doc.MainDocumentPart.FootnotesPart is FootnotesPart footnotesPart1)
{
RejectRevisionsForPart(footnotesPart1);
}
if (doc.MainDocumentPart.StyleDefinitionsPart is StyleDefinitionsPart styleDefinitionsPart1)
{
RejectRevisionsForStylesDefinitionPart(styleDefinitionsPart1);
}
// Invert the sense of the revisions and accept those reverse revisions.
ReverseRevisions(doc);
AcceptRevisionsForPart(doc.MainDocumentPart);
foreach (HeaderPart part in doc.MainDocumentPart.HeaderParts)
{
AcceptRevisionsForPart(part);
}
foreach (FooterPart part in doc.MainDocumentPart.FooterParts)
{
AcceptRevisionsForPart(part);
}
if (doc.MainDocumentPart.EndnotesPart is EndnotesPart endnotesPart2)
{
AcceptRevisionsForPart(endnotesPart2);
}
if (doc.MainDocumentPart.FootnotesPart is FootnotesPart footnotesPart2)
{
AcceptRevisionsForPart(footnotesPart2);
}
if (doc.MainDocumentPart.StyleDefinitionsPart is StyleDefinitionsPart styleDefinitionsPart2)
{
AcceptRevisionsForStylesDefinitionPart(styleDefinitionsPart2);
}
}
// Reject revisions for those revisions that can't be rejected by inverting the sense of the revision, and then accepting.
private static void RejectRevisionsForPart(OpenXmlPart part)
{
XElement root = part.GetXElement();
object newRoot = RejectRevisionsForPartTransform(root);
root.ReplaceWith(newRoot);
part.PutXElement();
}
private static object RejectRevisionsForPartTransform(XNode node)
{
if (node is XElement element)
{
////////////////////////////////////////////////////////////////////////////////////////
// Inserted Numbering Properties
#if false
This is a test.
#endif
if (element.Name == W.numPr && element.Element(W.ins) != null)
{
return null;
}
////////////////////////////////////////////////////////////////////////////////////////
// Paragraph properties change
#if false
When you click Online Video, you can paste in the embed code for the video you want to add.
#endif
if (element.Name == W.pPr && element.Element(W.pPrChange) is XElement pPrChange)
{
XElement newPPr = pPrChange.Element(W.pPr) is XElement pPr
? new XElement(pPr)
: new XElement(W.pPr);
newPPr.Add(RejectRevisionsForPartTransform(element.Element(W.rPr)));
return RejectRevisionsForPartTransform(newPPr);
}
////////////////////////////////////////////////////////////////////////////////////////
// Run properties change
#if false
When you click Online Video, you can paste in the embed code for the video you want to add.
#endif
if (element.Name == W.rPr && element.Element(W.rPrChange) is XElement rPrChange)
{
XElement newRPr = rPrChange.Element(W.rPr);
return RejectRevisionsForPartTransform(newRPr);
}
////////////////////////////////////////////////////////////////////////////////////////
// Field code numbering change
#if false
LISTNUM
Video provides a powerful way to help you prove your point.
#endif
if (element.Name == W.numberingChange)
{
return null;
}
////////////////////////////////////////////////////////////////////////////////////
// Change w:sectPr
#if false
#endif
if (element.Name == W.sectPr && element.Element(W.sectPrChange) is XElement sectPrChange)
{
XElement newSectPr = sectPrChange.Element(W.sectPr);
return RejectRevisionsForPartTransform(newSectPr);
}
////////////////////////////////////////////////////////////////////////////////////
// tblGridChange
#if false
#endif
if (element.Name == W.tblGrid && element.Element(W.tblGridChange) is XElement tblGridChange)
{
XElement newTblGrid = tblGridChange.Element(W.tblGrid);
return RejectRevisionsForPartTransform(newTblGrid);
}
////////////////////////////////////////////////////////////////////////////////////
// tcPrChange
#if false
1
#endif
if (element.Name == W.tcPr && element.Element(W.tcPrChange) is XElement tcPrChange)
{
XElement newTcPr = tcPrChange.Element(W.tcPr);
return RejectRevisionsForPartTransform(newTcPr);
}
////////////////////////////////////////////////////////////////////////////////////
// trPrChange
if (element.Name == W.trPr && element.Element(W.trPrChange) is XElement trPrChange)
{
XElement newTrPr = trPrChange.Element(W.trPr);
return RejectRevisionsForPartTransform(newTrPr);
}
////////////////////////////////////////////////////////////////////////////////////
// tblPrExChange
#if false
#endif
#if false
#endif
if (element.Name == W.tblPrEx && element.Element(W.tblPrExChange) is XElement tblPrExChange)
{
XElement newTblPrEx = tblPrExChange.Element(W.tblPrEx);
return RejectRevisionsForPartTransform(newTblPrEx);
}
////////////////////////////////////////////////////////////////////////////////////
// tblPrChange
#if false
#endif
if (element.Name == W.tblPr && element.Element(W.tblPrChange) is XElement tblPrChange)
{
XElement newTrPr = tblPrChange.Element(W.tblPr);
return RejectRevisionsForPartTransform(newTrPr);
}
////////////////////////////////////////////////////////////////////////////////////
// tblPrChange
#if false
#endif
if (element.Name == W.cellDel ||
element.Name == W.cellMerge)
{
return null;
}
if (element.Name == W.tc &&
element.Elements(W.tcPr).Elements(W.cellIns).Any())
{
return null;
}
return new XElement(element.Name,
element.Attributes(),
element.Nodes().Select(RejectRevisionsForPartTransform));
}
return node;
}
private static void RejectRevisionsForStylesDefinitionPart(StyleDefinitionsPart stylesDefinitionsPart)
{
XElement root = stylesDefinitionsPart.GetXElement();
object newRoot = RejectRevisionsForStylesTransform(root);
root.ReplaceWith(newRoot);
stylesDefinitionsPart.PutXElement();
}
private static object RejectRevisionsForStylesTransform(XNode node)
{
if (node is XElement element)
{
if (element.Name == W.pPr && element.Element(W.pPrChange) is XElement pPrChange)
{
XElement newPPr = pPrChange.Element(W.pPr);
return RejectRevisionsForStylesTransform(newPPr);
}
if (element.Name == W.rPr && element.Element(W.rPrChange) is XElement rPrChange)
{
XElement newRPr = rPrChange.Element(W.rPr);
return RejectRevisionsForStylesTransform(newRPr);
}
return new XElement(element.Name,
element.Attributes(),
element.Nodes().Select(RejectRevisionsForStylesTransform));
}
return node;
}
private static void ReverseRevisions(WordprocessingDocument doc)
{
ReverseRevisionsForPart(doc.MainDocumentPart);
foreach (HeaderPart part in doc.MainDocumentPart.HeaderParts)
{
ReverseRevisionsForPart(part);
}
foreach (FooterPart part in doc.MainDocumentPart.FooterParts)
{
ReverseRevisionsForPart(part);
}
if (doc.MainDocumentPart.EndnotesPart != null)
{
ReverseRevisionsForPart(doc.MainDocumentPart.EndnotesPart);
}
if (doc.MainDocumentPart.FootnotesPart != null)
{
ReverseRevisionsForPart(doc.MainDocumentPart.FootnotesPart);
}
}
private static void ReverseRevisionsForPart(OpenXmlPart part)
{
XElement root = part.GetXElement();
var rri = new ReverseRevisionsInfo { InInsert = false };
var newRoot = (XElement)ReverseRevisionsTransform(root, rri);
newRoot = (XElement)RemoveRsidTransform(newRoot);
root.ReplaceWith(newRoot);
part.PutXElement();
}
private static object RemoveRsidTransform(XNode node)
{
if (node is XElement element)
{
if (element.Name == W.rsid)
return null;
return new XElement(element.Name,
element.Attributes().Where(a => a.Name != W.rsid &&
a.Name != W.rsidDel &&
a.Name != W.rsidP &&
a.Name != W.rsidR &&
a.Name != W.rsidRDefault &&
a.Name != W.rsidRPr &&
a.Name != W.rsidSect &&
a.Name != W.rsidTr),
element.Nodes().Select(RemoveRsidTransform));
}
return node;
}
private static object MergeAdjacentTablesTransform(XNode node)
{
if (node is XElement element)
{
if (element.Element(W.tbl) != null)
{
IEnumerable> grouped = element
.Elements()
.GroupAdjacent(e =>
{
if (e.Name != W.tbl)
return "";
XElement bidiVisual = e.Elements(W.tblPr).Elements(W.bidiVisual).FirstOrDefault();
string bidiVisString = bidiVisual == null ? "" : "|bidiVisual";
string key = "tbl" + bidiVisString;
return key;
});
IEnumerable