dotnet / Open-XML-SDK

Open XML SDK by Microsoft
https://www.nuget.org/packages/DocumentFormat.OpenXml/
MIT License
4.02k stars 546 forks source link

Unable to Write Shared String Table Part using OpenXmlWriter #1755

Closed benjaminki closed 3 months ago

benjaminki commented 3 months ago

Describe the bug When using the OpenXmlWriter to write the Shared String Table part, the resulting sharedStrings.xml document saved to disk does not contain any string items. I've created a very simple script that creates an excel document, a default sheet, and tries to updated the shared string table to contain one entry. (The next step would be to use OpenXmlWriter to write one row with one cell with shared string value)

To Reproduce

CancellationToken ct = CancellationToken.None;

void Main()
{
    using (FileStream destinationFileStream = File.Create(@"C:\temp\Simple_Doc_Using_OpenXml_Sax_SharedString.xlsx", 1024 * 16))
    using (var destinationSpreadsheetDocument = CreateNewSpreadsheet(destinationFileStream))
    {
        var newWorksheetPart = CreateWorksheet(destinationSpreadsheetDocument, "Sheet1");

        // Use XmlWriter to write the SharedString contents - SAX model - writing start to end
        using (var sharedStringTablePartWriter = OpenXmlWriter.Create(destinationSpreadsheetDocument.WorkbookPart.SharedStringTablePart))
        {
            sharedStringTablePartWriter.WriteStartDocument();
            sharedStringTablePartWriter.WriteStartElement(new SharedStringTable());

            sharedStringTablePartWriter.WriteElement(new SharedStringItem(new Text("Is Current")));

            // End tag for SharedStringTable element.
            sharedStringTablePartWriter.WriteEndElement();
        }
    }
}

SpreadsheetDocument CreateNewSpreadsheet(Stream stream)
{
    SpreadsheetDocument newDoc = SpreadsheetDocument.Create(stream, SpreadsheetDocumentType.Workbook);

    // Add a WorkbookPart to the document.
    WorkbookPart workbookPart = newDoc.AddWorkbookPart();
    //workbookPart.Workbook = new Workbook();

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

    // Add a SharedStringTablePart to the WorkbookPart.
    SharedStringTablePart sharedStringTablePart = workbookPart.AddNewPart<SharedStringTablePart>();
    sharedStringTablePart.SharedStringTable = new SharedStringTable();

    // Add a WorkbookStylesPart to the WorkbookPart, and create default styles.
    WorkbookStylesPart workStylePart = workbookPart.AddNewPart<WorkbookStylesPart>();
    workStylePart.Stylesheet = new Stylesheet();

    workStylePart.Stylesheet.Borders = new Borders() { Count = 1 };
    workStylePart.Stylesheet.Borders.AppendChild(new Border { LeftBorder = new LeftBorder(), RightBorder = new RightBorder(), TopBorder = new TopBorder(), BottomBorder = new BottomBorder(), DiagonalBorder = new DiagonalBorder() });

    workStylePart.Stylesheet.CellFormats = new CellFormats() { Count = 1 };
    workStylePart.Stylesheet.CellFormats.AppendChild(new CellFormat { NumberFormatId = 0, FontId = 0, FillId = 0, BorderId = 0 }); // empty one for index 0, seems to be required

    workStylePart.Stylesheet.CellStyleFormats = new CellStyleFormats() { Count = 1 };
    workStylePart.Stylesheet.CellStyleFormats.AppendChild(new CellFormat { NumberFormatId = 0, FontId = 0, FillId = 0, BorderId = 0, FormatId = 0 });

    workStylePart.Stylesheet.DifferentialFormats = new DifferentialFormats() { Count = 0 };

    workStylePart.Stylesheet.Fills = new Fills() { Count = 2 };
    workStylePart.Stylesheet.Fills.AppendChild(new Fill { PatternFill = new PatternFill { PatternType = PatternValues.None } }); // required, reserved by Excel
    workStylePart.Stylesheet.Fills.AppendChild(new Fill { PatternFill = new PatternFill { PatternType = PatternValues.Gray125 } }); // required, reserved by Excel

    workStylePart.Stylesheet.Fonts = new Fonts() { Count = 1 };
    workStylePart.Stylesheet.Fonts.AppendChild(
        new Font()
        {
            FontName = new FontName() { Val = "Calibri" },
            FontSize = new FontSize() { Val = 11 }
        });

    workStylePart.Stylesheet.TableStyles = new TableStyles() { Count = 0, DefaultTableStyle = "TableStyleMedium2", DefaultPivotStyle = "PivotStyleLight16" };

    return newDoc;
}

WorksheetPart CreateWorksheet(SpreadsheetDocument doc, string sheetName)
{
    var newWorksheetPart = doc.WorkbookPart.AddNewPart<WorksheetPart>();

    // Updating the workbook part done via DOM since we just want to append a sheet to the end, and the workbook xml is not so large, loading it into memory won't be a significan cost.
    if (doc.WorkbookPart.Workbook == null)
    {
        doc.WorkbookPart.Workbook = new Workbook();
    }

    if (doc.WorkbookPart.Workbook.Sheets == null)
    {
        doc.WorkbookPart.Workbook.Sheets = new Sheets();
    }

    Sheet sheet = new Sheet() { Id = doc.WorkbookPart.GetIdOfPart(newWorksheetPart), SheetId = Convert.ToUInt32(doc.WorkbookPart.Workbook.Sheets.Count() + 1), Name = sheetName };
    doc.WorkbookPart.Workbook.Sheets.Append(sheet);

    // Saves data in Workbook DOM tree back to the WorkbookPart.
    doc.WorkbookPart.Workbook.Save();

    return newWorksheetPart;
}

Steps to reproduce the behavior:

  1. Execute Main method
  2. Rename xlsx file to .zip file
  3. Extract contents of zip file
  4. Review contents of sharedStrings.xml in text editor. Observe issue, that no Shared String has been added.

Observed behavior The sharedStrings.xml is minimal, containing only the xml root tag and shared string table tag, with no children.

Expected behavior The sharedStrings.xml should have xml defining a shared string item with child Text item with value "Is Current"

Desktop (please complete the following information):

Additional context I need to use the SAX model using OpenXmlWriter to write the content, and not the DOM model, as I'm working with large data sets across multiple spreadsheets and doing merge/copy operations, and need to be mindful of memory usage.