Compartilhar via


Copiar uma Folha de Cálculo Com SAX (API Simples para XML)

Este tópico mostra como utilizar o SDK Open XML para o Office para copiar programaticamente uma folha de cálculo grande com SAX (API Simples para XML). Para obter mais informações sobre a estrutura básica de um SpreadsheetML documento, veja Estrutura de um documento de Folha de CálculoML.


Porquê Utilizar a Abordagem SAX?

O SDK Open XML fornece duas formas de analisar ficheiros Open XML do Office: o Modelo de Objeto de Documento (DOM) e a API Simples para XML (SAX). A abordagem DOM foi concebida para facilitar a consulta e a análise de ficheiros Open XML através de classes com tipos fortes. No entanto, a abordagem DOM requer o carregamento de partes Open XML inteiras para a memória, o que pode levar a um processamento e Out of Memory exceções mais lentos ao trabalhar com partes muito grandes. A abordagem SAX lê no XML num elemento Open XML parte um de cada vez sem ler em toda a parte na memória dando acesso não encaixado e só de reencaminhamento aos dados XML, o que faz com que seja uma escolha melhor ao ler partes muito grandes, como uma WorksheetPart com centenas de milhares de linhas.

Utilizar a Abordagem DOM

Ao utilizar a abordagem DOM, podemos tirar partido das classes do SDK Open XML fortemente digitadas. O primeiro passo é aceder ao pacote WorksheetPart e certificar-se de que não é nulo.

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

    if (worksheetPart is not null)

Assim que for determinado que o WorksheetPart a copiar não é nulo, adicione um novo WorksheetPart para o copiar. Em seguida, clone os WorksheetPart's Worksheet e atribua o clonado Worksheet à propriedade Folha de Cálculo do novo WorksheetPart.

// 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;

Neste momento, o novo WorksheetPart foi adicionado, mas tem de ser adicionado um novo Sheet elemento aos WorkbookPartelementos subordinados do elemento subordinado da Sheetsmesma para que seja apresentado. Para tal, primeiro localize o ID do novo WorksheetParte crie um novo ID de folha ao incrementar a Sheets contagem por um e, em seguida, acrescente um novo Sheet subordinado ao Sheets elemento . Com isto, a Folha de Cálculo copiada é adicionada ao ficheiro.

// 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 });

Utilizar a Abordagem SAX

A abordagem SAX funciona em partes, por isso, ao utilizar a abordagem SAX, o primeiro passo é o mesmo. Aceda aos pacotes WorksheetPart e certifique-se de que não é nulo.

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

    if (worksheetPart is not null)

Com o SAX, não temos acesso ao Clone método . Em vez disso, comece por adicionar um novo WorksheetPart ao WorkbookPart.

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

Em seguida, crie uma instância do OpenXmlPartReader com a parte original da folha de cálculo e uma instância da OpenXmlPartWriter com a peça de folha de cálculo recém-criada.

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

Em seguida, leia os elementos um a um com o Read método . Se o elemento for um CellValue texto interno, tem de ser explicitamente adicionado com o GetText método para ler o texto, porque o WriteStartElement não escreve o texto interno de um elemento. Para outros elementos, só precisamos de utilizar o WriteStartElement método, porque não precisamos do texto interno do outro elemento.

// 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();
        }
    }
}

Neste momento, a parte da folha de cálculo foi copiada para a parte adicionada recentemente, mas tal como acontece com a abordagem DOM, ainda precisamos de adicionar um Sheet ao Workbookelemento '.Sheets Uma vez que a abordagem SAX dá acesso não encaixado e só de reencaminhamento aos dados XML, só é possível pré-acrescentar elementos subordinados, o que, neste caso, adicionaria a nova folha de cálculo ao início e não ao fim, alterando a ordem das folhas de cálculo. Portanto, a abordagem do DOM é necessária aqui, porque queremos acrescentar não anexar o novo Sheet e, uma vez que não WorkbookPart é normalmente uma grande parte, os ganhos de desempenho seriam mínimos.

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 });

Código de exemplo

Segue-se o código de exemplo para as abordagens DOM e SAX para copiar os dados de uma folha para uma nova e adicioná-la ao documento Folha de Cálculo. Embora a abordagem DOM seja mais simples e, em muitos casos, a escolha preferida, com documentos muito grandes, a abordagem SAX é melhor dado que é mais rápida e pode impedir Out of Memory exceções. Para ver a diferença, crie um documento de folha de cálculo com muitas linhas (mais de 10 000) e marcar os resultados do Stopwatch para marcar a diferença no tempo de execução. Aumente o número de linhas para mais de 100 000 para ver ganhos de desempenho ainda mais significativos.

Abordagem DOM

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");
}

Abordagem SAX

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");
        }
    }
}