Freigeben über


Kopieren eines Arbeitsblatts mithilfe von SAX (Simple API for XML)

In diesem Thema wird gezeigt, wie Sie das Open XML SDK für Office verwenden, um ein großes Arbeitsblatt mithilfe von SAX (Simple API for XML) programmgesteuert zu kopieren. Weitere Informationen zur grundlegenden Struktur eines SpreadsheetML Dokuments finden Sie unter Struktur eines SpreadsheetML-Dokuments.


Gründe für die Verwendung des SAX-Ansatzes

Das Open XML SDK bietet zwei Möglichkeiten zum Analysieren von Office Open XML-Dateien: das Dokumentobjektmodell (DOM) und die Simple API for XML (SAX). Der DOM-Ansatz wurde entwickelt, um das Abfragen und Analysieren von Open XML-Dateien mithilfe von stark typisierten Klassen zu vereinfachen. Der DOM-Ansatz erfordert jedoch das Laden ganzer Open XML-Teile in den Arbeitsspeicher, was bei der Arbeit mit sehr großen Teilen zu einer langsameren Verarbeitung und Out of Memory Ausnahmen führen kann. Der SAX-Ansatz liest den XML-Code in einem Open XML-Teil jeweils ein Element ein, ohne den gesamten Teil in den Arbeitsspeicher zu lesen, sodass nicht zwischengespeicherte Vorwärtszugriffe auf XML-Daten möglich sind. Dies macht es zu einer besseren Wahl beim Lesen sehr großer Teile, z. B. eines WorksheetPart mit Hunderttausenden von Zeilen.

Verwenden des DOM-Ansatzes

Mit dem DOM-Ansatz können wir die stark typisierten Klassen des Open XML SDK nutzen. Der erste Schritt besteht darin, auf die des Pakets WorksheetPart zuzugreifen und sicherzustellen, dass es nicht NULL ist.

using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Open(path, true))
{
    // Get the first sheet
    WorksheetPart? worksheetPart = spreadsheetDocument.WorkbookPart?.WorksheetParts?.FirstOrDefault();

    if (worksheetPart is not null)

Nachdem festgestellt wurde, dass der WorksheetPart zu kopierende nicht NULL ist, fügen Sie eine neue WorksheetPart hinzu, in die sie kopiert werden soll. Klonen Sie dann das WorksheetPart-Objekt von , Worksheet und weisen Sie das geklonte Worksheet der neuen WorksheetPartWorksheet-Eigenschaft zu.

// Add a new WorksheetPart
WorksheetPart newWorksheetPart = spreadsheetDocument.WorkbookPart!.AddNewPart<WorksheetPart>();

// Make a copy of the original worksheet
Worksheet newWorksheet = (Worksheet)worksheetPart.Worksheet.Clone();

// Add the new worksheet to the new worksheet part
newWorksheetPart.Worksheet = newWorksheet;

An diesem Punkt wurde das neue WorksheetPart hinzugefügt, aber ein neues Sheet Element muss den untergeordneten Elementen des WorkbookPart-Elements Sheetshinzugefügt werden, damit es angezeigt werden kann. Suchen Sie dazu zunächst die neue WorksheetPartID von , und erstellen Sie eine neue Blatt-ID, indem Sie die Sheets Anzahl um eins erhöhen, und fügen Sie dann ein neues Sheet untergeordnetes Element an das Sheets Element an. Damit wird der Datei das kopierte Arbeitsblatt hinzugefügt.

// Find the new WorksheetPart's Id and create a new sheet id
string id = spreadsheetDocument.WorkbookPart.GetIdOfPart(newWorksheetPart);
uint newSheetId = (uint)(sheets!.ChildElements.Count + 1);

// Append a new Sheet with the WorksheetPart's Id and sheet id to the Sheets element
sheets.AppendChild(new Sheet() { Name = "My New Sheet", SheetId = newSheetId, Id = id });

Verwenden des SAX-Ansatzes

Der SAX-Ansatz funktioniert auf Teilen, sodass der erste Schritt bei Verwendung des SAX-Ansatzes derselbe ist. Greifen Sie auf die des Pakets WorksheetPart zu, und stellen Sie sicher, dass es nicht NULL ist.

using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Open(path, true))
{
    // Get the first sheet
    WorksheetPart? worksheetPart = spreadsheetDocument.WorkbookPart?.WorksheetParts?.FirstOrDefault();

    if (worksheetPart is not null)

Mit SAX haben wir keinen Zugriff auf die Clone -Methode. Beginnen Sie stattdessen mit dem Hinzufügen eines neuen WorksheetPart zu .WorkbookPart

WorksheetPart newWorksheetPart = spreadsheetDocument.WorkbookPart!.AddNewPart<WorksheetPart>();

Erstellen Sie dann eine instance mit OpenXmlPartReader dem ursprünglichen Arbeitsblattteil und eine instance des OpenXmlPartWriter mit dem neu erstellten Arbeitsblattteil.

using (OpenXmlReader reader = OpenXmlPartReader.Create(worksheetPart))
using (OpenXmlWriter writer = OpenXmlPartWriter.Create(newWorksheetPart))

Lesen Sie dann die Elemente nacheinander mit der Read -Methode. Wenn das Element ein CellValue ist, muss der innere Text explizit mithilfe der GetText -Methode hinzugefügt werden, um den Text zu lesen, da der WriteStartElement den inneren Text eines Elements nicht schreibt. Für andere Elemente müssen wir nur die WriteStartElement -Methode verwenden, da wir den inneren Text des anderen Elements nicht benötigen.

// Write the XML declaration with the version "1.0".
writer.WriteStartDocument();

// Read the elements from the original worksheet part
while (reader.Read())
{
    // If the ElementType is CellValue it's necessary to explicitly add the inner text of the element
    // or the CellValue element will be empty
    if (reader.ElementType == typeof(CellValue))
    {
        if (reader.IsStartElement)
        {
            writer.WriteStartElement(reader);
            writer.WriteString(reader.GetText());
        }
        else if (reader.IsEndElement)
        {
            writer.WriteEndElement();
        }
    }
    // For other elements write the start and end elements
    else
    {
        if (reader.IsStartElement)
        {
            writer.WriteStartElement(reader);
        }
        else if (reader.IsEndElement)
        {
            writer.WriteEndElement();
        }
    }
}

An diesem Punkt wurde der Arbeitsblattteil in den neu hinzugefügten Teil kopiert, aber wie beim DOM-Ansatz müssen wir dem -Element des WorkbookSheets -Elements trotzdem eine Sheet hinzufügen. Da der SAX-Ansatz nicht zwischengespeicherten Vorwärtszugriff auf XML-Daten ermöglicht, ist es nur möglich, untergeordnete Elemente voran zu stellen, die in diesem Fall das neue Arbeitsblatt am Anfang und nicht am Ende hinzufügen und die Reihenfolge der Arbeitsblätter ändern würden. Daher ist hier der DOM-Ansatz erforderlich, da wir das neue Sheet nicht voranstellen möchten und da der WorkbookPart normalerweise nicht ein großer Teil ist, wären die Leistungssteigerungen minimal.

Sheets? sheets = spreadsheetDocument.WorkbookPart.Workbook.GetFirstChild<Sheets>();

if (sheets is null)
{
    spreadsheetDocument.WorkbookPart.Workbook.AddChild(new Sheets());
}

string id = spreadsheetDocument.WorkbookPart.GetIdOfPart(newWorksheetPart);
uint newSheetId = (uint)(sheets!.ChildElements.Count + 1);

sheets.AppendChild(new Sheet() { Name = "My New Sheet", SheetId = newSheetId, Id = id });

Beispielcode

Im Folgenden finden Sie den Beispielcode für den DOM- und den SAX-Ansatz, um die Daten von einem Blatt in ein neues zu kopieren und zum Tabellenkalkulationsdokument hinzuzufügen. Während der DOM-Ansatz einfacher ist und in vielen Fällen die bevorzugte Wahl ist, ist der SAX-Ansatz bei sehr großen Dokumenten besser, da er schneller ist und Ausnahmen verhindern Out of Memory kann. Um den Unterschied zu erkennen, erstellen Sie ein Tabellenkalkulationsdokument mit vielen (mehr als 10.000) Zeilen, und überprüfen Sie die Ergebnisse von , Stopwatch um die Differenz in der Ausführungszeit zu überprüfen. Erhöhen Sie die Anzahl der Zeilen auf mehr als 100.000, um noch größere Leistungssteigerungen zu erzielen.

DOM-Ansatz

void CopySheetDOM(string path)
{
    Console.WriteLine("Starting DOM method");

    Stopwatch sw = new();
    sw.Start();
    using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Open(path, true))
    {
        // Get the first sheet
        WorksheetPart? worksheetPart = spreadsheetDocument.WorkbookPart?.WorksheetParts?.FirstOrDefault();

        if (worksheetPart is not null)
        {
            // Add a new WorksheetPart
            WorksheetPart newWorksheetPart = spreadsheetDocument.WorkbookPart!.AddNewPart<WorksheetPart>();

            // Make a copy of the original worksheet
            Worksheet newWorksheet = (Worksheet)worksheetPart.Worksheet.Clone();

            // Add the new worksheet to the new worksheet part
            newWorksheetPart.Worksheet = newWorksheet;

            Sheets? sheets = spreadsheetDocument.WorkbookPart.Workbook.GetFirstChild<Sheets>();

            if (sheets is null)
            {
                spreadsheetDocument.WorkbookPart.Workbook.AddChild(new Sheets());
            }

            // Find the new WorksheetPart's Id and create a new sheet id
            string id = spreadsheetDocument.WorkbookPart.GetIdOfPart(newWorksheetPart);
            uint newSheetId = (uint)(sheets!.ChildElements.Count + 1);

            // Append a new Sheet with the WorksheetPart's Id and sheet id to the Sheets element
            sheets.AppendChild(new Sheet() { Name = "My New Sheet", SheetId = newSheetId, Id = id });
        }
    }

    sw.Stop();

    Console.WriteLine($"DOM method took {sw.Elapsed.TotalSeconds} seconds");
}

SAX-Ansatz

void CopySheetSAX(string path)
{
    Console.WriteLine("Starting SAX method");

    Stopwatch sw = new();
    sw.Start();
    using (SpreadsheetDocument spreadsheetDocument = SpreadsheetDocument.Open(path, true))
    {
        // Get the first sheet
        WorksheetPart? worksheetPart = spreadsheetDocument.WorkbookPart?.WorksheetParts?.FirstOrDefault();

        if (worksheetPart is not null)
        {
            WorksheetPart newWorksheetPart = spreadsheetDocument.WorkbookPart!.AddNewPart<WorksheetPart>();

            using (OpenXmlReader reader = OpenXmlPartReader.Create(worksheetPart))
            using (OpenXmlWriter writer = OpenXmlPartWriter.Create(newWorksheetPart))
            {
                // Write the XML declaration with the version "1.0".
                writer.WriteStartDocument();

                // Read the elements from the original worksheet part
                while (reader.Read())
                {
                    // If the ElementType is CellValue it's necessary to explicitly add the inner text of the element
                    // or the CellValue element will be empty
                    if (reader.ElementType == typeof(CellValue))
                    {
                        if (reader.IsStartElement)
                        {
                            writer.WriteStartElement(reader);
                            writer.WriteString(reader.GetText());
                        }
                        else if (reader.IsEndElement)
                        {
                            writer.WriteEndElement();
                        }
                    }
                    // For other elements write the start and end elements
                    else
                    {
                        if (reader.IsStartElement)
                        {
                            writer.WriteStartElement(reader);
                        }
                        else if (reader.IsEndElement)
                        {
                            writer.WriteEndElement();
                        }
                    }
                }
            }

            Sheets? sheets = spreadsheetDocument.WorkbookPart.Workbook.GetFirstChild<Sheets>();

            if (sheets is null)
            {
                spreadsheetDocument.WorkbookPart.Workbook.AddChild(new Sheets());
            }

            string id = spreadsheetDocument.WorkbookPart.GetIdOfPart(newWorksheetPart);
            uint newSheetId = (uint)(sheets!.ChildElements.Count + 1);

            sheets.AppendChild(new Sheet() { Name = "My New Sheet", SheetId = newSheetId, Id = id });

            sw.Stop();

            Console.WriteLine($"SAX method took {sw.Elapsed.TotalSeconds} seconds");
        }
    }
}