Поделиться через


Объединение двух смежных ячеек в документе электронной таблицы

В этом разделе показано, как использовать классы в пакете SDK Open XML для Office для программного объединения двух смежных ячеек в документе электронной таблицы.


Получение объекта SpreadsheetDocument

В пакете SDK SpreadsheetDocument Open XML класс представляет пакет документов Excel. Чтобы открыть документ Excel и работать с ним, создайте экземпляр SpreadsheetDocument класса из документа. После создания экземпляра вы сможете получить доступ к основной части книги, содержащей листы. Текст в документе представлен в пакете в виде XML с помощью SpreadsheetML разметки.

Создание экземпляра класса из документа, вызываемого одним из Open методов. Доступно несколько методов с разными подписями. В примере кода в этой статье используется метод Open(String, Boolean) с сигнатурой, требующей два параметра. Первый параметр — это строка полного пути, представляющая открываемый документ. Второй параметр — или truefalse и указывает, нужно ли открыть файл для редактирования. Любые изменения, внесенные в документ, не будут сохранены, если этот параметр имеет значение 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);
}

См. также