Что написано пером, или как проверить документы в форматах MS Office
Одной из часто встречающихся в процессе создания приложений задач является генерация различных документов в каком-либо из популярных форматов. Есть несколько привычных путей к желаемому результату – от подключения готовой библиотеки до брутального штудирования спецификации формата с последующим написанием необходимого кода. Но, независимо от выбранного варианта, было бы неплохо проверить, что полученный документ будет хорошо восприниматься и редактироваться стандартными средствами. О некоторых способах такой проверки и пойдет речь под катом.
Оговорюсь сразу, что под проверкой в данном случае я понимаю проверку на соответствие полученного результата стандарту выбранного формата, позволяющую с достаточно большой вероятностью утверждать, что документ как минимум откроется в 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:
После этого проверить подозрительный файл можно будет буквально в пару нажатий на клавиатуру. Другой сценарий, который имеет смысл — заставить приложение сгенерировать набор тестовых файлов, и затем при помощи несложного кода прогнать проверку по всему набору, например так:
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-редактором, то в начале данных можно будет увидеть инициалы Фила Каца:
Это означает, что файлы представляют собой переименованный в 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 сбережет ваше время и нервы. Спасибо за внимание!