Question

I have a program which modifies an existing spreadsheet. Part of the program creates copies of certain sheets. To create one copy, the program runs perfectly and the resulting file is also fine, presenting no errors when opened in Excel. However, when creating two copies of the same worksheet, the program still runs just fine but, when opened in Excel, the following error appears regarding unreadable content:

Repaired Records: Worksheet properties from /xl/workbook.xml part (Workbook)

Here is the code used to perform the copy:

private void CopySheet(int sNum, int pNum, string type)
{
    var tempSheet = SpreadsheetDocument.Create(new MemoryStream(), SpreadsheetDocumentType.Workbook);
    WorkbookPart tempWBP = tempSheet.AddWorkbookPart();
    var part = Document.XGetWorkSheetPart(sNum);
    var sheetData = part.Worksheet.ChildElements[5].Clone() as SheetData;
    var merge = part.Worksheet.ChildElements[6].Clone() as MergeCells;
    WorksheetPart tempWSP = tempWBP.AddPart<WorksheetPart>(part);

    var copy = Document.WorkbookPart.AddPart<WorksheetPart>(tempWSP);
    //copy.Worksheet.RemoveChild<SheetData>(copy.Worksheet.ChildElements[5] as SheetData);
    //copy.Worksheet.InsertAt<SheetData>(sheetData, 5);
    //copy.Worksheet.RemoveChild<MergeCells>(copy.Worksheet.ChildElements[6] as MergeCells);
    //copy.Worksheet.InsertAt<MergeCells>(merge, 6);
    //copy.Worksheet.SheetProperties.CodeName.Value = "Phase" + pNum + type;

    var sheets = Document.WorkbookPart.Workbook.Sheets;
    var sheet = new Sheet();
    sheet.Id = Document.WorkbookPart.GetIdOfPart(copy);
    sheet.Name = "Phase " + pNum + " " + type;
    sheet.SheetId = (uint)sheets.ChildElements.Count;
    sheets.Append(sheet);
}

This method makes use of the fact that .AddPart<>() performs a deep copy of any Part (and anything it references to) which does not already belong in the document to, with the help of a temporary sheet, create a deep copy of all referenced parts of a WorkSheet.

As stated above, this works quite well if the function is called only once for a given sheet. If it is called more than once, however, the file when opened in Excel gives an error of unreadable content. That being said, the file itself seems just fine, with no missing data or anything (which would help to figure out what exactly was wrong), just the error saying there was something wrong.

The lines that are commented out are a "hack" I had to do to deal with problems regarding .AddPart<>(), but I won't go into much detail with them here because I've already posted about this here (but I still haven't gotten a reply, so by all means, please answer that question, too!). That being said, those lines seem to have no relevance to this current problem since the error appears with or without those lines of code.

Was it helpful?

Solution

I noticed the source file's sheets were not numbered properly for whatever reason, so their SheetID's were not sequencial (6, 1, 3). Therefore, my program was, when creating two copies of the file with the code sheet.SheetID = sheets.ChildElements.Count + 1, setting the values as 4, 5, 6 and 7, which lead to a conflict with the existing sheets. I've therefore modified that section so that the program always gets a valid ID:

// ...
uint id = 1;
bool valid = false;
while (!valid)
{
    uint temp = id;
    foreach (OpenXmlElement e in sheets.ChildElements)
    {
        var s = e as Sheet;
        if (id == s.SheetId.Value)
        {
            id++;
            break;
        }
    }
    if (temp == id)
        valid = true;
}
sheet.SheetId = id;
//...

With this my sheetIDs become (6, 1, 3, 2, 4, 5, 7), and therefore there are no conflicts and no more errors!

OTHER TIPS

    static WorksheetPart GetWorkSheetPart(WorkbookPart workbookPart, string sheetName)
    {
        //Get the relationship id of the sheetname
        string relId = workbookPart.Workbook.Descendants<Sheet>()
        .Where(s => s.Name.Value.Equals(sheetName))
        .First()
        .Id;
        return (WorksheetPart)workbookPart.GetPartById(relId);
    }

    static void FixupTableParts(WorksheetPart worksheetPart, int numTableDefParts)
    {
        //Every table needs a unique id and name
        foreach (TableDefinitionPart tableDefPart in worksheetPart.TableDefinitionParts)
        {
            tableId++;
            tableDefPart.Table.Id = (uint)tableId;
            tableDefPart.Table.DisplayName = "CopiedTable" + tableId;
            tableDefPart.Table.Name = "CopiedTable" + tableId;
            tableDefPart.Table.Save();
        }
    }

    private void CopySheet(SpreadsheetDocument mySpreadsheet, string sheetName, string clonedSheetName)
    {
        WorkbookPart workbookPart = mySpreadsheet.WorkbookPart;
        IEnumerable<Sheet> source = workbookPart.Workbook.Descendants<Sheet>();
        Sheet sheet = Enumerable.First<Sheet>(source, (Func<Sheet, bool>)(s => (string)s.Name == sheetName));
        string sheetWorkbookPartId = (string)sheet.Id;
        WorksheetPart sourceSheetPart = (WorksheetPart)workbookPart.GetPartById(sheetWorkbookPartId);
        SpreadsheetDocument tempSheet = SpreadsheetDocument.Create(new MemoryStream(), mySpreadsheet.DocumentType);
        WorkbookPart tempWorkbookPart = tempSheet.AddWorkbookPart();
        WorksheetPart tempWorksheetPart = tempWorkbookPart.AddPart<WorksheetPart>(sourceSheetPart);
        //Add cloned sheet and all associated parts to workbook
        WorksheetPart clonedSheet = workbookPart.AddPart<WorksheetPart>(tempWorksheetPart);

        int numTableDefParts = sourceSheetPart.GetPartsCountOfType<TableDefinitionPart>();
        tableId = numTableDefParts;
        if (numTableDefParts != 0)
            FixupTableParts(clonedSheet, numTableDefParts);
        CleanView(clonedSheet);

        //Add new sheet to main workbook part
        Sheets sheets = workbookPart.Workbook.GetFirstChild<Sheets>();
        Sheet copiedSheet = new Sheet();
        copiedSheet.Name = clonedSheetName;
        copiedSheet.Id = workbookPart.GetIdOfPart(clonedSheet);
        copiedSheet.SheetId = (uint)sheets.ChildElements.Count + 1;
        sheets.Append(copiedSheet);
        workbookPart.Workbook.Save();
    }

    static void CleanView(WorksheetPart worksheetPart)
    {
        SheetViews views = worksheetPart.Worksheet.GetFirstChild<SheetViews>();
        if (views != null)
        {
            views.Remove();
            worksheetPart.Worksheet.Save();
        }
    }
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top