// 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<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 paragraphLevel = ListItemRetriever.GetParagraphLevel(element);
                        ListItemRetriever.LevelNumbers levelNums = element.Annotation<ListItemRetriever.LevelNumbers>();
                        string levelNumsString = levelNums
                            .LevelNumbersArray
                            .Take(paragraphLevel + 1)
                            .Select(i => i.ToString() + ".")
                            .StringConcatenate()
                            .TrimEnd('.');

                        var listItemRun = new XElement(W.r,
                            new XAttribute(PtOpenXml.ListItemRun, levelNumsString),
                            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),
                            element.Attribute(PtOpenXml.Unid),
                            new XAttribute(PtOpenXml.AbstractNumId, abstractNumId),
                            newParaProps,
                            listItemRun,
                            suffix != null ?
                                new XElement(W.r,
                                    new XAttribute(PtOpenXml.ListItemRun, levelNumsString),
                                    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 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 => (int)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.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<string, int> BorderTypePriority = new Dictionary<string, int>()
        {
            { "single", 1 },
            { "thick", 2 },
            { "double", 3 },
            { "dotted", 4 },
        };

        private static Dictionary<string, int> BorderNumber = new Dictionary<string, int>()
        {
            {"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<string, XName> TableStyleOverrideXNameMap = new Dictionary<string, XName>
        {
            {"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<XName, string> TableStyleOverrideXNameRevMap = new Dictionary<XName, string>
        {
            {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 = (int)e.Attribute(W.pos),
                    Pri = 1,
                    Element = e,
                }
            );
            var lps = lowerPriorityElement.Elements().Select(e =>
                new
                {
                    Pos = (int)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 => (int)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<XElement> 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<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 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<XElement> 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<ListItemRetriever.ListItemInfo>();
                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<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 IEnumerable<XElement> ParaStyleRunPropsStack(WordprocessingDocument wDoc, string paraStyleName)
        {
            var localParaStyleName = paraStyleName;
            var sXDoc = wDoc.MainDocumentPart.StyleDefinitionsPart.GetXDocument();
            var rValue = new Stack<XElement>();
            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<XElement> CharStyleStack(WordprocessingDocument wDoc, string charStyleName)
        {
            var localCharStyleName = charStyleName;
            var sXDoc = wDoc.MainDocumentPart.StyleDefinitionsPart.GetXDocument();
            var rValue = new Stack<XElement>();
            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<XName, bool?> ToggleProperties;
            public Dictionary<XName, XElement> Properties;

            public CharStyleAttributes(XElement pPr, XElement rPr)
            {
                ToggleProperties = new Dictionary<XName, bool?>();
                Properties = new Dictionary<XName, XElement>();

                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<char> WeakAndNeutralDirectionalCharacters = new HashSet<char>() {
            '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<string, XElement> RolledCharacterStyles;
            public FormattingAssemblerInfo()
            {
                RolledCharacterStyles = new Dictionary<string, XElement>();
            }
        }

        // 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) { }
        }
    }
}
