// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Xml.Linq; using DocumentFormat.OpenXml.Packaging; using System.Drawing; namespace OpenXmlPowerTools { public class FormattingAssemblerSettings { public bool RemoveStyleNamesFromParagraphAndRunProperties; public bool ClearStyles; public bool OrderElementsPerStandard; public bool CreateHtmlConverterAnnotationAttributes; public bool RestrictToSupportedNumberingFormats; public bool RestrictToSupportedLanguages; public ListItemRetrieverSettings ListItemRetrieverSettings; public FormattingAssemblerSettings() { RemoveStyleNamesFromParagraphAndRunProperties = true; ClearStyles = true; OrderElementsPerStandard = true; CreateHtmlConverterAnnotationAttributes = true; RestrictToSupportedNumberingFormats = false; RestrictToSupportedLanguages = false; ListItemRetrieverSettings = new ListItemRetrieverSettings(); } } public static class FormattingAssembler { public static WmlDocument AssembleFormatting(WmlDocument document, FormattingAssemblerSettings settings) { using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(document)) { using (WordprocessingDocument doc = streamDoc.GetWordprocessingDocument()) { AssembleFormatting(doc, settings); } return streamDoc.GetModifiedWmlDocument(); } } public static void AssembleFormatting(WordprocessingDocument wDoc, FormattingAssemblerSettings settings) { FormattingAssemblerInfo fai = new FormattingAssemblerInfo(); XDocument sXDoc = wDoc.MainDocumentPart.StyleDefinitionsPart.GetXDocument(); XElement defaultParagraphStyle = sXDoc .Root .Elements(W.style) .FirstOrDefault(st => st.Attribute(W._default).ToBoolean() == true && (string)st.Attribute(W.type) == "paragraph"); if (defaultParagraphStyle != null) fai.DefaultParagraphStyleName = (string)defaultParagraphStyle.Attribute(W.styleId); XElement defaultCharacterStyle = sXDoc .Root .Elements(W.style) .FirstOrDefault(st => st.Attribute(W._default).ToBoolean() == true && (string)st.Attribute(W.type) == "character"); if (defaultCharacterStyle != null) fai.DefaultCharacterStyleName = (string)defaultCharacterStyle.Attribute(W.styleId); XElement defaultTableStyle = sXDoc .Root .Elements(W.style) .FirstOrDefault(st => st.Attribute(W._default).ToBoolean() == true && (string)st.Attribute(W.type) == "table"); if (defaultTableStyle != null) fai.DefaultTableStyleName = (string)defaultTableStyle.Attribute(W.styleId); ListItemRetrieverSettings listItemRetrieverSettings = new ListItemRetrieverSettings(); AssembleListItemInformation(wDoc, settings.ListItemRetrieverSettings); foreach (var part in wDoc.ContentParts()) { var pxd = part.GetXDocument(); FixNonconformantHexValues(pxd.Root); AnnotateWithGlobalDefaults(wDoc, pxd.Root, settings); AnnotateTablesWithTableStyles(wDoc, pxd.Root); AnnotateParagraphs(fai, wDoc, pxd.Root, settings); AnnotateRuns(fai, wDoc, pxd.Root, settings); } NormalizeListItems(fai, wDoc, settings); if (settings.ClearStyles) ClearStyles(wDoc); foreach (var part in wDoc.ContentParts()) { var pxd = part.GetXDocument(); pxd.Root.Descendants().Attributes().Where(a => a.IsNamespaceDeclaration).Remove(); FormattingAssembler.NormalizePropsForPart(pxd, settings); var newRoot = (XElement)CleanupTransform(pxd.Root); pxd.Root.ReplaceWith(newRoot); part.PutXDocument(); } } private static void FixNonconformantHexValues(XElement root) { foreach (var tblLook in root.Descendants(W.tblLook)) { if (tblLook.Attributes().Any(a => a.Name != W.val)) continue; if (tblLook.Attribute(W.val) == null) continue; string hexValue = tblLook.Attribute(W.val).Value; int val = int.Parse(hexValue, System.Globalization.NumberStyles.HexNumber); tblLook.Add(new XAttribute(W.firstRow, (val & 0x0020) != 0 ? "1" : "0")); tblLook.Add(new XAttribute(W.lastRow, (val & 0x0040) != 0 ? "1" : "0")); tblLook.Add(new XAttribute(W.firstColumn, (val & 0x0080) != 0 ? "1" : "0")); tblLook.Add(new XAttribute(W.lastColumn, (val & 0x0100) != 0 ? "1" : "0")); tblLook.Add(new XAttribute(W.noHBand, (val & 0x0200) != 0 ? "1" : "0")); tblLook.Add(new XAttribute(W.noVBand, (val & 0x0400) != 0 ? "1" : "0")); } foreach (var cnfStyle in root.Descendants(W.cnfStyle)) { if (cnfStyle.Attributes().Any(a => a.Name != W.val)) continue; if (cnfStyle.Attribute(W.val) == null) continue; var va = cnfStyle.Attribute(W.val).Value.ToArray(); cnfStyle.Add(new XAttribute(W.firstRow, va[0])); cnfStyle.Add(new XAttribute(W.lastRow, va[1])); cnfStyle.Add(new XAttribute(W.firstColumn, va[2])); cnfStyle.Add(new XAttribute(W.lastColumn, va[3])); cnfStyle.Add(new XAttribute(W.oddVBand, va[4])); cnfStyle.Add(new XAttribute(W.evenVBand, va[5])); cnfStyle.Add(new XAttribute(W.oddHBand, va[6])); cnfStyle.Add(new XAttribute(W.evenHBand, va[7])); cnfStyle.Add(new XAttribute(W.firstRowLastColumn, va[8])); cnfStyle.Add(new XAttribute(W.firstRowFirstColumn, va[9])); cnfStyle.Add(new XAttribute(W.lastRowLastColumn, va[10])); cnfStyle.Add(new XAttribute(W.lastRowFirstColumn, va[11])); } } private static object CleanupTransform(XNode node) { XElement element = node as XElement; if (element != null) { if (element.Name == W.tabs && element.Element(W.tab) == null) return null; if (element.Name == W.tblStyleRowBandSize || element.Name == W.tblStyleColBandSize) return null; // a cleaner solution would be to not include the w:ins and w:del elements when rolling up the paragraph run properties into // the run properties. if ((element.Name == W.ins || element.Name == W.del) && element.Parent.Name == W.rPr) { if (element.Parent.Parent.Name == W.r || element.Parent.Parent.Name == W.rPrChange) return null; } return new XElement(element.Name, element.Attributes(), element.Nodes().Select(n => CleanupTransform(n))); } return node; } private static void ClearStyles(WordprocessingDocument wDoc) { var stylePart = wDoc.MainDocumentPart.StyleDefinitionsPart; var sXDoc = stylePart.GetXDocument(); var newRoot = new XElement(sXDoc.Root.Name, sXDoc.Root.Attributes(), sXDoc.Root.Elements().Select(e => { if (e.Name != W.style) return e; return new XElement(e.Name, e.Attributes(), e.Element(W.name), new XElement(W.pPr), new XElement(W.rPr)); })); var globalrPr = newRoot .Elements(W.docDefaults) .Elements(W.rPrDefault) .Elements(W.rPr) .FirstOrDefault(); if (globalrPr != null) globalrPr.ReplaceWith(new XElement(W.rPr)); var globalpPr = newRoot .Elements(W.docDefaults) .Elements(W.pPrDefault) .Elements(W.pPr) .FirstOrDefault(); if (globalpPr != null) globalpPr.ReplaceWith(new XElement(W.pPr)); sXDoc.Root.ReplaceWith(newRoot); stylePart.PutXDocument(); } private static void NormalizeListItems(FormattingAssemblerInfo fai, WordprocessingDocument wDoc, FormattingAssemblerSettings settings) { foreach (var part in wDoc.ContentParts()) { var pxd = part.GetXDocument(); XElement newRoot = (XElement)NormalizeListItemsTransform(fai, wDoc, pxd.Root, settings); if (newRoot.Attribute(XNamespace.Xmlns + "pt14") == null) newRoot.Add(new XAttribute(XNamespace.Xmlns + "pt14", PtOpenXml.pt.NamespaceName)); if (newRoot.Attribute(XNamespace.Xmlns + "mc") == null) newRoot.Add(new XAttribute(XNamespace.Xmlns + "mc", MC.mc.NamespaceName)); pxd.Root.ReplaceWith(newRoot); } } private static object NormalizeListItemsTransform(FormattingAssemblerInfo fai, WordprocessingDocument wDoc, XNode node, FormattingAssemblerSettings settings) { var element = node as XElement; if (element != null) { if (element.Name == W.p) { var li = ListItemRetriever.RetrieveListItem(wDoc, element, settings.ListItemRetrieverSettings); if (li != null) { ListItemRetriever.ListItemInfo listItemInfo = element.Annotation(); var newParaProps = new XElement(W.pPr, element.Elements(W.pPr).Elements().Where(e => e.Name != W.numPr) ); XElement listItemRunProps = null; List listItemHtmlAttributes = new List(); int? abstractNumId = null; if (listItemInfo != null) { abstractNumId = listItemInfo.AbstractNumId; var paraStyleRunProps = CharStyleRollup(fai, wDoc, element); var paragraphStyleName = (string)element .Elements(W.pPr) .Elements(W.pStyle) .Attributes(W.val) .FirstOrDefault(); string defaultStyleName = (string)wDoc .MainDocumentPart .StyleDefinitionsPart .GetXDocument() .Root .Elements(W.style) .Where(s => (string)s.Attribute(W.type) == "paragraph" && s.Attribute(W._default).ToBoolean() == true) .Attributes(W.styleId) .FirstOrDefault(); if (paragraphStyleName == null) paragraphStyleName = defaultStyleName; XDocument stylesXDoc = wDoc .MainDocumentPart .StyleDefinitionsPart .GetXDocument(); // put together run props for list item. XElement lvlStyleRpr = ParaStyleRunPropsStack(wDoc, paragraphStyleName) .Aggregate(new XElement(W.rPr), (r, s) => { var newCharStyleRunProps = MergeStyleElement(s, r); return newCharStyleRunProps; }); var mergedRunProps = MergeStyleElement(lvlStyleRpr, paraStyleRunProps); var accumulatedRunProps = element.Elements(PtOpenXml.pPr).Elements(W.rPr).FirstOrDefault(); if (accumulatedRunProps != null) mergedRunProps = MergeStyleElement(accumulatedRunProps, mergedRunProps); var listItemLvl = listItemInfo.Lvl(ListItemRetriever.GetParagraphLevel(element)); var listItemLvlRunProps = listItemLvl.Elements(W.rPr).FirstOrDefault(); listItemRunProps = MergeStyleElement(listItemLvlRunProps, mergedRunProps); string numFmt = null; string format = null; numFmt = (string)listItemLvl.Elements(W.numFmt).Attributes(W.val).FirstOrDefault(); if (numFmt == null) { var mcAlternativeContent = listItemLvl.Descendants(MC.AlternateContent).FirstOrDefault(); if (mcAlternativeContent != null) { var choice = mcAlternativeContent.Element(MC.Choice); if (choice != null) { numFmt = (string)choice.Elements(W.numFmt).Attributes(W.val).FirstOrDefault(); format = (string)choice.Elements(W.numFmt).Attributes(W.format).FirstOrDefault(); } } } if (numFmt == "bullet") { listItemRunProps.Elements(W.rtl).Remove(); listItemHtmlAttributes.Add( new XAttribute(PtOpenXml.HtmlStructure, "ul") ); listItemHtmlAttributes.Add( new XAttribute(PtOpenXml.HtmlStyle, "list-style-type: circle;") ); } else { var pPr = element.Element(PtOpenXml.pPr); if (pPr != null) { XElement bidiel = pPr.Element(W.bidi); bool bidi = bidiel != null && (bidiel.Attribute(W.val) == null || bidiel.Attribute(W.val).ToBoolean() == true); if (bidi) { listItemRunProps = MergeStyleElement(new XElement(W.rPr, new XElement(W.rtl)), listItemRunProps); } } listItemHtmlAttributes.Add( new XAttribute(PtOpenXml.HtmlStructure, "ol") ); if (numFmt == "decimal") { listItemHtmlAttributes.Add( new XAttribute(PtOpenXml.HtmlStyle, "list-style-type: decimal;") ); } else if (numFmt == "lowerLetter") { listItemHtmlAttributes.Add( new XAttribute(PtOpenXml.HtmlStyle, "list-style-type: lower-alpha;") ); } else if (numFmt == "lowerRoman") { listItemHtmlAttributes.Add( new XAttribute(PtOpenXml.HtmlStyle, "list-style-type: lower-roman;") ); } else if (numFmt == "upperLetter") { listItemHtmlAttributes.Add( new XAttribute(PtOpenXml.HtmlStyle, "list-style-type: upper-alpha;") ); } else if (numFmt == "upperRoman") { listItemHtmlAttributes.Add( new XAttribute(PtOpenXml.HtmlStyle, "list-style-type: upper-roman;") ); } else if (numFmt == "custom" && format.StartsWith("0")) { listItemHtmlAttributes.Add( new XAttribute(PtOpenXml.HtmlStyle, "list-style-type: decimal-leading-zero;") ); } } } var paragraphLevel = ListItemRetriever.GetParagraphLevel(element); ListItemRetriever.LevelNumbers levelNums = element.Annotation(); string levelNumsString = levelNums .LevelNumbersArray .Take(paragraphLevel + 1) .Select(i => i.ToString() + ".") .StringConcatenate() .TrimEnd('.'); ListItemRetriever.ListItemInfo listItemInfo2 = element.Annotation(); var listItemRun = new XElement(W.r, new XAttribute(PtOpenXml.ListItemRun, levelNumsString), listItemInfo2 != null ? new XAttribute(PtOpenXml.AbstractNumId, listItemInfo2.AbstractNumId) : null, element.Attribute(PtOpenXml.FontName), element.Attribute(PtOpenXml.LanguageType), listItemRunProps, new XElement(W.t, new XAttribute(XNamespace.Xml + "space", "preserve"), li)); AdjustFontAttributes(wDoc, listItemRun, null, listItemRunProps, settings); var lvl = listItemInfo.Lvl(ListItemRetriever.GetParagraphLevel(element)); XElement suffix = new XElement(W.tab); var su = (string)lvl.Elements(W.suff).Attributes(W.val).FirstOrDefault(); if (su == "space") suffix = new XElement(W.t, new XAttribute(XNamespace.Xml + "space", "preserve"), " "); else if (su == "nothing") suffix = null; var jc = (string)lvl.Elements(W.lvlJc).Attributes(W.val).FirstOrDefault(); if (jc == "right") { var accumulatedParaProps = element.Element(PtOpenXml.pPr); var hangingAtt = accumulatedParaProps.Elements(W.ind).Attributes(W.hanging).FirstOrDefault(); if (hangingAtt == null) { var listItemRunLength = WordprocessingMLUtil.CalcWidthOfRunInTwips(listItemRun); var ind = accumulatedParaProps.Element(W.ind); if (ind == null) { ind = new XElement(W.ind); accumulatedParaProps.Add(ind); } ind.Add(new XAttribute(W.hanging, listItemRunLength.ToString())); } else { var hanging = (int)hangingAtt; var listItemRunLength = WordprocessingMLUtil.CalcWidthOfRunInTwips(listItemRun); hanging += listItemRunLength; // should be width of list item, in twips hangingAtt.Value = hanging.ToString(); } } else if (jc == "center") { var accumulatedParaProps = element.Element(PtOpenXml.pPr); var hangingAtt = accumulatedParaProps.Elements(W.ind).Attributes(W.hanging).FirstOrDefault(); if (hangingAtt == null) { var listItemRunLength = WordprocessingMLUtil.CalcWidthOfRunInTwips(listItemRun); var ind = accumulatedParaProps.Element(W.ind); if (ind == null) { ind = new XElement(W.ind); accumulatedParaProps.Add(ind); } ind.Add(new XAttribute(W.hanging, (listItemRunLength / 2).ToString())); } else { var hanging = (int)hangingAtt; var listItemRunLength = WordprocessingMLUtil.CalcWidthOfRunInTwips(listItemRun); hanging += (listItemRunLength / 2); // should be half of width of list item, in twips hangingAtt.Value = hanging.ToString(); } } AddTabAtLeftIndent(element.Element(PtOpenXml.pPr)); XElement tabRun = suffix != null ? new XElement(W.r, new XAttribute(PtOpenXml.ListItemRun, levelNumsString), listItemRunProps, suffix) : null; bool isDeleted = false; bool isInserted = false; XAttribute authorAtt = null; XAttribute dateAtt = null; var paraDelElement = newParaProps .Elements(W.rPr) .Elements(W.del) .FirstOrDefault(); if (paraDelElement != null) { isDeleted = true; authorAtt = paraDelElement.Attribute(W.author); dateAtt = paraDelElement.Attribute(W.date); } var paraInsElement = newParaProps .Elements(W.rPr) .Elements(W.ins) .FirstOrDefault(); if (paraInsElement != null) { isInserted = true; authorAtt = paraInsElement.Attribute(W.author); dateAtt = paraInsElement.Attribute(W.date); } var paragraphBefore = element .SiblingsBeforeSelfReverseDocumentOrder() .FirstOrDefault(); if (paragraphBefore != null) { var paraInsElement2 = paragraphBefore .Elements(W.pPr) .Elements(W.rPr) .Elements(W.ins) .FirstOrDefault(); if (paraInsElement2 != null) { isInserted = true; authorAtt = paraInsElement2.Attribute(W.author); dateAtt = paraInsElement2.Attribute(W.date); } } #if false When you click Online Video, you can paste in the embed code for the video you want to add. #endif var pPrChange = element .Elements(W.pPr) .Elements(W.pPrChange) .FirstOrDefault(); if (pPrChange != null) { authorAtt = pPrChange.Attribute(W.author); dateAtt = pPrChange.Attribute(W.date); var thisNumPr = element .Elements(W.pPr) .Elements(W.numPr) .FirstOrDefault(); var thisNumPrChange = pPrChange .Elements(W.numPr) .FirstOrDefault(); if (thisNumPr != null && thisNumPrChange == null) isInserted = true; if (thisNumPr == null && thisNumPrChange != null) isDeleted = true; } if (isDeleted) { // convert listItemRun and tabRun to their deleted equivalents var highestId = wDoc .MainDocumentPart .GetXDocument() .Descendants() .Attributes(W.id) .Select(id => { int numId; if (int.TryParse((string)id, out numId)) return numId; else return 0; }) .Max(); listItemRun = new XElement(W.del, new XAttribute(W.id, highestId + 1), authorAtt, dateAtt, (XElement)TransformToDeleted(listItemRun)); tabRun = new XElement(W.del, new XAttribute(W.id, highestId + 2), authorAtt, dateAtt, (XElement)TransformToDeleted(tabRun)); } else { if (isInserted) { // convert listItemRun and tabRun to their inserted equivalents var highestId = wDoc .MainDocumentPart .GetXDocument() .Descendants() .Attributes(W.id) .Select(id => { int numId; if (int.TryParse((string)id, out numId)) return numId; else return 0; }) .Max(); listItemRun = new XElement(W.ins, new XAttribute(W.id, highestId + 1), authorAtt, dateAtt, listItemRun); tabRun = new XElement(W.ins, new XAttribute(W.id, highestId + 2), authorAtt, dateAtt, tabRun); } } XElement newPara = new XElement(W.p, element.Attribute(PtOpenXml.FontName), element.Attribute(PtOpenXml.LanguageType), element.Attribute(PtOpenXml.Unid), new XAttribute(PtOpenXml.AbstractNumId, abstractNumId), listItemHtmlAttributes, newParaProps, listItemRun, tabRun, element.Elements().Where(e => e.Name != W.pPr).Select(n => NormalizeListItemsTransform(fai, wDoc, n, settings))); return newPara; } } return new XElement(element.Name, element.Attributes(), element.Nodes().Select(n => NormalizeListItemsTransform(fai, wDoc, n, settings))); } return node; } private static object TransformToDeleted(XNode node) { XElement element = node as XElement; if (element != null) { if (element.Name == W.t) return new XElement(W.delText, element.Value); return new XElement(element.Name, element.Attributes(), element.Nodes().Select(n => TransformToDeleted(n))); } return node; } private static void AddTabAtLeftIndent(XElement pPr) { int left = 0; var ind = pPr.Element(W.ind); // todo need to handle W.start if (pPr.Attribute(W.left) != null) left = (int)pPr.Attribute(W.left); var tabs = pPr.Element(W.tabs); if (tabs == null) { tabs = new XElement(W.tabs); pPr.Add(tabs); } var tabAtLeft = tabs.Elements(W.tab).FirstOrDefault(t => WordprocessingMLUtil.StringToTwips((string)t.Attribute(W.pos)) == left); if (tabAtLeft == null) { tabs.Add( new XElement(W.tab, new XAttribute(W.val, "left"), new XAttribute(W.pos, left))); } } public static XName[] PtNamesToKeep = new[] { PtOpenXml.FontName, PtOpenXml.AbstractNumId, PtOpenXml.HtmlStructure, PtOpenXml.HtmlStyle, PtOpenXml.StyleName, PtOpenXml.LanguageType, PtOpenXml.ListItemRun, PtOpenXml.Unid, }; public static void NormalizePropsForPart(XDocument pxd, FormattingAssemblerSettings settings) { if (settings.CreateHtmlConverterAnnotationAttributes) { pxd.Root.Descendants().Attributes().Where(d => d.Name.Namespace == PtOpenXml.pt && !PtNamesToKeep.Contains(d.Name)).Remove(); if (pxd.Root.Attribute(XNamespace.Xmlns + "pt14") == null) pxd.Root.Add(new XAttribute(XNamespace.Xmlns + "pt14", PtOpenXml.pt.NamespaceName)); if (pxd.Root.Attribute(XNamespace.Xmlns + "mc") == null) pxd.Root.Add(new XAttribute(XNamespace.Xmlns + "mc", MC.mc.NamespaceName)); XAttribute mci = pxd.Root.Attribute(MC.Ignorable); if (mci != null) { if (!pxd.Root.Attribute(MC.Ignorable).Value.Contains("pt14")) { var ig = pxd.Root.Attribute(MC.Ignorable).Value + " pt14"; mci.Value = ig; } } else { pxd.Root.Add(new XAttribute(MC.Ignorable, "pt14")); } } else { pxd.Root.Descendants().Attributes().Where(d => d.Name.Namespace == PtOpenXml.pt).Remove(); } var runProps = pxd.Root.Descendants(PtOpenXml.rPr).ToList(); foreach (var item in runProps) { XElement newRunProps = new XElement(W.rPr, item.Attributes(), item.Elements()); XElement parent = item.Parent; if (parent.Name == W.p) { XElement existingParaProps = parent.Element(W.pPr); if (existingParaProps == null) { existingParaProps = new XElement(W.pPr); parent.Add(existingParaProps); } XElement existingRunProps = existingParaProps.Element(W.rPr); if (existingRunProps != null) { if (!settings.RemoveStyleNamesFromParagraphAndRunProperties) { if (newRunProps.Element(W.rStyle) == null) newRunProps.Add(existingRunProps.Element(W.rStyle)); } existingRunProps.ReplaceWith(newRunProps); } else existingParaProps.Add(newRunProps); } else { XElement existingRunProps = parent.Element(W.rPr); if (existingRunProps != null) { if (!settings.RemoveStyleNamesFromParagraphAndRunProperties) { if (newRunProps.Element(W.rStyle) == null) newRunProps.Add(existingRunProps.Element(W.rStyle)); } existingRunProps.ReplaceWith(newRunProps); } else parent.Add(newRunProps); } } var paraProps = pxd.Root.Descendants(PtOpenXml.pPr).ToList(); foreach (var item in paraProps) { var paraRunProps = item.Parent.Elements(W.pPr).Elements(W.rPr).FirstOrDefault(); var merged = MergeStyleElement(item.Element(W.rPr), paraRunProps); if (!settings.RemoveStyleNamesFromParagraphAndRunProperties) { if (merged.Element(W.rStyle) == null) { merged.Add(paraRunProps.Element(W.rStyle)); } } XElement newParaProps = new XElement(W.pPr, item.Attributes(), item.Elements().Where(e => e.Name != W.rPr), merged); XElement para = item.Parent; XElement existingParaProps = para.Element(W.pPr); if (existingParaProps != null) { if (!settings.RemoveStyleNamesFromParagraphAndRunProperties) { if (newParaProps.Element(W.pStyle) == null) newParaProps.Add(existingParaProps.Element(W.pStyle)); } existingParaProps.ReplaceWith(newParaProps); } else para.Add(newParaProps); } var tblProps = pxd.Root.Descendants(PtOpenXml.tblPr).ToList(); foreach (var item in tblProps) { XElement newTblProps = new XElement(item); newTblProps.Name = W.tblPr; XElement table = item.Parent; XElement existingTableProps = table.Element(W.tblPr); if (existingTableProps != null) existingTableProps.ReplaceWith(newTblProps); else table.AddFirst(newTblProps); } var trProps = pxd.Root.Descendants(PtOpenXml.trPr).ToList(); foreach (var item in trProps) { XElement newTrProps = new XElement(item); newTrProps.Name = W.trPr; XElement row = item.Parent; XElement existingRowProps = row.Element(W.trPr); if (existingRowProps != null) existingRowProps.ReplaceWith(newTrProps); else row.AddFirst(newTrProps); } var tcProps = pxd.Root.Descendants(PtOpenXml.tcPr).ToList(); foreach (var item in tcProps) { XElement newTcProps = new XElement(item); newTcProps.Name = W.tcPr; XElement row = item.Parent; XElement existingRowProps = row.Element(W.tcPr); if (existingRowProps != null) existingRowProps.ReplaceWith(newTcProps); else row.AddFirst(newTcProps); } pxd.Root.Descendants(W.numPr).Remove(); if (settings.RemoveStyleNamesFromParagraphAndRunProperties) { pxd.Root.Descendants(W.pStyle).Where(ps => ps.Parent.Name == W.pPr).Remove(); pxd.Root.Descendants(W.rStyle).Where(ps => ps.Parent.Name == W.rPr).Remove(); } pxd.Root.Descendants(W.tblStyle).Where(ps => ps.Parent.Name == W.tblPr).Remove(); pxd.Root.Descendants().Where(d => d.Name.Namespace == PtOpenXml.pt).Remove(); if (settings.OrderElementsPerStandard) { XElement newRoot = (XElement)WordprocessingMLUtil.WmlOrderElementsPerStandard(pxd.Root); pxd.Root.ReplaceWith(newRoot); } } private static void AssembleListItemInformation(WordprocessingDocument wordDoc, ListItemRetrieverSettings settings) { foreach (var part in wordDoc.ContentParts()) { XDocument xDoc = part.GetXDocument(); foreach (var para in xDoc.Descendants(W.p)) { ListItemRetriever.RetrieveListItem(wordDoc, para, settings); } } } private static void AnnotateWithGlobalDefaults(WordprocessingDocument wDoc, XElement rootElement, FormattingAssemblerSettings settings) { XElement globalDefaultParaProps = null; XElement globalDefaultParaPropsAsDefined = null; XElement globalDefaultRunProps = null; XElement globalDefaultRunPropsAsDefined = null; XDocument sXDoc = wDoc.MainDocumentPart.StyleDefinitionsPart.GetXDocument(); var defaultParaStyleName = (string)sXDoc .Root .Elements(W.style) .Where(st => (string)st.Attribute(W.type) == "paragraph" && st.Attribute(W._default).ToBoolean() == true) .Attributes(W.styleId) .FirstOrDefault(); var defaultCharStyleName = (string)sXDoc .Root .Elements(W.style) .Where(st => (string)st.Attribute(W.type) == "character" && st.Attribute(W._default).ToBoolean() == true) .Attributes(W.styleId) .FirstOrDefault(); XElement docDefaults = sXDoc.Root.Element(W.docDefaults); if (docDefaults != null) { globalDefaultParaPropsAsDefined = docDefaults.Elements(W.pPrDefault).Elements(W.pPr) .FirstOrDefault(); if (globalDefaultParaPropsAsDefined == null) globalDefaultParaPropsAsDefined = new XElement(W.pPr, new XElement(W.rPr)); globalDefaultRunPropsAsDefined = docDefaults.Elements(W.rPrDefault).Elements(W.rPr) .FirstOrDefault(); if (globalDefaultRunPropsAsDefined == null) globalDefaultRunPropsAsDefined = new XElement(W.rPr); if (globalDefaultRunPropsAsDefined.Element(W.rFonts) == null) globalDefaultRunPropsAsDefined.Add( new XElement(W.rFonts, new XAttribute(W.ascii, "Times New Roman"), new XAttribute(W.hAnsi, "Times New Roman"), new XAttribute(W.cs, "Times New Roman"))); if (globalDefaultRunPropsAsDefined.Element(W.sz) == null) globalDefaultRunPropsAsDefined.Add( new XElement(W.sz, new XAttribute(W.val, "20"))); if (globalDefaultRunPropsAsDefined.Element(W.szCs) == null) globalDefaultRunPropsAsDefined.Add( new XElement(W.szCs, new XAttribute(W.val, "20"))); var runPropsForGlobalDefaultParaProps = MergeStyleElement(globalDefaultRunPropsAsDefined, globalDefaultParaPropsAsDefined.Element(W.rPr)); globalDefaultParaProps = new XElement(globalDefaultParaPropsAsDefined.Name, globalDefaultParaPropsAsDefined.Attributes(), globalDefaultParaPropsAsDefined.Elements().Where(e => e.Name != W.rPr), runPropsForGlobalDefaultParaProps); globalDefaultRunProps = MergeStyleElement(globalDefaultParaPropsAsDefined.Element(W.rPr), globalDefaultRunPropsAsDefined); } var rPr = new XElement(W.rPr, new XElement(W.rFonts, new XAttribute(W.ascii, "Times New Roman"), new XAttribute(W.hAnsi, "Times New Roman"), new XAttribute(W.cs, "Times New Roman")), new XElement(W.sz, new XAttribute(W.val, "20")), new XElement(W.szCs, new XAttribute(W.val, "20"))); if (globalDefaultParaProps == null) globalDefaultParaProps = new XElement(W.pPr, rPr); if (globalDefaultRunProps == null) globalDefaultRunProps = rPr; XElement ptGlobalDefaultParaProps = new XElement(globalDefaultParaProps); XElement ptGlobalDefaultRunProps = new XElement(globalDefaultRunProps); ptGlobalDefaultParaProps.Name = PtOpenXml.pPr; ptGlobalDefaultRunProps.Name = PtOpenXml.rPr; var parasAndRuns = rootElement.Descendants().Where(d => { return d.Name == W.p || d.Name == W.r; }); if (settings.CreateHtmlConverterAnnotationAttributes) { foreach (var d in parasAndRuns) { if (d.Name == W.p) { var pStyle = (string)d.Elements(W.pPr).Elements(W.pStyle).Attributes(W.val).FirstOrDefault(); if (pStyle == null) pStyle = defaultParaStyleName; if (pStyle != null) { if (d.Attribute(PtOpenXml.StyleName) != null) d.Attribute(PtOpenXml.StyleName).Value = pStyle; else d.Add(new XAttribute(PtOpenXml.StyleName, pStyle)); } d.Add(ptGlobalDefaultParaProps); } else { var rStyle = (string)d.Elements(W.rPr).Elements(W.rStyle).Attributes(W.val).FirstOrDefault(); if (rStyle == null) rStyle = defaultCharStyleName; if (rStyle != null) { if (d.Attribute(PtOpenXml.StyleName) != null) d.Attribute(PtOpenXml.StyleName).Value = rStyle; else d.Add(new XAttribute(PtOpenXml.StyleName, rStyle)); } d.Add(ptGlobalDefaultRunProps); } } } else { foreach (var d in parasAndRuns) { if (d.Name == W.p) { d.Add(ptGlobalDefaultParaProps); } else { d.Add(ptGlobalDefaultRunProps); } } } } private static XElement BlankTcBorders = new XElement(W.tcBorders, new XElement(W.top, new XAttribute(W.val, "nil")), new XElement(W.left, new XAttribute(W.val, "nil")), new XElement(W.bottom, new XAttribute(W.val, "nil")), new XElement(W.right, new XAttribute(W.val, "nil"))); private static void AnnotateTablesWithTableStyles(WordprocessingDocument wDoc, XElement rootElement) { XDocument sXDoc = wDoc.MainDocumentPart.StyleDefinitionsPart.GetXDocument(); foreach (var tbl in rootElement.Descendants(W.tbl)) { string tblStyleName = (string)tbl.Elements(W.tblPr).Elements(W.tblStyle).Attributes(W.val).FirstOrDefault(); if (tblStyleName != null) { XElement style = TableStyleRollup(wDoc, tblStyleName); // annotate table with table style, in PowerTools namespace style.Name = PtOpenXml.style; tbl.Add(style); // merge tblPr in table style with tblPr of the table // annnotate in PowerTools namespace XElement tblPr2 = style.Element(W.tblPr); XElement tblPr3 = MergeStyleElement(tbl.Element(W.tblPr), tblPr2, true); if (tblPr3 != null) { XElement newTblPr = new XElement(tblPr3); newTblPr.Name = PtOpenXml.pt + "tblPr"; tbl.Add(newTblPr); } AddTcPrPtToEveryCell(tbl); AddOuterBorders(tbl, style); var tableTcPr = style.Element(W.tcPr); if (tableTcPr != null) { foreach (var row in tbl.Elements(W.tr)) { foreach (var cell in row.Elements(W.tc)) { bool tcPrPtExists = false; var tcPrPt = cell.Element(PtOpenXml.pt + "tcPr"); if (tcPrPt != null) tcPrPtExists = true; else tcPrPt = new XElement(W.tcPr); tcPrPt = MergeStyleElement(tableTcPr, tcPrPt); var newTcPrPt = new XElement(tcPrPt); newTcPrPt.Name = PtOpenXml.tcPr; if (tcPrPtExists) cell.Element(PtOpenXml.tcPr).ReplaceWith(newTcPrPt); else cell.Add(newTcPrPt); } } } // Iterate through every row and cell in the table, rolling up row properties and cell properties // as appropriate per the cnfStyle element, then replacing the row and cell properties foreach (var row in tbl.Elements(W.tr)) { XElement trPr2 = null; trPr2 = style.Element(W.trPr); if (trPr2 == null) trPr2 = new XElement(W.trPr); XElement rowCnf = row.Elements(W.trPr).Elements(W.cnfStyle).FirstOrDefault(); if (rowCnf != null) { foreach (var ot in TableStyleOverrideTypes) { XName attName = TableStyleOverrideXNameMap[ot]; if (rowCnf != null && rowCnf.Attribute(attName).ToBoolean() == true) { XElement o = style .Elements(W.tblStylePr) .Where(tsp => (string)tsp.Attribute(W.type) == ot) .FirstOrDefault(); if (o != null) { XElement ottrPr = o.Element(W.trPr); trPr2 = MergeStyleElement(ottrPr, trPr2); } } } } trPr2 = MergeStyleElement(row.Element(W.trPr), trPr2); if (trPr2.HasElements) { trPr2.Name = PtOpenXml.pt + "trPr"; row.Add(trPr2); } } foreach (var ot in TableStyleOverrideTypes) { XName attName = TableStyleOverrideXNameMap[ot]; if (attName == W.oddHBand || attName == W.evenHBand || attName == W.firstRow || attName == W.lastRow) { foreach (var row in tbl.Elements(W.tr)) { XElement rowCnf = row.Elements(W.trPr).Elements(W.cnfStyle).FirstOrDefault(); if (rowCnf != null && rowCnf.Attribute(attName).ToBoolean() == true) { XElement o = style .Elements(W.tblStylePr) .Where(tsp => (string)tsp.Attribute(W.type) == ot) .FirstOrDefault(); if (o != null) { foreach (var cell in row.Elements(W.tc)) { bool tcPrPtExists = false; var tcPrPt = cell.Element(PtOpenXml.pt + "tcPr"); if (tcPrPt != null) tcPrPtExists = true; else tcPrPt = new XElement(W.tcPr); tcPrPt = MergeStyleElement(o.Element(W.tcPr), tcPrPt); var newTcPrPt = new XElement(tcPrPt); newTcPrPt.Name = PtOpenXml.pt + "tcPr"; if (tcPrPtExists) cell.Element(PtOpenXml.pt + "tcPr").ReplaceWith(newTcPrPt); else cell.Add(newTcPrPt); } } } } } else if (attName == W.firstColumn || attName == W.lastColumn || attName == W.oddVBand || attName == W.evenVBand) { foreach (var row in tbl.Elements(W.tr)) { foreach (var cell in row.Elements(W.tc)) { ApplyCndFmtToCell(style, ot, attName, cell); } } } else if (attName == W.firstRowLastColumn) { var row = tbl.Elements(W.tr).FirstOrDefault(); if (row != null) { var cell = row.Elements(W.tc).LastOrDefault(); if (cell != null) ApplyCndFmtToCell(style, ot, attName, cell); } } else if (attName == W.firstRowFirstColumn) { var row = tbl.Elements(W.tr).FirstOrDefault(); if (row != null) { var cell = row.Elements(W.tc).FirstOrDefault(); if (cell != null) ApplyCndFmtToCell(style, ot, attName, cell); } } else if (attName == W.lastRowLastColumn) { var row = tbl.Elements(W.tr).LastOrDefault(); if (row != null) { var cell = row.Elements(W.tc).LastOrDefault(); if (cell != null) ApplyCndFmtToCell(style, ot, attName, cell); } } else if (attName == W.lastRowFirstColumn) { var row = tbl.Elements(W.tr).LastOrDefault(); if (row != null) { var cell = row.Elements(W.tc).FirstOrDefault(); if (cell != null) ApplyCndFmtToCell(style, ot, attName, cell); } } } ProcessInnerBorders(tbl, style); } else { var tblPr = new XElement(W.tblPr); XElement tblPr3 = MergeStyleElement(tbl.Element(W.tblPr), tblPr, true); if (tblPr3 != null) { XElement newTblPr = new XElement(tblPr3); newTblPr.Name = PtOpenXml.pt + "tblPr"; tbl.Add(newTblPr); } AddTcPrPtToEveryCell(tbl); } RollInDirectFormatting(tbl); // it is important that this is last. This merges in direct formatting. } } private static void AddTcPrPtToEveryCell(XElement tbl) { foreach (var row in tbl.Elements(W.tr)) { foreach (var cell in row.Elements(W.tc)) { var tcPrPt = cell.Element(PtOpenXml.pt + "tcPr"); if (tcPrPt != null) continue; tcPrPt = new XElement(PtOpenXml.pt + "tcPr", new XElement(W.tcBorders)); cell.Add(tcPrPt); } } } private static void ApplyCndFmtToCell(XElement style, string ot, XName attName, XElement cell) { XElement cellCnf = cell.Elements(W.tcPr).Elements(W.cnfStyle).FirstOrDefault(); if (cellCnf != null && cellCnf.Attribute(attName).ToBoolean() == true) { XElement o = style .Elements(W.tblStylePr) .Where(tsp => (string)tsp.Attribute(W.type) == ot) .FirstOrDefault(); if (o != null) { bool tcPrPtExists = false; var tcPrPt = cell.Element(PtOpenXml.pt + "tcPr"); if (tcPrPt != null) tcPrPtExists = true; else tcPrPt = new XElement(W.tcPr); tcPrPt = MergeStyleElement(o.Element(W.tcPr), tcPrPt); var newTcPrPt = new XElement(tcPrPt); newTcPrPt.Name = PtOpenXml.pt + "tcPr"; if (tcPrPtExists) cell.Element(PtOpenXml.pt + "tcPr").ReplaceWith(newTcPrPt); else cell.Add(newTcPrPt); } } } private static void RollInDirectFormatting(XElement tbl) { foreach (var row in tbl.Elements(W.tr)) { foreach (var cell in row.Elements(W.tc)) { var ptTcPr = cell.Element(PtOpenXml.pt + "tcPr"); var tcPr = cell.Element(W.tcPr); var mTcPr = MergeStyleElement(tcPr, ptTcPr); if (mTcPr == null) { mTcPr = new XElement(PtOpenXml.pt + "tcPr"); cell.Add(tcPr); } var newTcPr = new XElement(mTcPr); newTcPr.Name = PtOpenXml.pt + "tcPr"; var existing = cell.Element(PtOpenXml.pt + "tcPr"); if (existing != null) existing.ReplaceWith(newTcPr); else cell.Add(newTcPr); } } var tblBorders = tbl.Elements(PtOpenXml.pt + "tblPr").Elements(W.tblBorders).FirstOrDefault(); if (tblBorders != null && tblBorders.Attribute(PtOpenXml.pt + "fromDirect") != null) { ApplyTblBordersToTable(tbl, tblBorders); ProcessInnerBordersPerTblBorders(tbl, tblBorders); } } private static void ApplyTblBordersToTable(XElement tbl, XElement tblBorders) { var top = tblBorders.Element(W.top); if (top != null) { var firstRow = tbl.Elements(W.tr).FirstOrDefault(); if (firstRow != null) { foreach (var cell in firstRow.Elements(W.tc)) { var cellTcBorders = cell.Elements(PtOpenXml.pt + "tcPr").Elements(W.tcBorders).FirstOrDefault(); if (cellTcBorders != null) { var cellTop = cellTcBorders.Element(W.top); if (cellTop == null) cellTcBorders.Add(top); else cellTop.ReplaceAttributes(top.Attributes()); } } } } var bottom = tblBorders.Element(W.bottom); if (bottom != null) { var lastRow = tbl.Elements(W.tr).LastOrDefault(); if (lastRow != null) { foreach (var cell in lastRow.Elements(W.tc)) { var cellTcBorders = cell.Elements(PtOpenXml.pt + "tcPr").Elements(W.tcBorders).FirstOrDefault(); if (cellTcBorders != null) { var cellBottom = cellTcBorders.Element(W.bottom); if (cellBottom == null) cellTcBorders.Add(bottom); else cellBottom.ReplaceAttributes(bottom.Attributes()); } } } } foreach (var row in tbl.Elements(W.tr)) { var left = tblBorders.Element(W.left); if (left != null) { var firstCell = row.Elements(W.tc).FirstOrDefault(); if (firstCell != null) { var cellTcBorders = firstCell.Elements(PtOpenXml.pt + "tcPr").Elements(W.tcBorders).FirstOrDefault(); if (cellTcBorders != null) { var firstCellLeft = cellTcBorders.Element(W.left); if (firstCellLeft == null) cellTcBorders.Add(left); else firstCellLeft.ReplaceAttributes(left.Attributes()); } } } var right = tblBorders.Element(W.right); if (right != null) { var lastCell = row.Elements(W.tc).LastOrDefault(); if (lastCell != null) { var cellTcBorders = lastCell.Elements(PtOpenXml.pt + "tcPr").Elements(W.tcBorders).FirstOrDefault(); if (cellTcBorders != null) { var lastCellRight = cellTcBorders.Element(W.right); if (lastCellRight == null) cellTcBorders.Add(right); else lastCellRight.ReplaceAttributes(right.Attributes()); } } } } } private static void AddOuterBorders(XElement tbl, XElement style) { var tblBorders = tbl.Elements(PtOpenXml.pt + "tblPr").Elements(W.tblBorders).FirstOrDefault(); if (tblBorders != null) ApplyTblBordersToTable(tbl, tblBorders); } private static void ProcessInnerBorders(XElement tbl, XElement style) { var tblBorders = tbl.Elements(PtOpenXml.pt + "tblPr").Elements(W.tblBorders).FirstOrDefault(); if (tblBorders != null) ProcessInnerBordersPerTblBorders(tbl, tblBorders); foreach (var attName in new[] { W.oddHBand, W.evenHBand, W.firstRow, W.lastRow }) { int rowCount = tbl.Elements(W.tr).Count(); int lastRow = rowCount - 1; XElement insideV = null; foreach (var row in tbl.Elements(W.tr)) { var rowCnfStyle = row.Elements(W.trPr).Elements(W.cnfStyle).FirstOrDefault(); if (rowCnfStyle != null) { var shouldApply = rowCnfStyle.Attribute(attName).ToBoolean(); if (shouldApply == true) { var cndType = TableStyleOverrideXNameRevMap[attName]; var cndStyle = style .Elements(W.tblStylePr) .FirstOrDefault(tsp => (string)tsp.Attribute(W.type) == cndType); if (cndStyle != null) { var styleTcBorders = cndStyle.Elements(W.tcPr).Elements(W.tcBorders).FirstOrDefault(); if (styleTcBorders != null) { var top = styleTcBorders.Element(W.top); var left = styleTcBorders.Element(W.left); var bottom = styleTcBorders.Element(W.bottom); var right = styleTcBorders.Element(W.right); insideV = cndStyle.Elements(W.tcPr).Elements(W.tcBorders).Elements(W.insideV).FirstOrDefault(); if (insideV != null) { int lastCol = row.Elements(W.tc).Count() - 1; int colIdx = 0; foreach (var cell in row.Elements(W.tc)) { var tcBorders = cell.Elements(PtOpenXml.pt + "tcPr").Elements(W.tcBorders).FirstOrDefault(); if (colIdx == 0) { ResolveInsideWithExisting(tcBorders, insideV, W.right); } else if (colIdx == lastCol) { ResolveInsideWithExisting(tcBorders, insideV, W.left); } else { ResolveInsideWithExisting(tcBorders, insideV, W.left); ResolveInsideWithExisting(tcBorders, insideV, W.right); } colIdx++; } } } } } } } } foreach (var attName in new[] { W.oddVBand, W.evenVBand, W.firstColumn, W.lastColumn }) { int rowIdx = 0; int lastRow = tbl.Elements(W.tr).Count() - 1; foreach (var row in tbl.Elements(W.tr)) { foreach (var cell in row.Elements(W.tc)) { var cellCnfStyle = cell.Elements(W.tcPr).Elements(W.cnfStyle).FirstOrDefault(); if (cellCnfStyle != null) { var shouldApply = cellCnfStyle.Attribute(attName).ToBoolean(); if (shouldApply == true) { var cndType = TableStyleOverrideXNameRevMap[attName]; var cndStyle = style .Elements(W.tblStylePr) .FirstOrDefault(tsp => (string)tsp.Attribute(W.type) == cndType); if (cndStyle != null) { var insideH = cndStyle.Elements(W.tcPr).Elements(W.tcBorders).Elements(W.insideH).FirstOrDefault(); if (insideH != null) { var tcBorders = cell.Elements(PtOpenXml.pt + "tcPr").Elements(W.tcBorders).FirstOrDefault(); if (rowIdx == 0) { ResolveInsideWithExisting(tcBorders, insideH, W.bottom); } else if (rowIdx == lastRow) { ResolveInsideWithExisting(tcBorders, insideH, W.top); } else { ResolveInsideWithExisting(tcBorders, insideH, W.bottom); ResolveInsideWithExisting(tcBorders, insideH, W.top); } } } } } } rowIdx++; } } } private static void ProcessInnerBordersPerTblBorders(XElement tbl, XElement tblBorders) { var tblInsideV = tblBorders.Elements(W.insideV).FirstOrDefault(); if (tblInsideV != null) { foreach (var row in tbl.Elements(W.tr)) { var lastCell = row.Elements(W.tc).Count() - 1; int cellIdx = 0; foreach (var cell in row.Elements(W.tc)) { var tcPr = cell.Element(PtOpenXml.pt + "tcPr"); if (tcPr == null) { tcPr = new XElement(PtOpenXml.pt + "tcPr"); cell.Add(tcPr); } var tcBorders = tcPr.Element(W.tcBorders); if (tcBorders == null) { tcBorders = new XElement(W.tcBorders); tcPr.Add(tcBorders); } if (cellIdx == 0) { ResolveInsideWithExisting(tcBorders, tblInsideV, W.right); } else if (cellIdx == lastCell) { ResolveInsideWithExisting(tcBorders, tblInsideV, W.left); } else { ResolveInsideWithExisting(tcBorders, tblInsideV, W.left); ResolveInsideWithExisting(tcBorders, tblInsideV, W.right); } cellIdx++; } } } var tblInsideH = tblBorders.Elements(W.insideH).FirstOrDefault(); if (tblInsideH != null) { int rowIdx1 = 0; int lastRow1 = tbl.Elements(W.tr).Count() - 1; foreach (var row in tbl.Elements(W.tr)) { if (rowIdx1 == 0) { foreach (var cell in row.Elements(W.tc)) { var tcBorders = cell.Elements(PtOpenXml.pt + "tcPr").Elements(W.tcBorders).FirstOrDefault(); ResolveInsideWithExisting(tcBorders, tblInsideH, W.bottom); } } else if (rowIdx1 == lastRow1) { foreach (var cell in row.Elements(W.tc)) { var tcBorders = cell.Elements(PtOpenXml.pt + "tcPr").Elements(W.tcBorders).FirstOrDefault(); ResolveInsideWithExisting(tcBorders, tblInsideH, W.top); } } else { foreach (var cell in row.Elements(W.tc)) { var tcBorders = cell.Elements(PtOpenXml.pt + "tcPr").Elements(W.tcBorders).FirstOrDefault(); ResolveInsideWithExisting(tcBorders, tblInsideH, W.top); ResolveInsideWithExisting(tcBorders, tblInsideH, W.bottom); } } rowIdx1++; } } } private static void ResolveInsideWithExisting(XElement tcBorders, XElement inside, XName whichSide) { if (tcBorders.Element(whichSide) != null) { var newInsideH = ResolveInsideBorder(inside, tcBorders.Element(whichSide)); tcBorders.Element(whichSide).ReplaceAttributes( newInsideH.Attributes()); } else { tcBorders.Add( new XElement(whichSide, inside.Attributes())); } } private static Dictionary BorderTypePriority = new Dictionary() { { "single", 1 }, { "thick", 2 }, { "double", 3 }, { "dotted", 4 }, }; private static Dictionary BorderNumber = new Dictionary() { {"single", 1 }, {"thick", 2 }, {"double", 3 }, {"dotted", 4 }, {"dashed", 5 }, {"dotDash", 6 }, {"dotDotDash", 7 }, {"triple", 8 }, {"thinThickSmallGap", 9 }, {"thickThinSmallGap", 10 }, {"thinThickThinSmallGap", 11 }, {"thinThickMediumGap", 12 }, {"thickThinMediumGap", 13 }, {"thinThickThinMediumGap", 14 }, {"thinThickLargeGap", 15 }, {"thickThinLargeGap", 16 }, {"thinThickThinLargeGap", 17 }, {"wave", 18 }, {"doubleWave", 19 }, {"dashSmallGap", 20 }, {"dashDotStroked", 21 }, {"threeDEmboss", 22 }, {"threeDEngrave", 23 }, {"outset", 24 }, {"inset", 25 }, }; private static XElement ResolveInsideBorder(XElement inside1, XElement sideToReplace) { if (inside1 == null && sideToReplace == null) return null; if (inside1 == null) return sideToReplace; if (sideToReplace == null) return inside1; // The following handles the situation where // if table innerV is set, and cnd format for first row specifies nill border, then nil border wins. // if table innerH is set, and cnd format for first columns specifies nil border, then table innerH wins. if (sideToReplace.Name == W.left || sideToReplace.Name == W.right) { if ((string)inside1.Attribute(W.val) == "nil") return inside1; if ((string)sideToReplace.Attribute(W.val) == "nil") return sideToReplace; } else { if ((string)inside1.Attribute(W.val) == "nil") return sideToReplace; if ((string)sideToReplace.Attribute(W.val) == "nil") return inside1; } var inside1Val = (string)inside1.Attribute(W.val); var border1Weight = 1; if (BorderNumber.ContainsKey(inside1Val)) border1Weight = BorderNumber[inside1Val]; var sideToReplaceVal = (string)sideToReplace.Attribute(W.val); var sideToReplaceWeight = 1; if (BorderNumber.ContainsKey(sideToReplaceVal)) sideToReplaceWeight = BorderNumber[sideToReplaceVal]; if (border1Weight != sideToReplaceWeight) { if (border1Weight < sideToReplaceWeight) return sideToReplace; else return inside1; } if ((int)inside1.Attribute(W.sz) > (int)sideToReplace.Attribute(W.sz)) return inside1; if ((int)sideToReplace.Attribute(W.sz) > (int)inside1.Attribute(W.sz)) return sideToReplace; if (BorderTypePriority.ContainsKey(inside1Val) && BorderTypePriority.ContainsKey(sideToReplaceVal)) { var inside1Pri = BorderTypePriority[inside1Val]; var inside2Pri = BorderTypePriority[sideToReplaceVal]; if (inside1Pri > inside2Pri) return inside1; if (inside2Pri > inside1Pri) return sideToReplace; } if ((int)inside1.Attribute(W.sz) > (int)sideToReplace.Attribute(W.sz)) return inside1; if ((int)sideToReplace.Attribute(W.sz) > (int)inside1.Attribute(W.sz)) return sideToReplace; var color1str = (string)inside1.Attribute(W.color); if (color1str == "auto") color1str = "000000"; var color2str = (string)sideToReplace.Attribute(W.color); if (color2str == "auto") color2str = "000000"; if (color1str != null && color2str != null && color1str != color2str) { Int32 color1; Int32 color2; try { color1 = Convert.ToInt32(color1str, 16); } // if the above throws ArgumentException, FormatException, or OverflowException, then abort catch (Exception) { return sideToReplace; } try { color2 = Convert.ToInt32(color2str, 16); } // if the above throws ArgumentException, FormatException, or OverflowException, then abort catch (Exception) { return inside1; } if (color1 < color2) return inside1; if (color2 < color1) return sideToReplace; return inside1; } return inside1; } private static XElement TableStyleRollup(WordprocessingDocument wDoc, string tblStyleName) { var tblStyleChain = TableStyleStack(wDoc, tblStyleName) .Reverse(); XElement rolledStyle = new XElement(W.style); foreach (var style in tblStyleChain) { rolledStyle = MergeStyleElement(style, rolledStyle); } return rolledStyle; } private static XName[] SpecialCaseChildProperties = { W.tblPr, W.trPr, W.tcPr, W.pPr, W.rPr, W.pBdr, W.tabs, W.rFonts, W.ind, W.spacing, W.tblStylePr, W.tcBorders, W.tblBorders, W.lang, W.numPr, }; private static XName[] MergeChildProperties = { W.tblPr, W.trPr, W.tcPr, W.pPr, W.rPr, W.pBdr, W.tcBorders, W.tblBorders, W.numPr, }; private static string[] TableStyleOverrideTypes = { "band1Vert", "band2Vert", "band1Horz", "band2Horz", "firstCol", "lastCol", "firstRow", "lastRow", "neCell", "nwCell", "seCell", "swCell", }; private static Dictionary TableStyleOverrideXNameMap = new Dictionary { {"band1Vert", W.oddVBand}, {"band2Vert", W.evenVBand}, {"band1Horz", W.oddHBand}, {"band2Horz", W.evenHBand}, {"firstCol", W.firstColumn}, {"lastCol", W.lastColumn}, {"firstRow", W.firstRow}, {"lastRow", W.lastRow}, {"neCell", W.firstRowLastColumn}, {"nwCell", W.firstRowFirstColumn}, {"seCell", W.lastRowLastColumn}, {"swCell", W.lastRowFirstColumn}, }; private static Dictionary TableStyleOverrideXNameRevMap = new Dictionary { {W.oddVBand, "band1Vert"}, {W.evenVBand, "band2Vert"}, {W.oddHBand, "band1Horz"}, {W.evenHBand, "band2Horz"}, {W.firstColumn, "firstCol"}, {W.lastColumn, "lastCol"}, {W.firstRow, "firstRow"}, {W.lastRow, "lastRow"}, }; private static XElement MergeStyleElement(XElement higherPriorityElement, XElement lowerPriorityElement) { return MergeStyleElement(higherPriorityElement, lowerPriorityElement, null); } private static XElement MergeStyleElement(XElement higherPriorityElement, XElement lowerPriorityElement, bool? highPriIsDirectFormatting) { // If, when in the process of merging, the source element doesn't have a // corresponding element in the merged element, then include the source element // in the merged element. if (lowerPriorityElement == null) return higherPriorityElement; if (higherPriorityElement == null) return lowerPriorityElement; var hpe = higherPriorityElement .Elements() .Where(e => !SpecialCaseChildProperties.Contains(e.Name)) .ToArray(); if (highPriIsDirectFormatting == true) { hpe = hpe .Select(e => new XElement(e.Name, e.Attributes(), new XAttribute(PtOpenXml.pt + "fromDirect", true), e.Elements())) .ToArray(); } var lpe = lowerPriorityElement .Elements() .Where(e => !SpecialCaseChildProperties.Contains(e.Name) && !hpe.Select(z => z.Name).Contains(e.Name)) .ToArray(); var ma = SpacingMerge(higherPriorityElement.Element(W.spacing), lowerPriorityElement.Element(W.spacing)); var rFonts = FontMerge(higherPriorityElement.Element(W.rFonts), lowerPriorityElement.Element(W.rFonts)); var tabs = TabsMerge(higherPriorityElement.Element(W.tabs), lowerPriorityElement.Element(W.tabs)); var ind = IndMerge(higherPriorityElement.Element(W.ind), lowerPriorityElement.Element(W.ind)); var lang = LangMerge(higherPriorityElement.Element(W.lang), lowerPriorityElement.Element(W.lang)); var mcp = MergeChildProperties .Select(e => { // test is here to prevent unnecessary recursion to make debugging easier var h = higherPriorityElement.Element(e); var l = lowerPriorityElement.Element(e); if (h == null && l == null) return null; if (h == null && l != null) return l; if (h != null && l == null) { var newH = new XElement(h.Name, h.Attributes(), highPriIsDirectFormatting == true ? new XAttribute(PtOpenXml.pt + "fromDirect", true) : null, h.Elements()); return newH; } return MergeStyleElement(h, l, highPriIsDirectFormatting); }) .Where(m => m != null) .ToArray(); var tsor = TableStyleOverrideTypes .Select(e => { // test is here to prevent unnecessary recursion to make debugging easier var h = higherPriorityElement.Elements(W.tblStylePr).FirstOrDefault(tsp => (string)tsp.Attribute(W.type) == e); var l = lowerPriorityElement.Elements(W.tblStylePr).FirstOrDefault(tsp => (string)tsp.Attribute(W.type) == e); if (h == null && l == null) return null; if (h == null && l != null) return l; if (h != null && l == null) return h; return MergeStyleElement(h, l); }) .Where(m => m != null) .ToArray(); XElement newMergedElement = new XElement(higherPriorityElement.Name, new XAttribute(XNamespace.Xmlns + "w", W.w), higherPriorityElement.Attributes().Where(a => !a.IsNamespaceDeclaration), hpe, // higher priority elements lpe, // lower priority elements where there is not a higher priority element of same name ind, // w:ind has very special rules ma, // elements that require merged attributes lang, rFonts, // font merge is special case tabs, // tabs merge is special case mcp, // elements that need child properties to be merged tsor // merged table style override elements ); return newMergedElement; } private static XElement LangMerge(XElement hLang, XElement lLang) { if (hLang == null && lLang == null) return null; if (hLang != null && lLang == null) return hLang; if (lLang != null && hLang == null) return lLang; return new XElement(W.lang, hLang.Attribute(W.val) != null ? hLang.Attribute(W.val) : lLang.Attribute(W.val), hLang.Attribute(W.bidi) != null ? hLang.Attribute(W.bidi) : lLang.Attribute(W.bidi), hLang.Attribute(W.eastAsia) != null ? hLang.Attribute(W.eastAsia) : lLang.Attribute(W.eastAsia)); } private enum IndAttType { End, FirstLineOrHanging, Start, Left, Right, None, }; private static XElement IndMerge(XElement higherPriorityElement, XElement lowerPriorityElement) { if (higherPriorityElement == null && lowerPriorityElement == null) return null; if (higherPriorityElement != null && lowerPriorityElement == null) return higherPriorityElement; if (lowerPriorityElement != null && higherPriorityElement == null) return lowerPriorityElement; XElement hpe = new XElement(higherPriorityElement); XElement lpe = new XElement(lowerPriorityElement); if (hpe.Attribute(W.firstLine) != null) lpe.Attributes(W.hanging).Remove(); if (hpe.Attribute(W.firstLineChars) != null) lpe.Attributes(W.hangingChars).Remove(); if (hpe.Attribute(W.hanging) != null) lpe.Attributes(W.firstLine).Remove(); if (hpe.Attribute(W.hangingChars) != null) lpe.Attributes(W.firstLineChars).Remove(); var highPriAtts = hpe .Attributes() .Where(a => !a.IsNamespaceDeclaration) .ToList(); var highPriAttNames = highPriAtts .Select(a => a.Name); var lowPriAtts = lpe .Attributes() .Where(a => !a.IsNamespaceDeclaration) .Where(a => !highPriAttNames.Contains(a.Name)) .ToList(); var mergedElement = new XElement(higherPriorityElement.Name, highPriAtts, lowPriAtts); return mergedElement; } // merge child tab elements // they are additive, with the exception that if there are two elements at the same location, // we need to take the higher, and not take the lower. private static XElement TabsMerge(XElement higherPriorityElement, XElement lowerPriorityElement) { if (higherPriorityElement != null && lowerPriorityElement == null) return higherPriorityElement; if (higherPriorityElement == null && lowerPriorityElement != null) return lowerPriorityElement; if (higherPriorityElement == null && lowerPriorityElement == null) return null; var hps = higherPriorityElement.Elements().Select(e => new { Pos = WordprocessingMLUtil.StringToTwips((string)e.Attribute(W.pos)), Pri = 1, Element = e, } ); var lps = lowerPriorityElement.Elements().Select(e => new { Pos = WordprocessingMLUtil.StringToTwips((string)e.Attribute(W.pos)), Pri = 2, Element = e, } ); var newTabElements = hps.Concat(lps) .GroupBy(s => s.Pos) .Select(g => g.OrderBy(s => s.Pri).First().Element) .Where(e => (string)e.Attribute(W.val) != "clear") .OrderBy(e => WordprocessingMLUtil.StringToTwips((string)e.Attribute(W.pos))); var newTabs = new XElement(W.tabs, newTabElements); return newTabs; } private static XElement SpacingMerge(XElement hn, XElement ln) { if (hn == null && ln == null) return null; if (hn != null && ln == null) return hn; if (hn == null && ln != null) return ln; var mn1 = new XElement(W.spacing, hn.Attributes(), ln.Attributes().Where(a => hn.Attribute(a.Name) == null)); return mn1; } private static IEnumerable TableStyleStack(WordprocessingDocument wDoc, string tblStyleName) { XDocument sXDoc = wDoc.MainDocumentPart.StyleDefinitionsPart.GetXDocument(); string currentStyle = tblStyleName; while (true) { XElement style = sXDoc .Root .Elements(W.style).Where(s => (string)s.Attribute(W.type) == "table" && (string)s.Attribute(W.styleId) == currentStyle) .FirstOrDefault(); if (style == null) yield break; yield return style; currentStyle = (string)style.Elements(W.basedOn).Attributes(W.val).FirstOrDefault(); if (currentStyle == null) yield break; } } private static XElement FontMerge(XElement higherPriorityFont, XElement lowerPriorityFont) { XElement rFonts; if (higherPriorityFont == null) return lowerPriorityFont; if (lowerPriorityFont == null) return higherPriorityFont; if (higherPriorityFont == null && lowerPriorityFont == null) return null; rFonts = new XElement(W.rFonts, (higherPriorityFont.Attribute(W.ascii) != null || higherPriorityFont.Attribute(W.asciiTheme) != null) ? new[] { higherPriorityFont.Attribute(W.ascii), higherPriorityFont.Attribute(W.asciiTheme) } : new[] { lowerPriorityFont.Attribute(W.ascii), lowerPriorityFont.Attribute(W.asciiTheme) }, (higherPriorityFont.Attribute(W.hAnsi) != null || higherPriorityFont.Attribute(W.hAnsiTheme) != null) ? new[] { higherPriorityFont.Attribute(W.hAnsi), higherPriorityFont.Attribute(W.hAnsiTheme) } : new[] { lowerPriorityFont.Attribute(W.hAnsi), lowerPriorityFont.Attribute(W.hAnsiTheme) }, (higherPriorityFont.Attribute(W.eastAsia) != null || higherPriorityFont.Attribute(W.eastAsiaTheme) != null) ? new[] { higherPriorityFont.Attribute(W.eastAsia), higherPriorityFont.Attribute(W.eastAsiaTheme) } : new[] { lowerPriorityFont.Attribute(W.eastAsia), lowerPriorityFont.Attribute(W.eastAsiaTheme) }, (higherPriorityFont.Attribute(W.cs) != null || higherPriorityFont.Attribute(W.cstheme) != null) ? new[] { higherPriorityFont.Attribute(W.cs), higherPriorityFont.Attribute(W.cstheme) } : new[] { lowerPriorityFont.Attribute(W.cs), lowerPriorityFont.Attribute(W.cstheme) }, (higherPriorityFont.Attribute(W.hint) != null ? higherPriorityFont.Attribute(W.hint) : lowerPriorityFont.Attribute(W.hint)) ); return rFonts; } private static void AnnotateParagraphs(FormattingAssemblerInfo fai, WordprocessingDocument wDoc, XElement root, FormattingAssemblerSettings settings) { foreach (var para in root.Descendants(W.p)) { AnnotateParagraph(fai, wDoc, para, settings); } } private static void AnnotateParagraph(FormattingAssemblerInfo fai, WordprocessingDocument wDoc, XElement para, FormattingAssemblerSettings settings) { XElement localParaProps = para.Element(W.pPr); if (localParaProps == null) { localParaProps = new XElement(W.pPr); } // get para table props, to be merged. XElement tablepPr = null; var blockLevelContentContainer = para .Ancestors() .FirstOrDefault(a => a.Name == W.body || a.Name == W.tbl || a.Name == W.txbxContent || a.Name == W.ftr || a.Name == W.hdr || a.Name == W.footnote || a.Name == W.endnote); if (blockLevelContentContainer.Name == W.tbl) { XElement tbl = blockLevelContentContainer; XElement style = tbl.Element(PtOpenXml.pt + "style"); XElement cellCnf = para.Ancestors(W.tc).Take(1).Elements(W.tcPr).Elements(W.cnfStyle).FirstOrDefault(); XElement rowCnf = para.Ancestors(W.tr).Take(1).Elements(W.trPr).Elements(W.cnfStyle).FirstOrDefault(); if (style != null) { // roll up tblPr, trPr, and tcPr from within a specific style. // add each of these to the table, in PowerTools namespace. tablepPr = style.Element(W.pPr); if (tablepPr == null) tablepPr = new XElement(W.pPr); foreach (var ot in TableStyleOverrideTypes) { XName attName = TableStyleOverrideXNameMap[ot]; if ((cellCnf != null && cellCnf.Attribute(attName).ToBoolean() == true) || (rowCnf != null && rowCnf.Attribute(attName).ToBoolean() == true)) { XElement o = style .Elements(W.tblStylePr) .Where(tsp => (string)tsp.Attribute(W.type) == ot) .FirstOrDefault(); if (o != null) { XElement otpPr = o.Element(W.pPr); tablepPr = MergeStyleElement(otpPr, tablepPr); } } } } } var stylesPart = wDoc.MainDocumentPart.StyleDefinitionsPart; XDocument sXDoc = null; if (stylesPart != null) sXDoc = stylesPart.GetXDocument(); ListItemRetriever.ListItemInfo lif = para.Annotation(); XElement rolledParaProps = ParagraphStyleRollup(para, sXDoc, fai.DefaultParagraphStyleName); if (lif != null && lif.IsZeroNumId) rolledParaProps.Elements(W.ind).Remove(); XElement toggledParaProps = MergeStyleElement(rolledParaProps, tablepPr); XElement mergedParaProps = MergeStyleElement(localParaProps, toggledParaProps); string li = ListItemRetriever.RetrieveListItem(wDoc, para, settings.ListItemRetrieverSettings); if (lif != null && lif.IsListItem) { if (settings.RestrictToSupportedNumberingFormats) { string numFmtForLevel = (string)lif.Lvl(ListItemRetriever.GetParagraphLevel(para)).Elements(W.numFmt).Attributes(W.val).FirstOrDefault(); if (numFmtForLevel == null) { var numFmtElement = lif.Lvl(ListItemRetriever.GetParagraphLevel(para)).Elements(MC.AlternateContent).Elements(MC.Choice).Elements(W.numFmt).FirstOrDefault(); if (numFmtElement != null && (string)numFmtElement.Attribute(W.val) == "custom") numFmtForLevel = (string)numFmtElement.Attribute(W.format); } bool isLgl = lif.Lvl(ListItemRetriever.GetParagraphLevel(para)).Elements(W.isLgl).Any(); if (isLgl && numFmtForLevel != "decimalZero") numFmtForLevel = "decimal"; if (!AcceptableNumFormats.Contains(numFmtForLevel)) throw new UnsupportedNumberingFormatException(numFmtForLevel + " is not a supported numbering format"); } int paragraphLevel = ListItemRetriever.GetParagraphLevel(para); var numberingParaProps = lif .Lvl(paragraphLevel) .Elements(W.pPr) .FirstOrDefault(); if (numberingParaProps == null) { numberingParaProps = new XElement(W.pPr); } else { numberingParaProps .Elements() .Where(e => e.Name != W.ind) .Remove(); } // have: // - localParaProps // - toggledParaProps // - numberingParaProps // if a paragraph contains a numPr with a numId=0, in other words, it is NOT a numbered item, then the indentation from the style // hierarchy is ignored. ListItemRetriever.ListItemInfo lii = para.Annotation(); if (lii.FromParagraph != null) { // order // - toggledParaProps // - numberingParaProps // - localParaProps mergedParaProps = MergeStyleElement(numberingParaProps, toggledParaProps); mergedParaProps = MergeStyleElement(localParaProps, mergedParaProps); } else if (lii.FromStyle != null) { // order // - numberingParaProps // - toggledParaProps // - localParaProps mergedParaProps = MergeStyleElement(toggledParaProps, numberingParaProps); mergedParaProps = MergeStyleElement(localParaProps, mergedParaProps); } } else { mergedParaProps = MergeStyleElement(localParaProps, toggledParaProps); } // merge mergedParaProps with existing accumulatedParaProps, with mergedParaProps as high pri // replace accumulatedParaProps with newly merged XElement accumulatedParaProps = para.Element(PtOpenXml.pt + "pPr"); XElement newAccumulatedParaProps = MergeStyleElement(mergedParaProps, accumulatedParaProps); AdjustFontAttributes(wDoc, para, newAccumulatedParaProps, newAccumulatedParaProps.Element(W.rPr), settings); newAccumulatedParaProps.Name = PtOpenXml.pt + "pPr"; if (accumulatedParaProps != null) { accumulatedParaProps.ReplaceWith(newAccumulatedParaProps); } else { para.Add(newAccumulatedParaProps); } } private static string[] AcceptableNumFormats = new[] { "decimal", "decimalZero", "upperRoman", "lowerRoman", "upperLetter", "lowerLetter", "ordinal", "cardinalText", "ordinalText", "bullet", "0001, 0002, 0003, ...", "none", }; public static XElement ParagraphStyleRollup(XElement paragraph, XDocument stylesXDoc, string defaultParagraphStyleName) { var paraStyle = (string)paragraph .Elements(W.pPr) .Elements(W.pStyle) .Attributes(W.val) .FirstOrDefault(); if (paraStyle == null) paraStyle = defaultParagraphStyleName; var rolledUpParaStyleParaProps = new XElement(W.pPr); if (stylesXDoc == null) return rolledUpParaStyleParaProps; if (paraStyle != null) { rolledUpParaStyleParaProps = ParaStyleParaPropsStack(stylesXDoc, paraStyle, paragraph) .Reverse() .Aggregate(new XElement(W.pPr), (r, s) => { var newParaProps = MergeStyleElement(s, r); return newParaProps; }); } return rolledUpParaStyleParaProps; } private static IEnumerable ParaStyleParaPropsStack(XDocument stylesXDoc, string paraStyleName, XElement para) { if (stylesXDoc == null) yield break; var localParaStyleName = paraStyleName; while (localParaStyleName != null) { XElement paraStyle = stylesXDoc.Root.Elements(W.style).FirstOrDefault(s => s.Attribute(W.type).Value == "paragraph" && s.Attribute(W.styleId).Value == localParaStyleName); if (paraStyle == null) { yield break; } if (paraStyle.Element(W.pPr) == null) { if (paraStyle.Element(W.rPr) != null) { var elementToYield2 = new XElement(W.pPr, paraStyle.Element(W.rPr)); yield return elementToYield2; } localParaStyleName = (string)(paraStyle .Elements(W.basedOn) .Attributes(W.val) .FirstOrDefault()); continue; } var elementToYield = new XElement(W.pPr, paraStyle.Element(W.pPr).Attributes(), paraStyle.Element(W.pPr).Elements(), paraStyle.Element(W.rPr)); yield return (elementToYield); var listItemInfo = para.Annotation(); if (listItemInfo != null) { if (listItemInfo.IsListItem) { XElement lipPr = listItemInfo.Lvl(ListItemRetriever.GetParagraphLevel(para)).Element(W.pPr); if (lipPr == null) lipPr = new XElement(W.pPr); XElement lirPr = listItemInfo.Lvl(ListItemRetriever.GetParagraphLevel(para)).Element(W.rPr); var elementToYield2 = new XElement(W.pPr, lipPr.Attributes(), lipPr.Elements(), lirPr); yield return (elementToYield2); } } localParaStyleName = (string)paraStyle .Elements(W.basedOn) .Attributes(W.val) .FirstOrDefault(); } yield break; } private static void AnnotateRuns(FormattingAssemblerInfo fai, WordprocessingDocument wDoc, XElement root, FormattingAssemblerSettings settings) { var runsOrParas = root.Descendants() .Where(rp => { return rp.Name == W.r || rp.Name == W.p; }); foreach (var runOrPara in runsOrParas) { AnnotateRunProperties(fai, wDoc, runOrPara, settings); } } private static void AnnotateRunProperties(FormattingAssemblerInfo fai, WordprocessingDocument wDoc, XElement runOrPara, FormattingAssemblerSettings settings) { XElement localRunProps = null; if (runOrPara.Name == W.p) { var rPr = runOrPara.Elements(W.pPr).Elements(W.rPr).FirstOrDefault(); if (rPr != null) { localRunProps = rPr; } } else { localRunProps = runOrPara.Element(W.rPr); } if (localRunProps == null) { localRunProps = new XElement(W.rPr); } // get run table props, to be merged. XElement tablerPr = null; var blockLevelContentContainer = runOrPara .Ancestors() .FirstOrDefault(a => a.Name == W.body || a.Name == W.tbl || a.Name == W.txbxContent || a.Name == W.ftr || a.Name == W.hdr || a.Name == W.footnote || a.Name == W.endnote); if (blockLevelContentContainer.Name == W.tbl) { XElement tbl = blockLevelContentContainer; XElement style = tbl.Element(PtOpenXml.pt + "style"); XElement cellCnf = runOrPara.Ancestors(W.tc).Take(1).Elements(W.tcPr).Elements(W.cnfStyle).FirstOrDefault(); XElement rowCnf = runOrPara.Ancestors(W.tr).Take(1).Elements(W.trPr).Elements(W.cnfStyle).FirstOrDefault(); if (style != null) { tablerPr = style.Element(W.rPr); if (tablerPr == null) tablerPr = new XElement(W.rPr); foreach (var ot in TableStyleOverrideTypes) { XName attName = TableStyleOverrideXNameMap[ot]; if ((cellCnf != null && cellCnf.Attribute(attName).ToBoolean() == true) || (rowCnf != null && rowCnf.Attribute(attName).ToBoolean() == true)) { XElement o = style .Elements(W.tblStylePr) .Where(tsp => (string)tsp.Attribute(W.type) == ot) .FirstOrDefault(); if (o != null) { XElement otrPr = o.Element(W.rPr); tablerPr = MergeStyleElement(otrPr, tablerPr); } } } } } XElement rolledRunProps = CharStyleRollup(fai, wDoc, runOrPara); var toggledRunProps = ToggleMergeRunProps(rolledRunProps, tablerPr); var currentRunProps = runOrPara.Element(PtOpenXml.rPr); // this is already stored on the run from previous aggregation of props var mergedRunProps = MergeStyleElement(toggledRunProps, currentRunProps); var newMergedRunProps = MergeStyleElement(localRunProps, mergedRunProps); XElement pPr = null; if (runOrPara.Name == W.p) pPr = runOrPara.Element(PtOpenXml.pPr); AdjustFontAttributes(wDoc, runOrPara, pPr, newMergedRunProps, settings); newMergedRunProps.Name = PtOpenXml.rPr; if (currentRunProps != null) { currentRunProps.ReplaceWith(newMergedRunProps); } else { runOrPara.Add(newMergedRunProps); } } private static XElement CharStyleRollup(FormattingAssemblerInfo fai, WordprocessingDocument wDoc, XElement runOrPara) { var sXDoc = wDoc.MainDocumentPart.StyleDefinitionsPart.GetXDocument(); string charStyle = null; string paraStyle = null; XElement rPr = null; XElement pPr = null; XElement pStyle = null; XElement rStyle = null; CachedParaInfo cpi = null; // CachedParaInfo is an optimization for the case where a paragraph contains thousands of runs. if (runOrPara.Name == W.p) { cpi = runOrPara.Annotation(); if (cpi != null) pPr = cpi.ParagraphProperties; else { pPr = runOrPara.Element(W.pPr); if (pPr != null) { paraStyle = (string)pPr.Elements(W.pStyle).Attributes(W.val).FirstOrDefault(); } else { paraStyle = fai.DefaultParagraphStyleName; } cpi = new CachedParaInfo { ParagraphProperties = pPr, ParagraphStyleName = paraStyle, }; runOrPara.AddAnnotation(cpi); } if (pPr != null) { rPr = pPr.Element(W.rPr); } } else { rPr = runOrPara.Element(W.rPr); } if (rPr != null) { rStyle = rPr.Element(W.rStyle); if (rStyle != null) { charStyle = (string)rStyle.Attribute(W.val); } else { if (runOrPara.Name == W.r) charStyle = (string)runOrPara .Ancestors(W.p) .Take(1) .Elements(W.pPr) .Elements(W.pStyle) .Attributes(W.val) .FirstOrDefault(); else charStyle = (string)runOrPara .Elements(W.pPr) .Elements(W.pStyle) .Attributes(W.val) .FirstOrDefault(); } } if (charStyle == null) { if (runOrPara.Name == W.r) { var ancestorPara = runOrPara.Ancestors(W.p).First(); cpi = ancestorPara.Annotation(); if (cpi != null) charStyle = cpi.ParagraphStyleName; else charStyle = (string)runOrPara.Ancestors(W.p).First().Elements(W.pPr).Elements(W.pStyle).Attributes(W.val).FirstOrDefault(); } if (charStyle == null) { charStyle = fai.DefaultParagraphStyleName; } } // A run always must have an ancestor paragraph. XElement para = null; var rolledUpParaStyleRunProps = new XElement(W.rPr); if (runOrPara.Name == W.r) { para = runOrPara.Ancestors(W.p).FirstOrDefault(); } else { para = runOrPara; } cpi = para.Annotation(); if (cpi != null) { pPr = cpi.ParagraphProperties; } else { pPr = para.Element(W.pPr); } if (pPr != null) { pStyle = pPr.Element(W.pStyle); if (pStyle != null) { paraStyle = (string)pStyle.Attribute(W.val); } else { paraStyle = fai.DefaultParagraphStyleName; } } else paraStyle = fai.DefaultParagraphStyleName; string key = (paraStyle == null ? "[null]" : paraStyle) + "~|~" + (charStyle == null ? "[null]" : charStyle); XElement rolledRunProps = null; if (fai.RolledCharacterStyles.ContainsKey(key)) rolledRunProps = fai.RolledCharacterStyles[key]; else { XElement rolledUpCharStyleRunProps = new XElement(W.rPr); if (charStyle != null) { rolledUpCharStyleRunProps = CharStyleStack(wDoc, charStyle) .Aggregate(new XElement(W.rPr), (r, s) => { var newRunProps = MergeStyleElement(s, r); return newRunProps; }); } if (paraStyle != null) { rolledUpParaStyleRunProps = ParaStyleRunPropsStack(wDoc, paraStyle) .Aggregate(new XElement(W.rPr), (r, s) => { var newCharStyleRunProps = MergeStyleElement(s, r); return newCharStyleRunProps; }); } rolledRunProps = MergeStyleElement(rolledUpCharStyleRunProps, rolledUpParaStyleRunProps); fai.RolledCharacterStyles.Add(key, rolledRunProps); } return rolledRunProps; } private static IEnumerable ParaStyleRunPropsStack(WordprocessingDocument wDoc, string paraStyleName) { var localParaStyleName = paraStyleName; var sXDoc = wDoc.MainDocumentPart.StyleDefinitionsPart.GetXDocument(); var rValue = new Stack(); while (localParaStyleName != null) { var paraStyle = sXDoc.Root.Elements(W.style).FirstOrDefault(s => { return (string)s.Attribute(W.type) == "paragraph" && (string)s.Attribute(W.styleId) == localParaStyleName; }); if (paraStyle == null) { return rValue; } if (paraStyle.Element(W.rPr) != null) { rValue.Push(paraStyle.Element(W.rPr)); } localParaStyleName = (string)paraStyle .Elements(W.basedOn) .Attributes(W.val) .FirstOrDefault(); } return rValue; } // returns collection of run properties private static IEnumerable CharStyleStack(WordprocessingDocument wDoc, string charStyleName) { var localCharStyleName = charStyleName; var sXDoc = wDoc.MainDocumentPart.StyleDefinitionsPart.GetXDocument(); var rValue = new Stack(); while (localCharStyleName != null) { XElement basedOn = null; // first look for character style var charStyle = sXDoc.Root.Elements(W.style).FirstOrDefault(s => { return (string)s.Attribute(W.type) == "character" && (string)s.Attribute(W.styleId) == localCharStyleName; }); // if not found, look for paragraph style if (charStyle == null) { charStyle = sXDoc.Root.Elements(W.style).FirstOrDefault(s => { return (string)s.Attribute(W.styleId) == localCharStyleName; }); } if (charStyle == null) { return rValue; } if (charStyle.Element(W.rPr) == null) { basedOn = charStyle.Element(W.basedOn); if (basedOn != null) { localCharStyleName = (string)basedOn.Attribute(W.val); } else { return rValue; } } rValue.Push(charStyle.Element(W.rPr)); localCharStyleName = null; basedOn = charStyle.Element(W.basedOn); if (basedOn != null) { localCharStyleName = (string)basedOn.Attribute(W.val); } } return rValue; } private static XElement ToggleMergeRunProps(XElement higherPriorityElement, XElement lowerPriorityElement) { if (lowerPriorityElement == null) return higherPriorityElement; if (higherPriorityElement == null) return lowerPriorityElement; var hpe = higherPriorityElement.Elements().Select(e => e.Name).ToArray(); var newMergedElement = new XElement(higherPriorityElement.Name, higherPriorityElement.Attributes(), // process toggle properties higherPriorityElement.Elements() .Where(e => { return e.Name != W.rFonts; }) .Select(higherChildElement => { if (TogglePropertyNames.Contains(higherChildElement.Name)) { var lowerChildElement = lowerPriorityElement.Element(higherChildElement.Name); if (lowerChildElement == null) { return higherChildElement; } var bHigher = higherChildElement.Attribute(W.val) == null || higherChildElement.Attribute(W.val).ToBoolean() == true; var bLower = lowerChildElement.Attribute(W.val) == null || lowerChildElement.Attribute(W.val).ToBoolean() == true; // if higher is true and lower is false, then return true element if (bHigher && !bLower) { return higherChildElement; } // if higher is false and lower is true, then return false element if (!bHigher && bLower) { return higherChildElement; } // if higher and lower are both true, then return false if (bHigher && bLower) { return new XElement(higherChildElement.Name, new XAttribute(W.val, "0")); } // otherwise, both higher and lower are false so can return higher element. return higherChildElement; } return higherChildElement; }), FontMerge(higherPriorityElement.Element(W.rFonts), lowerPriorityElement.Element(W.rFonts)), // take lower priority elements where there is not a higher priority element of same name lowerPriorityElement.Elements() .Where(e => { return e.Name != W.rFonts && !hpe.Contains(e.Name); })); return newMergedElement; } private static XName[] TogglePropertyNames = new[] { W.b, W.bCs, W.caps, W.emboss, W.i, W.iCs, W.imprint, W.outline, W.shadow, W.smallCaps, W.strike, W.vanish }; private static XName[] PropertyNames = new[] { W.cs, W.rtl, W.u, W.color, W.highlight, W.shd }; public class CharStyleAttributes { public string AsciiFont; public string HAnsiFont; public string EastAsiaFont; public string CsFont; public string Hint; public bool Rtl; public string LatinLang; public string BidiLang; public string EastAsiaLang; public Dictionary ToggleProperties; public Dictionary Properties; public CharStyleAttributes(XElement pPr, XElement rPr) { ToggleProperties = new Dictionary(); Properties = new Dictionary(); if (rPr == null) return; foreach (XName xn in TogglePropertyNames) { ToggleProperties[xn] = GetBoolProperty(rPr, xn); } foreach (XName xn in PropertyNames) { Properties[xn] = GetXmlProperty(rPr, xn); } var rFonts = rPr.Element(W.rFonts); if (rFonts == null) { this.AsciiFont = null; this.HAnsiFont = null; this.EastAsiaFont = null; this.CsFont = null; this.Hint = null; } else { this.AsciiFont = (string)(rFonts.Attribute(W.ascii)); this.HAnsiFont = (string)(rFonts.Attribute(W.hAnsi)); this.EastAsiaFont = (string)(rFonts.Attribute(W.eastAsia)); this.CsFont = (string)(rFonts.Attribute(W.cs)); this.Hint = (string)(rFonts.Attribute(W.hint)); } XElement csel = this.Properties[W.cs]; bool cs = csel != null && (csel.Attribute(W.val) == null || csel.Attribute(W.val).ToBoolean() == true); XElement rtlel = this.Properties[W.rtl]; bool rtl = rtlel != null && (rtlel.Attribute(W.val) == null || rtlel.Attribute(W.val).ToBoolean() == true); var bidi = false; if (pPr != null) { XElement bidiel = pPr.Element(W.bidi); bidi = bidiel != null && (bidiel.Attribute(W.val) == null || bidiel.Attribute(W.val).ToBoolean() == true); } Rtl = cs || rtl || bidi; var lang = rPr.Element(W.lang); if (lang != null) { LatinLang = (string)lang.Attribute(W.val); BidiLang = (string)lang.Attribute(W.bidi); EastAsiaLang = (string)lang.Attribute(W.eastAsia); } } private static bool? GetBoolProperty(XElement rPr, XName propertyName) { if (rPr.Element(propertyName) == null) return null; var s = (string)rPr.Element(propertyName).Attribute(W.val); if (s == null) return true; if (s == "1") return true; if (s == "0") return false; if (s == "true") return true; if (s == "false") return false; if (s == "on") return true; if (s == "off") return false; return (bool)(rPr.Element(propertyName).Attribute(W.val)); } private static XElement GetXmlProperty(XElement rPr, XName propertyName) { return rPr.Element(propertyName); } private static XName[] TogglePropertyNames = new[] { W.b, W.bCs, W.caps, W.emboss, W.i, W.iCs, W.imprint, W.outline, W.shadow, W.smallCaps, W.strike, W.vanish }; private static XName[] PropertyNames = new[] { W.cs, W.rtl, W.u, W.color, W.highlight, W.shd }; } private static HashSet WeakAndNeutralDirectionalCharacters = new HashSet() { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '-', ':', ',', '.', '|', '\t', '\r', '\n', ' ', '\x00A0', // non breaking space '\x00B0', // degree sign '\x066B', // arabic decimal separator '\x066C', // arabic thousands separator '\x0627', // arabic pipe '\x20A0', // start currency symbols '\x20A1', '\x20A2', '\x20A3', '\x20A4', '\x20A5', '\x20A6', '\x20A7', '\x20A8', '\x20A9', '\x20AA', '\x20AB', '\x20AC', '\x20AD', '\x20AE', '\x20AF', '\x20B0', '\x20B1', '\x20B2', '\x20B3', '\x20B4', '\x20B5', '\x20B6', '\x20B7', '\x20B8', '\x20B9', '\x20BA', '\x20BB', '\x20BC', '\x20BD', '\x20BE', '\x20BF', '\x20C0', '\x20C1', '\x20C2', '\x20C3', '\x20C4', '\x20C5', '\x20C6', '\x20C7', '\x20C8', '\x20C9', '\x20CA', '\x20CB', '\x20CC', '\x20CD', '\x20CE', '\x20CF', // end currency symbols '\x0660', // "Arabic" Indic Numeral Forms Iraq and West '\x0661', '\x0662', '\x0663', '\x0664', '\x0665', '\x0666', '\x0667', '\x0668', '\x0669', '\x06F0', // "Arabic" Indic Numberal Forms Iran and East '\x06F1', '\x06F2', '\x06F3', '\x06F4', '\x06F5', '\x06F6', '\x06F7', '\x06F8', '\x06F9', }; private static void AdjustFontAttributes(WordprocessingDocument wDoc, XElement paraOrRun, XElement pPr, XElement rPr, FormattingAssemblerSettings settings) { XDocument themeXDoc = null; if (wDoc.MainDocumentPart.ThemePart != null) themeXDoc = wDoc.MainDocumentPart.ThemePart.GetXDocument(); XElement fontScheme = null; XElement majorFont = null; XElement minorFont = null; if (themeXDoc != null) { fontScheme = themeXDoc.Root.Element(A.themeElements).Element(A.fontScheme); majorFont = fontScheme.Element(A.majorFont); minorFont = fontScheme.Element(A.minorFont); } var rFonts = rPr.Element(W.rFonts); if (rFonts == null) { return; } var asciiTheme = (string)rFonts.Attribute(W.asciiTheme); var hAnsiTheme = (string)rFonts.Attribute(W.hAnsiTheme); var eastAsiaTheme = (string)rFonts.Attribute(W.eastAsiaTheme); var cstheme = (string)rFonts.Attribute(W.cstheme); string ascii = null; string hAnsi = null; string eastAsia = null; string cs = null; XElement minorLatin = null; string minorLatinTypeface = null; XElement majorLatin = null; string majorLatinTypeface = null; if (minorFont != null) { minorLatin = minorFont.Element(A.latin); minorLatinTypeface = (string)minorLatin.Attribute("typeface"); } if (majorFont != null) { majorLatin = majorFont.Element(A.latin); majorLatinTypeface = (string)majorLatin.Attribute("typeface"); } if (asciiTheme != null) { if (asciiTheme.StartsWith("minor") && minorLatinTypeface != null) { ascii = minorLatinTypeface; } else if (asciiTheme.StartsWith("major") && majorLatinTypeface != null) { ascii = majorLatinTypeface; } } if (hAnsiTheme != null) { if (hAnsiTheme.StartsWith("minor") && minorLatinTypeface != null) { hAnsi = minorLatinTypeface; } else if (hAnsiTheme.StartsWith("major") && majorLatinTypeface != null) { hAnsi = majorLatinTypeface; } } if (eastAsiaTheme != null) { if (eastAsiaTheme.StartsWith("minor") && minorLatinTypeface != null) { eastAsia = minorLatinTypeface; } else if (eastAsiaTheme.StartsWith("major") && majorLatinTypeface != null) { eastAsia = majorLatinTypeface; } } if (cstheme != null) { if (cstheme.StartsWith("minor") && minorFont != null) { cs = (string)minorFont.Element(A.cs).Attribute("typeface"); } else if (cstheme.StartsWith("major") && majorFont != null) { cs = (string)majorFont.Element(A.cs).Attribute("typeface"); } } if (ascii != null) { rFonts.SetAttributeValue(W.ascii, ascii); } if (hAnsi != null) { rFonts.SetAttributeValue(W.hAnsi, hAnsi); } if (eastAsia != null) { rFonts.SetAttributeValue(W.eastAsia, eastAsia); } if (cs != null) { rFonts.SetAttributeValue(W.cs, cs); } var firstTextNode = paraOrRun.Descendants(W.t).FirstOrDefault(t => t.Value.Length > 0); string str = " "; // if there is a run with no text in it, then no need to do any of the rest of this method. if (firstTextNode == null && paraOrRun.Name == W.r) return; if (firstTextNode != null) str = firstTextNode.Value; var csa = new CharStyleAttributes(pPr, rPr); // This module determines the font based on just the first character. // Technically, a run can contain characters from different Unicode code blocks, and hence should be rendered with different fonts. // However, Word breaks up runs that use more than one font into multiple runs. Other producers of WordprocessingML may not, so in // that case, this routine may need to be augmented to look at all characters in a run. /* old code var fontFamilies = str.select(function (c) { var ft = Pav.DetermineFontTypeFromCharacter(c, csa); switch (ft) { case Pav.FontType.Ascii: return cast(rFonts.attribute(W.ascii)); case Pav.FontType.HAnsi: return cast(rFonts.attribute(W.hAnsi)); case Pav.FontType.EastAsia: return cast(rFonts.attribute(W.eastAsia)); case Pav.FontType.CS: return cast(rFonts.attribute(W.cs)); default: return null; } }) .where(function (f) { return f != null && f != ""; }) .distinct() .select(function (f) { return new Pav.FontFamily(f); }) .toArray(); */ var charToExamine = str.FirstOrDefault(c => ! WeakAndNeutralDirectionalCharacters.Contains(c)); if (charToExamine == '\0') charToExamine = str[0]; var ft = DetermineFontTypeFromCharacter(charToExamine, csa); string fontType = null; string languageType = null; switch (ft) { case FontType.Ascii: fontType = (string)rFonts.Attribute(W.ascii); languageType = "western"; break; case FontType.HAnsi: fontType = (string)rFonts.Attribute(W.hAnsi); languageType = "western"; break; case FontType.EastAsia: if (settings.RestrictToSupportedLanguages) throw new UnsupportedLanguageException("EastAsia languages are not supported"); fontType = (string)rFonts.Attribute(W.eastAsia); languageType = "eastAsia"; break; case FontType.CS: if (settings.RestrictToSupportedLanguages) throw new UnsupportedLanguageException("Complex script (RTL) languages are not supported"); fontType = (string)rFonts.Attribute(W.cs); languageType = "bidi"; break; } if (fontType != null) { if (paraOrRun.Attribute(PtOpenXml.FontName) == null) { XAttribute fta = new XAttribute(PtOpenXml.FontName, fontType.ToString()); paraOrRun.Add(fta); } else { paraOrRun.Attribute(PtOpenXml.FontName).Value = fontType.ToString(); } } if (languageType != null) { if (paraOrRun.Attribute(PtOpenXml.LanguageType) == null) { XAttribute lta = new XAttribute(PtOpenXml.LanguageType, languageType); paraOrRun.Add(lta); } else { paraOrRun.Attribute(PtOpenXml.LanguageType).Value = languageType; } } } public enum FontType { Ascii, HAnsi, EastAsia, CS }; // The algorithm for this method comes from the implementer notes in [MS-OI29500].pdf // section 2.1.87 // The implementer notes are at: // http://msdn.microsoft.com/en-us/library/ee908652.aspx public static FontType DetermineFontTypeFromCharacter(char ch, CharStyleAttributes csa) { // If the run has the cs element ("[ISO/IEC-29500-1] §17.3.2.7; cs") or the rtl element ("[ISO/IEC-29500-1] §17.3.2.30; rtl"), // then the cs (or cstheme if defined) font is used, regardless of the Unicode character values of the run’s content. if (csa.Rtl) { return FontType.CS; } // A large percentage of characters will fall in the following rule. // Unicode Block: Basic Latin if (ch >= 0x00 && ch <= 0x7f) { return FontType.Ascii; } // If the eastAsia (or eastAsiaTheme if defined) attribute’s value is “Times New Roman” and the ascii (or asciiTheme if defined) // and hAnsi (or hAnsiTheme if defined) attributes are equal, then the ascii (or asciiTheme if defined) font is used. if (csa.EastAsiaFont == "Times New Roman" && csa.AsciiFont == csa.HAnsiFont) { return FontType.Ascii; } // Unicode BLock: Latin-1 Supplement if (ch >= 0xA0 && ch <= 0xFF) { if (csa.Hint == "eastAsia") { if (ch == 0xA1 || ch == 0xA4 || ch == 0xA7 || ch == 0xA8 || ch == 0xAA || ch == 0xAD || ch == 0xAF || (ch >= 0xB0 && ch <= 0xB4) || (ch >= 0xB6 && ch <= 0xBA) || (ch >= 0xBC && ch <= 0xBF) || ch == 0xD7 || ch == 0xF7) { return FontType.EastAsia; } if (csa.EastAsiaLang == "zh-hant" || csa.EastAsiaLang == "zh-hans") { if (ch == 0xE0 || ch == 0xE1 || (ch >= 0xE8 && ch <= 0xEA) || (ch >= 0xEC && ch <= 0xED) || (ch >= 0xF2 && ch <= 0xF3) || (ch >= 0xF9 && ch <= 0xFA) || ch == 0xFC) { return FontType.EastAsia; } } } return FontType.HAnsi; } // Unicode Block: Latin Extended-A if (ch >= 0x0100 && ch <= 0x017F) { if (csa.Hint == "eastAsia") { if (csa.EastAsiaLang == "zh-hant" || csa.EastAsiaLang == "zh-hans" /* || the character set of the east Asia (or east Asia theme) font is Chinese5 || GB2312 todo */) { return FontType.EastAsia; } } return FontType.HAnsi; } // Unicode Block: Latin Extended-B if (ch >= 0x0180 && ch <= 0x024F) { if (csa.Hint == "eastAsia") { if (csa.EastAsiaLang == "zh-hant" || csa.EastAsiaLang == "zh-hans" /* || the character set of the east Asia (or east Asia theme) font is Chinese5 || GB2312 todo */) { return FontType.EastAsia; } } return FontType.HAnsi; } // Unicode Block: IPA Extensions if (ch >= 0x0250 && ch <= 0x02AF) { if (csa.Hint == "eastAsia") { if (csa.EastAsiaLang == "zh-hant" || csa.EastAsiaLang == "zh-hans" /* || the character set of the east Asia (or east Asia theme) font is Chinese5 || GB2312 todo */) { return FontType.EastAsia; } } return FontType.HAnsi; } // Unicode Block: Spacing Modifier Letters if (ch >= 0x02B0 && ch <= 0x02FF) { if (csa.Hint == "eastAsia") { return FontType.EastAsia; } return FontType.HAnsi; } // Unicode Block: Combining Diacritic Marks if (ch >= 0x0300 && ch <= 0x036F) { if (csa.Hint == "eastAsia") { return FontType.EastAsia; } return FontType.HAnsi; } // Unicode Block: Greek if (ch >= 0x0370 && ch <= 0x03CF) { if (csa.Hint == "eastAsia") { return FontType.EastAsia; } return FontType.HAnsi; } // Unicode Block: Cyrillic if (ch >= 0x0400 && ch <= 0x04FF) { if (csa.Hint == "eastAsia") { return FontType.EastAsia; } return FontType.HAnsi; } // Unicode Block: Hebrew if (ch >= 0x0590 && ch <= 0x05FF) { return FontType.Ascii; } // Unicode Block: Arabic if (ch >= 0x0600 && ch <= 0x06FF) { return FontType.Ascii; } // Unicode Block: Syriac if (ch >= 0x0700 && ch <= 0x074F) { return FontType.Ascii; } // Unicode Block: Arabic Supplement if (ch >= 0x0750 && ch <= 0x077F) { return FontType.Ascii; } // Unicode Block: Thanna if (ch >= 0x0780 && ch <= 0x07BF) { return FontType.Ascii; } // Unicode Block: Hangul Jamo if (ch >= 0x1100 && ch <= 0x11FF) { return FontType.EastAsia; } // Unicode Block: Latin Extended Additional if (ch >= 0x1E00 && ch <= 0x1EFF) { if (csa.Hint == "eastAsia" && (csa.EastAsiaLang == "zh-hant" || csa.EastAsiaLang == "zh-hans")) { return FontType.EastAsia; } return FontType.HAnsi; } // Unicode Block: General Punctuation if (ch >= 0x2000 && ch <= 0x206F) { if (csa.Hint == "eastAsia") { return FontType.EastAsia; } return FontType.HAnsi; } // Unicode Block: Superscripts and Subscripts if (ch >= 0x2070 && ch <= 0x209F) { if (csa.Hint == "eastAsia") { return FontType.EastAsia; } return FontType.HAnsi; } // Unicode Block: Currency Symbols if (ch >= 0x20A0 && ch <= 0x20CF) { if (csa.Hint == "eastAsia") { return FontType.EastAsia; } return FontType.HAnsi; } // Unicode Block: Combining Diacritical Marks for Symbols if (ch >= 0x20D0 && ch <= 0x20FF) { if (csa.Hint == "eastAsia") { return FontType.EastAsia; } return FontType.HAnsi; } // Unicode Block: Letter-like Symbols if (ch >= 0x2100 && ch <= 0x214F) { if (csa.Hint == "eastAsia") { return FontType.EastAsia; } return FontType.HAnsi; } // Unicode Block: Number Forms if (ch >= 0x2150 && ch <= 0x218F) { if (csa.Hint == "eastAsia") { return FontType.EastAsia; } return FontType.HAnsi; } // Unicode Block: Arrows if (ch >= 0x2190 && ch <= 0x21FF) { if (csa.Hint == "eastAsia") { return FontType.EastAsia; } return FontType.HAnsi; } // Unicode Block: Mathematical Operators if (ch >= 0x2200 && ch <= 0x22FF) { if (csa.Hint == "eastAsia") { return FontType.EastAsia; } return FontType.HAnsi; } // Unicode Block: Miscellaneous Technical if (ch >= 0x2300 && ch <= 0x23FF) { if (csa.Hint == "eastAsia") { return FontType.EastAsia; } return FontType.HAnsi; } // Unicode Block: Control Pictures if (ch >= 0x2400 && ch <= 0x243F) { if (csa.Hint == "eastAsia") { return FontType.EastAsia; } return FontType.HAnsi; } // Unicode Block: Optical Character Recognition if (ch >= 0x2440 && ch <= 0x245F) { if (csa.Hint == "eastAsia") { return FontType.EastAsia; } return FontType.HAnsi; } // Unicode Block: Enclosed Alphanumerics if (ch >= 0x2460 && ch <= 0x24FF) { if (csa.Hint == "eastAsia") { return FontType.EastAsia; } return FontType.HAnsi; } // Unicode Block: Box Drawing if (ch >= 0x2500 && ch <= 0x257F) { if (csa.Hint == "eastAsia") { return FontType.EastAsia; } return FontType.HAnsi; } // Unicode Block: Block Elements if (ch >= 0x2580 && ch <= 0x259F) { if (csa.Hint == "eastAsia") { return FontType.EastAsia; } return FontType.HAnsi; } // Unicode Block: Geometric Shapes if (ch >= 0x25A0 && ch <= 0x25FF) { if (csa.Hint == "eastAsia") { return FontType.EastAsia; } return FontType.HAnsi; } // Unicode Block: Miscellaneous Symbols if (ch >= 0x2600 && ch <= 0x26FF) { if (csa.Hint == "eastAsia") { return FontType.EastAsia; } return FontType.HAnsi; } // Unicode Block: Dingbats if (ch >= 0x2700 && ch <= 0x27BF) { if (csa.Hint == "eastAsia") { return FontType.EastAsia; } return FontType.HAnsi; } // Unicode Block: CJK Radicals Supplement if (ch >= 0x2E80 && ch <= 0x2EFF) { if (csa.Hint == "eastAsia") { return FontType.EastAsia; } return FontType.HAnsi; } // Unicode Block: Kangxi Radicals if (ch >= 0x2F00 && ch <= 0x2FDF) { return FontType.EastAsia; } // Unicode Block: Ideographic Description Characters if (ch >= 0x2FF0 && ch <= 0x2FFF) { return FontType.EastAsia; } // Unicode Block: CJK Symbols and Punctuation if (ch >= 0x3000 && ch <= 0x303F) { return FontType.EastAsia; } // Unicode Block: Hiragana if (ch >= 0x3040 && ch <= 0x309F) { return FontType.EastAsia; } // Unicode Block: Katakana if (ch >= 0x30A0 && ch <= 0x30FF) { return FontType.EastAsia; } // Unicode Block: Bopomofo if (ch >= 0x3100 && ch <= 0x312F) { return FontType.EastAsia; } // Unicode Block: Hangul Compatibility Jamo if (ch >= 0x3130 && ch <= 0x318F) { return FontType.EastAsia; } // Unicode Block: Kanbun if (ch >= 0x3190 && ch <= 0x319F) { return FontType.EastAsia; } // Unicode Block: Enclosed CJK Letters and Months if (ch >= 0x3200 && ch <= 0x32FF) { return FontType.EastAsia; } // Unicode Block: CJK Compatibility if (ch >= 0x3300 && ch <= 0x33FF) { return FontType.EastAsia; } // Unicode Block: CJK Unified Ideographs Extension A if (ch >= 0x3400 && ch <= 0x4DBF) { return FontType.EastAsia; } // Unicode Block: CJK Unified Ideographs if (ch >= 0x4E00 && ch <= 0x9FAF) { return FontType.EastAsia; } // Unicode Block: Yi Syllables if (ch >= 0xA000 && ch <= 0xA48F) { return FontType.EastAsia; } // Unicode Block: Yi Radicals if (ch >= 0xA490 && ch <= 0xA4CF) { return FontType.EastAsia; } // Unicode Block: Hangul Syllables if (ch >= 0xAC00 && ch <= 0xD7AF) { return FontType.EastAsia; } // Unicode Block: High Surrogates if (ch >= 0xD800 && ch <= 0xDB7F) { return FontType.EastAsia; } // Unicode Block: High Private Use Surrogates if (ch >= 0xDB80 && ch <= 0xDBFF) { return FontType.EastAsia; } // Unicode Block: Low Surrogates if (ch >= 0xDC00 && ch <= 0xDFFF) { return FontType.EastAsia; } // Unicode Block: Private Use Area if (ch >= 0xE000 && ch <= 0xF8FF) { if (csa.Hint == "eastAsia") { return FontType.EastAsia; } return FontType.HAnsi; } // Unicode Block: CJK Compatibility Ideographs if (ch >= 0xF900 && ch <= 0xFAFF) { return FontType.EastAsia; } // Unicode Block: Alphabetic Presentation Forms if (ch >= 0xFB00 && ch <= 0xFB4F) { if (csa.Hint == "eastAsia") { if (ch >= 0xFB00 && ch <= 0xFB1C) return FontType.EastAsia; if (ch >= 0xFB1D && ch <= 0xFB4F) return FontType.Ascii; } return FontType.HAnsi; } // Unicode Block: Arabic Presentation Forms-A if (ch >= 0xFB50 && ch <= 0xFDFF) { return FontType.Ascii; } // Unicode Block: CJK Compatibility Forms if (ch >= 0xFE30 && ch <= 0xFE4F) { return FontType.EastAsia; } // Unicode Block: Small Form Variants if (ch >= 0xFE50 && ch <= 0xFE6F) { return FontType.EastAsia; } // Unicode Block: Arabic Presentation Forms-B if (ch >= 0xFE70 && ch <= 0xFEFE) { return FontType.Ascii; } // Unicode Block: Halfwidth and Fullwidth Forms if (ch >= 0xFF00 && ch <= 0xFFEF) { return FontType.EastAsia; } return FontType.HAnsi; } private class FormattingAssemblerInfo { public string DefaultParagraphStyleName; public string DefaultCharacterStyleName; public string DefaultTableStyleName; public Dictionary RolledCharacterStyles; public FormattingAssemblerInfo() { RolledCharacterStyles = new Dictionary(); } } // CachedParaInfo is an optimization for the case where a paragraph contains thousands of runs. private class CachedParaInfo { public string ParagraphStyleName; public XElement ParagraphProperties; } public class UnsupportedNumberingFormatException : Exception { public UnsupportedNumberingFormatException(string message) : base(message) { } } public class UnsupportedLanguageException : Exception { public UnsupportedLanguageException(string message) : base(message) { } } } }