3

I want to export an excel file with data so that my users can:

  • Download an Excel file (export from my program)
  • Edit the data in the file
  • Save it
  • Upload the Excel file (reimport it into my program)

Basically I will give them the experience of having an offline file that they can edit if they do not have any internet access (as ours is a web application)

When creating Excel files using the OpenXml SDK I use the OpenXmlElement.SetAttribute method to add attributes to the columns, rows and cells of the worksheet. The attributes I add are used so that on reimport I can match the edited data with the location where it should be stored.

The attributes I export are:

  • Database Id's
  • Original value (database value at time of export to allow for easy synchronisation on import)
  • The date the exported data was last modified

The export routine looks like this for a cell:

var cell = new Cell {
   CellReference = string.Format("{0}{1}", Column.Reference, Row.Index),
   DataType = this.CellDataType
};

foreach (var keyValuePair in this.AttributeDictionary) {
   cell.SetAttribute(new OpenXmlAttribute {
      LocalName = keyValuePair.Key,
      Value = keyValuePair.Value.ToString()
   });
}

This export works fine. When examining the exported file in the OpenXml Productivity Tool I can see the attributes are added correctly. When the file is saved after editing in Excel the attributes are not preserved. Is there a way to tell Excel to preserve the attributes or is there another procedure that would be best used here to preserve the data I need for easy re-importation of the data?

Side Question:

What are the attributes for if Excel does not preserve them?

Aran Mulholland
  • 23,555
  • 29
  • 141
  • 228

1 Answers1

2

I don't think you can force Excel to round trip unknown attributes but you can add extension elements using ExtensionLists and Extensions. Excel will roundtrip these elements and are designed (as far as I can make out) for storing application specific data just as you are after.

There doesn't seem to be too much documentation around that I can find but Part 3 of the ECMA-376 spec mentions extensions.

The following code will create a sheet with a value in cell A1 and an ExtensionList with one Extension in it as a child of that cell:

public static void CreateSpreadsheetWorkbook(string filepath)
{
    if (File.Exists(filepath))
        File.Delete(filepath);

    using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Create(filepath, SpreadsheetDocumentType.Workbook))
    {
        // Add a WorkbookPart to the document.
        WorkbookPart workbookpart = spreadsheetDocument.AddWorkbookPart();
        workbookpart.Workbook = new Workbook();

        // Add a WorksheetPart to the WorkbookPart.
        WorksheetPart worksheetPart = workbookpart.AddNewPart<WorksheetPart>();

        SheetData sheetData = new SheetData();
        worksheetPart.Worksheet = new Worksheet(sheetData);

        // Add Sheets to the Workbook.
        Sheets sheets = spreadsheetDocument.WorkbookPart.Workbook.AppendChild<Sheets>(new Sheets());

        // Append a new worksheet and associate it with the workbook.
        Sheet sheet = new Sheet()
        {
            Id = spreadsheetDocument.WorkbookPart.
                GetIdOfPart(worksheetPart),
            SheetId = 1,
            Name = "Sheet1"
        };
        sheets.Append(sheet);

        Row row = new Row()
        {
            RowIndex = 1U
        };

        Cell cell = new Cell()
        {
            CellReference = "A1",
            CellValue = new CellValue("A Test"),
            DataType = CellValues.String
        };

        ExtensionList extensions = new ExtensionList();
        Extension extension = new Extension()
            {
                Uri = "Testing1234"
            };
        extensions.AppendChild(extension);
        extension.AddNamespaceDeclaration("ns", "http://tempuri/someUrl");

        cell.AppendChild(extensions);

        row.Append(cell);

        sheetData.Append(row);

        workbookpart.Workbook.Save();

        // Close the document.
        spreadsheetDocument.Close();
    }
}

The following will read the value back again, even if the file has been round tripped through Excel.

public static void ReadSheet(string filename, string sheetName)
{
    using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Open(filename, false))
    {
        WorkbookPart workbookPart = spreadsheetDocument.WorkbookPart;
        //get the correct sheet
        Sheet sheet = workbookPart.Workbook.Descendants<Sheet>().Where(s => s.Name == sheetName).First();
        if (sheet != null)
        {
            WorksheetPart worksheetPart = workbookPart.GetPartById(sheet.Id) as WorksheetPart;
            foreach (Cell cell in  worksheetPart.Worksheet.Descendants<Cell>())
            {
                ExtensionList extensions = cell.GetFirstChild<ExtensionList>();
                if (extensions != null)
                {
                    Extension extension = extensions.GetFirstChild<Extension>();
                    if (extension != null)
                    {
                        Console.WriteLine("Cell {0} has value {1}", cell.CellReference, extension.Uri);
                    }
                }
            }
        }
    }
}

The output from which is

Cell A1 has value Testing1234

As for your side question:

What are the attributes for if Excel does not preserve them?

I'm not too sure - the only time I've used the OpenXmlAttribute class is when I've used a SAX approach to write a document. In that case you need to write the attributes explicitly along with the elements. For example:

List<OpenXmlAttribute> oxa = new List<OpenXmlAttribute>();

//cell reference attribute
oxa.Add(new OpenXmlAttribute("r", "", "A1"));
//cell type attribute
oxa.Add(new OpenXmlAttribute("t", "", "str"));
//write the start element of a cell with the above attributes
oxw.WriteStartElement(new Cell(), oxa);
//write a value to the cell
oxw.WriteElement(new CellValue("Test"));
//write the end element
oxw.WriteEndElement();

My answer here has a full example of using a SAX approach.

Community
  • 1
  • 1
petelids
  • 12,305
  • 3
  • 47
  • 57
  • Works like a charm, I can also add attributes to the extension element and they are preserved as well. Thank you. – Aran Mulholland Jul 03 '15 at 00:45
  • @petelids ExtensionList and Extension can be used for excel but if i want to do the same thing in case of word what should i do? can you please help me out here. – Pallav Oct 26 '16 at 05:26