5

How can I change the text color of CellValue in excel? I am able to change foreground color of a cell but it changes color of all text present inside the cell, which I don't want. I want to highlight only particular text inside the cell i.e CellValue text.

I am using below code to highlight cell text, how it can be done for CellValue?

foreach (DocumentFormat.OpenXml.Spreadsheet.Cell currentCell in allCells)
{    
Fill fill = new Fill()
    {
         PatternFill = new PatternFill
         {
             PatternType = PatternValues.Solid,
             ForegroundColor = new ForegroundColor() { Rgb = "FFFF00" }
          }
    };
    styleSheet.Fills.AppendChild(fill);

   //Adding the  CellFormat which uses the Fill element 
    CellFormats cellFormats = styleSheet.CellFormats;
    CellFormat cf = new CellFormat();
    cf.FillId = styleSheet.Fills.Count;
    cellFormats.AppendChild(cf);
    currentCell.StyleIndex = styleSheet.CellFormats.Count;
}

I dont see any property of Style in CellValue

CellValue currentCellValue = currentCell.GetFirstChild<CellValue>();
if (currentCell.DataType == CellValues.SharedString) // cell has a cell value that is a string, thus, stored else where
    {
             data = doc.WorkbookPart.GetPartsOfType<SharedStringTablePart>().FirstOrDefault().SharedStringTable.ElementAt(int.Parse(currentCellValue.Text)).InnerText;
    }

Generated code from OpenXML Tool -

SharedStringTable sharedStringTable1 = new SharedStringTable(){ Count = (UInt32Value)1U, UniqueCount = (UInt32Value)1U };

            SharedStringItem sharedStringItem1 = new SharedStringItem();

            Run run1 = new Run();

            RunProperties runProperties1 = new RunProperties();
            FontSize fontSize3 = new FontSize(){ Val = 11D };
            Color color3 = new Color(){ Rgb = "FFFF0000" };
            RunFont runFont1 = new RunFont(){ Val = "Calibri" };
            FontFamily fontFamily1 = new FontFamily(){ Val = 2 };
            FontScheme fontScheme4 = new FontScheme(){ Val = FontSchemeValues.Minor };

            runProperties1.Append(fontSize3);
            runProperties1.Append(color3);
            runProperties1.Append(runFont1);
            runProperties1.Append(fontFamily1);
            runProperties1.Append(fontScheme4);
            Text text1 = new Text();
            text1.Text = "Microsoft";

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

            Run run2 = new Run();

            RunProperties runProperties2 = new RunProperties();
            FontSize fontSize4 = new FontSize(){ Val = 11D };
            Color color4 = new Color(){ Theme = (UInt32Value)1U };
            RunFont runFont2 = new RunFont(){ Val = "Calibri" };
            FontFamily fontFamily2 = new FontFamily(){ Val = 2 };
            FontScheme fontScheme5 = new FontScheme(){ Val = FontSchemeValues.Minor };

            runProperties2.Append(fontSize4);
            runProperties2.Append(color4);
            runProperties2.Append(runFont2);
            runProperties2.Append(fontFamily2);
            runProperties2.Append(fontScheme5);
            Text text2 = new Text(){ Space = SpaceProcessingModeValues.Preserve };
            text2.Text = " is great";

            run2.Append(runProperties2);
            run2.Append(text2);

            sharedStringItem1.Append(run1);
            sharedStringItem1.Append(run2);

            sharedStringTable1.Append(sharedStringItem1);

            sharedStringTablePart1.SharedStringTable = sharedStringTable1;
Matt Ellen
  • 11,268
  • 4
  • 68
  • 90
Bokambo
  • 4,204
  • 27
  • 79
  • 130
  • 2
    My standard comment. Open a new worksheet in Excel. Put text in a cell. Save the worksheet. Change the color the way you want, and save the result in a different file. Now download the _OpenXML Productivity Tool_ from the Microsoft site. Open the tool. Choose Compare Files (I think that's it, it's a button) and open your two files. What you need to do should jump out at you. – Flydog57 Nov 08 '18 at 19:16
  • @Flygog57 : This tool only gives help with if we are creating the new spreadsheet my problem is i have existing spreadsheet and i want to read from there and make the above change. Can you help with same ? I dont know how to read cell value using Run property from my excel – Bokambo Nov 09 '18 at 21:58
  • If you create 2 spreadsheets, your original one, and the one you want to change to, and you diff, you can find the differences. Once you get that far, you know what things should look like. I'm guessing that by "Run", you mean that a cell has multiple "runs" in it? If you look into how Word's format works, you can find out what a "Run" is. A run of text is like an HTML ``, it's text with the same properties. A paragraph of text can consist of many runs. I've never done any fancy formatting in OpenXml Excel, bolding column headings is as far as I've gotten – Flydog57 Nov 09 '18 at 22:02
  • The problem is Word hierarchy is like Paragraph -> Run -> Text but spreadsheet does not have paragraph so i am stuck how to read from Run , i have done same formatting in Word it working fine but spreadsheet is giving me hard time...i want to change color of text inside Cell, i am able to change cell color in excel but that i dont want , i want to change color of particular text inside cell not whole cell... – Bokambo Nov 09 '18 at 22:08
  • So, I just opened Excel, typed some text into a cell, and changed the color of the middle three characters. So it is possible. The only way to figure out how to do this is save an Excel xlsx file with that change and see how it's implemented. I can't do that - I can't install random tools on my work PC. – Flydog57 Nov 09 '18 at 22:31
  • @Flydog: i have done that , example - "Microsoft is great", i changed color of Microsoft. but the problem is the code it is generating is for inserting it into excel.... i already have my document ready...i am not able to read run property what it makes..Let me post the generated code – Bokambo Nov 09 '18 at 22:36
  • So, you read the existing cell, get the text, split it the way you want. Create the cell, but not its contents. Instead create N runs and fill the cell with those runs, the same way that it's done in the code you posted. I can't tell you much more than that. – Flydog57 Nov 09 '18 at 22:56
  • @Flydog: Problem is here they are creating new run as they are inserting into documents but in my case i have existing document in which change needs to be made...Back to starting point...how to read run from spreadsheet doc ? – Bokambo Nov 09 '18 at 22:59

1 Answers1

3

You have to go via the SharedStringItem elements.
Such a SharedStringItem can contain Run elements.
You apply the styling on this Run element.

It is important that your code also covers the situation where a SharedStringItem does not contain any Run child elements. This is the case when the cell only holds text, without any formatted child elements.
Here you have to create a new Run in order to apply the styling.

The code below sets the color of the word RED to red for the cells in the first row using the Excel file as shown in the image below.
Cell A1 contains Run elements, cell B1 doesn't.

input

The endresult looks like

result

String pathToYourExcelFile = @"C:\Folder\ExcelFile.xlsx";
using (SpreadsheetDocument document = SpreadsheetDocument.Open(pathToYourExcelFile, true))
{
    WorkbookPart workbook =  document.WorkbookPart;                
    WorksheetPart firstWorksheet = document.WorkbookPart.WorksheetParts.FirstOrDefault();
    SharedStringTablePart stringTable = workbook.GetPartsOfType<SharedStringTablePart>().FirstOrDefault();                              

    IEnumerable<Row> rows = firstWorksheet.Worksheet.GetFirstChild<SheetData>().Elements<Row>();
    Row firstRow = rows.FirstOrDefault();

    foreach (Cell cell in firstRow.Elements<Cell>())
    {                    
        foreach (CellValue cellValue in cell.Elements<CellValue>())
        {   
            IEnumerable<SharedStringItem> sharedStrings = 
                stringTable.SharedStringTable.Elements<SharedStringItem>()
                    .Where((o, i) => i == Convert.ToInt32(cellValue.InnerText));

            foreach (SharedStringItem sharedString in sharedStrings)
            { 
                IEnumerable<Run> runs = sharedString.Elements<Run>();
                if (runs.Count() > 0)
                {                                
                    foreach (Run run in runs)
                    {
                        if (run.InnerText == "RED")
                        {
                            RunProperties properties = run.RunProperties ?? new RunProperties();
                            Color color = properties.Elements<Color>().FirstOrDefault();
                            if (color != null)
                            {
                                properties.RemoveChild<Color>(color);
                            }

                            properties.Append(new Color { Rgb = "FFFF0000" }) ;
                        }
                    }
                }
                else
                {       
                    // No Runs, only text; create a Run.                                                     
                    Text text = new Text(sharedString.InnerText);                                
                    sharedString.RemoveAllChildren();
                    Run run = new Run();
                    run.Append(text);
                    run.RunProperties = new RunProperties();
                    run.RunProperties.Append(new Color { Rgb = "FFFF0000" }) ;
                    sharedString.Append(run);
                }
            }
        }
    }

    document.Save();

(I 'll leave the cleanup and exception handling in code above to you ...)


EDIT

For your specific case, having the cell value "Microsoft is great", you'll have to split this string into separate parts and create a Run for each part. Only on the part having the text value 'Microsoft' you apply a custom font color.

The minimalistic code below shows this concept.
(This code can use some improvements, as it is better not to split on separate words, but you get the idea ...)

// No Runs, only text.              

const String MS = "Microsoft";
String innerText = sharedString.InnerText;
if (innerText.IndexOf(MS, StringComparison.OrdinalIgnoreCase) >= 0)
{ 
    sharedString.RemoveAllChildren();

    String[] parts = innerText.Split(' ');
    for (Int32 i = 0; i < parts.Length; i++)
    {
        String part = parts[i];
        Text text = new Text((i > 0 ? " " : String.Empty) + part);
        text.Space = SpaceProcessingModeValues.Preserve;         

        Run run = new Run();                                        
        run.Append(text);

        if (part.Equals(MS, StringComparison.OrdinalIgnoreCase))
        {
            run.RunProperties = new RunProperties();
            run.RunProperties.Append(new Color { Rgb = "FFFF0000" }) ;
        }

        sharedString.Append(run);                                        
    }

The image below shows before and after.

Before and after


EDIT

In response to your comment about how to loop over all cells in the Excel document; see the code below.

String pathToYourExcelFile = @"C:\Folder\ExcelFile.xlsx";
using (SpreadsheetDocument document = SpreadsheetDocument.Open(pathToYourExcelFile, true))
{
    WorkbookPart workbook =  document.WorkbookPart;

    // Loop over all worksheets.
    IEnumerable<WorksheetPart> worksheets = document.WorkbookPart.WorksheetParts;
    foreach (WorksheetPart worksheet in worksheets)
    {
        // Loop over all rows.
        IEnumerable<Row> rows = worksheet.Worksheet.GetFirstChild<SheetData>().Elements<Row>();   
        foreach (Row row in rows) 
        {
            // Loop over all cells.
            foreach (Cell cell in row.Elements<Cell>())
            {
                // Loop over all cell values.
                foreach (CellValue cellValue in cell.Elements<CellValue>())
                {
                    // Apply content formatting as in code above ...
                }
            }
        }
    }
}
pfx
  • 20,323
  • 43
  • 37
  • 57
  • Your code works, the only problem is the spreadsheet what i will have will not have any color like you have green initially which breaks this into runs..For me value will be "Microsoft is great" and i will have text as microsoft which needs to be highlighted. How this can be achieved ? – Bokambo Nov 14 '18 at 00:17
  • Ok i will take a look and one for thing your code only works for first row , how it can expanded to all rows and all sheets. Thank you. – Bokambo Nov 14 '18 at 16:22
  • Is their any other better way instead of spliting seperate words ? – Bokambo Nov 15 '18 at 22:45
  • Via some `String`manipulation; looking for only the word to be highlighted via `IndexOf` and taking only the parts you need via `Substring`, etc. – pfx Nov 16 '18 at 06:26
  • I did not get you , the same thing you have suggested above right breaking with string array parts.Do you mean something else ? Can you provide some sample code ? What you meant to say ? – Bokambo Nov 16 '18 at 17:30
  • Can you help me on this ? - https://stackoverflow.com/questions/53384447/highlight-text-in-a-sentence-openxml – Bokambo Nov 20 '18 at 15:27
  • Above code does not work for if i want to highlight "Microsoft and great" in same cell only great is highlighted may be because it overwrite the old value as you are creating everytime new instance of run properties, how this can handled in above code ? As i seperate microsoft and great with a comma and first microsoft comes then great – Bokambo Nov 30 '18 at 23:07