Что написано пером, или как проверить документы в форматах MS Office

Одной из часто встречающихся в процессе создания приложений задач является генерация различных документов в каком-либо из популярных форматов. Есть несколько привычных путей к желаемому результату – от подключения готовой библиотеки до брутального штудирования спецификации формата с последующим написанием необходимого кода. Но, независимо от выбранного варианта, было бы неплохо проверить, что полученный документ будет хорошо восприниматься и редактироваться стандартными средствами. О некоторых способах такой проверки и пойдет речь под катом.

1bf1ff163fe443e49ed67cfb58879a3a.jpg
Оговорюсь сразу, что под проверкой в данном случае я понимаю проверку на соответствие полученного результата стандарту выбранного формата, позволяющую с достаточно большой вероятностью утверждать, что документ как минимум откроется в Word или Excel без неприятных сообщений о возникших проблемах с предложением попытаться восстановить поврежденный файл. Независимо от того, насколько серьёзным является разрабатываемое приложение и насколько солидным должен быть набор поддерживаемых типов файлов, в него скорее всего будут входить стандартные форматы Office Open XML, более известные как docx и xlsx, а также их двоичные предшественники, doc и xls. О некоторых механизмах работы с ними я и буду рассказывать.

Прогулки с динозаврами


Хотя Microsoft и выложила на своем сайте официальные спецификации форматов doc и xls, они нередко оказываются скупы и немногословны в своих описаниях. Стоит понимать, что со временем форматы претерпевали существенные изменения (с сохранением обратной совместимости). При этом единый подход использовался не только для хранения документов Word, но и для таблиц Excel и презентаций PowerPoint. Проще говоря, даже при наличии мануалов возможностей почувствовать себя Жоржем Кювье, пытающимся восстановить облик загадочной зверюшки по разрозненным костям, предостаточно. К счастью, существует способ локализовать имеющиеся в документе проблемы без необходимости расхлебывать байтовую кашу в любимом hex-редакторе.

Способ этот заключается в использовании Microsoft Office Binary File Format Validator. Это достаточно простая в использовании утилита командной строки, в комплекте с которой идут три dll-ки (по одной на каждый поддерживаемый формат – doc, xls, ppt). Несмотря на то, что с момента первого официального анонса на сайте так и осталась выложенной бета-версия, инструмент вполне себе работоспособен и справляется со своими задачами. Единственная существенная проблема, с которой я столкнулся за время работы с валидатором – это отсутствие поддержки кириллических имен файлов. Для запуска утилиты достаточно ввести команду

bffvalidator.exe [-l log.xml] filename.ext

где filename.ext — это имя исследуемого файла, а -l log.xml – необязательный параметр, указывающий куда сохранить лог (по умолчанию лог пишется в ту же папку, где лежит проверяемый документ).
Для облегчения жизни и уменьшения количества рутинных действий я использую два сценария работы с валидатором. При проверке отдельного файла удобно пользоваться Far-ом: достаточно завести отдельную папку, например, c:\Temp\Bff, положить туда экзешник валидатора и сопутствующие dll-ли, а потом завести команду через F9-Commands-File Associations:

2354f2896ae341808c1601a144e95e3b.png
8b32cd9b167b4109b1c0a13f42df9918.png

После этого проверить подозрительный файл можно будет буквально в пару нажатий на клавиатуру. Другой сценарий, который имеет смысл — заставить приложение сгенерировать набор тестовых файлов, и затем при помощи несложного кода прогнать проверку по всему набору, например так:

public class FileFormatValidationFailedException : Exception {
    public FileFormatValidationFailedException(string msg) : base(msg) { }
}
public void RunBFFValidator(string filePath) {
    string fileName = Path.GetFileName(filePath);
    string workingDirectory = Path.GetDirectoryName(filePath);
    string startupPath = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);
    StageName = String.Format("RUNNING BFFValidator for FILE {0}", fileName);
    outputManager.BeginWriteInfoLine(String.Format("Running BFFValidator for saved file '{0}'", fileName));
    ProcessStartInfo startInfo = new ProcessStartInfo(Path.Combine(startupPath, "BFFValidator.exe"));
    startInfo.Arguments = string.Format("-l bfflog.xml \"{0}\"", Path.Combine(workingDirectory, fileName));
    startInfo.WorkingDirectory = workingDirectory;
    startInfo.WindowStyle = ProcessWindowStyle.Hidden;
    Process validatorProcess = Process.Start(startInfo);
    validatorProcess.WaitForExit();
    if (validatorProcess.ExitCode != 0) {
        using (StreamReader reader = new StreamReader(Path.Combine(workingDirectory, "bfflog.xml"))) {
            string logContent = reader.ReadToEnd();
            throw new FileFormatValidationFailedException(logContent);
        }
    }
}


Вторая часть Мерлезонского балета


Ситуация с Office Open XML существенно проще. Если открыть несколько файлов hex-редактором, то в начале данных можно будет увидеть инициалы Фила Каца:

9dfa4766439d472c87af740aa3bd8afa.png
Это означает, что файлы представляют собой переименованный в docx/xlsx zip-архив, который можно открыть и увидеть вполне читаемую структуру. Однако и в этом случае можно не пытаться вручную искать расхождения с документацией из Редмонда, а поручить анализ файла специально предназначенным для этого инструментам. Для этого качаем Open XML SDK 2.5 и устанавливаем его (нам понадобятся OpenXMLSDKV25.msi и OpenXMLSDKToolV25.msi). После этого можно будет создать приложение для проверки файлов на наличие невалидной разметки (понадобится референс на DocumentFormat.OpenXml.dll). Простейший код для анализа документов выглядит следующим образом:

public void RunOpenXmlValidation(string filePath, string openXmlFormatVersion) {
    string fileName = Path.GetFileName(filePath);
    StageName = String.Format("RUNNING OpenXmlValidation for FILE {0}", fileName);
    outputManager.BeginWriteInfoLine(String.Format("Running OpenXmlValidation for saved file '{0}'", fileName));
    using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(filePath, false)) {
        DocumentFormat.OpenXml.FileFormatVersions formatVersion = DocumentFormat.OpenXml.FileFormatVersions.Office2010;
        if (openXmlFormatVersion == "office2007")
            formatVersion = DocumentFormat.OpenXml.FileFormatVersions.Office2007;
        else if (openXmlFormatVersion == "office2013")
            formatVersion = DocumentFormat.OpenXml.FileFormatVersions.Office2013;
        OpenXmlValidator validator = new OpenXmlValidator(formatVersion);
        var errors = validator.Validate(wordDoc);
        StringBuilder builder = new StringBuilder();
        foreach (ValidationErrorInfo error in errors) {
            string errorMsg = string.Format("{0}: {1}, {2}, {3}", error.ErrorType.ToString(), error.Part.Uri, error.Path.XPath, error.Node.LocalName);
            builder.AppendLine(errorMsg);
            builder.AppendLine(error.Description);
        }
        string logContent = builder.ToString();
        if (!string.IsNullOrEmpty(logContent))
            throw new FileFormatValidationFailedException(logContent);
    }
}


And we need to go deeper…


Рассмотренные выше способы валидации стоит рассматривать как попытку быстрого поиска, где именно могут быть проблемы в документе, а не как абсолютную гарантию того, что все в порядке. Вполне реальны ситуации, когда валидатор выдает сообщение об ошибке, а Word или Excel нормально открывает файл и наоборот – не удается открыть прошедший валидацию документ. Поэтому если необходима более надежная проверка, то не обойтись без использования COM. Это требует установленного Microsoft Office, не является thread-safe, требует дополнительных телодвижений для x64, зато позволяет убедиться в соответствие документа требованиям MS-Office и поисследовать его структуру с точки зрения целевой платформы.

Внеклассное чтение


Если понадобится более серьезный анализ файла с углубленным пониманием его структуры, то можно обратиться к документации по OpenXML SDK и непосредственно форматам.
Также при исследовании внутреннего устройства документов может помочь утилита OffVis.
Несколько полезных ссылок по взаимодействию с офисными приложениями: Primary Interop Assemblies (PIAs), Microsoft.Office.Interop.Excel namespace, Microsoft.Office.Interop.Word namespace.

Надеюсь, что использование полезных инструментов от Microsoft сбережет ваше время и нервы. Спасибо за внимание!

© Habrahabr.ru