/*************************************************************************** Copyright (c) Microsoft Corporation 2011. This code is licensed using the Microsoft Public License (Ms-PL). The text of the license can be found here: http://www.microsoft.com/resources/sharedsource/licensingbasics/publiclicense.mspx ***************************************************************************/ #define TestForUnsupportedDocuments using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Xml.Linq; using DocumentFormat.OpenXml.Packaging; namespace OpenXmlPowerTools { public partial class WmlDocument : OpenXmlPowerToolsDocument { public IEnumerable SplitOnSections() { return DocumentBuilder.SplitOnSections(this); } } public class Source { public WmlDocument WmlDocument { get; set; } public int Start { get; set; } public int Count { get; set; } public bool KeepSections { get; set; } public string InsertId { get; set; } public Source(WmlDocument source, bool keepSections) { WmlDocument = source; Start = 0; Count = Int32.MaxValue; KeepSections = keepSections; InsertId = null; } public Source(WmlDocument source, string insertId) { WmlDocument = source; Start = 0; Count = Int32.MaxValue; KeepSections = false; InsertId = insertId; } public Source(WmlDocument source, int start, bool keepSections) { WmlDocument = source; Start = start; Count = Int32.MaxValue; KeepSections = keepSections; InsertId = null; } public Source(WmlDocument source, int start, string insertId) { WmlDocument = source; Start = start; Count = Int32.MaxValue; KeepSections = false; InsertId = insertId; } public Source(WmlDocument source, int start, int count, bool keepSections) { WmlDocument = source; Start = start; Count = count; KeepSections = keepSections; InsertId = null; } public Source(WmlDocument source, int start, int count, string insertId) { WmlDocument = source; Start = start; Count = count; KeepSections = false; InsertId = insertId; } } public static class DocumentBuilder { public static void BuildDocument(List sources, string fileName) { using (OpenXmlMemoryStreamDocument streamDoc = OpenXmlMemoryStreamDocument.CreateWordprocessingDocument()) { using (WordprocessingDocument output = streamDoc.GetWordprocessingDocument()) { BuildDocument(sources, output); output.Close(); } streamDoc.GetModifiedDocument().SaveAs(fileName); } } public static WmlDocument BuildDocument(List sources) { using (OpenXmlMemoryStreamDocument streamDoc = OpenXmlMemoryStreamDocument.CreateWordprocessingDocument()) { using (WordprocessingDocument output = streamDoc.GetWordprocessingDocument()) { BuildDocument(sources, output); output.Close(); } return streamDoc.GetModifiedWmlDocument(); } } private struct TempSource { public int Start; public int Count; }; public static IEnumerable SplitOnSections(WmlDocument doc) { List tempSourceList; using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(doc)) using (WordprocessingDocument document = streamDoc.GetWordprocessingDocument()) { XDocument mainDocument = document.MainDocumentPart.GetXDocument(); var divs = mainDocument .Root .Element(W.body) .Elements() .Select((p, i) => new { BlockLevelContent = p, Index = i, }) .Rollup(new { BlockLevelContent = (XElement)null, Index = -1, Div = 0, }, (b, p) => { XElement elementBefore = b.BlockLevelContent .ElementsBeforeSelfReverseDocumentOrder() .FirstOrDefault(); if (elementBefore != null && elementBefore.Descendants(W.sectPr).Any()) return new { BlockLevelContent = b.BlockLevelContent, Index = b.Index, Div = p.Div + 1, }; return new { BlockLevelContent = b.BlockLevelContent, Index = b.Index, Div = p.Div, }; }); var groups = divs .GroupAdjacent(b => b.Div); tempSourceList = groups .Select(g => new TempSource { Start = g.First().Index, Count = g.Count(), }) .ToList(); foreach (var ts in tempSourceList) { List sources = new List() { new Source(doc, ts.Start, ts.Count, true) }; WmlDocument newDoc = DocumentBuilder.BuildDocument(sources); newDoc = AdjustSectionBreak(newDoc); yield return newDoc; } } } private static WmlDocument AdjustSectionBreak(WmlDocument doc) { using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(doc)) { using (WordprocessingDocument document = streamDoc.GetWordprocessingDocument()) { XDocument mainXDoc = document.MainDocumentPart.GetXDocument(); XElement lastElement = mainXDoc.Root .Element(W.body) .Elements() .LastOrDefault(); if (lastElement != null) { if (lastElement.Name != W.sectPr && lastElement.Descendants(W.sectPr).Any()) { mainXDoc.Root.Element(W.body).Add(lastElement.Descendants(W.sectPr).First()); lastElement.Descendants(W.sectPr).Remove(); if (!lastElement.Elements() .Where(e => e.Name != W.pPr) .Any()) lastElement.Remove(); document.MainDocumentPart.PutXDocument(); } } } return streamDoc.GetModifiedWmlDocument(); } } private static void BuildDocument(List sources, WordprocessingDocument output) { if (RelationshipMarkup == null) RelationshipMarkup = new Dictionary() { //{ button, new [] { image }}, { A.blip, new [] { R.embed, R.link }}, { A.hlinkClick, new [] { R.id }}, { A.relIds, new [] { R.cs, R.dm, R.lo, R.qs }}, //{ a14:imgLayer, new [] { R.embed }}, //{ ax:ocx, new [] { R.id }}, { C.chart, new [] { R.id }}, { C.externalData, new [] { R.id }}, { C.userShapes, new [] { R.id }}, { DGM.relIds, new [] { R.cs, R.dm, R.lo, R.qs }}, { O.OLEObject, new [] { R.id }}, { VML.fill, new [] { R.id }}, { VML.imagedata, new [] { R.href, R.id, R.pict }}, { VML.stroke, new [] { R.id }}, { W.altChunk, new [] { R.id }}, { W.attachedTemplate, new [] { R.id }}, { W.control, new [] { R.id }}, { W.dataSource, new [] { R.id }}, { W.embedBold, new [] { R.id }}, { W.embedBoldItalic, new [] { R.id }}, { W.embedItalic, new [] { R.id }}, { W.embedRegular, new [] { R.id }}, { W.footerReference, new [] { R.id }}, { W.headerReference, new [] { R.id }}, { W.headerSource, new [] { R.id }}, { W.hyperlink, new [] { R.id }}, { W.printerSettings, new [] { R.id }}, { W.recipientData, new [] { R.id }}, // Mail merge, not required { W.saveThroughXslt, new [] { R.id }}, { W.sourceFileName, new [] { R.id }}, // Framesets, not required { W.src, new [] { R.id }}, // Mail merge, not required { W.subDoc, new [] { R.id }}, // Sub documents, not required //{ w14:contentPart, new [] { R.id }}, { WNE.toolbarData, new [] { R.id }}, }; // This list is used to eliminate duplicate images List images = new List(); XDocument mainPart = output.MainDocumentPart.GetXDocument(); mainPart.Declaration.Standalone = "yes"; mainPart.Declaration.Encoding = "UTF-8"; mainPart.Root.ReplaceWith( new XElement(W.document, NamespaceAttributes, new XElement(W.body))); if (sources.Count > 0) { using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(sources[0].WmlDocument)) using (WordprocessingDocument doc = streamDoc.GetWordprocessingDocument()) { CopyStartingParts(doc, output, images); } int sourceNum = 0; foreach (Source source in sources) { if (source.InsertId != null) { while (true) { XDocument mainXDoc = output.MainDocumentPart.GetXDocument(); if (!mainXDoc.Descendants(PtOpenXml.Insert).Any(d => (string)d.Attribute(PtOpenXml.Id) == source.InsertId)) break; using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(source.WmlDocument)) using (WordprocessingDocument doc = streamDoc.GetWordprocessingDocument()) { #if TestForUnsupportedDocuments // throws exceptions if a document contains unsupported content TestForUnsupportedDocument(doc, sources.IndexOf(source)); #endif List contents = doc.MainDocumentPart.GetXDocument() .Root .Element(W.body) .Elements() .Skip(source.Start) .Take(source.Count) .ToList(); try { AppendDocument(doc, output, contents, source.KeepSections, source.InsertId, images); } catch (DocumentBuilderInternalException dbie) { if (dbie.Message.Contains("{0}")) throw new DocumentBuilderException(string.Format(dbie.Message, sourceNum)); else throw dbie; } } } } else { using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(source.WmlDocument)) using (WordprocessingDocument doc = streamDoc.GetWordprocessingDocument()) { #if TestForUnsupportedDocuments // throws exceptions if a document contains unsupported content TestForUnsupportedDocument(doc, sources.IndexOf(source)); #endif List contents = doc.MainDocumentPart.GetXDocument() .Root .Element(W.body) .Elements() .Skip(source.Start) .Take(source.Count) .ToList(); try { AppendDocument(doc, output, contents, source.KeepSections, null, images); } catch (DocumentBuilderInternalException dbie) { if (dbie.Message.Contains("{0}")) throw new DocumentBuilderException(string.Format(dbie.Message, sourceNum)); else throw dbie; } } } ++sourceNum; } if (!sources.Any(s => s.KeepSections)) { using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(sources[0].WmlDocument)) using (WordprocessingDocument doc = streamDoc.GetWordprocessingDocument()) { var sectPr = doc.MainDocumentPart.GetXDocument().Root.Element(W.body) .Elements().Last(); if (sectPr.Name == W.sectPr) { AddSectionAndDependencies(doc, output, sectPr, images); output.MainDocumentPart.GetXDocument().Root.Element(W.body).Add(sectPr); } } } else FixUpSectionProperties(output); } foreach (var part in output.GetAllParts()) if (part.Annotation() != null) part.PutXDocument(); } private static void TestPartForUnsupportedContent(OpenXmlPart part, int sourceNumber) { XNamespace[] obsoleteNamespaces = new[] { XNamespace.Get("http://schemas.microsoft.com/office/word/2007/5/30/wordml"), XNamespace.Get("http://schemas.microsoft.com/office/word/2008/9/16/wordprocessingDrawing"), XNamespace.Get("http://schemas.microsoft.com/office/word/2009/2/wordml"), }; XDocument xDoc = part.GetXDocument(); XElement invalidElement = xDoc.Descendants() .FirstOrDefault(d => { bool b = d.Name == W.subDoc || d.Name == W.control || d.Name == W.altChunk || d.Name.LocalName == "contentPart" || obsoleteNamespaces.Contains(d.Name.Namespace); bool b2 = b || d.Attributes().Any(a => obsoleteNamespaces.Contains(a.Name.Namespace)); return b2; }); if (invalidElement != null) { if (invalidElement.Name == W.subDoc) throw new DocumentBuilderException(String.Format("Source {0} is unsupported document - contains sub document", sourceNumber)); if (invalidElement.Name == W.control) throw new DocumentBuilderException(String.Format("Source {0} is unsupported document - contains ActiveX controls", sourceNumber)); if (invalidElement.Name == W.altChunk) throw new DocumentBuilderException(String.Format("Source {0} is unsupported document - contains altChunk", sourceNumber)); if (invalidElement.Name.LocalName == "contentPart") throw new DocumentBuilderException(String.Format("Source {0} is unsupported document - contains contentPart content", sourceNumber)); if (obsoleteNamespaces.Contains(invalidElement.Name.Namespace) || invalidElement.Attributes().Any(a => obsoleteNamespaces.Contains(a.Name.Namespace))) throw new DocumentBuilderException(String.Format("Source {0} is unsupported document - contains obsolete namespace", sourceNumber)); } } //What does not work: //- sub docs //- bidi text appears to work but has not been tested //- languages other than en-us appear to work but have not been tested //- documents with activex controls //- mail merge source documents (look for dataSource in settings) //- documents with ink //- documents with frame sets and frames private static void TestForUnsupportedDocument(WordprocessingDocument doc, int sourceNumber) { TestPartForUnsupportedContent(doc.MainDocumentPart, sourceNumber); foreach (var hdr in doc.MainDocumentPart.HeaderParts) TestPartForUnsupportedContent(hdr, sourceNumber); foreach (var ftr in doc.MainDocumentPart.FooterParts) TestPartForUnsupportedContent(ftr, sourceNumber); if (doc.MainDocumentPart.FootnotesPart != null) TestPartForUnsupportedContent(doc.MainDocumentPart.FootnotesPart, sourceNumber); if (doc.MainDocumentPart.EndnotesPart != null) TestPartForUnsupportedContent(doc.MainDocumentPart.EndnotesPart, sourceNumber); if (doc.MainDocumentPart.DocumentSettingsPart != null && doc.MainDocumentPart.DocumentSettingsPart.GetXDocument().Descendants().Any(d => d.Name == W.src || d.Name == W.recipientData || d.Name == W.mailMerge)) throw new DocumentBuilderException(String.Format("Source {0} is unsupported document - contains Mail Merge content", sourceNumber)); if (doc.MainDocumentPart.WebSettingsPart != null && doc.MainDocumentPart.WebSettingsPart.GetXDocument().Descendants().Any(d => d.Name == W.frameset)) throw new DocumentBuilderException(String.Format("Source {0} is unsupported document - contains a frameset", sourceNumber)); if (doc.MainDocumentPart.GetXDocument().Descendants(W.numPr).Any() && doc.MainDocumentPart.NumberingDefinitionsPart == null) throw new DocumentBuilderException(String.Format( "Source {0} is invalid document - contains numbering markup but no numbering part", sourceNumber)); } private static void FixUpSectionProperties(WordprocessingDocument newDocument) { XDocument mainDocumentXDoc = newDocument.MainDocumentPart.GetXDocument(); mainDocumentXDoc.Declaration.Standalone = "yes"; mainDocumentXDoc.Declaration.Encoding = "UTF-8"; XElement body = mainDocumentXDoc.Root.Element(W.body); var sectionPropertiesToMove = body .Elements() .Take(body.Elements().Count() - 1) .Where(e => e.Name == W.sectPr) .ToList(); foreach (var s in sectionPropertiesToMove) { var p = s.ElementsBeforeSelfReverseDocumentOrder().First(); if (p.Element(W.pPr) == null) p.AddFirst(new XElement(W.pPr)); p.Element(W.pPr).Add(s); } foreach (var s in sectionPropertiesToMove) s.Remove(); } private static void AddSectionAndDependencies(WordprocessingDocument sourceDocument, WordprocessingDocument newDocument, XElement sectionMarkup, List images) { var headerReferences = sectionMarkup.Descendants(W.headerReference); foreach (var headerReference in headerReferences) { string oldRid = headerReference.Attribute(R.id).Value; HeaderPart oldHeaderPart = (HeaderPart)sourceDocument.MainDocumentPart.GetPartById(oldRid); XDocument oldHeaderXDoc = oldHeaderPart.GetXDocument(); HeaderPart newHeaderPart = newDocument.MainDocumentPart.AddNewPart(); XDocument newHeaderXDoc = newHeaderPart.GetXDocument(); newHeaderXDoc.Declaration.Standalone = "yes"; newHeaderXDoc.Declaration.Encoding = "UTF-8"; newHeaderXDoc.Add(oldHeaderXDoc.Root); headerReference.Attribute(R.id).Value = newDocument.MainDocumentPart.GetIdOfPart(newHeaderPart); AddRelationships(oldHeaderPart, newHeaderPart, new[] { newHeaderXDoc.Root }); CopyRelatedPartsForContentParts(oldHeaderPart, newHeaderPart, new[] { newHeaderXDoc.Root }, images); } var footerReferences = sectionMarkup.Descendants(W.footerReference); foreach (var footerReference in footerReferences) { string oldRid = footerReference.Attribute(R.id).Value; FooterPart oldFooterPart = (FooterPart)sourceDocument.MainDocumentPart.GetPartById(oldRid); XDocument oldFooterXDoc = oldFooterPart.GetXDocument(); FooterPart newFooterPart = newDocument.MainDocumentPart.AddNewPart(); XDocument newFooterXDoc = newFooterPart.GetXDocument(); newFooterXDoc.Declaration.Standalone = "yes"; newFooterXDoc.Declaration.Encoding = "UTF-8"; newFooterXDoc.Add(oldFooterXDoc.Root); footerReference.Attribute(R.id).Value = newDocument.MainDocumentPart.GetIdOfPart(newFooterPart); AddRelationships(oldFooterPart, newFooterPart, new[] { newFooterXDoc.Root }); CopyRelatedPartsForContentParts(oldFooterPart, newFooterPart, new[] { newFooterXDoc.Root }, images); } } private static void MergeStyles(WordprocessingDocument sourceDocument, WordprocessingDocument newDocument, XDocument fromStyles, XDocument toStyles) { foreach (XElement style in fromStyles.Root.Elements(W.style)) { string name = style.Attribute(W.styleId).Value; if (toStyles .Root .Elements(W.style) .Where(o => o.Attribute(W.styleId).Value == name) .Count() == 0) { int number = 1; int abstractNumber = 0; XDocument oldNumbering = null; XDocument newNumbering = null; foreach (XElement numReference in style.Descendants(W.numPr)) { XElement idElement = numReference.Descendants(W.numId).FirstOrDefault(); if (idElement != null) { if (oldNumbering == null) oldNumbering = sourceDocument.MainDocumentPart.NumberingDefinitionsPart.GetXDocument(); if (newNumbering == null) { if (newDocument.MainDocumentPart.NumberingDefinitionsPart != null) { newNumbering = newDocument.MainDocumentPart.NumberingDefinitionsPart.GetXDocument(); newNumbering.Declaration.Standalone = "yes"; newNumbering.Declaration.Encoding = "UTF-8"; var numIds = newNumbering .Root .Elements(W.num) .Select(f => (int)f.Attribute(W.numId)); if (numIds.Any()) number = numIds.Max() + 1; numIds = newNumbering .Root .Elements(W.abstractNum) .Select(f => (int)f.Attribute(W.abstractNumId)); if (numIds.Any()) abstractNumber = numIds.Max() + 1; } else { newDocument.MainDocumentPart.AddNewPart(); newNumbering = newDocument.MainDocumentPart.NumberingDefinitionsPart.GetXDocument(); newNumbering.Declaration.Standalone = "yes"; newNumbering.Declaration.Encoding = "UTF-8"; newNumbering.Add(new XElement(W.numbering, NamespaceAttributes)); } } string numId = idElement.Attribute(W.val).Value; if (numId != "0") { XElement element = oldNumbering .Descendants() .Elements(W.num) .Where(p => ((string)p.Attribute(W.numId)) == numId) .First(); // Copy abstract numbering element, if necessary (use matching NSID) string abstractNumId = element .Elements(W.abstractNumId) .First() .Attribute(W.val) .Value; XElement abstractElement = oldNumbering .Descendants() .Elements(W.abstractNum) .Where(p => ((string)p.Attribute(W.abstractNumId)) == abstractNumId) .First(); string abstractNSID = abstractElement .Elements(W.nsid) .First() .Attribute(W.val) .Value; XElement newAbstractElement = newNumbering .Descendants() .Elements(W.abstractNum) .Where(p => ((string)p.Elements(W.nsid).First().Attribute(W.val)) == abstractNSID) .FirstOrDefault(); if (newAbstractElement == null) { newAbstractElement = new XElement(abstractElement); newAbstractElement.Attribute(W.abstractNumId).Value = abstractNumber.ToString(); abstractNumber++; if (newNumbering.Root.Elements(W.abstractNum).Any()) newNumbering.Root.Elements(W.abstractNum).Last().AddAfterSelf(newAbstractElement); else newNumbering.Root.Add(newAbstractElement); foreach (XElement pictId in newAbstractElement.Descendants(W.lvlPicBulletId)) { string bulletId = (string)pictId.Attribute(W.val); XElement numPicBullet = oldNumbering .Descendants(W.numPicBullet) .FirstOrDefault(d => (string)d.Attribute(W.numPicBulletId) == bulletId); int maxNumPicBulletId = new int[] { -1 }.Concat( newNumbering.Descendants(W.numPicBullet) .Attributes(W.numPicBulletId) .Select(a => (int)a)) .Max() + 1; XElement newNumPicBullet = new XElement(numPicBullet); newNumPicBullet.Attribute(W.numPicBulletId).Value = maxNumPicBulletId.ToString(); pictId.Attribute(W.val).Value = maxNumPicBulletId.ToString(); newNumbering.Root.AddFirst(newNumPicBullet); } } string newAbstractId = newAbstractElement.Attribute(W.abstractNumId).Value; // Copy numbering element, if necessary (use matching element with no overrides) XElement newElement = null; if (!element.Elements(W.lvlOverride).Any()) newElement = newNumbering .Descendants() .Elements(W.num) .Where(p => !p.Elements(W.lvlOverride).Any() && ((string)p.Elements(W.abstractNumId).First().Attribute(W.val)) == newAbstractId) .FirstOrDefault(); if (newElement == null) { newElement = new XElement(element); newElement .Elements(W.abstractNumId) .First() .Attribute(W.val).Value = newAbstractId; newElement.Attribute(W.numId).Value = number.ToString(); number++; newNumbering.Root.Add(newElement); } idElement.Attribute(W.val).Value = newElement.Attribute(W.numId).Value; } } } toStyles.Root.Add(new XElement(style)); } } } private static void MergeFontTables(XDocument fromFontTable, XDocument toFontTable) { foreach (XElement font in fromFontTable.Root.Elements(W.font)) { string name = font.Attribute(W.name).Value; if (toFontTable .Root .Elements(W.font) .Where(o => o.Attribute(W.name).Value == name) .Count() == 0) toFontTable.Root.Add(new XElement(font)); } } private static void CopyStylesAndFonts(WordprocessingDocument sourceDocument, WordprocessingDocument newDocument, IEnumerable newContent) { // Copy all styles to the new document if (sourceDocument.MainDocumentPart.StyleDefinitionsPart != null) { XDocument oldStyles = sourceDocument.MainDocumentPart.StyleDefinitionsPart.GetXDocument(); if (newDocument.MainDocumentPart.StyleDefinitionsPart == null) { newDocument.MainDocumentPart.AddNewPart(); XDocument newStyles = newDocument.MainDocumentPart.StyleDefinitionsPart.GetXDocument(); newStyles.Declaration.Standalone = "yes"; newStyles.Declaration.Encoding = "UTF-8"; newStyles.Add(oldStyles.Root); } else { XDocument newStyles = newDocument.MainDocumentPart.StyleDefinitionsPart.GetXDocument(); MergeStyles(sourceDocument, newDocument, oldStyles, newStyles); } } // Copy all styles with effects to the new document if (sourceDocument.MainDocumentPart.StylesWithEffectsPart != null) { XDocument oldStyles = sourceDocument.MainDocumentPart.StylesWithEffectsPart.GetXDocument(); if (newDocument.MainDocumentPart.StylesWithEffectsPart == null) { newDocument.MainDocumentPart.AddNewPart(); XDocument newStyles = newDocument.MainDocumentPart.StylesWithEffectsPart.GetXDocument(); newStyles.Declaration.Standalone = "yes"; newStyles.Declaration.Encoding = "UTF-8"; newStyles.Add(oldStyles.Root); } else { XDocument newStyles = newDocument.MainDocumentPart.StylesWithEffectsPart.GetXDocument(); MergeStyles(sourceDocument, newDocument, oldStyles, newStyles); } } // Copy fontTable to the new document if (sourceDocument.MainDocumentPart.FontTablePart != null) { XDocument oldFontTable = sourceDocument.MainDocumentPart.FontTablePart.GetXDocument(); if (newDocument.MainDocumentPart.FontTablePart == null) { newDocument.MainDocumentPart.AddNewPart(); XDocument newFontTable = newDocument.MainDocumentPart.FontTablePart.GetXDocument(); newFontTable.Declaration.Standalone = "yes"; newFontTable.Declaration.Encoding = "UTF-8"; newFontTable.Add(oldFontTable.Root); } else { XDocument newFontTable = newDocument.MainDocumentPart.FontTablePart.GetXDocument(); MergeFontTables(oldFontTable, newFontTable); } } } private static void CopyComments(WordprocessingDocument sourceDocument, WordprocessingDocument newDocument, IEnumerable newContent, List images) { Dictionary commentIdMap = new Dictionary(); int number = 0; XDocument oldComments = null; XDocument newComments = null; foreach (XElement comment in newContent.DescendantsAndSelf(W.commentReference)) { if (oldComments == null) oldComments = sourceDocument.MainDocumentPart.WordprocessingCommentsPart.GetXDocument(); if (newComments == null) { if (newDocument.MainDocumentPart.WordprocessingCommentsPart != null) { newComments = newDocument.MainDocumentPart.WordprocessingCommentsPart.GetXDocument(); newComments.Declaration.Standalone = "yes"; newComments.Declaration.Encoding = "UTF-8"; var ids = newComments.Root.Elements(W.comment).Select(f => (int)f.Attribute(W.id)); if (ids.Any()) number = ids.Max() + 1; } else { newDocument.MainDocumentPart.AddNewPart(); newComments = newDocument.MainDocumentPart.WordprocessingCommentsPart.GetXDocument(); newComments.Declaration.Standalone = "yes"; newComments.Declaration.Encoding = "UTF-8"; newComments.Add(new XElement(W.comments, NamespaceAttributes)); } } int id = (int)comment.Attribute(W.id); XElement element = oldComments .Descendants() .Elements(W.comment) .Where(p => ((int)p.Attribute(W.id)) == id) .First(); XElement newElement = new XElement(element); newElement.Attribute(W.id).Value = number.ToString(); newComments.Root.Add(newElement); commentIdMap.Add(id, number); number++; } foreach (var item in newContent.DescendantsAndSelf() .Where(d => d.Name == W.commentReference || d.Name == W.commentRangeStart || d.Name == W.commentRangeEnd)) item.Attribute(W.id).Value = commentIdMap[(int)item.Attribute(W.id)].ToString(); if (sourceDocument.MainDocumentPart.WordprocessingCommentsPart != null && newDocument.MainDocumentPart.WordprocessingCommentsPart != null) { AddRelationships(sourceDocument.MainDocumentPart.WordprocessingCommentsPart, newDocument.MainDocumentPart.WordprocessingCommentsPart, new[] { newDocument.MainDocumentPart.WordprocessingCommentsPart.GetXDocument().Root }); CopyRelatedPartsForContentParts(sourceDocument.MainDocumentPart.WordprocessingCommentsPart, newDocument.MainDocumentPart.WordprocessingCommentsPart, new[] { newDocument.MainDocumentPart.WordprocessingCommentsPart.GetXDocument().Root }, images); } } private static void AdjustUniqueIds(WordprocessingDocument sourceDocument, WordprocessingDocument newDocument, IEnumerable newContent) { // adjust bookmark unique ids int maxId = 0; if (newDocument.MainDocumentPart.GetXDocument().Descendants(W.bookmarkStart).Any()) maxId = newDocument.MainDocumentPart.GetXDocument().Descendants(W.bookmarkStart) .Select(d => (int)d.Attribute(W.id)).Max(); Dictionary bookmarkIdMap = new Dictionary(); foreach (var item in newContent.DescendantsAndSelf().Where(bm => bm.Name == W.bookmarkStart || bm.Name == W.bookmarkEnd)) { int id = (int)item.Attribute(W.id); if (!bookmarkIdMap.ContainsKey(id)) bookmarkIdMap.Add(id, ++maxId); } foreach (var bookmarkElement in newContent.DescendantsAndSelf().Where(e => e.Name == W.bookmarkStart || e.Name == W.bookmarkEnd)) bookmarkElement.Attribute(W.id).Value = bookmarkIdMap[(int)bookmarkElement.Attribute(W.id)].ToString(); // adjust shape unique ids // This doesn't work because OLEObjects refer to shapes by ID. // Punting on this, because sooner or later, this will be a non-issue. //foreach (var item in newContent.DescendantsAndSelf(VML.shape)) //{ // Guid g = Guid.NewGuid(); // string s = "R" + g.ToString().Replace("-", ""); // item.Attribute(NoNamespace.id).Value = s; //} } private static void AdjustDocPrIds(WordprocessingDocument newDocument) { int docPrId = 0; foreach (var item in newDocument.MainDocumentPart.GetXDocument().Descendants(WP.docPr)) item.Attribute(NoNamespace.id).Value = (++docPrId).ToString(); foreach (var header in newDocument.MainDocumentPart.HeaderParts) foreach (var item in header.GetXDocument().Descendants(WP.docPr)) item.Attribute(NoNamespace.id).Value = (++docPrId).ToString(); foreach (var footer in newDocument.MainDocumentPart.FooterParts) foreach (var item in footer.GetXDocument().Descendants(WP.docPr)) item.Attribute(NoNamespace.id).Value = (++docPrId).ToString(); if (newDocument.MainDocumentPart.FootnotesPart != null) foreach (var item in newDocument.MainDocumentPart.FootnotesPart.GetXDocument().Descendants(WP.docPr)) item.Attribute(NoNamespace.id).Value = (++docPrId).ToString(); if (newDocument.MainDocumentPart.EndnotesPart != null) foreach (var item in newDocument.MainDocumentPart.EndnotesPart.GetXDocument().Descendants(WP.docPr)) item.Attribute(NoNamespace.id).Value = (++docPrId).ToString(); } // This probably doesn't need to be done, except that the Open XML SDK will not validate // documents that contain the o:gfxdata attribute. private static void RemoveGfxdata(IEnumerable newContent) { newContent.DescendantsAndSelf().Attributes(O.gfxdata).Remove(); } private static object InsertTransform(XNode node, List newContent) { XElement element = node as XElement; if (element != null) { if (element.Annotation() != null) return newContent; return new XElement(element.Name, element.Attributes(), element.Nodes().Select(n => InsertTransform(n, newContent))); } return node; } private class ReplaceSemaphore { } // Rules for sections // - if KeepSections for all documents in the source collection are false, then it takes the section // from the first document. // - if you specify true for any document, and if the last section is part of the specified content, // then that section is copied. If any paragraph in the content has a section, then that section // is copied. // - if you specify true for any document, and there are no sections for any paragraphs, then no // sections are copied. private static void AppendDocument(WordprocessingDocument sourceDocument, WordprocessingDocument newDocument, List newContent, bool keepSection, string insertId, List images) { FixRanges(sourceDocument.MainDocumentPart.GetXDocument(), newContent); AddRelationships(sourceDocument.MainDocumentPart, newDocument.MainDocumentPart, newContent); CopyRelatedPartsForContentParts(sourceDocument.MainDocumentPart, newDocument.MainDocumentPart, newContent, images); // Append contents XDocument newMainXDoc = newDocument.MainDocumentPart.GetXDocument(); newMainXDoc.Declaration.Standalone = "yes"; newMainXDoc.Declaration.Encoding = "UTF-8"; if (keepSection == false) { List adjustedContents = newContent.Where(e => e.Name != W.sectPr).ToList(); adjustedContents.DescendantsAndSelf(W.sectPr).Remove(); newContent = adjustedContents; } foreach (var sectPr in newContent.DescendantsAndSelf(W.sectPr)) AddSectionAndDependencies(sourceDocument, newDocument, sectPr, images); CopyStylesAndFonts(sourceDocument, newDocument, newContent); CopyNumbering(sourceDocument, newDocument, newContent, images); CopyComments(sourceDocument, newDocument, newContent, images); CopyFootnotes(sourceDocument, newDocument, newContent, images); CopyEndnotes(sourceDocument, newDocument, newContent, images); AdjustUniqueIds(sourceDocument, newDocument, newContent); RemoveGfxdata(newContent); CopyCustomXml(sourceDocument, newDocument, newContent); if (insertId != null) { XElement insertElementToReplace = newMainXDoc .Descendants(PtOpenXml.Insert) .FirstOrDefault(i => (string)i.Attribute(PtOpenXml.Id) == insertId); if (insertElementToReplace != null) insertElementToReplace.AddAnnotation(new ReplaceSemaphore()); newMainXDoc.Element(W.document).ReplaceWith((XElement)InsertTransform(newMainXDoc.Root, newContent)); } else newMainXDoc.Root.Element(W.body).Add(newContent); AdjustDocPrIds(newDocument); } private static void CopyCustomXml(WordprocessingDocument sourceDocument, WordprocessingDocument newDocument, IEnumerable newContent) { List itemList = new List(); foreach (string itemId in newContent .Descendants(W.dataBinding) .Select(e => e.Attribute(W.storeItemID).Value)) if (!itemList.Contains(itemId)) itemList.Add(itemId); foreach (CustomXmlPart customXmlPart in sourceDocument.MainDocumentPart.CustomXmlParts) { OpenXmlPart propertyPart = customXmlPart .Parts .Select(p => p.OpenXmlPart) .Where(p => p.ContentType == "application/vnd.openxmlformats-officedocument.customXmlProperties+xml") .First(); XDocument propertyPartDoc = propertyPart.GetXDocument(); if (itemList.Contains(propertyPartDoc.Root.Attribute(DS.itemID).Value)) { CustomXmlPart newPart = newDocument.MainDocumentPart.AddCustomXmlPart(customXmlPart.ContentType); newPart.GetXDocument().Add(customXmlPart.GetXDocument().Root); foreach (OpenXmlPart propPart in customXmlPart.Parts.Select(p => p.OpenXmlPart)) { CustomXmlPropertiesPart newPropPart = newPart.AddNewPart(); newPropPart.GetXDocument().Add(propPart.GetXDocument().Root); } } } } private static Dictionary RelationshipMarkup = null; private static void UpdateContent(IEnumerable newContent, XName elementToModify, string oldRid, string newRid) { foreach (var attributeName in RelationshipMarkup[elementToModify]) { var elementsToUpdate = newContent .Descendants(elementToModify) .Where(e => (string)e.Attribute(attributeName) == oldRid); foreach (var element in elementsToUpdate) element.Attribute(attributeName).Value = newRid; } } private static void AddRelationships(OpenXmlPart oldPart, OpenXmlPart newPart, IEnumerable newContent) { var relevantElements = newContent.DescendantsAndSelf() .Where(d => RelationshipMarkup.ContainsKey(d.Name) && d.Attributes().Any(a => RelationshipMarkup[d.Name].Contains(a.Name))) .ToList(); foreach (var e in relevantElements) { if (e.Name == W.hyperlink) { string relId = (string)e.Attribute(R.id); if (string.IsNullOrEmpty(relId)) continue; var tempHyperlink = newPart.HyperlinkRelationships.FirstOrDefault(h => h.Id == relId); if (tempHyperlink != null) continue; Guid g = Guid.NewGuid(); string newRid = "R" + g.ToString().Replace("-", ""); var oldHyperlink = oldPart.HyperlinkRelationships.FirstOrDefault(h => h.Id == relId); if (oldHyperlink == null) continue; //throw new DocumentBuilderInternalException("Internal Error 0002"); newPart.AddHyperlinkRelationship(oldHyperlink.Uri, oldHyperlink.IsExternal, newRid); UpdateContent(newContent, e.Name, relId, newRid); } if (e.Name == W.attachedTemplate || e.Name == W.saveThroughXslt) { string relId = (string)e.Attribute(R.id); if (string.IsNullOrEmpty(relId)) continue; var tempExternalRelationship = newPart.ExternalRelationships.FirstOrDefault(h => h.Id == relId); if (tempExternalRelationship != null) continue; Guid g = Guid.NewGuid(); string newRid = "R" + g.ToString().Replace("-", ""); var oldRel = oldPart.ExternalRelationships.FirstOrDefault(h => h.Id == relId); if (oldRel == null) throw new DocumentBuilderInternalException("Source {0} is invalid document - hyperlink contains invalid references"); newPart.AddExternalRelationship(oldRel.RelationshipType, oldRel.Uri, newRid); UpdateContent(newContent, e.Name, relId, newRid); } if (e.Name == A.hlinkClick) { string relId = (string)e.Attribute(R.id); if (string.IsNullOrEmpty(relId)) continue; var tempHyperlink = newPart.HyperlinkRelationships.FirstOrDefault(h => h.Id == relId); if (tempHyperlink != null) continue; Guid g = Guid.NewGuid(); string newRid = "R" + g.ToString().Replace("-", ""); var oldHyperlink = oldPart.HyperlinkRelationships.FirstOrDefault(h => h.Id == relId); if (oldHyperlink == null) continue; newPart.AddHyperlinkRelationship(oldHyperlink.Uri, oldHyperlink.IsExternal, newRid); UpdateContent(newContent, e.Name, relId, newRid); } if (e.Name == VML.imagedata) { string relId = (string)e.Attribute(R.href); if (string.IsNullOrEmpty(relId)) continue; var tempExternalRelationship = newPart.ExternalRelationships.FirstOrDefault(h => h.Id == relId); if (tempExternalRelationship != null) continue; Guid g = Guid.NewGuid(); string newRid = "R" + g.ToString().Replace("-", ""); var oldRel = oldPart.ExternalRelationships.FirstOrDefault(h => h.Id == relId); if (oldRel == null) throw new DocumentBuilderInternalException("Internal Error 0006"); newPart.AddExternalRelationship(oldRel.RelationshipType, oldRel.Uri, newRid); UpdateContent(newContent, e.Name, relId, newRid); } if (e.Name == A.blip) { string relId = (string)e.Attribute(R.link); if (string.IsNullOrEmpty(relId)) continue; var tempExternalRelationship = newPart.ExternalRelationships.FirstOrDefault(h => h.Id == relId); if (tempExternalRelationship != null) continue; Guid g = Guid.NewGuid(); string newRid = "R" + g.ToString().Replace("-", ""); var oldRel = oldPart.ExternalRelationships.FirstOrDefault(h => h.Id == relId); if (oldRel == null) continue; newPart.AddExternalRelationship(oldRel.RelationshipType, oldRel.Uri, newRid); UpdateContent(newContent, e.Name, relId, newRid); } } } private class FromPreviousSourceSemaphore { }; private static void CopyNumbering(WordprocessingDocument sourceDocument, WordprocessingDocument newDocument, IEnumerable newContent, List images) { // Note that this does not need to use a map as CopyComments method does, as it needs to update only // a single attribute value. The problem in the CopyComments method is that there are multiple elements // for which we need to update attribute values. Searching for those elements in the paragraphs collection // causes the problem that must be solved by first creating a map, and then wholesale updating all // attributes appropriately using the map. int number = 1; int abstractNumber = 0; XDocument oldNumbering = null; XDocument newNumbering = null; foreach (XElement numReference in newContent.DescendantsAndSelf(W.numPr)) { XElement idElement = numReference.Descendants(W.numId).FirstOrDefault(); if (idElement != null) { if (oldNumbering == null) oldNumbering = sourceDocument.MainDocumentPart.NumberingDefinitionsPart.GetXDocument(); if (newNumbering == null) { if (newDocument.MainDocumentPart.NumberingDefinitionsPart != null) { newNumbering = newDocument.MainDocumentPart.NumberingDefinitionsPart.GetXDocument(); var numIds = newNumbering .Root .Elements(W.num) .Select(f => (int)f.Attribute(W.numId)); if (numIds.Any()) number = numIds.Max() + 1; numIds = newNumbering .Root .Elements(W.abstractNum) .Select(f => (int)f.Attribute(W.abstractNumId)); if (numIds.Any()) abstractNumber = numIds.Max() + 1; } else { newDocument.MainDocumentPart.AddNewPart(); newNumbering = newDocument.MainDocumentPart.NumberingDefinitionsPart.GetXDocument(); newNumbering.Declaration.Standalone = "yes"; newNumbering.Declaration.Encoding = "UTF-8"; newNumbering.Add(new XElement(W.numbering, NamespaceAttributes)); } } string numId = idElement.Attribute(W.val).Value; if (numId != "0") { XElement element = oldNumbering .Descendants(W.num) .Where(p => ((string)p.Attribute(W.numId)) == numId) .FirstOrDefault(); if (element == null) continue; // Copy abstract numbering element, if necessary (use matching NSID) string abstractNumId = element .Elements(W.abstractNumId) .First() .Attribute(W.val) .Value; XElement abstractElement = oldNumbering .Descendants() .Elements(W.abstractNum) .Where(p => ((string)p.Attribute(W.abstractNumId)) == abstractNumId) .First(); string abstractNSID = abstractElement .Elements(W.nsid) .First() .Attribute(W.val) .Value; XElement newAbstractElement = newNumbering .Descendants() .Elements(W.abstractNum) .Where(e => e.Annotation() == null) .Where(p => ((string)p.Elements(W.nsid).First().Attribute(W.val)) == abstractNSID) .FirstOrDefault(); if (newAbstractElement == null) { newAbstractElement = new XElement(abstractElement); newAbstractElement.Attribute(W.abstractNumId).Value = abstractNumber.ToString(); abstractNumber++; if (newNumbering.Root.Elements(W.abstractNum).Any()) newNumbering.Root.Elements(W.abstractNum).Last().AddAfterSelf(newAbstractElement); else newNumbering.Root.Add(newAbstractElement); foreach (XElement pictId in newAbstractElement.Descendants(W.lvlPicBulletId)) { string bulletId = (string)pictId.Attribute(W.val); XElement numPicBullet = oldNumbering .Descendants(W.numPicBullet) .FirstOrDefault(d => (string)d.Attribute(W.numPicBulletId) == bulletId); int maxNumPicBulletId = new int[] { -1 }.Concat( newNumbering.Descendants(W.numPicBullet) .Attributes(W.numPicBulletId) .Select(a => (int)a)) .Max() + 1; XElement newNumPicBullet = new XElement(numPicBullet); newNumPicBullet.Attribute(W.numPicBulletId).Value = maxNumPicBulletId.ToString(); pictId.Attribute(W.val).Value = maxNumPicBulletId.ToString(); newNumbering.Root.AddFirst(newNumPicBullet); } } string newAbstractId = newAbstractElement.Attribute(W.abstractNumId).Value; // Copy numbering element, if necessary (use matching element with no overrides) XElement newElement = newNumbering .Descendants() .Elements(W.num) .Where(e => e.Annotation() == null) .Where(p => ((string)p.Elements(W.abstractNumId).First().Attribute(W.val)) == newAbstractId) .FirstOrDefault(); if (newElement == null) { newElement = new XElement(element); newElement .Elements(W.abstractNumId) .First() .Attribute(W.val).Value = newAbstractId; newElement.Attribute(W.numId).Value = number.ToString(); number++; newNumbering.Root.Add(newElement); } idElement.Attribute(W.val).Value = newElement.Attribute(W.numId).Value; } } } if (newNumbering != null) { foreach (var abstractNum in newNumbering.Descendants(W.abstractNum)) abstractNum.AddAnnotation(new FromPreviousSourceSemaphore()); foreach (var num in newNumbering.Descendants(W.num)) num.AddAnnotation(new FromPreviousSourceSemaphore()); } if (newDocument.MainDocumentPart.NumberingDefinitionsPart != null && sourceDocument.MainDocumentPart.NumberingDefinitionsPart != null) { AddRelationships(sourceDocument.MainDocumentPart.NumberingDefinitionsPart, newDocument.MainDocumentPart.NumberingDefinitionsPart, new[] { newDocument.MainDocumentPart.NumberingDefinitionsPart.GetXDocument().Root }); CopyRelatedPartsForContentParts(sourceDocument.MainDocumentPart.NumberingDefinitionsPart, newDocument.MainDocumentPart.NumberingDefinitionsPart, new[] { newDocument.MainDocumentPart.NumberingDefinitionsPart.GetXDocument().Root }, images); } } private static void CopyRelatedImage(OpenXmlPart oldContentPart, OpenXmlPart newContentPart, XElement imageReference, XName attributeName, List images) { string relId = (string)imageReference.Attribute(attributeName); if (string.IsNullOrEmpty(relId)) return; try { // First look to see if this relId has already been added to the new document. // This is necessary for those parts that get processed with both old and new ids, such as the comments // part. This is not necessary for parts such as the main document part, but this code won't malfunction // in that case. try { OpenXmlPart tempPart = newContentPart.GetPartById(relId); return; } catch (ArgumentOutOfRangeException) { try { ExternalRelationship tempEr = newContentPart.GetExternalRelationship(relId); return; } catch (KeyNotFoundException) { // nothing to do } } ImagePart oldPart = (ImagePart)oldContentPart.GetPartById(relId); ImageData temp = ManageImageCopy(oldPart, newContentPart, images); if (temp.ResourceID == null) { ImagePart newPart = null; if (newContentPart is MainDocumentPart) newPart = ((MainDocumentPart)newContentPart).AddImagePart(oldPart.ContentType); if (newContentPart is HeaderPart) newPart = ((HeaderPart)newContentPart).AddImagePart(oldPart.ContentType); if (newContentPart is FooterPart) newPart = ((FooterPart)newContentPart).AddImagePart(oldPart.ContentType); if (newContentPart is EndnotesPart) newPart = ((EndnotesPart)newContentPart).AddImagePart(oldPart.ContentType); if (newContentPart is FootnotesPart) newPart = ((FootnotesPart)newContentPart).AddImagePart(oldPart.ContentType); if (newContentPart is ThemePart) newPart = ((ThemePart)newContentPart).AddImagePart(oldPart.ContentType); if (newContentPart is WordprocessingCommentsPart) newPart = ((WordprocessingCommentsPart)newContentPart).AddImagePart(oldPart.ContentType); if (newContentPart is DocumentSettingsPart) newPart = ((DocumentSettingsPart)newContentPart).AddImagePart(oldPart.ContentType); if (newContentPart is ChartPart) newPart = ((ChartPart)newContentPart).AddImagePart(oldPart.ContentType); if (newContentPart is NumberingDefinitionsPart) newPart = ((NumberingDefinitionsPart)newContentPart).AddImagePart(oldPart.ContentType); if (newContentPart is DiagramDataPart) newPart = ((DiagramDataPart)newContentPart).AddImagePart(oldPart.ContentType); if (newContentPart is ChartDrawingPart) newPart = ((ChartDrawingPart)newContentPart).AddImagePart(oldPart.ContentType); temp.ResourceID = newContentPart.GetIdOfPart(newPart); temp.WriteImage(newPart); imageReference.Attribute(attributeName).Value = temp.ResourceID; } else imageReference.Attribute(attributeName).Value = temp.ResourceID; } catch (ArgumentOutOfRangeException) { try { ExternalRelationship er = oldContentPart.GetExternalRelationship(relId); ExternalRelationship newEr = newContentPart.AddExternalRelationship(er.RelationshipType, er.Uri); imageReference.Attribute(R.id).Value = newEr.Id; } catch (KeyNotFoundException) { throw new DocumentBuilderInternalException("Source {0} is unsupported document - contains reference to NULL image"); } } } private static void CopyRelatedPartsForContentParts(OpenXmlPart oldContentPart, OpenXmlPart newContentPart, IEnumerable newContent, List images) { var relevantElements = newContent.DescendantsAndSelf() .Where(d => d.Name == VML.imagedata || d.Name == VML.fill || d.Name == VML.stroke || d.Name == A.blip) .ToList(); foreach (XElement imageReference in relevantElements) { CopyRelatedImage(oldContentPart, newContentPart, imageReference, R.embed, images); CopyRelatedImage(oldContentPart, newContentPart, imageReference, R.pict, images); CopyRelatedImage(oldContentPart, newContentPart, imageReference, R.id, images); } foreach (XElement diagramReference in newContent.DescendantsAndSelf().Where(d => d.Name == DGM.relIds || d.Name == A.relIds)) { // dm attribute string relId = diagramReference.Attribute(R.dm).Value; try { OpenXmlPart tempPart = newContentPart.GetPartById(relId); continue; } catch (ArgumentOutOfRangeException) { try { ExternalRelationship tempEr = newContentPart.GetExternalRelationship(relId); continue; } catch (KeyNotFoundException) { } } OpenXmlPart oldPart = oldContentPart.GetPartById(relId); OpenXmlPart newPart = newContentPart.AddNewPart(); newPart.GetXDocument().Add(oldPart.GetXDocument().Root); diagramReference.Attribute(R.dm).Value = newContentPart.GetIdOfPart(newPart); AddRelationships(oldPart, newPart, new[] { newPart.GetXDocument().Root }); CopyRelatedPartsForContentParts(oldPart, newPart, new[] { newPart.GetXDocument().Root }, images); // lo attribute relId = diagramReference.Attribute(R.lo).Value; try { OpenXmlPart tempPart = newContentPart.GetPartById(relId); continue; } catch (ArgumentOutOfRangeException) { try { ExternalRelationship tempEr = newContentPart.GetExternalRelationship(relId); continue; } catch (KeyNotFoundException) { } } oldPart = oldContentPart.GetPartById(relId); newPart = newContentPart.AddNewPart(); newPart.GetXDocument().Add(oldPart.GetXDocument().Root); diagramReference.Attribute(R.lo).Value = newContentPart.GetIdOfPart(newPart); AddRelationships(oldPart, newPart, new[] { newPart.GetXDocument().Root }); CopyRelatedPartsForContentParts(oldPart, newPart, new[] { newPart.GetXDocument().Root }, images); // qs attribute relId = diagramReference.Attribute(R.qs).Value; try { OpenXmlPart tempPart = newContentPart.GetPartById(relId); continue; } catch (ArgumentOutOfRangeException) { try { ExternalRelationship tempEr = newContentPart.GetExternalRelationship(relId); continue; } catch (KeyNotFoundException) { } } oldPart = oldContentPart.GetPartById(relId); newPart = newContentPart.AddNewPart(); newPart.GetXDocument().Add(oldPart.GetXDocument().Root); diagramReference.Attribute(R.qs).Value = newContentPart.GetIdOfPart(newPart); AddRelationships(oldPart, newPart, new[] { newPart.GetXDocument().Root }); CopyRelatedPartsForContentParts(oldPart, newPart, new[] { newPart.GetXDocument().Root }, images); // cs attribute relId = diagramReference.Attribute(R.cs).Value; try { OpenXmlPart tempPart = newContentPart.GetPartById(relId); continue; } catch (ArgumentOutOfRangeException) { try { ExternalRelationship tempEr = newContentPart.GetExternalRelationship(relId); continue; } catch (KeyNotFoundException) { } } oldPart = oldContentPart.GetPartById(relId); newPart = newContentPart.AddNewPart(); newPart.GetXDocument().Add(oldPart.GetXDocument().Root); diagramReference.Attribute(R.cs).Value = newContentPart.GetIdOfPart(newPart); AddRelationships(oldPart, newPart, new[] { newPart.GetXDocument().Root }); CopyRelatedPartsForContentParts(oldPart, newPart, new[] { newPart.GetXDocument().Root }, images); } foreach (XElement oleReference in newContent.DescendantsAndSelf(O.OLEObject)) { string relId = oleReference.Attribute(R.id).Value; try { // First look to see if this relId has already been added to the new document. // This is necessary for those parts that get processed with both old and new ids, such as the comments // part. This is not necessary for parts such as the main document part, but this code won't malfunction // in that case. try { OpenXmlPart tempPart = newContentPart.GetPartById(relId); continue; } catch (ArgumentOutOfRangeException) { try { ExternalRelationship tempEr = newContentPart.GetExternalRelationship(relId); continue; } catch (KeyNotFoundException) { // nothing to do } } OpenXmlPart oldPart = oldContentPart.GetPartById(relId); OpenXmlPart newPart = null; if (oldPart is EmbeddedObjectPart) { if (newContentPart is HeaderPart) newPart = ((HeaderPart)newContentPart).AddEmbeddedObjectPart(oldPart.ContentType); if (newContentPart is FooterPart) newPart = ((FooterPart)newContentPart).AddEmbeddedObjectPart(oldPart.ContentType); if (newContentPart is MainDocumentPart) newPart = ((MainDocumentPart)newContentPart).AddEmbeddedObjectPart(oldPart.ContentType); if (newContentPart is FootnotesPart) newPart = ((FootnotesPart)newContentPart).AddEmbeddedObjectPart(oldPart.ContentType); if (newContentPart is EndnotesPart) newPart = ((EndnotesPart)newContentPart).AddEmbeddedObjectPart(oldPart.ContentType); if (newContentPart is WordprocessingCommentsPart) newPart = ((WordprocessingCommentsPart)newContentPart).AddEmbeddedObjectPart(oldPart.ContentType); } else if (oldPart is EmbeddedPackagePart) { if (newContentPart is HeaderPart) newPart = ((HeaderPart)newContentPart).AddEmbeddedPackagePart(oldPart.ContentType); if (newContentPart is FooterPart) newPart = ((FooterPart)newContentPart).AddEmbeddedPackagePart(oldPart.ContentType); if (newContentPart is MainDocumentPart) newPart = ((MainDocumentPart)newContentPart).AddEmbeddedPackagePart(oldPart.ContentType); if (newContentPart is FootnotesPart) newPart = ((FootnotesPart)newContentPart).AddEmbeddedPackagePart(oldPart.ContentType); if (newContentPart is EndnotesPart) newPart = ((EndnotesPart)newContentPart).AddEmbeddedPackagePart(oldPart.ContentType); if (newContentPart is WordprocessingCommentsPart) newPart = ((WordprocessingCommentsPart)newContentPart).AddEmbeddedPackagePart(oldPart.ContentType); if (newContentPart is ChartPart) newPart = ((ChartPart)newContentPart).AddEmbeddedPackagePart(oldPart.ContentType); } using (Stream oldObject = oldPart.GetStream(FileMode.Open, FileAccess.Read)) using (Stream newObject = newPart.GetStream(FileMode.Create, FileAccess.ReadWrite)) { int byteCount; byte[] buffer = new byte[65536]; while ((byteCount = oldObject.Read(buffer, 0, 65536)) != 0) newObject.Write(buffer, 0, byteCount); } oleReference.Attribute(R.id).Value = newContentPart.GetIdOfPart(newPart); } catch (ArgumentOutOfRangeException) { ExternalRelationship er = oldContentPart.GetExternalRelationship(relId); ExternalRelationship newEr = newContentPart.AddExternalRelationship(er.RelationshipType, er.Uri); oleReference.Attribute(R.id).Value = newEr.Id; } } foreach (XElement chartReference in newContent.DescendantsAndSelf(C.chart)) { try { string relId = (string)chartReference.Attribute(R.id); if (string.IsNullOrEmpty(relId)) continue; try { OpenXmlPart tempPart = newContentPart.GetPartById(relId); continue; } catch (ArgumentOutOfRangeException) { try { ExternalRelationship tempEr = newContentPart.GetExternalRelationship(relId); continue; } catch (KeyNotFoundException) { } } ChartPart oldPart = (ChartPart)oldContentPart.GetPartById(relId); XDocument oldChart = oldPart.GetXDocument(); ChartPart newPart = newContentPart.AddNewPart(); XDocument newChart = newPart.GetXDocument(); newChart.Add(oldChart.Root); chartReference.Attribute(R.id).Value = newContentPart.GetIdOfPart(newPart); CopyChartObjects(oldPart, newPart); CopyRelatedPartsForContentParts(oldPart, newPart, new[] { newChart.Root }, images); } catch (ArgumentOutOfRangeException) { continue; } } foreach (XElement userShape in newContent.DescendantsAndSelf(C.userShapes)) { try { string relId = (string)userShape.Attribute(R.id); if (string.IsNullOrEmpty(relId)) continue; try { OpenXmlPart tempPart = newContentPart.GetPartById(relId); continue; } catch (ArgumentOutOfRangeException) { try { ExternalRelationship tempEr = newContentPart.GetExternalRelationship(relId); continue; } catch (KeyNotFoundException) { } } ChartDrawingPart oldPart = (ChartDrawingPart)oldContentPart.GetPartById(relId); XDocument oldXDoc = oldPart.GetXDocument(); ChartDrawingPart newPart = newContentPart.AddNewPart(); XDocument newXDoc = newPart.GetXDocument(); newXDoc.Add(oldXDoc.Root); userShape.Attribute(R.id).Value = newContentPart.GetIdOfPart(newPart); AddRelationships(oldPart, newPart, newContent); CopyRelatedPartsForContentParts(oldPart, newPart, new[] { newXDoc.Root }, images); } catch (ArgumentOutOfRangeException) { continue; } } } private static void CopyFontTable(FontTablePart oldFontTablePart, FontTablePart newFontTablePart) { var relevantElements = oldFontTablePart.GetXDocument().Descendants().Where(d => d.Name == W.embedRegular || d.Name == W.embedBold || d.Name == W.embedItalic || d.Name == W.embedBoldItalic).ToList(); foreach (XElement fontReference in relevantElements) { string relId = (string)fontReference.Attribute(R.id); if (string.IsNullOrEmpty(relId)) continue; try { OpenXmlPart tempPart = newFontTablePart.GetPartById(relId); continue; } catch (ArgumentOutOfRangeException) { try { ExternalRelationship tempEr = newFontTablePart.GetExternalRelationship(relId); continue; } catch (KeyNotFoundException) { } } FontPart oldPart = (FontPart)oldFontTablePart.GetPartById(relId); FontPart newPart = newFontTablePart.AddFontPart(oldPart.ContentType); var ResourceID = newFontTablePart.GetIdOfPart(newPart); using (Stream oldFont = oldPart.GetStream(FileMode.Open, FileAccess.Read)) using (Stream newFont = newPart.GetStream(FileMode.Create, FileAccess.ReadWrite)) { int byteCount; byte[] buffer = new byte[65536]; while ((byteCount = oldFont.Read(buffer, 0, 65536)) != 0) newFont.Write(buffer, 0, byteCount); } fontReference.Attribute(R.id).Value = ResourceID; } } private static void CopyChartObjects(ChartPart oldChart, ChartPart newChart) { foreach (XElement dataReference in newChart.GetXDocument().Descendants(C.externalData)) { string relId = dataReference.Attribute(R.id).Value; try { EmbeddedPackagePart oldPart = (EmbeddedPackagePart)oldChart.GetPartById(relId); EmbeddedPackagePart newPart = newChart.AddEmbeddedPackagePart(oldPart.ContentType); using (Stream oldObject = oldPart.GetStream(FileMode.Open, FileAccess.Read)) using (Stream newObject = newPart.GetStream(FileMode.Create, FileAccess.ReadWrite)) { int byteCount; byte[] buffer = new byte[65536]; while ((byteCount = oldObject.Read(buffer, 0, 65536)) != 0) newObject.Write(buffer, 0, byteCount); } dataReference.Attribute(R.id).Value = newChart.GetIdOfPart(newPart); } catch (ArgumentOutOfRangeException) { ExternalRelationship oldRelationship = oldChart.GetExternalRelationship(relId); Guid g = Guid.NewGuid(); string newRid = "R" + g.ToString().Replace("-", ""); var oldRel = oldChart.ExternalRelationships.FirstOrDefault(h => h.Id == relId); if (oldRel == null) throw new DocumentBuilderInternalException("Internal Error 0007"); newChart.AddExternalRelationship(oldRel.RelationshipType, oldRel.Uri, newRid); dataReference.Attribute(R.id).Value = newRid; } } } private static void CopyStartingParts(WordprocessingDocument sourceDocument, WordprocessingDocument newDocument, List images) { // A Core File Properties part does not have implicit or explicit relationships to other parts. CoreFilePropertiesPart corePart = sourceDocument.CoreFilePropertiesPart; if (corePart != null && corePart.GetXDocument().Root != null) { newDocument.AddCoreFilePropertiesPart(); XDocument newXDoc = newDocument.CoreFilePropertiesPart.GetXDocument(); newXDoc.Declaration.Standalone = "yes"; newXDoc.Declaration.Encoding = "UTF-8"; XDocument sourceXDoc = corePart.GetXDocument(); newXDoc.Add(sourceXDoc.Root); } // An application attributes part does not have implicit or explicit relationships to other parts. ExtendedFilePropertiesPart extPart = sourceDocument.ExtendedFilePropertiesPart; if (extPart != null) { OpenXmlPart newPart = newDocument.AddExtendedFilePropertiesPart(); XDocument newXDoc = newDocument.ExtendedFilePropertiesPart.GetXDocument(); newXDoc.Declaration.Standalone = "yes"; newXDoc.Declaration.Encoding = "UTF-8"; newXDoc.Add(extPart.GetXDocument().Root); } // An custom file properties part does not have implicit or explicit relationships to other parts. CustomFilePropertiesPart customPart = sourceDocument.CustomFilePropertiesPart; if (customPart != null) { newDocument.AddCustomFilePropertiesPart(); XDocument newXDoc = newDocument.CustomFilePropertiesPart.GetXDocument(); newXDoc.Declaration.Standalone = "yes"; newXDoc.Declaration.Encoding = "UTF-8"; newXDoc.Add(customPart.GetXDocument().Root); } DocumentSettingsPart oldSettingsPart = sourceDocument.MainDocumentPart.DocumentSettingsPart; if (oldSettingsPart != null) { DocumentSettingsPart newSettingsPart = newDocument.MainDocumentPart.AddNewPart(); XDocument settingsXDoc = oldSettingsPart.GetXDocument(); AddRelationships(oldSettingsPart, newSettingsPart, new[] { settingsXDoc.Root }); CopyFootnotesPart(sourceDocument, newDocument, settingsXDoc, images); CopyEndnotesPart(sourceDocument, newDocument, settingsXDoc, images); XDocument newXDoc = newDocument.MainDocumentPart.DocumentSettingsPart.GetXDocument(); newXDoc.Declaration.Standalone = "yes"; newXDoc.Declaration.Encoding = "UTF-8"; newXDoc.Add(settingsXDoc.Root); CopyRelatedPartsForContentParts(oldSettingsPart, newSettingsPart, new[] { newXDoc.Root }, images); } WebSettingsPart oldWebSettingsPart = sourceDocument.MainDocumentPart.WebSettingsPart; if (oldWebSettingsPart != null) { WebSettingsPart newWebSettingsPart = newDocument.MainDocumentPart.AddNewPart(); XDocument settingsXDoc = oldWebSettingsPart.GetXDocument(); AddRelationships(oldWebSettingsPart, newWebSettingsPart, new[] { settingsXDoc.Root }); XDocument newXDoc = newDocument.MainDocumentPart.WebSettingsPart.GetXDocument(); newXDoc.Declaration.Standalone = "yes"; newXDoc.Declaration.Encoding = "UTF-8"; newXDoc.Add(settingsXDoc.Root); } ThemePart themePart = sourceDocument.MainDocumentPart.ThemePart; if (themePart != null) { ThemePart newThemePart = newDocument.MainDocumentPart.AddNewPart(); XDocument newXDoc = newDocument.MainDocumentPart.ThemePart.GetXDocument(); newXDoc.Declaration.Standalone = "yes"; newXDoc.Declaration.Encoding = "UTF-8"; newXDoc.Add(themePart.GetXDocument().Root); CopyRelatedPartsForContentParts(themePart, newThemePart, new[] { newThemePart.GetXDocument().Root }, images); } // If needed to handle GlossaryDocumentPart in the future, then // this code should handle the following parts: // MainDocumentPart.GlossaryDocumentPart.StyleDefinitionsPart // MainDocumentPart.GlossaryDocumentPart.StylesWithEffectsPart // A Style Definitions part shall not have implicit or explicit relationships to any other part. StyleDefinitionsPart stylesPart = sourceDocument.MainDocumentPart.StyleDefinitionsPart; if (stylesPart != null) { newDocument.MainDocumentPart.AddNewPart(); XDocument newXDoc = newDocument.MainDocumentPart.StyleDefinitionsPart.GetXDocument(); newXDoc.Declaration.Standalone = "yes"; newXDoc.Declaration.Encoding = "UTF-8"; newXDoc.Add(stylesPart.GetXDocument().Root); } // A StylesWithEffects part shall not have implicit or explicit relationships to any other part. StylesWithEffectsPart stylesWithEffectsPart = sourceDocument.MainDocumentPart.StylesWithEffectsPart; if (stylesWithEffectsPart != null) { newDocument.MainDocumentPart.AddNewPart(); XDocument newXDoc = newDocument.MainDocumentPart.StylesWithEffectsPart.GetXDocument(); newXDoc.Declaration.Standalone = "yes"; newXDoc.Declaration.Encoding = "UTF-8"; newXDoc.Add(stylesWithEffectsPart.GetXDocument().Root); } // Note: Do not copy the numbering part. For every source, create new numbering definitions from // scratch. //NumberingDefinitionsPart numberingPart = sourceDocument.MainDocumentPart.NumberingDefinitionsPart; //if (numberingPart != null) //{ // newDocument.MainDocumentPart.AddNewPart(); // XDocument newXDoc = newDocument.MainDocumentPart.NumberingDefinitionsPart.GetXDocument(); // newXDoc.Declaration.Standalone = "yes"; // newXDoc.Declaration.Encoding = "UTF-8"; // newXDoc.Add(numberingPart.GetXDocument().Root); // newXDoc.Descendants(W.numIdMacAtCleanup).Remove(); //} // A Font Table part shall not have any implicit or explicit relationships to any other part. FontTablePart fontTablePart = sourceDocument.MainDocumentPart.FontTablePart; if (fontTablePart != null) { newDocument.MainDocumentPart.AddNewPart(); XDocument newXDoc = newDocument.MainDocumentPart.FontTablePart.GetXDocument(); newXDoc.Declaration.Standalone = "yes"; newXDoc.Declaration.Encoding = "UTF-8"; CopyFontTable(sourceDocument.MainDocumentPart.FontTablePart, newDocument.MainDocumentPart.FontTablePart); newXDoc.Add(fontTablePart.GetXDocument().Root); } } private static void CopyFootnotesPart(WordprocessingDocument sourceDocument, WordprocessingDocument newDocument, XDocument settingsXDoc, List images) { int number = 0; XDocument oldFootnotes = null; XDocument newFootnotes = null; XElement footnotePr = settingsXDoc.Root.Element(W.footnotePr); if (footnotePr == null) return; foreach (XElement footnote in footnotePr.Elements(W.footnote)) { if (oldFootnotes == null) oldFootnotes = sourceDocument.MainDocumentPart.FootnotesPart.GetXDocument(); if (newFootnotes == null) { if (newDocument.MainDocumentPart.FootnotesPart != null) { newFootnotes = newDocument.MainDocumentPart.FootnotesPart.GetXDocument(); newFootnotes.Declaration.Standalone = "yes"; newFootnotes.Declaration.Encoding = "UTF-8"; var ids = newFootnotes.Root.Elements(W.footnote).Select(f => (int)f.Attribute(W.id)); if (ids.Any()) number = ids.Max() + 1; } else { newDocument.MainDocumentPart.AddNewPart(); newFootnotes = newDocument.MainDocumentPart.FootnotesPart.GetXDocument(); newFootnotes.Declaration.Standalone = "yes"; newFootnotes.Declaration.Encoding = "UTF-8"; newFootnotes.Add(new XElement(W.footnotes, NamespaceAttributes)); } } string id = (string)footnote.Attribute(W.id); XElement element = oldFootnotes.Descendants() .Elements(W.footnote) .Where(p => ((string)p.Attribute(W.id)) == id) .First(); XElement newElement = new XElement(element); // the following adds the footnote into the new settings part newElement.Attribute(W.id).Value = number.ToString(); newFootnotes.Root.Add(newElement); footnote.Attribute(W.id).Value = number.ToString(); number++; } } private static void CopyEndnotesPart(WordprocessingDocument sourceDocument, WordprocessingDocument newDocument, XDocument settingsXDoc, List images) { int number = 0; XDocument oldEndnotes = null; XDocument newEndnotes = null; XElement endnotePr = settingsXDoc.Root.Element(W.endnotePr); if (endnotePr == null) return; foreach (XElement endnote in endnotePr.Elements(W.endnote)) { if (oldEndnotes == null) oldEndnotes = sourceDocument.MainDocumentPart.EndnotesPart.GetXDocument(); if (newEndnotes == null) { if (newDocument.MainDocumentPart.EndnotesPart != null) { newEndnotes = newDocument.MainDocumentPart.EndnotesPart.GetXDocument(); newEndnotes.Declaration.Standalone = "yes"; newEndnotes.Declaration.Encoding = "UTF-8"; var ids = newEndnotes.Root .Elements(W.endnote) .Select(f => (int)f.Attribute(W.id)); if (ids.Any()) number = ids.Max() + 1; } else { newDocument.MainDocumentPart.AddNewPart(); newEndnotes = newDocument.MainDocumentPart.EndnotesPart.GetXDocument(); newEndnotes.Declaration.Standalone = "yes"; newEndnotes.Declaration.Encoding = "UTF-8"; newEndnotes.Add(new XElement(W.endnotes, NamespaceAttributes)); } } string id = (string)endnote.Attribute(W.id); XElement element = oldEndnotes.Descendants() .Elements(W.endnote) .Where(p => ((string)p.Attribute(W.id)) == id) .First(); XElement newElement = new XElement(element); newElement.Attribute(W.id).Value = number.ToString(); newEndnotes.Root.Add(newElement); endnote.Attribute(W.id).Value = number.ToString(); number++; } } public static void FixRanges(XDocument sourceDocument, IEnumerable newContent) { FixRange(sourceDocument, newContent, W.commentRangeStart, W.commentRangeEnd, W.id, W.commentReference); FixRange(sourceDocument, newContent, W.bookmarkStart, W.bookmarkEnd, W.id, null); FixRange(sourceDocument, newContent, W.permStart, W.permEnd, W.id, null); FixRange(sourceDocument, newContent, W.moveFromRangeStart, W.moveFromRangeEnd, W.id, null); FixRange(sourceDocument, newContent, W.moveToRangeStart, W.moveToRangeEnd, W.id, null); DeleteUnmatchedRange(sourceDocument, newContent, W.moveFromRangeStart, W.moveFromRangeEnd, W.moveToRangeStart, W.name, W.id); DeleteUnmatchedRange(sourceDocument, newContent, W.moveToRangeStart, W.moveToRangeEnd, W.moveFromRangeStart, W.name, W.id); } private static void AddAtBeginning(IEnumerable newContent, XElement contentToAdd) { if (newContent.First().Element(W.pPr) != null) newContent.First().Element(W.pPr).AddAfterSelf(contentToAdd); else newContent.First().AddFirst(new XElement(contentToAdd)); } private static void AddAtEnd(IEnumerable newContent, XElement contentToAdd) { if (newContent.Last().Element(W.pPr) != null) newContent.Last().Element(W.pPr).AddAfterSelf(new XElement(contentToAdd)); else newContent.Last().Add(new XElement(contentToAdd)); } // If the set of paragraphs from sourceDocument don't have a complete start/end for bookmarks, // comments, etc., then this adds them to the paragraph. Note that this adds them to // sourceDocument, and is impure. private static void FixRange(XDocument sourceDocument, IEnumerable newContent, XName startElement, XName endElement, XName idAttribute, XName refElement) { foreach (XElement start in newContent.DescendantsAndSelf(startElement)) { string rangeId = start.Attribute(idAttribute).Value; if (newContent .DescendantsAndSelf(endElement) .Where(e => e.Attribute(idAttribute).Value == rangeId) .Count() == 0) { XElement end = sourceDocument .Descendants(endElement) .Where(o => o.Attribute(idAttribute).Value == rangeId) .FirstOrDefault(); if (end != null) { AddAtEnd(newContent, new XElement(end)); if (refElement != null) { XElement newRef = new XElement(refElement, new XAttribute(idAttribute, rangeId)); AddAtEnd(newContent, new XElement(newRef)); } } } } foreach (XElement end in newContent.Elements(endElement)) { string rangeId = end.Attribute(idAttribute).Value; if (newContent .DescendantsAndSelf(startElement) .Where(s => s.Attribute(idAttribute).Value == rangeId) .Count() == 0) { XElement start = sourceDocument .Descendants(startElement) .Where(o => o.Attribute(idAttribute).Value == rangeId) .FirstOrDefault(); if (start != null) AddAtBeginning(newContent, new XElement(start)); } } } private static void DeleteUnmatchedRange(XDocument sourceDocument, IEnumerable newContent, XName startElement, XName endElement, XName matchTo, XName matchAttr, XName idAttr) { List deleteList = new List(); foreach (XElement start in newContent.Elements(startElement)) { string id = start.Attribute(matchAttr).Value; if (!newContent.Elements(matchTo).Where(n => n.Attribute(matchAttr).Value == id).Any()) deleteList.Add(start.Attribute(idAttr).Value); } foreach (string item in deleteList) { newContent.Elements(startElement).Where(n => n.Attribute(idAttr).Value == item).Remove(); newContent.Elements(endElement).Where(n => n.Attribute(idAttr).Value == item).Remove(); newContent.Where(p => p.Name == startElement && p.Attribute(idAttr).Value == item).Remove(); newContent.Where(p => p.Name == endElement && p.Attribute(idAttr).Value == item).Remove(); } } private static void CopyFootnotes(WordprocessingDocument sourceDocument, WordprocessingDocument newDocument, IEnumerable newContent, List images) { int number = 0; XDocument oldFootnotes = null; XDocument newFootnotes = null; foreach (XElement footnote in newContent.DescendantsAndSelf(W.footnoteReference)) { if (oldFootnotes == null) oldFootnotes = sourceDocument.MainDocumentPart.FootnotesPart.GetXDocument(); if (newFootnotes == null) { if (newDocument.MainDocumentPart.FootnotesPart != null) { newFootnotes = newDocument.MainDocumentPart.FootnotesPart.GetXDocument(); var ids = newFootnotes .Root .Elements(W.footnote) .Select(f => (int)f.Attribute(W.id)); if (ids.Any()) number = ids.Max() + 1; } else { newDocument.MainDocumentPart.AddNewPart(); newFootnotes = newDocument.MainDocumentPart.FootnotesPart.GetXDocument(); newFootnotes.Declaration.Standalone = "yes"; newFootnotes.Declaration.Encoding = "UTF-8"; newFootnotes.Add(new XElement(W.footnotes, NamespaceAttributes)); } } string id = (string)footnote.Attribute(W.id); XElement element = oldFootnotes .Descendants() .Elements(W.footnote) .Where(p => ((string)p.Attribute(W.id)) == id) .First(); XElement newElement = new XElement(element); newElement.Attribute(W.id).Value = number.ToString(); newFootnotes.Root.Add(newElement); footnote.Attribute(W.id).Value = number.ToString(); number++; } if (sourceDocument.MainDocumentPart.FootnotesPart != null && newDocument.MainDocumentPart.FootnotesPart != null) { AddRelationships(sourceDocument.MainDocumentPart.FootnotesPart, newDocument.MainDocumentPart.FootnotesPart, new[] { newDocument.MainDocumentPart.FootnotesPart.GetXDocument().Root }); CopyRelatedPartsForContentParts(sourceDocument.MainDocumentPart.FootnotesPart, newDocument.MainDocumentPart.FootnotesPart, new[] { newDocument.MainDocumentPart.FootnotesPart.GetXDocument().Root }, images); } } private static void CopyEndnotes(WordprocessingDocument sourceDocument, WordprocessingDocument newDocument, IEnumerable newContent, List images) { int number = 0; XDocument oldEndnotes = null; XDocument newEndnotes = null; foreach (XElement endnote in newContent.DescendantsAndSelf(W.endnoteReference)) { if (oldEndnotes == null) oldEndnotes = sourceDocument.MainDocumentPart.EndnotesPart.GetXDocument(); if (newEndnotes == null) { if (newDocument.MainDocumentPart.EndnotesPart != null) { newEndnotes = newDocument .MainDocumentPart .EndnotesPart .GetXDocument(); var ids = newEndnotes .Root .Elements(W.endnote) .Select(f => (int)f.Attribute(W.id)); if (ids.Any()) number = ids.Max() + 1; } else { newDocument.MainDocumentPart.AddNewPart(); newEndnotes = newDocument.MainDocumentPart.EndnotesPart.GetXDocument(); newEndnotes.Declaration.Standalone = "yes"; newEndnotes.Declaration.Encoding = "UTF-8"; newEndnotes.Add(new XElement(W.endnotes, NamespaceAttributes)); } } string id = (string)endnote.Attribute(W.id); XElement element = oldEndnotes .Descendants() .Elements(W.endnote) .Where(p => ((string)p.Attribute(W.id)) == id) .First(); XElement newElement = new XElement(element); newElement.Attribute(W.id).Value = number.ToString(); newEndnotes.Root.Add(newElement); endnote.Attribute(W.id).Value = number.ToString(); number++; } if (sourceDocument.MainDocumentPart.EndnotesPart != null && newDocument.MainDocumentPart.EndnotesPart != null) { AddRelationships(sourceDocument.MainDocumentPart.EndnotesPart, newDocument.MainDocumentPart.EndnotesPart, new[] { newDocument.MainDocumentPart.EndnotesPart.GetXDocument().Root }); CopyRelatedPartsForContentParts(sourceDocument.MainDocumentPart.EndnotesPart, newDocument.MainDocumentPart.EndnotesPart, new[] { newDocument.MainDocumentPart.EndnotesPart.GetXDocument().Root }, images); } } // General function for handling images that tries to use an existing image if they are the same private static ImageData ManageImageCopy(ImagePart oldImage, OpenXmlPart newContentPart, List images) { ImageData oldImageData = new ImageData(newContentPart, oldImage); foreach (ImageData item in images) { if (newContentPart != item.ContentPart) continue; if (item.Compare(oldImageData)) return item; } images.Add(oldImageData); return oldImageData; } private static XAttribute[] NamespaceAttributes = { new XAttribute(XNamespace.Xmlns + "wpc", WPC.wpc), new XAttribute(XNamespace.Xmlns + "mc", MC.mc), new XAttribute(XNamespace.Xmlns + "o", O.o), new XAttribute(XNamespace.Xmlns + "r", R.r), new XAttribute(XNamespace.Xmlns + "m", M.m), new XAttribute(XNamespace.Xmlns + "v", VML.vml), new XAttribute(XNamespace.Xmlns + "wp14", WP14.wp14), new XAttribute(XNamespace.Xmlns + "wp", WP.wp), new XAttribute(XNamespace.Xmlns + "w10", W10.w10), new XAttribute(XNamespace.Xmlns + "w", W.w), new XAttribute(XNamespace.Xmlns + "w14", W14.w14), new XAttribute(XNamespace.Xmlns + "wpg", WPG.wpg), new XAttribute(XNamespace.Xmlns + "wpi", WPI.wpi), new XAttribute(XNamespace.Xmlns + "wne", WNE.wne), new XAttribute(XNamespace.Xmlns + "wps", WPS.wps), new XAttribute(MC.Ignorable, "w14 wp14"), }; } // This class is used to prevent duplication of images class ImageData { private byte[] Image { get; set; } private string ContentType { get; set; } public string ResourceID { get; set; } public OpenXmlPart ContentPart { get; set; } public ImageData(OpenXmlPart contentPart, ImagePart part) { ContentType = part.ContentType; ContentPart = contentPart; using (Stream s = part.GetStream(FileMode.Open, FileAccess.Read)) { Image = new byte[s.Length]; s.Read(Image, 0, (int)s.Length); } } public void WriteImage(ImagePart part) { using (Stream s = part.GetStream(FileMode.Create, FileAccess.ReadWrite)) s.Write(Image, 0, Image.GetUpperBound(0) + 1); } public bool Compare(ImageData arg) { if (ContentType != arg.ContentType) return false; if (Image.GetLongLength(0) != arg.Image.GetLongLength(0)) return false; // Compare the arrays byte by byte long length = Image.GetLongLength(0); byte[] image1 = Image; byte[] image2 = arg.Image; for (long n = 0; n < length; n++) if (image1[n] != image2[n]) return false; return true; } } public class DocumentBuilderException : Exception { public DocumentBuilderException(string message) : base(message) { } } public class DocumentBuilderInternalException : Exception { public DocumentBuilderInternalException(string message) : base(message) { } } }