Skip to content

Instantly share code, notes, and snippets.

@sergey-tihon
Created September 28, 2012 13:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sergey-tihon/3799925 to your computer and use it in GitHub Desktop.
Save sergey-tihon/3799925 to your computer and use it in GitHub Desktop.
ISource for DocumentBuilder
/***************************************************************************
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<WmlDocument> SplitOnSections()
{
return DocumentBuilder.SplitOnSections(this);
}
}
public interface ISource
{
WmlDocument WmlDocument { get; set; }
bool KeepSections { get; set; }
string InsertId { get; set; }
IEnumerable<XElement> GetElements(WordprocessingDocument document);
}
public class Source : ISource
{
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 IEnumerable<XElement> GetElements(WordprocessingDocument document)
{
return document.MainDocumentPart.GetXDocument()
.Root
.Element(W.body)
.Elements()
.Skip(Start)
.Take(Count)
.ToList();
}
}
public static class DocumentBuilder
{
public static void BuildDocument(List<ISource> 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<ISource> 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<WmlDocument> SplitOnSections(WmlDocument doc)
{
List<TempSource> 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<ISource> sources = new List<ISource>()
{
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<ISource> sources, WordprocessingDocument output)
{
if (RelationshipMarkup == null)
RelationshipMarkup = new Dictionary<XName, XName[]>()
{
//{ 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<ImageData> images = new List<ImageData>();
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 (ISource 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<XElement> contents = source.GetElements(doc).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<XElement> contents = source.GetElements(doc).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<XDocument>() != 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<ImageData> 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<HeaderPart>();
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<FooterPart>();
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<NumberingDefinitionsPart>();
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<XElement> 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<StyleDefinitionsPart>();
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<StylesWithEffectsPart>();
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<FontTablePart>();
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<XElement> newContent, List<ImageData> images)
{
Dictionary<int, int> commentIdMap = new Dictionary<int, int>();
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<WordprocessingCommentsPart>();
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<XElement> 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<int, int> bookmarkIdMap = new Dictionary<int, int>();
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<XElement> newContent)
{
newContent.DescendantsAndSelf().Attributes(O.gfxdata).Remove();
}
private static object InsertTransform(XNode node, List<XElement> newContent)
{
XElement element = node as XElement;
if (element != null)
{
if (element.Annotation<ReplaceSemaphore>() != 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<XElement> newContent, bool keepSection, string insertId, List<ImageData> 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<XElement> 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<XElement> newContent)
{
List<string> itemList = new List<string>();
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<CustomXmlPropertiesPart>();
newPropPart.GetXDocument().Add(propPart.GetXDocument().Root);
}
}
}
}
private static Dictionary<XName, XName[]> RelationshipMarkup = null;
private static void UpdateContent(IEnumerable<XElement> 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<XElement> 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<XElement> newContent, List<ImageData> 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<NumberingDefinitionsPart>();
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<FromPreviousSourceSemaphore>() == 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<FromPreviousSourceSemaphore>() == 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<ImageData> 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<XElement> newContent, List<ImageData> 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<DiagramDataPart>();
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<DiagramLayoutDefinitionPart>();
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<DiagramStylePart>();
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<DiagramColorsPart>();
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<ChartPart>();
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<ChartDrawingPart>();
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<ImageData> 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<DocumentSettingsPart>();
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<WebSettingsPart>();
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<ThemePart>();
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<StyleDefinitionsPart>();
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<StylesWithEffectsPart>();
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<NumberingDefinitionsPart>();
// 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<FontTablePart>();
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<ImageData> 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<FootnotesPart>();
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<ImageData> 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<EndnotesPart>();
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<XElement> 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<XElement> 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<XElement> 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<XElement> 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<XElement> newContent,
XName startElement, XName endElement, XName matchTo, XName matchAttr, XName idAttr)
{
List<string> deleteList = new List<string>();
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<XElement> newContent, List<ImageData> 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<FootnotesPart>();
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<XElement> newContent, List<ImageData> 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<EndnotesPart>();
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<ImageData> 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) { }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment