1
votes

I am using below code to highlight text in power point presentation (.pptx) using openxml but below code for pptx - it corrupts the file and ask to repair while opening pptx and after opening it highlights the word but it does not preserves the formatting. So total 2 problems:

1. File gets corrupted
2. Formatting is not preserved
But it highlights the text which i want (but it does not preserve formatting)

I have debuged my code line by line and problem is at below line, but i am not able to figure out what is the problem.

    highlightRun.InsertAt(runPro, 0);

I have used openxml productivity tool to compare two files of pptx one with highlighting one without highlighting: The difference i see is as below : i am not using two times RunProperties but it is showing 2 times :

Corrupted file :

public Run GenerateRun()
        {
            Run run1 = new Run();

            RunProperties runProperties1 = new RunProperties(){ Language = "en-US", Dirty = false };
            runProperties1.SetAttribute(new OpenXmlAttribute("", "smtClean", "", "0"));

            SolidFill solidFill1 = new SolidFill();
            RgbColorModelHex rgbColorModelHex1 = new RgbColorModelHex(){ Val = "FFF000" };

            solidFill1.Append(rgbColorModelHex1);

            runProperties1.Append(solidFill1);

            RunProperties runProperties2 = new RunProperties(){ Language = "en-US", Dirty = false };
            runProperties2.SetAttribute(new OpenXmlAttribute("", "smtClean", "", "0"));

            SolidFill solidFill2 = new SolidFill();
            RgbColorModelHex rgbColorModelHex2 = new RgbColorModelHex(){ Val = "FFFF00" };

            solidFill2.Append(rgbColorModelHex2);

            runProperties2.Append(solidFill2);
            Text text1 = new Text();
            text1.Text = "gaits";

            run1.Append(runProperties1);
            run1.Append(runProperties2);
            run1.Append(text1);
            return run1;

}

<a:r xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
  <a:rPr lang="en-US" dirty="0" smtClean="0">
    <a:solidFill>
      <a:srgbClr val="FFF000" />
    </a:solidFill>
  </a:rPr>
  <a:rPr lang="en-US" dirty="0" smtClean="0">
    <a:solidFill>
      <a:srgbClr val="FFFF00" />
    </a:solidFill>
  </a:rPr>
  <a:t>gaits</a:t>
</a:r>

Correct file :

    public class GeneratedClass
    {
        // Creates an Run instance and adds its children.
        public Run GenerateRun()
        {
            Run run1 = new Run();

            RunProperties runProperties1 = new RunProperties(){ Language = "en-US", Dirty = false };
            runProperties1.SetAttribute(new OpenXmlAttribute("", "smtClean", "", "0"));

            SolidFill solidFill1 = new SolidFill();
            RgbColorModelHex rgbColorModelHex1 = new RgbColorModelHex(){ Val = "FFFF00" };

            solidFill1.Append(rgbColorModelHex1);

            runProperties1.Append(solidFill1);
            Text text1 = new Text();
            text1.Text = "gaits";

            run1.Append(runProperties1);
            run1.Append(text1);
            return run1;
        }

<a:r xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
  <a:rPr lang="en-US" dirty="0" smtClean="0">
    <a:solidFill>
      <a:srgbClr val="FFFF00" />
    </a:solidFill>
  </a:rPr>
  <a:t>gaits</a:t>
</a:r>

What i am missing ? My complete code is as below :

using OpenXmlDrawing = DocumentFormat.OpenXml.Drawing;
      private void HighLightTextPresentation(OpenXmlDrawing.Paragraph paragraph, string text)
            {           

                var found = paragraph
                    .Descendants<OpenXmlDrawing.Run>()
                    .Where(r => !string.IsNullOrEmpty(r.InnerText) && r.InnerText != "\\s")
                    .Select(r =>
                    {
                        var runText = r.GetFirstChild<OpenXmlDrawing.Text>();
                        int index = runText.Text.IndexOf(text, StringComparison.OrdinalIgnoreCase);

                        // 'Run' is a reference to the text run we found,
                        // TextNode is a reference to the run's Text object,
                        // 'TokenIndex` is the index of the search string in run's text
                        return new { Run = r, TextNode = runText, TokenIndex = index };
                    })
                    .FirstOrDefault(o => o.TokenIndex >= 0);

                // Nothing found -- escape
                if (found == null)
                {
                    return;
                }

                // Create a node for highlighted text as a clone (to preserve formatting etc)
                var highlightRun = found.Run.CloneNode(true);

                // Add the highlight node after the found text run and set up the highlighting
                paragraph.InsertAfter(highlightRun, found.Run);
                highlightRun.GetFirstChild<OpenXmlDrawing.Text>().Text = text;

                DocumentFormat.OpenXml.Drawing.RunProperties runPro = new DocumentFormat.OpenXml.Drawing.RunProperties() { Language = "en-US", Dirty = false };
                runPro.SetAttribute(new OpenXmlAttribute("", "smtClean", "", "0"));

                //Apply color to searched text
                DocumentFormat.OpenXml.Drawing.SolidFill solidFill1 = new DocumentFormat.OpenXml.Drawing.SolidFill();
                DocumentFormat.OpenXml.Drawing.RgbColorModelHex rgbColorModelHex1 = new DocumentFormat.OpenXml.Drawing.RgbColorModelHex() { Val = "FFF000" };//Set Font-Color to Green (Hex "00B050").
                solidFill1.Append(rgbColorModelHex1);

                runPro.Append(solidFill1);
                highlightRun.InsertAt(runPro, 0);

                // Check if there's some text in the text run *after* the found text
                int remainderLength = found.TextNode.Text.Length - found.TokenIndex - text.Length;
                if (remainderLength > 0)
                {
                    // There is some text after the highlighted section --
                    // insert it in a separate text run after the highlighted text run
                    var remainderRun = found.Run.CloneNode(true);
                    paragraph.InsertAfter(remainderRun, highlightRun);
                    OpenXmlDrawing.Text textNode = remainderRun.GetFirstChild<OpenXmlDrawing.Text>();
                    textNode.Text = found.TextNode.Text.Substring(found.TokenIndex + text.Length);

                    // We need to set up this to preserve the spaces between text runs
                    //textNode.Space = new EnumValue<SpaceProcessingModeValues>(SpaceProcessingModeValues.Preserve);
                }

                // Check if there's some text *before* the found text
                if (found.TokenIndex > 0)
                {
                    // Something is left before the highlighted text,
                    // so make the original text run contain only that portion
                    found.TextNode.Text = found.TextNode.Text.Remove(found.TokenIndex);

                    // We need to set up this to preserve the spaces between text runs
                    //found.TextNode.Space = new EnumValue<SpaceProcessingModeValues>(SpaceProcessingModeValues.Preserve);
                }
                else
                {
                    // There's nothing before the highlighted text -- remove the unneeded text run
                    paragraph.RemoveChild(found.Run);
                }
            }
1
My standard comment: Download the OpenXml Productivity Tool from the Microsoft Web site. Create a PPTX without the highlighting (using PowerPoint). Save it. Then copy the file, add the highlighting (in PowerPoint) and save that file. Open the tool. Used the Compare/Diff function to see what the changes are between the two files. That tool will also help you fix corrupted files (not always, but often).Flydog57
In your code and from the document xml you added to your post it looks to me like you're always creating a new RunProperties instance and insert that into a Run, without checking if there is already an existing RunProperties element in it. And IIRC only 1 RunProperties is allowed per Run. Have you tried checking for an existing one to replace or update before trying to add a new one?bassfader
@bassfader : What do you mean ? can you post some sample code for this or you mean to say this : if (!highlightRun.Contains(runProperties1)) highlightRun.InsertAt(runProperties1, 0);Bokambo

1 Answers

2
votes

As noted in the comments, the reason of "getting corrupted" is that you make the XML structure invalid by creating an additional a:rPr element (RunProperties) in the a:r element (Run) while only one is allowed.

So you should first check whether there already is a RunProperties element in the Run before inserting a new one. If a RunProperties element already exists, you should instead reuse it.

// Either reuse an existing RunProperties element,
// or create a new one if there's none
RunProperties runPro = highlightRun.Descendants<RunProperties>().FirstOrDefault() ??
    new RunProperties { Language = "en-US", Dirty = false };

// only add the element if it's really new, don't add existing one
if (runPro.Parent == null)
{
    highlightRun.InsertAt(runPro, 0);
}