Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
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 WorkbookPart
elementos subordinados do elemento subordinado da Sheetsmesma para que seja apresentado. Para tal, primeiro localize o ID do novo WorksheetPart
e 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 Workbook
elemento '.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");
}
}
}