Объединение двух смежных ячеек в документе электронной таблицы
В этом разделе показано, как использовать классы в пакете SDK Open XML для Office для программного объединения двух смежных ячеек в документе электронной таблицы.
Получение объекта SpreadsheetDocument
В пакете SDK SpreadsheetDocument Open XML класс представляет пакет документов Excel. Чтобы открыть документ Excel и работать с ним, создайте экземпляр SpreadsheetDocument
класса из документа.
После создания экземпляра вы сможете получить доступ к основной части книги, содержащей листы. Текст в документе представлен в пакете в виде XML с помощью SpreadsheetML
разметки.
Создание экземпляра класса из документа, вызываемого одним из Open методов. Доступно несколько методов с разными подписями. В примере кода в этой статье используется метод Open(String, Boolean) с сигнатурой, требующей два параметра. Первый параметр — это строка полного пути, представляющая открываемый документ. Второй параметр — или true
false
и указывает, нужно ли открыть файл для редактирования. Любые изменения, внесенные в документ, не будут сохранены, если этот параметр имеет значение false
.
Базовая структура документа spreadsheetML
Базовая структура SpreadsheetML
документа состоит из Sheets элементов и Sheet , ссылающихся на листы в книге. Для каждого листа создается отдельный XML-файл. Например, SpreadsheetML
объект для , Workbook имеющий два листа с именами MySheet1 и MySheet2, находится в файле Workbook.xml и показан в следующем примере кода.
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://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>
XML-файлы листа содержат один или несколько элементов уровня блока, SheetData например представляет таблицу ячеек и содержит один или несколько Row элементов. Содержит row
один или несколько Cell элементов. Каждая ячейка CellValue содержит элемент, представляющий значение ячейки. Например, SpreadsheetML
для первого листа в книге, который имеет только значение 100 в ячейке A1, находится в файле Sheet1.xml и показан в следующем примере кода.
<?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>
С помощью пакета SDK Open XML можно создать структуру документа и содержимое, в котором используются строго типизированные классы, соответствующие SpreadsheetML
элементам. Эти классы можно найти в DocumentFormat.OpenXML.Spreadsheet
пространстве имен. В следующей таблице перечислены имена классов, которые соответствуют workbook
элементам , sheets
, sheet
, worksheet
и sheetData
.
Элемент SpreadsheetML | Класс пакета SDK Open XML | Описание |
---|---|---|
<workbook/> |
DocumentFormat.OpenXML.Spreadsheet.Workbook | Корневой элемент основной части документа. |
<sheets/> |
DocumentFormat.OpenXML.Spreadsheet.Sheets | Контейнер для структур уровня блока, таких как sheet, fileVersion и других, указанных в спецификации ISO/IEC 29500 . |
<sheet/> |
DocumentFormat.OpenXml.Spreadsheet.Sheet | Лист, указывающий на файл определения листа. |
<worksheet/> |
DocumentFormat.OpenXML.Spreadsheet. Worksheet | Файл определения листа, который содержит данные листа. |
<sheetData/> |
DocumentFormat.OpenXML.Spreadsheet.SheetData | Таблица ячеек, сгруппированных по строкам. |
<row/> |
DocumentFormat.OpenXml.Spreadsheet.Row | Строка в таблице ячеек. |
<c/> |
DocumentFormat.OpenXml.Spreadsheet.Cell | Ячейка в строке. |
<v/> |
DocumentFormat.OpenXml.Spreadsheet.CellValue | Значение ячейки. |
Механизм работы примера кода
После открытия файла электронной таблицы для редактирования код проверяет, существуют ли указанные ячейки, и если они не существуют, он создает их, вызывая CreateSpreadsheetCellIfNotExist
метод и добавляя его в соответствующий Row объект.
// Given a Worksheet and a cell name, verifies that the specified cell exists.
// If it does not exist, creates a new cell.
static void CreateSpreadsheetCellIfNotExist(Worksheet worksheet, string cellName)
{
string columnName = GetColumnName(cellName);
uint rowIndex = GetRowIndex(cellName);
IEnumerable<Row> rows = worksheet.Descendants<Row>().Where(r => r.RowIndex?.Value == rowIndex);
// If the Worksheet does not contain the specified row, create the specified row.
// Create the specified cell in that row, and insert the row into the Worksheet.
if (rows.Count() == 0)
{
Row row = new Row() { RowIndex = new UInt32Value(rowIndex) };
Cell cell = new Cell() { CellReference = new StringValue(cellName) };
row.Append(cell);
worksheet.Descendants<SheetData>().First().Append(row);
}
else
{
Row row = rows.First();
IEnumerable<Cell> cells = row.Elements<Cell>().Where(c => c.CellReference?.Value == cellName);
// If the row does not contain the specified cell, create the specified cell.
if (cells.Count() == 0)
{
Cell cell = new Cell() { CellReference = new StringValue(cellName) };
row.Append(cell);
}
}
}
Чтобы получить имя столбца, код создает регулярное выражение в соответствии с именем столбца из имени ячейки. Оно сопоставляет любую комбинацию прописных и строчных букв. Дополнительные сведения о регулярных выражениях см. в справочнике Элементы языка регулярных выражений. Код получает имя столбца, вызывая метод Regex.Match.
// 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 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);
}
Пример кода
Следующий код объединяет две смежные ячейки в пакете Row документа. При объединении двух ячеек сохраняется содержимое только одной из них. В языках с написанием слева направо сохраняется содержимое верхней левой ячейки, а в языках с написанием справа налево — содержимое верхней правой ячейки.
Далее представлен полный пример кода на языках C# и Visual Basic.
static void MergeTwoCells(string docName, string sheetName, string cell1Name, string cell2Name)
{
// Open the document for editing.
using (SpreadsheetDocument document = SpreadsheetDocument.Open(docName, true))
{
Worksheet? worksheet = GetWorksheet(document, sheetName);
if (worksheet is null || string.IsNullOrEmpty(cell1Name) || string.IsNullOrEmpty(cell2Name))
{
return;
}
// Verify if the specified cells exist, and if they do not exist, create them.
CreateSpreadsheetCellIfNotExist(worksheet, cell1Name);
CreateSpreadsheetCellIfNotExist(worksheet, cell2Name);
MergeCells mergeCells;
if (worksheet.Elements<MergeCells>().Count() > 0)
{
mergeCells = worksheet.Elements<MergeCells>().First();
}
else
{
mergeCells = new MergeCells();
// Insert a MergeCells object into the specified position.
if (worksheet.Elements<CustomSheetView>().Count() > 0)
{
worksheet.InsertAfter(mergeCells, worksheet.Elements<CustomSheetView>().First());
}
else if (worksheet.Elements<DataConsolidate>().Count() > 0)
{
worksheet.InsertAfter(mergeCells, worksheet.Elements<DataConsolidate>().First());
}
else if (worksheet.Elements<SortState>().Count() > 0)
{
worksheet.InsertAfter(mergeCells, worksheet.Elements<SortState>().First());
}
else if (worksheet.Elements<AutoFilter>().Count() > 0)
{
worksheet.InsertAfter(mergeCells, worksheet.Elements<AutoFilter>().First());
}
else if (worksheet.Elements<Scenarios>().Count() > 0)
{
worksheet.InsertAfter(mergeCells, worksheet.Elements<Scenarios>().First());
}
else if (worksheet.Elements<ProtectedRanges>().Count() > 0)
{
worksheet.InsertAfter(mergeCells, worksheet.Elements<ProtectedRanges>().First());
}
else if (worksheet.Elements<SheetProtection>().Count() > 0)
{
worksheet.InsertAfter(mergeCells, worksheet.Elements<SheetProtection>().First());
}
else if (worksheet.Elements<SheetCalculationProperties>().Count() > 0)
{
worksheet.InsertAfter(mergeCells, worksheet.Elements<SheetCalculationProperties>().First());
}
else
{
worksheet.InsertAfter(mergeCells, worksheet.Elements<SheetData>().First());
}
}
// Create the merged cell and append it to the MergeCells collection.
MergeCell mergeCell = new MergeCell() { Reference = new StringValue(cell1Name + ":" + cell2Name) };
mergeCells.Append(mergeCell);
}
}
// Given a Worksheet and a cell name, verifies that the specified cell exists.
// If it does not exist, creates a new cell.
static void CreateSpreadsheetCellIfNotExist(Worksheet worksheet, string cellName)
{
string columnName = GetColumnName(cellName);
uint rowIndex = GetRowIndex(cellName);
IEnumerable<Row> rows = worksheet.Descendants<Row>().Where(r => r.RowIndex?.Value == rowIndex);
// If the Worksheet does not contain the specified row, create the specified row.
// Create the specified cell in that row, and insert the row into the Worksheet.
if (rows.Count() == 0)
{
Row row = new Row() { RowIndex = new UInt32Value(rowIndex) };
Cell cell = new Cell() { CellReference = new StringValue(cellName) };
row.Append(cell);
worksheet.Descendants<SheetData>().First().Append(row);
}
else
{
Row row = rows.First();
IEnumerable<Cell> cells = row.Elements<Cell>().Where(c => c.CellReference?.Value == cellName);
// If the row does not contain the specified cell, create the specified cell.
if (cells.Count() == 0)
{
Cell cell = new Cell() { CellReference = new StringValue(cellName) };
row.Append(cell);
}
}
}
// Given a SpreadsheetDocument and a worksheet name, get the specified worksheet.
static Worksheet? GetWorksheet(SpreadsheetDocument document, string worksheetName)
{
WorkbookPart workbookPart = document.WorkbookPart ?? document.AddWorkbookPart();
IEnumerable<Sheet> sheets = workbookPart.Workbook.Descendants<Sheet>().Where(s => s.Name == worksheetName);
string? id = sheets.First().Id;
WorksheetPart? worksheetPart = id is not null ? (WorksheetPart)workbookPart.GetPartById(id) : null;
return worksheetPart?.Worksheet;
}
// 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 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);
}