Делаю очередную попытку вернуться к написанию чего-то полезного в блог. К сожалению, времени катастрофически не хватает, но буду делать все возможное :)
Итак, всеобщее внимание приковано к грядущему выходу семейства продуктов для разработки нового поколения, под грифом %Product_Name% 2010. Но я, будучи верным поклонником творчества Monty Python, расскажу про нечто совершенно другое.
Вышел (уже довольно давно, так что это не новость) релиз OpenXML SDK 2.0. С помощью этого SDK можно генерировать документы офиса без автоматизации офисных приложений. По сути дела, взяли xml-схему, и сгенерировали по ней объектную модель, а потом немного причесали. В результате, пока пользоваться не очень удобно, но неплохо поддерживается сценарий с использованием шаблонов. Можно взять заготовку отчета, вставить в нужные места данные с помощью SDK, и – отчет готов.
Также в состав SDK входит очень полезная утилита, Productivity Tool. Это по сути дела reflector для офисных документов. В ней можно открыть документ и сгенерировать код, который создает его, или часть документа. Можно посмотреть иерархию, что от чего зависит, сравнить два документа и найти различия.
Здесь я уже писал про OpenXML SDK, поэтому особо повторяться не буду и просто приведу еще один code snippet. Здесь мы добавляем метод-расширение к типу из SDK, и у объекта типа TableColumn появляется метод SetColumnName. Проще сделать у меня не получилось, поэтому убедительная просьба – если заметите ошибки, или возможность сделать лучше, пожалуйста напишите об этом.
/// <summary>
/// Дополнительная функциональность для типа <see cref="TableColumn" /> из OpenXML SDK
/// </summary>
public static class TableColumnExtensions
{
/// <summary>
/// Задать имя колонки таблицы
/// </summary>
/// <param name="column">колонка таблицы</param>
/// <param name="name">имя колонки</param>
public static void SetColumnName(this TableColumn column, string name)
{
if (null == column) throw new ArgumentNullException("column");
if (null == name) throw new ArgumentNullException("name");
column.Name = name;
var table = column.Ancestors<Table>().Single();
var tablePart = table.TableDefinitionPart;
var doc = (SpreadsheetDocument)tablePart.OpenXmlPackage;
var workbookPart = doc.WorkbookPart;
// находим соответствующий таблице worksheetPart
// для этого выбираем из WorksheetParts ту часть, у которой
// есть tableDefinitionPart, соответствующая нашей таблице
var targetPart = workbookPart.WorksheetParts.Where(
wp => wp.GetPartsOfType<TableDefinitionPart>()
.Where(tdp => tdp == tablePart).Count() == 1)
.SingleOrDefault();
// Если worksheetPart не обнаружен, то просто ничего не делаем
// возможно, здесь лучше использовать Exception
if (null == targetPart) return;
// В tableReference хранится запись вида A1:B5, где А1 - верхняя левая ячейка таблицы,
// а B5 - нижняя правая. Нам нужна верхняя левая, как первая ячейка строки заголовка.
// пустым и без двоеточия это значение не может быть никак
string firstTableCellName = table.Reference.Value.Split(':')[0];
// Находим верхнюю левую ячейку в объектной модели
Cell firstHeaderCell = targetPart.Worksheet.Descendants<Cell>()
.Where(
c => string.Equals(c.CellReference, firstTableCellName,
StringComparison.InvariantCultureIgnoreCase))
.Single();
// Получаем строку заголовка, и выбираем из нее ячейку, соответствующую нашей колонке
// не уверен что Id колонки это правильный способ узнать номер ячейки в строке, но
// пока другого способа нет, а колонки всегда идут последовательно
var headerRow = firstHeaderCell.Ancestors<Row>().Single();
var headerCells = headerRow.Descendants<Cell>();
var headerCell = headerCells.ElementAtOrDefault((int)column.Id.Value - 1);
if (headerCell.DataType != CellValues.SharedString)
{
// Если в ячейке данные не из SharedStringPart, то просто устанавливаем ее текст
headerCell.CellValue.Text = name;
}
else
{
// Если в ячейке номер ресурса из SharedStringPart, то находим SharedStringPart,
// находим ресурс по номеру, и изменяем его значение
var elementNumber = Convert.ToInt32(headerCell.CellValue.Text);
var sharedStringsPart = workbookPart.GetPartsOfType<SharedStringTablePart>().Single();
var stringTable = sharedStringsPart.SharedStringTable;
var items = stringTable.Descendants<SharedStringItem>();
var ourItem = items.ElementAt(elementNumber);
ourItem.Text.Text = name;
// сохраняем изменения в SharedStringPart
stringTable.Save(sharedStringsPart);
}
}
}