45

I want to update a cell in a spreadsheet that is used by a chart, using the Open XML SDK 2.0 (CTP). All the code samples I have found insert new cells. I am struggling with retrieving the right worksheet.

public static void InsertText(string docName, string text, uint rowIndex, 
  string columnName)
{
  // Open the document for editing.
  using (SpreadsheetDocument spreadSheet = 
    SpreadsheetDocument.Open(docName, true))
  {
    Workbook workBook = spreadSheet.WorkbookPart.Workbook;

    WorksheetPart worksheetPart = workBook.WorkbookPart.
      WorksheetParts.First();

    SheetData sheetData = worksheetPart.Worksheet.
      GetFirstChild<SheetData>();

    // If the worksheet does not contain a row with the specified
    // row index, insert one.
    Row row;

    if (sheetData.Elements<Row>().Where(
      r => r.RowIndex == rowIndex).Count() != 0)
      // At this point I am expecting a match for a row that exists
      // in sheet1 but I am not getting one

When I navigate the tree in Visual Studio, I am seeing three sheets, but none of them has any children. What am I missing?

Petros Koutsolampros
  • 2,790
  • 1
  • 14
  • 20
cdonner
  • 37,019
  • 22
  • 105
  • 153
  • 1
    I am making progress. One thing that stumped me was that all examples assume that WorksheetParts.First() gets you the "Sheet1" worksheet. This is not the case, it rather returns whatever is the first element in workbook.xml. I will post the code when I have it working. – cdonner Feb 09 '09 at 14:46
  • try WorksheetParts.First(). It will get the first element that is Sheet type. – lipeiran Jun 03 '14 at 18:00

6 Answers6

81

Here is the working code. This is a prototype. For a larger number of changes, one might open the document only once. Also, there are some hard-coded things like sheet name and cell type that would have to be parameterized before this can be called production-ready. http://openxmldeveloper.org/forums/4005/ShowThread.aspx was very helpful.

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
using System.Xml;
using System.IO;
using System.Diagnostics;

namespace OpenXMLWindowsApp
{
    public class OpenXMLWindowsApp
    {
        public void UpdateSheet()
        {
            UpdateCell("Chart.xlsx", "20", 2, "B");
            UpdateCell("Chart.xlsx", "80", 3, "B");
            UpdateCell("Chart.xlsx", "80", 2, "C");
            UpdateCell("Chart.xlsx", "20", 3, "C");

            ProcessStartInfo startInfo = new ProcessStartInfo("Chart.xlsx");
            startInfo.WindowStyle = ProcessWindowStyle.Normal;
            Process.Start(startInfo);
        }

        public static void UpdateCell(string docName, string text,
            uint rowIndex, string columnName)
        {
            // Open the document for editing.
            using (SpreadsheetDocument spreadSheet = 
                     SpreadsheetDocument.Open(docName, true))
            {
                WorksheetPart worksheetPart = 
                      GetWorksheetPartByName(spreadSheet, "Sheet1");

                if (worksheetPart != null)
                {
                    Cell cell = GetCell(worksheetPart.Worksheet, 
                                             columnName, rowIndex);

                    cell.CellValue = new CellValue(text);
                    cell.DataType = 
                        new EnumValue<CellValues>(CellValues.Number);

                    // Save the worksheet.
                    worksheetPart.Worksheet.Save();
                }
            }

        }

        private static WorksheetPart 
             GetWorksheetPartByName(SpreadsheetDocument document, 
             string sheetName)
        {
            IEnumerable<Sheet> sheets =
               document.WorkbookPart.Workbook.GetFirstChild<Sheets>().
               Elements<Sheet>().Where(s => s.Name == sheetName);

            if (sheets.Count() == 0)
            {
                // The specified worksheet does not exist.

                return null;
            }

            string relationshipId = sheets.First().Id.Value;
            WorksheetPart worksheetPart = (WorksheetPart)
                 document.WorkbookPart.GetPartById(relationshipId);
            return worksheetPart;

        }

        // Given a worksheet, a column name, and a row index, 
        // gets the cell at the specified column and 
        private static Cell GetCell(Worksheet worksheet, 
                  string columnName, uint rowIndex)
        {
            Row row = GetRow(worksheet, rowIndex);

            if (row == null)
                return null;

            return row.Elements<Cell>().Where(c => string.Compare
                   (c.CellReference.Value, columnName + 
                   rowIndex, true) == 0).First();
        }


        // Given a worksheet and a row index, return the row.
        private static Row GetRow(Worksheet worksheet, uint rowIndex)
        {
            return worksheet.GetFirstChild<SheetData>().
              Elements<Row>().Where(r => r.RowIndex == rowIndex).First();
        } 
    }
}
cdonner
  • 37,019
  • 22
  • 105
  • 153
  • 1
    Thanks for the working code ... I was easily able to adapt it to my situation. You are right, most examples create new workbooks/worksheets and cells. I just want to update some existing cells. – Edward Leno Oct 11 '09 at 12:39
  • 2
    When I use your (excellent) sample code I get the intended result, but when I open the XLSX file in Excel 2010 (haven't tested with 2007) I get a warning that something isn't quite right (Excel found undreadable content) and offers to fix it. I wonder if that's related to this code not inserting the text into the string table first. How would I modify this code to eliminate the warning? – Philipp Schmid Sep 03 '10 at 21:38
  • I can't offer any help with Office 2010, Philipp. I have not looked at this in years. – cdonner Sep 08 '10 at 14:13
  • see also: http://stackoverflow.com/questions/7089745/openxml-writing-a-date-into-excel-spreadsheet-results-in-unreadable-content (writing a DateTime results in unreadable content) – Michael Paulukonis Dec 01 '11 at 21:13
  • 2
    Since I was adding in a string value, I changed `EnumValue(CellValues.Number)` to `EnumValue(CellValues.String)` -- and the "unreadable content" message went away. Curiously, the content I added was displayed all along. – Michael Paulukonis Dec 01 '11 at 21:19
  • What if I wanted to change the name of a column? – gabsferreira Jul 27 '12 at 16:14
  • Is there any way to work with column numbers as opposed to names? For example, I want to read / change the value for cell in the 2nd column and 3rd row (`B3`)? I'm asking because from a coding perspective it's easier to address the columns by numbers as opposed to letters. – MBender Oct 29 '12 at 12:23
  • This works, thanks. I do recommend that in GetRow() and GetCell() that you use FirstOrDefault() instead of First() and then add a check for NULL before returning the value. If you use First() on a Row or Cell that does not exist you will get a "sequence contains no elements" error. – Kevin Adams Oct 12 '17 at 15:34
7

I've been working with excel and found this helper library to be of great help (I've created my own helpers for word, would have saved at least 2 weeks if I was aware of this): https://www.nuget.org/packages/SimpleOOXML/

This is what is needed to update cell (writer.PasteText(...)):

MemoryStream stream = SpreadsheetReader.Create();
SpreadsheetDocument doc = SpreadsheetDocument.Open(stream, true);
WorksheetPart worksheetPart = SpreadsheetReader.GetWorksheetPartByName(doc, "Sheet1");
WorksheetWriter writer = new WorksheetWriter(doc, worksheetPart);

writer.PasteText("B2", "Hello World");

//Save to the memory stream
SpreadsheetWriter.Save(doc);

byte[] result = stream.ToArray();
FileStream file = new FileStream(@"D:\x1.xlsx", FileMode.Create);
file.Write(result, 0, result.Length);
file.Close();
windowsgm
  • 1,566
  • 4
  • 23
  • 55
pajics
  • 2,938
  • 3
  • 23
  • 27
  • Microsoft do all this work to create really useful methods such as `PasteText` and then don't create even a simple article around using it. At least we have MSDN but still we are on ooxml 2.5 there should be more on these gems! =) – Paul C Jun 05 '13 at 11:16
  • Thanks, this does make my life easier! – GeorgDangl Jun 27 '15 at 20:36
5

The Code posted by @CDonner throws some exceptions, i have added some of the code that will take care of code, which throws an Exceptions, here it is

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
using System.Xml;
using System.IO;
using System.Diagnostics;

namespace Application.Model{
public class TempCode
{
    public TempCode()
    {
        UpdateCell("E:/Visual Studio Code/Book1.xlsx", "120", 1, "A");
        UpdateCell("E:/Visual Studio Code/Book1.xlsx", "220", 2, "B");
        UpdateCell("E:/Visual Studio Code/Book1.xlsx", "320", 3, "C");
        UpdateCell("E:/Visual Studio Code/Book1.xlsx", "420", 4, "D");
        UpdateCell("E:/Visual Studio Code/Book1.xlsx", "520", 5, "E");

        ProcessStartInfo startInfo = new ProcessStartInfo("E:/Visual Studio Code/Book1.xlsx");
        startInfo.WindowStyle = ProcessWindowStyle.Normal;
        Process.Start(startInfo);



    }

    public static void UpdateCell(string docName, string text,uint rowIndex, string columnName){
        // Open the document for editing.
        using (SpreadsheetDocument spreadSheet = SpreadsheetDocument.Open(docName, true))
        {
            WorksheetPart worksheetPart = GetWorksheetPartByName(spreadSheet, "Sheet2");
            if (worksheetPart != null)
            {
                Cell cell = GetCell(worksheetPart.Worksheet, columnName, rowIndex);
                cell.CellValue = new CellValue(text);
                cell.DataType = new EnumValue<CellValues>(CellValues.Number);
                // Save the worksheet.
                worksheetPart.Worksheet.Save();
            }
        }

    }

    private static WorksheetPart GetWorksheetPartByName(SpreadsheetDocument document, string sheetName){
        IEnumerable<Sheet> sheets =document.WorkbookPart.Workbook.GetFirstChild<Sheets>().
                        Elements<Sheet>().Where(s => s.Name == sheetName);
        if (sheets.Count() == 0){
            return null;
        }
        string relationshipId = sheets.First().Id.Value;
        WorksheetPart worksheetPart = (WorksheetPart)document.WorkbookPart.GetPartById(relationshipId);
        return worksheetPart;
    }


    private static Cell GetCell(Worksheet worksheet, string columnName, uint rowIndex)
    {
        Row row;
        string cellReference = columnName + rowIndex;
        if (worksheet.Elements<Row>().Where(r => r.RowIndex == rowIndex).Count() != 0)
            row = worksheet.GetFirstChild<SheetData>().Elements<Row>().Where(r => r.RowIndex == rowIndex).FirstOrDefault();
        else{
            row = new Row() { RowIndex = rowIndex };
            worksheet.Append(row);
        }

        if (row == null)
            return null;

        if (row.Elements<Cell>().Where(c => c.CellReference.Value == cellReference).Count() > 0) {
            return row.Elements<Cell>().Where(c => c.CellReference.Value == cellReference).First();
        }
        else{
            Cell refCell = null;
            foreach (Cell cell in row.Elements<Cell>()){
                if (string.Compare(cell.CellReference.Value, cellReference, true) > 0){
                    refCell = cell;
                    break;
                }
            }
            Cell newCell = new Cell() {
                CellReference = cellReference, 
                StyleIndex = (UInt32Value)1U

            };
            row.InsertBefore(newCell, refCell);
            worksheet.Save();
            return newCell;
        }
    }
}

}

Ashwin
  • 431
  • 1
  • 5
  • 11
  • check string.Compare(cell.CellReference.Value, cellReference, true) > 0) as it gives "AA9" < "B9" which is wrong in terms of excel column precedence – avestnik Oct 27 '15 at 20:16
  • i used this entire code and it save anything to the worksheet. any ideas – Ab Bennett Sep 11 '19 at 06:28
2

This is SDK 2.5 though, however, was very useful code found here: http://fczaja.blogspot.dk/2013/05/how-to-read-and-write-excel-cells-with.html

Needed to do a slight modification for text values to add them to the SharedStringTablePart.

// Given text and a SharedStringTablePart, creates a SharedStringItem with the specified text 
// and inserts it into the SharedStringTablePart. If the item already exists, returns its index.
private static int InsertSharedStringItem(string text, SharedStringTablePart shareStringPart)
{
    // If the part does not contain a SharedStringTable, create one.
    if (shareStringPart.SharedStringTable == null)
    {
        shareStringPart.SharedStringTable = new SharedStringTable();
    }

    int i = 0;

    // Iterate through all the items in the SharedStringTable. If the text already exists, return its index.
    foreach (SharedStringItem item in shareStringPart.SharedStringTable.Elements<SharedStringItem>())
    {
        if (item.InnerText == text)
        {
            return i;
        }

        i++;
    }

    // The text does not exist in the part. Create the SharedStringItem and return its index.
    shareStringPart.SharedStringTable.AppendChild(new SharedStringItem(new Text(text)));
    shareStringPart.SharedStringTable.Save();

    return i;
}

And use it like this:

SharedStringTablePart shareStringPart = GetSharedStringTablePart(excelDoc);

// Insert the text into the SharedStringTablePart.
int index = InsertSharedStringItem(cellValue, shareStringPart);

// Set the value of cell A1.
cell.CellValue = new CellValue(index.ToString());
cell.DataType = new EnumValue<CellValues>(CellValues.SharedString);
Janis S.
  • 2,526
  • 22
  • 32
  • `GetSharedStringTablePart` is missing, you can get the value (if it exists) via `spreadSheet.WorkbookPart.GetPartsOfType().First();` or create it with `spreadSheet.WorkbookPart.AddNewPart();` -- More info at https://learn.microsoft.com/en-us/office/open-xml/how-to-insert-text-into-a-cell-in-a-spreadsheet – BurnsBA Aug 05 '20 at 23:41
0

I made some changes on @AZ code.

First, on GetCell function there is a problem on selecting the current row. Just change:

if (worksheet.GetFirstChild<SheetData>().Elements<Row>().Where(r => r.RowIndex == rowIndex).Count() != 0)

instead of:

if (worksheet.Elements<Row>().Where(r => r.RowIndex == rowIndex).Count() != 0)

And in the section:

if (string.Compare(cell.CellReference.Value, cellReference, true) > 0)

If you are using Columns above Z Column (as AA column, for example) will not work properly. To some this, I'm using the column numbers to determinate where insert the Cell.

For this, I created a function ColumnIndex, with convert the column letters to numbers:

private static int ColumnIndex(string reference)
    {
        int ci = 0;
        reference = reference.ToUpper();
        for (int ix = 0; ix < reference.Length && reference[ix] >= 'A'; ix++)
            ci = (ci * 26) + ((int)reference[ix] - 64);
        return ci;
    }

So I changed the string compare function for this:

string columnNew = new String(cellReference.Where(c => c != '-' && (c < '0' || c > '9')).ToArray());
            foreach (Cell cell in row.Elements<Cell>())
            {
                string columnBase = new String(cell.CellReference.Value.Where(c => c != '-' && (c < '0' || c > '9')).ToArray());

                if (ColumnIndex(columnBase) > ColumnIndex(columnNew))
                {
                    refCell = cell;
                    break;
                }
            }

Best Regards.

zerala
  • 49
  • 2
-1
var sheetData = new SheetData();
var row = UpdateCell("A","Hello World", 5);
sheetData.Append(row);
worksheet.Append(sheetData);

private static Row UpdateCell(string columnName, string value, int rowIndex)
{
       Row row = new Row { RowIndex = (uint)rowIndex };
       Cell  c1 = new TextCell(columnName, value, rowIndex);
       row.Append(c1);
       return row;            
}


public class TextCell : Cell
{
    public TextCell(string header, string text, int index)
    {
        this.DataType = CellValues.InlineString;
        this.CellReference = header + index;
        //Add text to the text cell.
        this.InlineString = new InlineString { Text = new Text { Text = text } };
    }
}
windowsgm
  • 1,566
  • 4
  • 23
  • 55
  • This gives me a "System.InvalidOperationException: 'Non-composite elements do not have child elements." error. – windowsgm Feb 06 '20 at 12:59