Gollum's den

ASP.NET (и не только) здесь и сейчас!

View Eugene Agafonov's profile on LinkedIn

April 2010 - Posts

OpenXML SDK 2.0 RTM.

Делаю очередную попытку вернуться к написанию чего-то полезного в блог. К сожалению, времени катастрофически не хватает, но буду делать все возможное :)

 

Итак, всеобщее внимание приковано к грядущему выходу семейства продуктов для разработки нового поколения, под грифом %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);

      }

    }

  }