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 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<ListItemRetriever.ListItemInfo>(); var newParaProps = new XElement(W.pPr, element.Elements(W.pPr).Elements().Where(e => e.Name != W.numPr) ); XElement listItemRunProps = null; 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); if ((string)listItemLvl.Elements(W.numFmt).Attributes(W.val).FirstOrDefault() == "bullet") { listItemRunProps.Elements(W.rtl).Remove(); } 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); } } } } var listItemRun = new XElement(W.r, 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 newPara = new XElement(W.p, element.Attribute(PtOpenXml.FontName), element.Attribute(PtOpenXml.LanguageType), new XAttribute(PtOpenXml.AbstractNumId, abstractNumId), newParaProps, listItemRun, suffix != null ? new XElement(W.r, listItemRunProps, suffix) : null, 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 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<CachedParaInfo>(); 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<CachedParaInfo>(); 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<CachedParaInfo>(); 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 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 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 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 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<ListItemRetriever.ListItemInfo>(); 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<ListItemRetriever.ListItemInfo>(); 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 void AnnotateParagraphs(FormattingAssemblerInfo fai, WordprocessingDocument wDoc, XElement root, FormattingAssemblerSettings settings) { foreach (var para in root.Descendants(W.p)) { AnnotateParagraph(fai, wDoc, para, settings); } }