Calcular a soma de intervalo de células em um documento de planilha
Este tópico mostra como usar as classes no SDK Open XML para Office para calcular a soma de um intervalo contíguo de células em um documento de planilha programaticamente.
Estrutura básica de um documento de planilha
A estrutura básica de um documento SpreadsheetML consiste nos elementos Sheets e Sheet, que referenciam as planilhas na pasta de trabalho. Um arquivo XML separado é criado para cada planilha. Por exemplo, o SpreadsheetML de uma Pasta de trabalho que tem duas planilhas chamadas MySheet1 e MySheet2 está localizado no arquivo Workbook.xml e é mostrado no exemplo de código a seguir.
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<workbook xmlns=https://schemas.openxmlformats.org/spreadsheetml/2006/main xmlns:r="https://schemas.openxmlformats.org/officeDocument/2006/relationships">
<sheets>
<sheet name="MySheet1" sheetId="1" r:id="rId1" />
<sheet name="MySheet2" sheetId="2" r:id="rId2" />
</sheets>
</workbook>
Os arquivos XML da planilha contêm um ou mais elementos de nível de bloco, como sheetData , representa a tabela de células e contém um ou mais elementos row . Uma linha contém um ou mais elementos Cell. Cada célula contém um elemento CellValue que representa o valor da célula. Por exemplo, a PlanilhaML da primeira planilha em uma pasta de trabalho, que tem apenas o valor 100 na célula A1, está localizada no arquivo Sheet1.xml e é mostrada no exemplo de código a seguir.
<?xml version="1.0" encoding="UTF-8" ?>
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
<sheetData>
<row r="1">
<c r="A1">
<v>100</v>
</c>
</row>
</sheetData>
</worksheet>
Usando o SDK Do Open XML, você pode criar a estrutura de documentos e o conteúdo que usa classes fortemente tipdas que correspondem a elementos SpreadsheetML . Você pode encontrar essas classes no namespace DocumentFormat.OpenXML.Spreadsheet. A tabela a seguir lista os nomes das classes que correspondem aos elementos workbook, sheets, sheet, worksheet e sheetData.
Elemento SpreadsheetML | Classe SDK Open XML | Descrição |
---|---|---|
pasta de trabalho | DocumentFormat.OpenXML.Spreadsheet.Workbook | O elemento raiz para a parte do documento principal. |
sheets | DocumentFormat.OpenXML.Spreadsheet.Sheets | O contêiner para as estruturas de nível de bloco, como sheet, fileVersion e outras indicadas na especificação ISO/IEC 29500. |
sheet | DocumentFormat.OpenXml.Spreadsheet.Sheet | Uma planilha que aponta para um arquivo de definição de planilha. |
planilha | DocumentFormat.OpenXML.Spreadsheet. Planilha | Um arquivo de definição de planilha que contém os dados de planilha. |
sheetData | DocumentFormat.OpenXML.Spreadsheet.SheetData | A tabela de células, agrupadas por linhas. |
row | DocumentFormat.OpenXml.Spreadsheet.Row | Uma linha na tabela de células. |
c | DocumentFormat.OpenXml.Spreadsheet.Cell | Uma célula em uma linha. |
v | DocumentFormat.OpenXml.Spreadsheet.CellValue | O valor de uma célula. |
Como funciona o código de exemplo
O código de exemplo começa passando para o método CalculateSumOfCellRange um parâmetro que representa o caminho completo para o arquivo SpreadsheetML de origem, um parâmetro que representa o nome da planilha que contém as células, um parâmetro que representa o nome da primeira célula no intervalo contíguo, um parâmetro que representa o nome da última célula no intervalo contíguo, e um parâmetro que representa o nome da célula em que você deseja que o resultado seja exibido.
Em seguida, o código abre o arquivo para edição como um pacote de documento de PlanilhaDocument para acesso de leitura/gravação, o código obtém o objeto planilha especificado. Em seguida, ele obtém o índice da linha para a primeira e última célula no intervalo contíguo chamando o método GetRowIndex . Ele obtém o nome da coluna para a primeira e última célula no intervalo contíguo chamando o método GetColumnName .
Para cada objeto Row dentro do intervalo contíguo, o código itera por meio de cada objeto Cell e determina se a coluna da célula está dentro do intervalo contíguo chamando o método CompareColumn . Se a célula estiver dentro do intervalo contíguo, o código adicionará o valor da célula à soma. Em seguida, ele obtém o objeto SharedStringTablePart se ele existir. Se ele não existir, ele criará um usando o método AddNewPart . Ele insere o resultado no objeto SharedStringTablePart chamando o método InsertSharedStringItem .
O código insere uma nova célula para o resultado na planilha chamando o método InsertCellInWorksheet e defina o valor da célula. Para obter mais informações, confira como inserir uma célula em uma planilha e, em seguida, salvar a planilha.
static void CalculateSumOfCellRange(string docName, string worksheetName, string firstCellName, string lastCellName, string resultCell)
{
// Open the document for editing.
using (SpreadsheetDocument document = SpreadsheetDocument.Open(docName, true))
{
IEnumerable<Sheet>? sheets = document.WorkbookPart?.Workbook.Descendants<Sheet>().Where(s => s.Name == worksheetName);
string? firstId = sheets?.First().Id;
if (sheets is null || firstId is null || sheets.Count() == 0)
{
// The specified worksheet does not exist.
return;
}
WorksheetPart worksheetPart = (WorksheetPart)document.WorkbookPart!.GetPartById(firstId);
Worksheet worksheet = worksheetPart.Worksheet;
// Get the row number and column name for the first and last cells in the range.
uint firstRowNum = GetRowIndex(firstCellName);
uint lastRowNum = GetRowIndex(lastCellName);
string firstColumn = GetColumnName(firstCellName);
string lastColumn = GetColumnName(lastCellName);
double sum = 0;
// Iterate through the cells within the range and add their values to the sum.
foreach (Row row in worksheet.Descendants<Row>().Where(r => r.RowIndex is not null && r.RowIndex.Value >= firstRowNum && r.RowIndex.Value <= lastRowNum))
{
foreach (Cell cell in row)
{
if (cell.CellReference is not null && cell.CellReference.Value is not null)
{
string columnName = GetColumnName(cell.CellReference.Value);
if (CompareColumn(columnName, firstColumn) >= 0 && CompareColumn(columnName, lastColumn) <= 0 && double.TryParse(cell.CellValue?.Text, out double num))
{
sum += num;
}
}
}
}
// Get the SharedStringTablePart and add the result to it.
// If the SharedStringPart does not exist, create a new one.
SharedStringTablePart shareStringPart;
if (document.WorkbookPart.GetPartsOfType<SharedStringTablePart>().Count() > 0)
{
shareStringPart = document.WorkbookPart.GetPartsOfType<SharedStringTablePart>().First();
}
else
{
shareStringPart = document.WorkbookPart.AddNewPart<SharedStringTablePart>();
}
// Insert the result into the SharedStringTablePart.
int index = InsertSharedStringItem("Result: " + sum, shareStringPart);
Cell result = InsertCellInWorksheet(GetColumnName(resultCell), GetRowIndex(resultCell), worksheetPart);
// Set the value of the cell.
result.CellValue = new CellValue(index.ToString());
result.DataType = new EnumValue<CellValues>(CellValues.SharedString);
worksheetPart.Worksheet.Save();
}
}
Para obter o índice de linha, o código passa um parâmetro que representa o nome da célula e cria uma nova expressão regular para corresponder à parte do índice de linha do nome da célula. Para obter mais informações sobre expressões regulares, consulte Elementos de Linguagem de Expressão Regular. Ele obtém o índice de linha chamando o método Regex.Match e retorna o índice de linha.
// Given a cell name, parses the specified cell to get the row index.
static uint GetRowIndex(string cellName)
{
// Create a regular expression to match the row index portion the cell name.
Regex regex = new Regex(@"\d+");
Match match = regex.Match(cellName);
return uint.Parse(match.Value);
}
Em seguida, o código obtém o nome da coluna passando um parâmetro que representa o nome da célula e cria uma nova expressão regular para corresponder à parte do nome da coluna do nome da célula. Essa expressão regular corresponde a qualquer combinação de letras maiúsculas ou minúsculas. Ele obtém o nome da coluna chamando o método Regex.Match e retorna o nome da coluna.
// Given a cell name, parses the specified cell to get the column name.
static string GetColumnName(string cellName)
{
// Create a regular expression to match the column name portion of the cell name.
Regex regex = new Regex("[A-Za-z]+");
Match match = regex.Match(cellName);
return match.Value;
}
Para comparar duas colunas, o código passa em dois parâmetros que representam as colunas a serem comparadas. Se a primeira coluna for maior que a segunda coluna, ela retornará 1. Se a segunda coluna for maior que a primeira coluna, ela retornará -1. Caso contrário, ele compara os valores das colunas usando o Compare e retorna o resultado.
// Given two columns, compares the columns.
static int CompareColumn(string column1, string column2)
{
if (column1.Length > column2.Length)
{
return 1;
}
else if (column1.Length < column2.Length)
{
return -1;
}
else
{
return string.Compare(column1, column2, true);
}
}
Para inserir um SharedStringItem, o código passa em um parâmetro que representa o texto a ser inserido na célula e um parâmetro que representa o objeto SharedStringTablePart para a planilha. Se o objeto ShareStringTablePart não contiver um objeto SharedStringTable , ele criará um. Se o texto já existir no objeto ShareStringTable , ele retornará o índice do objeto SharedStringItem que representa o texto. Se o texto não existir, crie um novo objeto SharedStringItem que represente o texto. Em seguida, ele retorna o índice do objeto SharedStringItem que representa o texto.
// Given text and a SharedStringTablePart, creates a SharedStringItem with the specified text
// and inserts it into the SharedStringTablePart. If the item already exists, returns its index.
static int InsertSharedStringItem(string text, SharedStringTablePart shareStringPart)
{
// If the part does not contain a SharedStringTable, create it.
if (shareStringPart.SharedStringTable is null)
{
shareStringPart.SharedStringTable = new SharedStringTable();
}
int i = 0;
foreach (SharedStringItem item in shareStringPart.SharedStringTable.Elements<SharedStringItem>())
{
if (item.InnerText == text)
{
// The text already exists in the part. Return its index.
return i;
}
i++;
}
// The text does not exist in the part. Create the SharedStringItem.
shareStringPart.SharedStringTable.AppendChild(new SharedStringItem(new DocumentFormat.OpenXml.Spreadsheet.Text(text)));
shareStringPart.SharedStringTable.Save();
return i;
}
A etapa final é inserir uma célula na planilha. O código faz isso passando parâmetros que representam o nome da coluna e o número da linha da célula e um parâmetro que representa a planilha que contém a célula. Se a linha especificada não existir, ela criará a linha e a acrescentará à planilha. Se a coluna especificada existir, ela localizará a célula que corresponde à linha nessa coluna e retornará a célula. Se a coluna especificada não existir, ela criará a coluna e a inserirá na planilha. Em seguida, ele determina onde inserir a nova célula na coluna iterando através dos elementos de linha para localizar a célula que vem diretamente após a linha especificada, em ordem sequencial. Ele salva essa linha na variável refCell . Ele insere a nova célula antes da célula referenciada por refCell usando o método InsertBefore . Em seguida, ele retorna o novo objeto Cell .
// Given a column name, a row index, and a WorksheetPart, inserts a cell into the worksheet.
// If the cell already exists, returns it.
static Cell InsertCellInWorksheet(string columnName, uint rowIndex, WorksheetPart worksheetPart)
{
Worksheet worksheet = worksheetPart.Worksheet;
SheetData sheetData = worksheet.GetFirstChild<SheetData>() ?? worksheet.AppendChild(new SheetData());
string cellReference = columnName + rowIndex;
// If the worksheet does not contain a row with the specified row index, insert one.
Row row;
if (sheetData.Elements<Row>().Where(r => r.RowIndex is not null && r.RowIndex == rowIndex).Count() != 0)
{
row = sheetData.Elements<Row>().Where(r => r.RowIndex is not null && r.RowIndex == rowIndex).First();
}
else
{
row = new Row() { RowIndex = rowIndex };
sheetData.Append(row);
}
// If there is not a cell with the specified column name, insert one.
if (row.Elements<Cell>().Where(c => c.CellReference is not null && c.CellReference.Value == columnName + rowIndex).Count() > 0)
{
return row.Elements<Cell>().Where(c => c.CellReference is not null && c.CellReference.Value == cellReference).First();
}
else
{
// Cells must be in sequential order according to CellReference. Determine where to insert the new cell.
Cell? refCell = null;
foreach (Cell cell in row.Elements<Cell>())
{
if (string.Compare(cell.CellReference?.Value, cellReference, true) > 0)
{
refCell = cell;
break;
}
}
Cell newCell = new Cell() { CellReference = cellReference };
row.InsertBefore(newCell, refCell);
worksheet.Save();
return newCell;
}
}
Código de exemplo
Este é o código de exemplo completo em C# e em Visual Basic.
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
static void CalculateSumOfCellRange(string docName, string worksheetName, string firstCellName, string lastCellName, string resultCell)
{
// Open the document for editing.
using (SpreadsheetDocument document = SpreadsheetDocument.Open(docName, true))
{
IEnumerable<Sheet>? sheets = document.WorkbookPart?.Workbook.Descendants<Sheet>().Where(s => s.Name == worksheetName);
string? firstId = sheets?.First().Id;
if (sheets is null || firstId is null || sheets.Count() == 0)
{
// The specified worksheet does not exist.
return;
}
WorksheetPart worksheetPart = (WorksheetPart)document.WorkbookPart!.GetPartById(firstId);
Worksheet worksheet = worksheetPart.Worksheet;
// Get the row number and column name for the first and last cells in the range.
uint firstRowNum = GetRowIndex(firstCellName);
uint lastRowNum = GetRowIndex(lastCellName);
string firstColumn = GetColumnName(firstCellName);
string lastColumn = GetColumnName(lastCellName);
double sum = 0;
// Iterate through the cells within the range and add their values to the sum.
foreach (Row row in worksheet.Descendants<Row>().Where(r => r.RowIndex is not null && r.RowIndex.Value >= firstRowNum && r.RowIndex.Value <= lastRowNum))
{
foreach (Cell cell in row)
{
if (cell.CellReference is not null && cell.CellReference.Value is not null)
{
string columnName = GetColumnName(cell.CellReference.Value);
if (CompareColumn(columnName, firstColumn) >= 0 && CompareColumn(columnName, lastColumn) <= 0 && double.TryParse(cell.CellValue?.Text, out double num))
{
sum += num;
}
}
}
}
// Get the SharedStringTablePart and add the result to it.
// If the SharedStringPart does not exist, create a new one.
SharedStringTablePart shareStringPart;
if (document.WorkbookPart.GetPartsOfType<SharedStringTablePart>().Count() > 0)
{
shareStringPart = document.WorkbookPart.GetPartsOfType<SharedStringTablePart>().First();
}
else
{
shareStringPart = document.WorkbookPart.AddNewPart<SharedStringTablePart>();
}
// Insert the result into the SharedStringTablePart.
int index = InsertSharedStringItem("Result: " + sum, shareStringPart);
Cell result = InsertCellInWorksheet(GetColumnName(resultCell), GetRowIndex(resultCell), worksheetPart);
// Set the value of the cell.
result.CellValue = new CellValue(index.ToString());
result.DataType = new EnumValue<CellValues>(CellValues.SharedString);
worksheetPart.Worksheet.Save();
}
}
// Given a cell name, parses the specified cell to get the row index.
static uint GetRowIndex(string cellName)
{
// Create a regular expression to match the row index portion the cell name.
Regex regex = new Regex(@"\d+");
Match match = regex.Match(cellName);
return uint.Parse(match.Value);
}
// Given a cell name, parses the specified cell to get the column name.
static string GetColumnName(string cellName)
{
// Create a regular expression to match the column name portion of the cell name.
Regex regex = new Regex("[A-Za-z]+");
Match match = regex.Match(cellName);
return match.Value;
}
// Given two columns, compares the columns.
static int CompareColumn(string column1, string column2)
{
if (column1.Length > column2.Length)
{
return 1;
}
else if (column1.Length < column2.Length)
{
return -1;
}
else
{
return string.Compare(column1, column2, true);
}
}
// Given text and a SharedStringTablePart, creates a SharedStringItem with the specified text
// and inserts it into the SharedStringTablePart. If the item already exists, returns its index.
static int InsertSharedStringItem(string text, SharedStringTablePart shareStringPart)
{
// If the part does not contain a SharedStringTable, create it.
if (shareStringPart.SharedStringTable is null)
{
shareStringPart.SharedStringTable = new SharedStringTable();
}
int i = 0;
foreach (SharedStringItem item in shareStringPart.SharedStringTable.Elements<SharedStringItem>())
{
if (item.InnerText == text)
{
// The text already exists in the part. Return its index.
return i;
}
i++;
}
// The text does not exist in the part. Create the SharedStringItem.
shareStringPart.SharedStringTable.AppendChild(new SharedStringItem(new DocumentFormat.OpenXml.Spreadsheet.Text(text)));
shareStringPart.SharedStringTable.Save();
return i;
}
// Given a column name, a row index, and a WorksheetPart, inserts a cell into the worksheet.
// If the cell already exists, returns it.
static Cell InsertCellInWorksheet(string columnName, uint rowIndex, WorksheetPart worksheetPart)
{
Worksheet worksheet = worksheetPart.Worksheet;
SheetData sheetData = worksheet.GetFirstChild<SheetData>() ?? worksheet.AppendChild(new SheetData());
string cellReference = columnName + rowIndex;
// If the worksheet does not contain a row with the specified row index, insert one.
Row row;
if (sheetData.Elements<Row>().Where(r => r.RowIndex is not null && r.RowIndex == rowIndex).Count() != 0)
{
row = sheetData.Elements<Row>().Where(r => r.RowIndex is not null && r.RowIndex == rowIndex).First();
}
else
{
row = new Row() { RowIndex = rowIndex };
sheetData.Append(row);
}
// If there is not a cell with the specified column name, insert one.
if (row.Elements<Cell>().Where(c => c.CellReference is not null && c.CellReference.Value == columnName + rowIndex).Count() > 0)
{
return row.Elements<Cell>().Where(c => c.CellReference is not null && c.CellReference.Value == cellReference).First();
}
else
{
// Cells must be in sequential order according to CellReference. Determine where to insert the new cell.
Cell? refCell = null;
foreach (Cell cell in row.Elements<Cell>())
{
if (string.Compare(cell.CellReference?.Value, cellReference, true) > 0)
{
refCell = cell;
break;
}
}
Cell newCell = new Cell() { CellReference = cellReference };
row.InsertBefore(newCell, refCell);
worksheet.Save();
return newCell;
}
}