Пример #1
0
 private static void InitializeListItemRetriever(WordprocessingDocument wordDoc)
 {
     foreach (var part in wordDoc.ContentParts())
     {
         InitializeListItemRetrieverForPart(wordDoc, part);
     }
 }
Пример #2
0
        public void Interop()
        {
            foreach (Microsoft.Office.Interop.Word.Paragraph paragraph in doc.ContentParts())
            {
                Microsoft.Office.Interop.Word.Style style =
                    paragraph.get_Style() as Microsoft.Office.Interop.Word.Style;

                string styleName = style.NameLocal;
                Debug.WriteLine(styleName);
                string text = paragraph.Range.Text;

                if (styleName == "Title")
                {
                    string title = text.ToString();
                    Debug.WriteLine(title);
                }
                else if (styleName == "Subtitle")
                {
                    string st = text.ToString() + "\n";
                    Debug.WriteLine(st);
                }
                else if (styleName == "Heading 1")
                {
                    string heading1 = text.ToString() + "\n";
                    Debug.WriteLine(heading1);
                }
            }
        }
Пример #3
0
 public static void SimplifyMarkup(WordprocessingDocument doc,
                                   SimplifyMarkupSettings settings)
 {
     if (settings.RemoveMarkupForDocumentComparison)
     {
         settings.RemoveRsidInfo = true;
         RemoveElementsForDocumentComparison(doc);
     }
     if (settings.RemoveRsidInfo)
     {
         RemoveRsidInfoInSettings(doc);
     }
     if (settings.AcceptRevisions)
     {
         RevisionAccepter.AcceptRevisions(doc);
     }
     foreach (var part in doc.ContentParts())
     {
         SimplifyMarkupForPart(part, settings);
     }
     if (doc.MainDocumentPart.StyleDefinitionsPart != null)
     {
         SimplifyMarkupForPart(doc.MainDocumentPart.StyleDefinitionsPart, settings);
     }
     if (doc.MainDocumentPart.StylesWithEffectsPart != null)
     {
         SimplifyMarkupForPart(doc.MainDocumentPart.StylesWithEffectsPart, settings);
     }
 }
 public static void TransformToSingleCharacterRuns(WordprocessingDocument doc)
 {
     if (RevisionAccepter.HasTrackedRevisions(doc))
         throw new OpenXmlPowerToolsException(
             "Transforming a document to single character runs is not supported for " +
             "a document with tracked revisions.");
     foreach (var part in doc.ContentParts())
         TransformPartToSingleCharacterRuns(part);
 }
Пример #5
0
        private static void ExtractAllTemplateFields(WordprocessingDocument wordDoc, FieldAccumulator fieldAccumulator,
                                                     bool removeCustomProperties = true, IEnumerable <string> keepPropertyNames = null)
        {
            if (RevisionAccepter.HasTrackedRevisions(wordDoc))
            {
                throw new FieldParseException("Invalid template - contains tracked revisions");
            }

            // extract fields from each part of the document
            foreach (var part in wordDoc.ContentParts())
            {
                ExtractFieldsFromPart(part, fieldAccumulator);

                if (removeCustomProperties)
                {
                    // remove document variables and custom properties
                    // (in case they have any sensitive information that should not carry over to assembled documents!)
                    MainDocumentPart main = part as MainDocumentPart;
                    if (main != null)
                    {
                        var docVariables = main.DocumentSettingsPart.Settings.Descendants <DocumentVariables>();
                        foreach (DocumentVariables docVars in docVariables.ToList())
                        {
                            foreach (DocumentVariable docVar in docVars.ToList())
                            {
                                if (keepPropertyNames == null || !Enumerable.Contains <string>(keepPropertyNames, docVar.Name))
                                {
                                    docVar.Remove();
                                    //docVar.Name = "Id";
                                    //docVar.Val.Value = "123";
                                }
                            }
                        }
                    }
                }
            }
            if (removeCustomProperties)
            {
                // remove custom properties if there are any (custom properties are the new/non-legacy version of document variables)
                var custom = wordDoc.CustomFilePropertiesPart;
                if (custom != null)
                {
                    foreach (CustomDocumentProperty prop in custom.Properties.ToList())
                    {
                        if (keepPropertyNames == null || !Enumerable.Contains <string>(keepPropertyNames, prop.Name))
                        {
                            prop.Remove();
                            // string propName = prop.Name;
                            // string value = prop.VTLPWSTR.InnerText;
                        }
                    }
                }
            }
        }
 public static void SimplifyMarkup(WordprocessingDocument doc,
     SimplifyMarkupSettings settings)
 {
     if (settings.AcceptRevisions)
         RevisionAccepter.AcceptRevisions(doc);
     foreach (var part in doc.ContentParts())
         SimplifyMarkupForPart(part, settings);
     if (doc.MainDocumentPart.StyleDefinitionsPart != null)
         SimplifyMarkupForPart(doc.MainDocumentPart.StyleDefinitionsPart, settings);
     if (doc.MainDocumentPart.StylesWithEffectsPart != null)
         SimplifyMarkupForPart(doc.MainDocumentPart.StylesWithEffectsPart, settings);
 }
Пример #7
0
 public static void TransformToSingleCharacterRuns(WordprocessingDocument doc)
 {
     if (RevisionAccepter.HasTrackedRevisions(doc))
     {
         throw new OpenXmlPowerToolsException(
                   "Transforming a document to single character runs is not supported for " +
                   "a document with tracked revisions.");
     }
     foreach (var part in doc.ContentParts())
     {
         TransformPartToSingleCharacterRuns(part);
     }
 }
Пример #8
0
        private static void InitializeListItemRetriever(WordprocessingDocument wordDoc, ListItemRetrieverSettings settings)
        {
            foreach (var part in wordDoc.ContentParts())
            {
                InitializeListItemRetrieverForPart(wordDoc, part, settings);
            }

#if false
            foreach (var part in wordDoc.ContentParts())
            {
                var xDoc  = part.GetXDocument();
                var paras = xDoc
                            .Descendants(W.p)
                            .Where(p =>
                                   p.Annotation <ListItemInfo>() == null);
                if (paras.Any())
                {
                    Console.WriteLine("Error");
                }
            }
#endif
        }
Пример #9
0
        private static void PrepareTemplate(WordprocessingDocument wordDoc)
        {
            if (RevisionAccepter.HasTrackedRevisions(wordDoc))
            {
                throw new FieldParseException("Invalid template - contains tracked revisions");
            }

            SimplifyTemplateMarkup(wordDoc);

            foreach (var part in wordDoc.ContentParts())
            {
                PrepareTemplatePart(part);
            }
        }
Пример #10
0
        private static TemplateErrorList PrepareTemplate(WordprocessingDocument wordDoc, FieldTransformIndex xm)
        {
            if (RevisionAccepter.HasTrackedRevisions(wordDoc))
            {
                throw new FieldParseException("Invalid template - contains tracked revisions");
            }

            SimplifyTemplateMarkup(wordDoc);

            var te = new TemplateErrorList();

            foreach (var part in wordDoc.ContentParts())
            {
                PrepareTemplatePart(part, xm, te);
            }
            return(te);
        }
Пример #11
0
 private static void TestForInvalidContent(WordprocessingDocument wDoc)
 {
     foreach (OpenXmlPart part in wDoc.ContentParts())
     {
         XDocument xDoc = part.GetXDocument();
         if (xDoc.Descendants(W.altChunk).Any())
         {
             throw new OpenXmlPowerToolsException("Unsupported document, contains w:altChunk");
         }
         if (xDoc.Descendants(W.subDoc).Any())
         {
             throw new OpenXmlPowerToolsException("Unsupported document, contains w:subDoc");
         }
         if (xDoc.Descendants(W.contentPart).Any())
         {
             throw new OpenXmlPowerToolsException("Unsupported document, contains w:contentPart");
         }
     }
 }
 public static void SimplifyMarkup(WordprocessingDocument doc,
     SimplifyMarkupSettings settings)
 {
     if (settings.RemoveMarkupForDocumentComparison)
     {
         settings.RemoveRsidInfo = true;
         RemoveElementsForDocumentComparison(doc);
     }
     if (settings.RemoveRsidInfo)
         RemoveRsidInfoInSettings(doc);
     if (settings.AcceptRevisions)
         RevisionAccepter.AcceptRevisions(doc);
     foreach (var part in doc.ContentParts())
         SimplifyMarkupForPart(part, settings);
     if (doc.MainDocumentPart.StyleDefinitionsPart != null)
         SimplifyMarkupForPart(doc.MainDocumentPart.StyleDefinitionsPart, settings);
     if (doc.MainDocumentPart.StylesWithEffectsPart != null)
         SimplifyMarkupForPart(doc.MainDocumentPart.StylesWithEffectsPart, settings);
 }
        private static WmlDocument CleanPowerToolsAndRsid(WmlDocument producedDocument)
        {
            using (var ms = new MemoryStream())
            {
                ms.Write(producedDocument.DocumentByteArray, 0, producedDocument.DocumentByteArray.Length);
                using (WordprocessingDocument wDoc = WordprocessingDocument.Open(ms, true))
                {
                    foreach (OpenXmlPart cp in wDoc.ContentParts())
                    {
                        XDocument xd      = cp.GetXDocument();
                        object    newRoot = CleanPartTransform(xd.Root);
                        xd.Root?.ReplaceWith(newRoot);
                        cp.PutXDocument();
                    }
                }

                var cleaned = new WmlDocument("cleaned.docx", ms.ToArray());
                return(cleaned);
            }
        }
Пример #14
0
 public static void SimplifyMarkup(WordprocessingDocument doc,
                                   SimplifyMarkupSettings settings)
 {
     if (settings.AcceptRevisions)
     {
         RevisionAccepter.AcceptRevisions(doc);
     }
     foreach (var part in doc.ContentParts())
     {
         SimplifyMarkupForPart(part, settings);
     }
     if (doc.MainDocumentPart.StyleDefinitionsPart != null)
     {
         SimplifyMarkupForPart(doc.MainDocumentPart.StyleDefinitionsPart, settings);
     }
     if (doc.MainDocumentPart.StylesWithEffectsPart != null)
     {
         SimplifyMarkupForPart(doc.MainDocumentPart.StylesWithEffectsPart, settings);
     }
 }
        public static WmlDocument AssembleDocument(WmlDocument templateDoc, XElement data, out bool templateError)
        {
            byte[] byteArray = templateDoc.DocumentByteArray;
            using (MemoryStream mem = new MemoryStream())
            {
                mem.Write(byteArray, 0, (int)byteArray.Length);
                using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(mem, true))
                {
                    if (RevisionAccepter.HasTrackedRevisions(wordDoc))
                    {
                        throw new OpenXmlPowerToolsException("Invalid DocumentAssembler template - contains tracked revisions");
                    }

                    var te = new TemplateError();
                    foreach (var part in wordDoc.ContentParts())
                    {
                        ProcessTemplatePart(data, te, part);
                    }
                    templateError = te.HasError;
                }
                WmlDocument assembledDocument = new WmlDocument("TempFileName.docx", mem.ToArray());
                return(assembledDocument);
            }
        }
 private static void InsertAppropriateNonbreakingSpaces(WordprocessingDocument wordDoc)
 {
     foreach (var part in wordDoc.ContentParts())
     {
         XDocument pxd = part.GetXDocument();
         XElement newRoot = (XElement)InsertAppropriateNonbreakingSpacesTransform(pxd.Root);
         pxd.Root.ReplaceWith(newRoot);
         part.PutXDocument();
     }
 }
        private static void InsertAppropriateNonbreakingSpaces(WordprocessingDocument wordDoc)
        {
            foreach (var part in wordDoc.ContentParts())
            {
                var pxd = part.GetXDocument();
                var root = pxd.Root;
                if (root == null) return;

                var newRoot = (XElement)InsertAppropriateNonbreakingSpacesTransform(root);
                root.ReplaceWith(newRoot);
                part.PutXDocument();
            }
        }
        private static void InitializeListItemRetriever(WordprocessingDocument wordDoc, ListItemRetrieverSettings settings)
        {
            foreach (var part in wordDoc.ContentParts())
                InitializeListItemRetrieverForPart(wordDoc, part, settings);

#if false
            foreach (var part in wordDoc.ContentParts())
	        {
                var xDoc = part.GetXDocument();
                var paras = xDoc
                    .Descendants(W.p)
                    .Where(p =>
                        p.Annotation<ListItemInfo>() == null);
                if (paras.Any())
                    Console.WriteLine("Error");
	        }
#endif
        }
        private static bool ValidateWordprocessingDocument(WordprocessingDocument wDoc, List<XElement> metrics, List<string> notes, Dictionary<XName, int> metricCountDictionary)
        {
            bool valid = ValidateAgainstSpecificVersion(wDoc, metrics, DocumentFormat.OpenXml.FileFormatVersions.Office2007, H.SdkValidationError2007);
            valid |= ValidateAgainstSpecificVersion(wDoc, metrics, DocumentFormat.OpenXml.FileFormatVersions.Office2010, H.SdkValidationError2010);
#if !NET35
            valid |= ValidateAgainstSpecificVersion(wDoc, metrics, DocumentFormat.OpenXml.FileFormatVersions.Office2013, H.SdkValidationError2013);
#endif

            int elementCount = 0;
            int paragraphCount = 0;
            int textCount = 0;
            foreach (var part in wDoc.ContentParts())
            {
                XDocument xDoc = part.GetXDocument();
                foreach (var e in xDoc.Descendants())
                {
                    if (e.Name == W.txbxContent)
                        IncrementMetric(metricCountDictionary, H.TextBox);
                    else if (e.Name == W.sdt)
                        IncrementMetric(metricCountDictionary, H.ContentControl);
                    else if (e.Name == W.customXml)
                        IncrementMetric(metricCountDictionary, H.CustomXmlMarkup);
                    else if (e.Name == W.fldChar)
                        IncrementMetric(metricCountDictionary, H.ComplexField);
                    else if (e.Name == W.fldSimple)
                        IncrementMetric(metricCountDictionary, H.SimpleField);
                    else if (e.Name == W.altChunk)
                        IncrementMetric(metricCountDictionary, H.AltChunk);
                    else if (e.Name == W.tbl)
                        IncrementMetric(metricCountDictionary, H.Table);
                    else if (e.Name == W.hyperlink)
                        IncrementMetric(metricCountDictionary, H.Hyperlink);
                    else if (e.Name == W.framePr)
                        IncrementMetric(metricCountDictionary, H.LegacyFrame);
                    else if (e.Name == W.control)
                        IncrementMetric(metricCountDictionary, H.ActiveX);
                    else if (e.Name == W.subDoc)
                        IncrementMetric(metricCountDictionary, H.SubDocument);
                    else if (e.Name == VML.imagedata || e.Name == VML.fill || e.Name == VML.stroke || e.Name == A.blip)
                    {
                        var relId = (string)e.Attribute(R.embed);
                        if (relId != null)
                            ValidateImageExists(part, relId, metricCountDictionary);
                        relId = (string)e.Attribute(R.pict);
                        if (relId != null)
                            ValidateImageExists(part, relId, metricCountDictionary);
                        relId = (string)e.Attribute(R.id);
                        if (relId != null)
                            ValidateImageExists(part, relId, metricCountDictionary);
                    }

                    if (part.Uri == wDoc.MainDocumentPart.Uri)
                    {
                        elementCount++;
                        if (e.Name == W.p)
                            paragraphCount++;
                        if (e.Name == W.t)
                            textCount += ((string)e).Length;
                    }
                }
            }

            foreach (var item in metricCountDictionary)
            {
                metrics.Add(
                    new XElement(item.Key, new XAttribute(H.Val, item.Value)));
            }

            metrics.Add(new XElement(H.ElementCount, new XAttribute(H.Val, elementCount)));
            metrics.Add(new XElement(H.AverageParagraphLength, new XAttribute(H.Val, (int)((double)textCount / (double)paragraphCount))));

            if (RevisionAccepter.HasTrackedRevisions(wDoc))
                metrics.Add(new XElement(H.RevisionTracking, new XAttribute(H.Val, true)));

            if (wDoc.GetAllParts().Any(part => part.ContentType == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))
                metrics.Add(new XElement(H.EmbeddedXlsx, new XAttribute(H.Val, true)));

            NumberingFormatListAssembly(wDoc, metrics);

            XDocument wxDoc = wDoc.MainDocumentPart.GetXDocument();

            foreach (var d in wxDoc.Descendants())
            {
                if (d.Name == W.saveThroughXslt)
                {
                    string rid = (string)d.Attribute(R.id);
                    var tempExternalRelationship = wDoc
                        .MainDocumentPart
                        .DocumentSettingsPart
                        .ExternalRelationships
                        .FirstOrDefault(h => h.Id == rid);
                    if (tempExternalRelationship == null)
                        metrics.Add(new XElement(H.InvalidSaveThroughXslt, new XAttribute(H.Val, true)));
                    valid = false;
                }
                else if (d.Name == W.trackRevisions)
                    metrics.Add(new XElement(H.TrackRevisionsEnabled, new XAttribute(H.Val, true)));
                else if (d.Name == W.documentProtection)
                    metrics.Add(new XElement(H.DocumentProtection, new XAttribute(H.Val, true)));
            }

            FontAndCharSetAnalysis(wDoc, metrics, notes);

            return valid;
        }
 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);
         }
     }
 }
 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 NormalizeListItems(FormattingAssemblerInfo fai, WordprocessingDocument wDoc, FormattingAssemblerSettings settings)
 {
     foreach (var part in wDoc.ContentParts())
     {
         var pxd = part.GetXDocument();
         XElement newRoot = (XElement)NormalizeListItemsTransform(fai, wDoc, pxd.Root, settings);
         if (newRoot.Attribute(XNamespace.Xmlns + "pt14") == null)
             newRoot.Add(new XAttribute(XNamespace.Xmlns + "pt14", PtOpenXml.pt.NamespaceName));
         if (newRoot.Attribute(XNamespace.Xmlns + "mc") == null)
             newRoot.Add(new XAttribute(XNamespace.Xmlns + "mc", MC.mc.NamespaceName));
         pxd.Root.ReplaceWith(newRoot);
     }
 }
 private static void NumberingFormatListAssembly(WordprocessingDocument wDoc, List<XElement> metrics)
 {
     List<string> numFmtList = new List<string>();
     foreach (var part in wDoc.ContentParts())
     {
         var xDoc = part.GetXDocument();
         numFmtList = numFmtList.Concat(xDoc
             .Descendants(W.p)
             .Select(p =>
             {
                 ListItemRetriever.RetrieveListItem(wDoc, p, null);
                 ListItemRetriever.ListItemInfo lif = p.Annotation<ListItemRetriever.ListItemInfo>();
                 if (lif != null && lif.IsListItem && lif.Lvl(ListItemRetriever.GetParagraphLevel(p)) != null)
                 {
                     string numFmtForLevel = (string)lif.Lvl(ListItemRetriever.GetParagraphLevel(p)).Elements(W.numFmt).Attributes(W.val).FirstOrDefault();
                     if (numFmtForLevel == null)
                     {
                         var numFmtElement = lif.Lvl(ListItemRetriever.GetParagraphLevel(p)).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);
                     }
                     return numFmtForLevel;
                 }
                 return null;
             })
             .Where(s => s != null)
             .Distinct())
             .ToList();
     }
     if (numFmtList.Any())
     {
         var nfls = numFmtList.StringConcatenate(s => s + ",").TrimEnd(',');
         metrics.Add(new XElement(H.NumberingFormatList, new XAttribute(H.Val, PtUtils.MakeValidXml(nfls))));
     }
 }
        private static void FontAndCharSetAnalysis(WordprocessingDocument wDoc, List<XElement> metrics, List<string> notes)
        {
            FormattingAssemblerSettings settings = new FormattingAssemblerSettings
            {
                RemoveStyleNamesFromParagraphAndRunProperties = false,
                ClearStyles = true,
                RestrictToSupportedNumberingFormats = false,
                RestrictToSupportedLanguages = false,
            };
            FormattingAssembler.AssembleFormatting(wDoc, settings);
            var formattingMetrics = new FormattingMetrics();

            foreach (var part in wDoc.ContentParts())
            {
                var xDoc = part.GetXDocument();
                foreach (var run in xDoc.Descendants(W.r))
                {
                    formattingMetrics.RunCount++;
                    AnalyzeRun(run, metrics, notes, formattingMetrics, part.Uri.ToString());
                }
            }

            metrics.Add(new XElement(H.RunCount, new XAttribute(H.Val, formattingMetrics.RunCount)));
            if (formattingMetrics.RunWithoutRprCount > 0)
                metrics.Add(new XElement(H.RunWithoutRprCount, new XAttribute(H.Val, formattingMetrics.RunWithoutRprCount)));
            if (formattingMetrics.ZeroLengthText > 0)
                metrics.Add(new XElement(H.ZeroLengthText, new XAttribute(H.Val, formattingMetrics.ZeroLengthText)));
            if (formattingMetrics.MultiFontRun > 0)
                metrics.Add(new XElement(H.MultiFontRun, new XAttribute(H.Val, formattingMetrics.MultiFontRun)));
            if (formattingMetrics.AsciiCharCount > 0)
                metrics.Add(new XElement(H.AsciiCharCount, new XAttribute(H.Val, formattingMetrics.AsciiCharCount)));
            if (formattingMetrics.CSCharCount > 0)
                metrics.Add(new XElement(H.CSCharCount, new XAttribute(H.Val, formattingMetrics.CSCharCount)));
            if (formattingMetrics.EastAsiaCharCount > 0)
                metrics.Add(new XElement(H.EastAsiaCharCount, new XAttribute(H.Val, formattingMetrics.EastAsiaCharCount)));
            if (formattingMetrics.HAnsiCharCount > 0)
                metrics.Add(new XElement(H.HAnsiCharCount, new XAttribute(H.Val, formattingMetrics.HAnsiCharCount)));
            if (formattingMetrics.AsciiRunCount > 0)
                metrics.Add(new XElement(H.AsciiRunCount, new XAttribute(H.Val, formattingMetrics.AsciiRunCount)));
            if (formattingMetrics.CSRunCount > 0)
                metrics.Add(new XElement(H.CSRunCount, new XAttribute(H.Val, formattingMetrics.CSRunCount)));
            if (formattingMetrics.EastAsiaRunCount > 0)
                metrics.Add(new XElement(H.EastAsiaRunCount, new XAttribute(H.Val, formattingMetrics.EastAsiaRunCount)));
            if (formattingMetrics.HAnsiRunCount > 0)
                metrics.Add(new XElement(H.HAnsiRunCount, new XAttribute(H.Val, formattingMetrics.HAnsiRunCount)));

            if (formattingMetrics.Languages.Any())
            {
                var uls = formattingMetrics.Languages.StringConcatenate(s => s + ",").TrimEnd(',');
                metrics.Add(new XElement(H.Languages, new XAttribute(H.Val, PtUtils.MakeValidXml(uls))));
            }
        }