Что такое parser для word

Парсер поддерживает следующие типы файлов Word:

  • .doc, .docx, .docm, и т.п. — «обычные» документы Word
  • .rtf, .odt и другие форматы, поддерживаемые Word

Для сбора данных из файлов Word используется стандартный алгоритм парсера файлов, со следующими особенностями:

  1. Для чтения файла используется действие «Загрузить содержимое из файла MS Word», с параметром «Тип результата» text или HTML:
     

    Действие Параметр Значение
    Загрузить содержимое из файла MS Word Путь к файлу  
    Запускать новый экземпляр MS Word нет
    Открыть в режиме «только чтение» да
    Тип результата text / HTML

    Принудительно закрывать файлы Word не требуется (данные из файла считываются, и файл сразу закрывается)
     

  2. Тип результата «text» подойдет для простых документов, где есть к чему привязаться для извлечения значений.
    Например, если данные в документе имеют вид Дата: 25.04.2021, то парсер для получения даты может взять текст между словом Дата: и переводом строки {NL}
    Учитывать стили абзацев в данном случае ни к чему (потому разметка HTML не нужна для парсинга)
     
  3. Для документов сложной структуры, или с большим объёмом данных, имеет смысл использовать тип результата HTML, и производить разбор данных из файла с учётом стилей (размер шрифта и т.п.).
    Кроме того, в этом случае есть возможность считывать данные из различных объектов Word, таких как например надписи и колонтитулы.
     
  4. Есть возможность выгружать фотографии из документа, при помощи действия «Выгрузить картинки из файла Word»

Примеры настройки парсера файлов Word можно найти в каталоге парсеров файлов:

  • парсер пропусков
  • парсер дипломов
  • парсер базы контактов

Разобрать файл Word и извлечь данные с помощью любого современного браузера

Loading…

Обработка Пожалуйста, подождите…

Копировать ссылку

copy link


send to email

Обработка Пожалуйста, подождите…

Файл отправлен на

clear email

Ваше мнение важно для нас, пожалуйста, оцените это приложение.





Спасибо за оценку нашего приложения!

Word parser

Word Parser – это бесплатный инструмент, который позволяет извлекать текст и таблицы из Word файлов, управлять содержимым документов без установки дополнительного программного обеспечения, из любого браузера и с любого устройства. Извлекайте текст из XLS, XLSX, PPT, PPTX, PDF, DOC, DOCX, RTF, HTML, EPUB и многих других форматов файлов и документов.

youtube

Скоро будет

Как Разобрать Word

  1. Щелкните внутри области перетаскивания или перетащите файл.
  2. Дождитесь завершения загрузки и обработки.
  3. После завершения загрузки и обработки файла вы увидите страницу результатов.
  4. На странице результатов нажмите кнопку «Синтаксический анализ», чтобы начать анализ вашего файла.
  5. Вы также можете поделиться своим файлом с помощью ссылки для копирования или электронной почты.

How to

часто задаваемые вопросы

Q: Как разобрать файл Word?

A: Во-первых, вам нужно загрузить файл: перетащите файл или щелкните внутри области загрузки, чтобы выбрать файл, чтобы начать его обработку. После завершения обработки нажмите кнопку «Разобрать», чтобы начать анализ вашего документа.

Q: Нужно ли мне устанавливать дополнительное программное обеспечение, чтобы иметь возможность анализировать файл Word?

A: Нет, приложение для анализа документов Conholdate — это полностью облачная служба, которая не требует установки дополнительного программного обеспечения.

Q: Могу ли я анализировать файлы в ОС Linux, Mac или Android?

A: Конечно, Conholdate Parser — это полностью облачный сервис, который не требует установки какого-либо программного обеспечения и может использоваться в любой операционной системе с веб-браузером.

Q: Безопасно ли анализировать файлы Word с помощью бесплатного Conholdate.App?

A: Да, это абсолютно безопасно. Ваши файлы хранятся на нашем защищенном сервере и защищены от любого несанкционированного доступа. Через 24 часа все файлы удаляются безвозвратно.

Q: Можно ли разобрать текст из документа Word?

A: Да, выберите инструмент текстового поля на верхней панели инструментов синтаксического анализатора Word, выберите нужную область на странице документа Word и нажмите кнопку синтаксического анализа.

Q: Могу ли я разобрать таблицу из документа Word?

A: Да, выберите инструмент таблицы на верхней панели инструментов синтаксического анализатора Word, выберите таблицу на странице документа Word и нажмите кнопку синтаксического анализа.

Q: Как получить Word результатов синтаксического анализа?

A: Вы можете загрузить результаты в виде файла CSV, нажав кнопку загрузки, расположенную в верхней части диалогового окна анализатора Word. Также вы можете сохранить шаблон синтаксического анализа для дальнейшего использования, для этого просто щелкните значок загрузки, расположенный в верхней правой части панели инструментов синтаксического анализатора.

Еще приложений

Еще parser приложений

Выбрать язык

В процессе анализа информации часто возникает потребность в обработке данных из документов в форматах MS Word или Excel. Как считывать информации из таких файлов с использованием языка C #.

Из практического опыта сотрудника, который всегда успешно решает поставленные задачи.

Для работы с файлами Word и Excel я решил выбрать библиотеки Microsoft.Office.Interop.Word и Microsoft.Office.Interop.Excel, предоставляющие программные интерфейсы для взаимодействия с объектами MS Word и Excel.

Преимущества использования этих библиотек:

  • созданы корпорацией Microsoft, следовательно, взаимодействие с объектами программ пакета MS Office реализовано наиболее оптимально,
  • нужный пакет Visual Studio Tool for Office поставляется вместе с Visual Studio (достаточно отметить его при установке VS).

Также следует заметить, что у такого похода есть и недостаток: для того, чтобы написанная программа работала на ПК пользователя необходимо, чтобы на нём были установлены программы MS Office и MS Excel. Поэтому такой подход плохо подходит для серверных решений. Также такая программа не будет являться кроссплатформенной.

Добавление библиотек в проект Visual Studio

Библиотеки поставляются вместе с пакетом Visual Studio Tool for Office (платформа .NET Framework).

Для использования библиотеки нужно:

  • добавить ссылку на неё: в обозревателе решений необходимо кликнуть правой кнопкой мыши по пункту Ссылки (Рис. 1) и найти нужную библиотеку по ключевым словам (после добавления ссылка появится в списке),
  • указать используемое пространство имён в файле программы (в примере ему назначен алиас Word): (Рис. 2):
Пример парсинга файла MS Word​

Можно прочитать основные форматы: .doc,. docx,. rtf.

Ниже приведён листинг с примером считывания текста из документа MS Word:

object FileName = @»C:test.doc»;
object rOnly = true;
object SaveChanges = false;
object MissingObj = System.Reflection.Missing.Value;

Word.Application app = new Word.Application();
Word.Document doc = null;
Word.Range range = null;
try
{
doc = app.Documents.Open(ref FileName, ref MissingObj, ref rOnly, ref MissingObj,
ref MissingObj, ref MissingObj, ref MissingObj, ref MissingObj,
ref MissingObj, ref MissingObj, ref MissingObj, ref MissingObj,
ref MissingObj, ref MissingObj, ref MissingObj, ref MissingObj);

object StartPosition = 0;
object EndPositiojn = doc.Characters.Count;
range = doc.Range(ref StartPosition, ref EndPositiojn);

// Получение основного текста со страниц (без учёта сносок и колонтитулов)
string MainText = (range == null || range.Text == null) ? null : range.Text;
if (MainText != null)
{
/* Обработка основного текста документа*/
}

// Получение текста из нижних и верхних колонтитулов
foreach (Word.Section section in doc.Sections)
{
// Нижние колонтитулы
foreach (Word.HeaderFooter footer in section.Footers)
{
string FooterText = (footer.Range == null || footer.Range.Text == null) ? null : footer.Range.Text;
if (FooterText != null)
{
/* Обработка текста */
}
}

// Верхние колонтитулы
foreach (Word.HeaderFooter header in section.Headers)
{
string HeaderText = (header.Range == null || header.Range.Text == null) ? null : header.Range.Text;
if (HeaderText != null)
{
/* Обработка текста */
}
}
}
// Получение текста сносок
if (doc.Footnotes.Count != 0)
{
foreach (Word.Footnote footnote in doc.Footnotes)
{
string FooteNoteText = (footnote.Range == null || footnote.Range.Text == null) ? null : footnote.Range.Text;
if (FooteNoteText != null)
{
/* Обработка текста */
}
}
}
} catch (Exception ex)
{
/* Обработка исключений */
}
finally
{
/* Очистка неуправляемых ресурсов */
if(doc != null)
{
doc.Close(ref SaveChanges);
}
if(range != null)
{
Marshal.ReleaseComObject(range);
range = null;
}
if(app != null)
{
app.Quit();
Marshal.ReleaseComObject(app);
app = null;
}
}

Примечания:

  • в коде приводится пример считывания основного текста документа, текста верхних и нижних колонтитулов, а также текста сносок,
  • в коде производится очистка неуправляемых ресурсов с использованием класса Marshal

Пример парсинга файла MS Excel

Можно прочитать основные форматы: .xls,. xlsx.

Ниже приведён листинг с примером считывания текста из документа MS Excel (по ячейкам):

string FileName = @»C:UsersbeeDownloadstest.xlsx»;
object rOnly = true;
object SaveChanges = false;
object MissingObj = System.Reflection.Missing.Value;

Excel.Application app = new Excel.Application();
Excel.Workbooks workbooks = null;
Excel.Workbook workbook = null;
Excel.Sheets sheets = null;
try
{
workbooks = app.Workbooks;
workbook = workbooks.Open(FileName, MissingObj, rOnly, MissingObj, MissingObj,
MissingObj, MissingObj, MissingObj, MissingObj, MissingObj,
MissingObj, MissingObj, MissingObj, MissingObj, MissingObj);

// Получение всех страниц докуента
sheets = workbook.Sheets;

foreach(Excel.Worksheet worksheet in sheets)
{
// Получаем диапазон используемых на странице ячеек
Excel.Range UsedRange = worksheet.UsedRange;
// Получаем строки в используемом диапазоне
Excel.Range urRows = UsedRange.Rows;
// Получаем столбцы в используемом диапазоне
Excel.Range urColums = UsedRange.Columns;

// Количества строк и столбцов
int RowsCount = urRows.Count;
int ColumnsCount = urColums.Count;
for(int i = 1; i <= RowsCount; i++)
{
for(int j = 1; j <= ColumnsCount; j++)
{
Excel.Range CellRange = UsedRange.Cells[i, j];
// Получение текста ячейки
string CellText = (CellRange == null || CellRange.Value2 == null) ? null :
(CellRange as Excel.Range).Value2.ToString();

if(CellText != null)
{
/* Обработка текста */
}
}
}
// Очистка неуправляемых ресурсов на каждой итерации
if (urRows != null) Marshal.ReleaseComObject(urRows);
if (urColums != null) Marshal.ReleaseComObject(urColums);
if (UsedRange != null) Marshal.ReleaseComObject(UsedRange);
if (worksheet != null) Marshal.ReleaseComObject(worksheet);
}
} catch (Exception ex)
{
/* Обработка исключений */
}
finally
{
/* Очистка оставшихся неуправляемых ресурсов */
if (sheets != null) Marshal.ReleaseComObject(sheets);
if (workbook != null)
{
workbook.Close(SaveChanges);
Marshal.ReleaseComObject(workbook);
workbook = null;
}

if (workbooks != null)
{
workbooks.Close();
Marshal.ReleaseComObject(workbooks);
workbooks = null;
}
if (app != null)
{
app.Quit();
Marshal.ReleaseComObject(app);
app = null;
}
}

Примечания:

  • при обработке текста каждой ячейки приходится заранее знать количество задействованных строк и столбцов на текущем листе документа,
  • такой перебор не совсем оптимален (временная сложность алгоритма O(n2)): при желании его можно ускорить (например, разбив обработку на несколько потоков): в данной статье приводится лишь пример получения текста из каждой ячейки,
  • при таком переборе ячеек необходимо на каждой итерации освобождать неуправляемые ресурсы, чтобы избежать утечек памяти (аналогично предыдущему примеру, используется класс Marshal).

Приведенные примеры хорошо подходят для реализации приложения по обработке документов Word и Excel на платформе .NET Framework.

С помощью указанных библиотек можно не только читать текст из документов, но и создавать новые файлы форматов MS Word и Excel.

Время на прочтение
10 мин

Количество просмотров 20K

Хочу представить вашему вниманию способ извлечения данных с документа word в виде картинок. Возможно, представленные идеи будут для кого-то примитивными и очевидными. Но мне пришлось провести пару бессонных ночей, прежде чем дойти до нормального решения. Итак, начинаю.

Было начало 2015-го. Зима. Я радовался хорошей погоде и восхищенно думал, что универ я наконец закончу (вру, сейчас поступаю в магистратуру). Свой диплом я недавно закончил, поэтому радовался еще сильнее. Однако вскоре, по натуре человеческой, состояние безмятежности плавно стало сменяться скукой. И тут, как будто специально, тишину сменил телефонный звонок.

«Алло, привет, как поживаешь?» — прозвучал голос знакомой.

Интуиция тут же определила, что скорее всего разговор будет на тему «тыжпрограммиста». Так оно и было. С унылым видом сначала я слушал про тяжкие времена знакомой и про все остальное, однако ее конечная просьба заставила меня заинтересоваться.

«Ты не мог мне помочь с дипломом? В общем, необходимо сделать сайт-тренажер по математике» — сказала она.

Это было интересно. Меня как раз увлекает разработка сложных фронтендов. Просьбу я тут же одобрил.

На реализацию тренажера ушло суммарно не более 2-х дней. Сайт-тренажер позволяет проходить тесты по высшей математике и просматривать теорию. Тесты можно проходить в двух режимах: в режиме тренировки с подсвечиванием ответов и в режиме тестирования с выводом результата в конце. Реализация была сделана на ReactJS и Bootstrap и сам процесс был достаточно приятным. Но это было только начало. Необходимо было как заполнить данными базу вопросов тестов, которые отнюдь не были в виде готовых упорядоченных данных.

Постановка задачи

Пока я удовлетворенно смотрел на результат работы, знакомая позвонила повторно. Она уведомила меня, что прислала мне небольшой архивчик с вопросами в *.doc файлах на почту. «Если что, я могу помочь перекинуть вопросы и ответы в БД» — добавила она.

Это немного подпортило мое настроение, ведь я не думал, что придется еще заполнять самостоятельно базу тестовых вопросов.

Ладно. Пошел я открывать свой GMail и тут:

И в каждом файле ~50 тестовых заданий в виде:

Мда уж, вручную забивать все это в БД вряд ли получится. К слову, каждое тестовое задание в БД хранилось в виде одной картинки-вопроса, пяти картинок-ответов, уровня сложности (A, B, C) и номера верного ответа (1-5). Расстроенный, я решил пока что отложить этот вопрос на долгий ящик, благо времени было достаточно. Но через несколько дней на почту упало еще одно сообщение от знакомой, а потом еще… В итоге, набралось 4 раздела по высшей математике, каждый из разделов состоял из 14-23 подразделов, каждый подраздел содержал примерно 30-100 тестовых заданий. И тут я окончательно убедился, что вручную все это вбить в базу данных точно не получится.

Кстати про БД. Это MySQL с тремя таблицами: разделов, подразделов и тестовых вопросов. Картинки вопроса задания и пяти ответов хранятся непосредственно в БД, в столбце BLOB. Мне кажется так удобней, поскольку этих картинок очень много, к тому же весят они мало. И будут все они храниться в одном месте, вместе с другими данными.

Итак, что было необходимо? В наилучшем варианте нужно было из папки со всеми word файлами тестовых заданий получить готовые соответствующие записи в БД, что практически и было получено в конечном результате. Нас же интересует главное: непосредственно само извлечение картинок.

Входные данные: файл word с тестовыми заданиями.
Выходные данные (например): папка с изображениями PNG, где задание имеет имя вида 1.png, а ответы имеют имена 1.1.png, 1.2.png, 1.3.png, 1.4.png, 1.5.png, плюс к этому файл answers.txt, внутри которого i-я строка содержит число от 1 до 5, соответствующую правильному ответу i-го задания.

Реализация

Я люблю Qt Creator. А с чего мне его любить? Скорее всего потому, что в универе нас натаскивают именно по нему, я сам не знаю. А еще во время пользования им я испытываю какой-то тихий восторг. Ну в общем вы поняли, на чем я стал писать программу-парсер.

Вначале я гадал, как же вообще взаимодействовать с этим вордом, чтобы как то уж выдергивать оттуда данные. На ум приходили всякие ужасные мысли наподобие конвертации word файла в HTML с последующей обработкой. Но гугление сразу же направило меня на адекватный путь, дав информацию о языке VBA. Я тут же был поражен обилием готовых функций, узнал, что такое параграфы, позиции и т.п., одновременно офигевая от сложности устройства дерева документа word.

Однако я был разочарован, поскольку так и не понял, как же превратить кусок текста в картинку. Сначала было хотел использовать что-либо наподобие text2png, предварительно выдернув нужный кусок текста. Но как же с формулами и картинками? Встроенной функции в VBA не было. В один момент у меня ненарком промелькнуло мыслишко, что вроде бы как я раньше вставлял в документ word ячейки из excel в виде картинок. Так и было! Называлось это «специальной вставкой» и позволяло вставлять любой участок документа в виде картинки. Допустим мы занесли в буфер обмена некий кусок документа, который необходимо сохранить в виде картинки. Но как эту картинку сохранить на диск? Гугление также помогло найти решение. Участок кода ниже сохраняет содержимое буфера обмена на диск в виде универсального векторного файла EMF.

#include <windows.h>

void clipboardDataToEmfFile(QString fileName){
    OpenClipboard(0);
    GetEnhMetaFileBits((HENHMETAFILE)GetClipboardData(14),0,0);
    HENHMETAFILE returnValue = CopyEnhMetaFileA((HENHMETAFILE)GetClipboardData(14),
                                                QDir::toNativeSeparators(fileName).toStdString().c_str());
    EmptyClipboard();
    CloseClipboard();
    DeleteEnhMetaFile(returnValue);
}

Отлично. Однако что это за зверь такой, этот EMF? Необходимо было его превратить в PNG. Начал я искать конвертеры изображений. Перебрав кучу, так и не нашел адекватного. И тут опять (кто-нибудь верит в интуицию?) в голове начал вспоминаться какой-то навороченный просмотрщик изображений, который я ставил в школьные годы из диска с «Золотым софтом» забавы ради. Но вроде это был не конвертер. Однако необходимо было убедиться. В голове крутилось какое-то то ли «Ifran», то ли «Irfan», в общем программа была найдена. Бесплатная, с функцией пакетной обработки изображений, поддерживает командную строку! И самое главное, поддерживает EMF. Это было то, что надо. Исполняемый файл IrfanView с нужными DLL и ini-файлом параметров лежит в одной папке со скомпилированной программой (надеюсь это не нарушает лицензию) и используется через функцию вот так.

void convertEmfsToPng(QString inFolder, QString outFolder){
    QProcess proc;
    QString exeStr = """ + QDir::toNativeSeparators(QDir::currentPath()+"/i_view32.exe") + """;
    QString inFilesStr = """ + QDir::toNativeSeparators(inFolder + "*.emf") + """;
    QString outFilesStr = """ + QDir::toNativeSeparators(outFolder + "*.png") + """;
    QString iniFolderStr = """ + QDir::toNativeSeparators(QDir::currentPath()) + """;
    proc.start(exeStr + " " + inFilesStr + " /advancedbatch /ini=" + iniFolderStr + " /convert=" + outFilesStr);
    proc.waitForFinished(30*60*1000);
}

Теперь осталось копировать в буфер нужные куски из документа word. Для этого необходимо придумать алгоритм разбиения исходного текста на отдельные блоки с заданием, с ответами, с номером верного ответа и уровня сложности.

Первой попыткой реализации было следующее. Берем исходный документ, заменяем в нем текст вида ([1-5])) на n$1), т.е. перед началом каждого ответа добавляем перевод строки. На VBA строки замены пишутся по-другому, я уже не помню. Теперь в параметрах документа ставим ширину страницы максимальной, а шрифт для всего документа уменьшаем. В результате получается, что в документе каждое задание будет занимать ровно 8 строк, причем:

  • строка 8*i — это текст с номером верного ответа и уровнем сложности
  • строка 8*i+1 — это задание
  • строка 8*i+2 — это вариант ответа №1
  • строка 8*i+6 — это вариант ответа №5
  • строка 8*i+7 — пустая

Повторно, как выглядят задания

Теперь после этой обработки ничего не остается, кроме как пройтись по массиву коллекции строк документа, заведя счетчик i, и в зависимости от i % 8 сохранять картинки задания/ответов или извлекать номер верного ответа с уровнем сложности.

Но это не подошло. Виноваты длинные задания, которые будучи написаны в одну строку, выглядят ужасно, мелко и не всегда вмещаются. К тому же иногда замена текста «1)» затрагивает другие места кроме номеров ответов. Опечаленный результатом, я вновь начал думать, что можно будет сделать в данном случае. И тут я вспомнил про конечные автоматы. Вспомнил про состояние, вспомнил про посимвольный ввод. Вспомнил синтаксический анализатор. Возможно это другим было очевидным решением, но я как человек далекий от сложных алгоритмов был безумно рад своей идее.

Теперь настал черед писать и пробовать код парсера на основе конечного автомата. Состояний у нас 7:

  • считывание пространства между заданиями, начинается с пустой строки
  • считывание строки с номером задания, в котором номер верного ответа и уровень сложности, начинается с «Номер»
  • считывание текста задания, начинается с «Задача»
  • считывание текста ответа №1, начинается с «Ответы: 1).»
  • считывание текста ответа №2, начинается с «2).»
  • считывание текста ответа №5, начинается с «5).»

Реализуем, используя условия начала следующего состояния. После тестирования первой версии парсера все прошло великолепно. Картинки получались как в самом word документе, красивенько, крупно. Но тут… время от времени появлялись косяки, например в одной картинке захватывался лишний кусок до следующего блока задания. Значит парсер некорректно распознавал. В чем же дело? Все оказалось просто — задания в word документе набирались вручную и поэтому имел место человеческий фактор, например:

  • вместо «Задача» писалось «Задание»
  • вместо «Ответы» писалось «Ответ»
  • вместо «1).» писалось «1 ).» или «1)» или вообще «1) .»

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

Итоговым куском парсера является код ниже. Он ужасен, просьба не судить строго. Для хранения VBA-объектов используется QAxObject.

Разъяснения имен переменных, состояния автомата, используемых дополнительных функций

  • status — состояние автомата:
    • -3 — между заданиями
    • -2 — внутри номера задания
    • -1 — внутри задания
    • 0 — после слова Ответы
    • 1 — внутри ответа 1
    • 2 — внутри ответа 2
    • 3 — внутри ответа 3
    • 4 — внутри ответа 4
    • 5 — внутри ответа 5

  • startind — позиция начала текущего блока (задание, ответ, строка с номером верного ответа и уровнем сложности)
  • n — порядковый номер задания
  • nstr — строка порядкового номера задания с ведущими нулями до трехзначного
  • str — строка текущего блока до текущей позиции
  • lineStart, lineEnd — номера позиций начала и конца текущего абзаца
  • lines — объект коллекции абзацев документа
  • tline — объект текущего абзаца
  • line — объект Range текущего абзаца
  • ipar — номер текущего абзаца
  • tmpObj — объект Range текущего символа
  • currChar — текущий символ
  • outdir — строка пути выходной папки картинок
  • функция getAnswerLine(QString) — возвращает строку из двух чисел: уровень сложности (1-3) и номер верного ответа (1-5), например 24 — это задание с уровнем сложности B и правильным ответом под номером 4
  • функция rangeToEmfFile(QString fname, int start, int end, QAxObject *activeDoc) — сохраняет кусок документа между позициями start и end документа activeDoc как EMF-файл с именем fname

Ужасный, длинный код

QAxObject *activeDoc = wordApp->querySubObject("ActiveDocument");

int status = -3;
int startind = 0;
int n=0;
QString nstr;
QString str = "";
int lineStart, lineEnd;
QAxObject *lines = activeDoc->querySubObject("Paragraphs");

if (onlyAsnwers)
    for (int ipar = 1; ipar <= lines->property("Count").toInt(); ipar++){
        QAxObject *tline = lines->querySubObject("Item(QVariant)", ipar);
        QAxObject *line = tline->querySubObject("Range");
        QString str = line->property("Text").toString();
        line->clear(); delete line;
        tline->clear(); delete tline;
        int ind = str.indexOf("Номер:");
        if (ind != -1){
            str = str.mid(ind+6);
            answersTxt << getAnswerLine(str);
        }
    }
else
    for (int ipar = 1; ipar <= lines->property("Count").toInt(); ipar++){
        QAxObject *tline = lines->querySubObject("Item(QVariant)", ipar);
        QAxObject *line = tline->querySubObject("Range");
        lineStart = line->property("Start").toInt();
        lineEnd = line->property("End").toInt();
        line->clear(); delete line;
        tline->clear(); delete tline;
        str = "";
        for (int j=lineStart; j<lineEnd; j++){
            QAxObject *tmpObj = activeDoc->querySubObject("Range(QVariant,QVariant)", j, j+1);
            QString currChar = tmpObj->property("Text").toString();
            tmpObj->clear(); delete tmpObj;
            str += currChar;
            switch (status){
            case -3:
                if (j>=4 && str.right(5) == "Номер"){
                    status = -2;
                    startind = j+1;
                }
                break;
            case -2:
                if (str.right(6) == "Задача"){
                    n++; nstr = QString::number(n); while (nstr.length() < 3) nstr = "0" + nstr;
                    status = -1;
                    QAxObject *tmpObj = activeDoc->querySubObject("Range(QVariant,QVariant)", startind, j-6);
                    QString tmp = tmpObj->property("Text").toString();
                    tmpObj->clear(); delete tmpObj;
                    answersTxt << getAnswerLine(tmp);
                    startind = j+2;
                } else if (str.right(7) == "Задание"){
                    n++; nstr = QString::number(n); while (nstr.length() < 3) nstr = "0" + nstr;
                    status = -1;
                    QAxObject *tmpObj = activeDoc->querySubObject("Range(QVariant,QVariant)", startind, j-7);
                    QString tmp = tmpObj->property("Text").toString();
                    tmpObj->clear(); delete tmpObj;
                    answersTxt << getAnswerLine(tmp);
                    startind = j+2;
                }
                break;
            case -1:
                if (str.right(7) == "Ответы:"){
                    status = 0;
                    rangeToEmfFile(outdir+nstr+".emf", startind, j-7, activeDoc);
                    startind = j+1;
                } else if (str.right(6) == "Ответ:"){
                    status = 0;
                    rangeToEmfFile(outdir+nstr+".emf", startind, j-6, activeDoc);
                    startind = j+1;
                }
                break;
            case 0:
                if (str.right(2) == "1)" || str.right(3) == "1 )"){
                    status = 1;
                    startind = j+2;
                }
                break;
            case 1:
                if (str.right(2) == "2)"){
                    rangeToEmfFile(outdir+nstr+".1.emf", startind, j-2, activeDoc);
                    status = 2;
                    startind = j+2;
                } else if (str.right(3) == "2 )"){
                    rangeToEmfFile(outdir+nstr+".1.emf", startind, j-3, activeDoc);
                    status = 2;
                    startind = j+2;
                }
                break;
            case 2:
                if (str.right(2) == "3)"){
                    rangeToEmfFile(outdir+nstr+".2.emf", startind, j-2, activeDoc);
                    status = 3;
                    startind = j+2;
                } else if (str.right(3) == "3 )"){
                    rangeToEmfFile(outdir+nstr+".2.emf", startind, j-3, activeDoc);
                    status = 3;
                    startind = j+2;
                }
                break;
            case 3:
                if (str.right(2) == "4)"){
                    rangeToEmfFile(outdir+nstr+".3.emf", startind, j-2, activeDoc);
                    status = 4;
                    startind = j+2;
                } else if (str.right(3) == "4 )"){
                    rangeToEmfFile(outdir+nstr+".3.emf", startind, j-3, activeDoc);
                    status = 4;
                    startind = j+2;
                }
                break;
            case 4:
                if (str.right(2) == "5)"){
                    rangeToEmfFile(outdir+nstr+".4.emf", startind, j-2, activeDoc);
                    status = 5;
                    startind = j+2;
                } else if (str.right(3) == "5 )"){
                    rangeToEmfFile(outdir+nstr+".4.emf", startind, j-3, activeDoc);
                    status = 5;
                    startind = j+2;
                }
                break;
            case 5:
                if (j>=4 && str.right(5) == "Номер"){
                    rangeToEmfFile(outdir+nstr+".5.emf", startind, j-5, activeDoc);
                    status = -2;
                    str = "Номер";
                } else if (lineEnd-lineStart < 2){
                    rangeToEmfFile(outdir+nstr+".5.emf", startind, j, activeDoc);
                    status = -3;
                }
                break;
            }
        }
        if (status == 5)
            rangeToEmfFile(outdir+nstr+".5.emf", startind, lineEnd, activeDoc);
    }
lines->clear(); delete lines;

activeDoc->clear(); delete activeDoc;

Логика работы приведенного кода немного отличается от описанной выше. Она использует еще разбиение на абзацы. Но это не сильно меняет главную идею.

Вот таким вот образом получилось «победить» этот Word!

Заключение

В итоге были извлечены все задания в количестве ~4 тыс. Нужная оболочка парсера была написана. Программа закачки заданий на удаленную БД и администрирования ею также была написана. Гонорар был получен, ее диплом защищен на отлично, мой также защищен на отлично.

Спасибо за внимание, надеюсь этот пост поможет кому нибудь в аналогичной проблеме. А может кто знает лучшую реализацию?

Update:

Word парсер это бесплатное простое онлайн приложение которое позволяет извлекать картинки и текст из Word документа. Когда это приложение будет вам полезно? Например вам прислали фотоальбом в виде Word документа, и вам необходимо извлечь все фотографии в оригинальном формате. В решении этой задачи вам поможет приложение Word парсер, просто откройте страницу приложения, выберите исходный документ и нажмите кнопку извлечь. Ваш документ будет отправлен на сервер, через мгновение вы получите архив в котором будет все извлеченные данные из вашего документа.
Парсер картинок и текста из Word документа работает онлайн через любой популярный браузер, на любой платформе Windows, MacOs, Linux или Android. Ваши документы хранятся у нас в надежном хранилище на протяжении 24 часов, а потом автоматически удаляются. Для немедленного удаления ваших документов просто нажмите кнопку удалить в окне скачивания результата.

Быстрый и легкий парсинг

Быстрый и легкий парсинг

Загрузите свой документ и нажмите кнопку «PARSE». Вы получите текстовый файл или zip-файл с текстом и изображениями сразу после выполнения парсинга.

Парсинг где угодно

Парсинг где угодно

Работает со всех платформ, включая Windows, Mac, Android и iOS. Все файлы обрабатываются на наших серверах. Вам не требуется установка плагинов или программного обеспечения.

Качественный парсинг

Качественный парсинг

. Все файлы обрабатываются с использованием Aspose APIs, которое используются многими компаниями из списка Fortune 100 в 114 странах мира.

0 / 0 / 0

Регистрация: 25.01.2018

Сообщений: 19

1

26.01.2018, 01:21. Показов 9442. Ответов 29


Студворк — интернет-сервис помощи студентам

Уважаемые форумчане!

Вот посоветовали задать вопрос в этой теме. Суть решаемой мной задачи: есть файл Word, который содержит данные (текстовые, числовые, даты). Необходимо их упорядочить и результат записать в файл Excel. Т.е. используя данные из файла Word надо создать таблицу в Excel.
Для более точного описания задачи прилагаю исходный файл Word и требуемый результат его обработки в Excel.

Помогите а?

п.с. — сам не являюсь программистом, но вроде как уже написал (ну … подправил чужой код) в VB net код для создания таблицы в Excel с нужным количеством столбцов и сохранения ее в виде файла .xls. Теперь вот парсер пробую …..сотворить.



0



99 / 94 / 23

Регистрация: 30.08.2015

Сообщений: 457

26.01.2018, 10:17

2

Данные word можно связать с excel и далее средствами excel управлять ими…
пример:

Кликните здесь для просмотра всего текста

Парсинг данных из файла Word и сохранение результата в Excel



0



0 / 0 / 0

Регистрация: 25.01.2018

Сообщений: 19

26.01.2018, 11:02

 [ТС]

3

Спасибо за ответ!

Я наверное не до конца изложил все нюансы. Конечно, Вы правы, если записей (ну в смысле переменных хотя-бы десяток), то можно и руками «прописать» связи как на приведенном примере. Но когда в файле Word содержится примерно 1200 переменных (это порядка 600 листов тестовой информации с шрифтом 12), к каждой из них надо «привязывать» по 10 значений…. и эти значения изменяются примерно раз в две недели. Тут уж точно требуется автоматизация.



0



1588 / 661 / 225

Регистрация: 09.06.2011

Сообщений: 1,334

26.01.2018, 12:13

4

Цитата
Сообщение от KorSar72
Посмотреть сообщение

ну … подправил чужой код

На данный момент вижу только постановку задачи — где ваши попытки?
…на форуме множество примеров открытия вордовских файлов, извлечения из них текста, поиска. Что не получается?



1



0 / 0 / 0

Регистрация: 25.01.2018

Сообщений: 19

26.01.2018, 17:48

 [ТС]

5

Кодю….. !

Ну вообще-то примеры кода и правда присутствуют на форуме. Но… совместимость с VB 2010 Express мягко говоря хромает… Рою кучу самоучителей.. и т.п. …

Плюс народ подсказывает всякие вещи — ну например я не знал, что обработка данных в .txt осуществляется быстрее чем в формате .doc. Следовательно в моем коде должна быть подпрограмма перевода файла .doc в .txt.
Пока не отчаиваюсь и пытаюсь разобраться. Уж не взыщите, что не сразу тут отвечаю…. работаю!

п.с. — но был-бы признателен если вдруг найдется «Дед мороз», который чирканет хотя бы ссылочку на существующий парсер (ищем текст в Ворде и записываем в ячейку Экселя).



0



4278 / 3417 / 827

Регистрация: 02.02.2013

Сообщений: 3,308

Записей в блоге: 2

26.01.2018, 19:46

6

Конечно, странно использовать Word для сохранения данных, когда в данном случае за глаза хватит txt. Привожу пример, как можно выполнить описываемую вами операцию, к сожалению, у меня старый Office (2003) и все расписано для doc и xls. Хотя при переходе к новым версиям справедливость представляемых операций в целом сохраняется. Правда нужно сразу сказать, что данное решение не быстрое, а для версий Office2007 и старше учитывая формат файлов можно предложить более оптимальное решение. Можно вообще отказаться от использования Word и Excel, а заменить их на доступные библиотеки на базе OpenOfficeXML.



0



0 / 0 / 0

Регистрация: 25.01.2018

Сообщений: 19

27.01.2018, 00:13

 [ТС]

7

Ovva, огромное спасибо за Вашу помощь!

п.с. — немного надо «подкрутить» чем и занимаюсь сейчас….. Для сведения (вдруг Вам тоже будет интересно) приложи некий скрин ….. (пытаюсь сам разобраться ибо интересно…).

Миниатюры

Парсинг данных из файла Word и сохранение результата в Excel
 



0



4278 / 3417 / 827

Регистрация: 02.02.2013

Сообщений: 3,308

Записей в блоге: 2

27.01.2018, 12:03

8

KorSar72, судя по картинке, вы не подключили требуемые библиотеки, а именно
— Microsoft.Office.Interop.Excel
— Microsoft.Office.Interop.Word
См. меню VS: Project/Add Reference…/NET



0



0 / 0 / 0

Регистрация: 25.01.2018

Сообщений: 19

27.01.2018, 17:26

 [ТС]

9

Ну приплыли…
Как оказалось в моем Microsoft Visual Basic 2010 Express нет необходимых библиотек:
— Microsoft.Office.Interop.Excel
— Microsoft.Office.Interop.Word

Но… Бро Гугл помог в очередной раз! ))))

Вот, вдруг у кого тоже проблемки с отсуствием указанных библиотек:

https://www.microsoft.com/en-u… px?id=3508

The redistributable contains the PIAs for the following products:
Microsoft Access 2010
Microsoft Excel 2010
Microsoft InfoPath 2010
Microsoft Outlook 2010
Microsoft PowerPoint 2010
Microsoft Project 2010
Microsoft Publisher 2010
Microsoft SharePoint Designer 2010
Microsoft Visio 2010
Microsoft Word 2010
The redistributable package also contains additional PIAs that are associated with many of these products.

Supported Operating System
Windows 2000 Service Pack 4, Windows Server 2003, Windows XP Service Pack 2
Microsoft Windows XP, Microsoft Windows Vista, Microsoft Windows Server 2003, Microsoft Windows Server 2008, Microsoft Windows 7
The following is also required:
Microsoft .NET Framework 2.0 or higher

Добавлено через 37 минут
Все интереснее и интереснее ….. : скачал, распаковал, запусти файл установщик (o2010pia.msi), установщик отработал … и не появилось ни одной библиотеки Microsoft.Office.Interop.
Assemblies included in O2010PIA.MSI
1. ADODB
2. extensibility
3. ipdmctrl
4. Microsoft.mshtml
5. Microsoft.Office.InfoPath.Permission
6. Microsoft.Office.Interop.Access
7. Microsoft.Office.interop.access.dao
8. Microsoft.Office.Interop.Excel
9. Microsoft.Office.Interop.Graph
10. Microsoft.Office.Interop.InfoPath
11. Microsoft.Office.Interop.InfoPath.SemiTrust
12. Microsoft.Office.Interop.InfoPath.Xml
13. Microsoft.Office.Interop.MSProject
14. Microsoft.Office.Interop.OneNote (Assembly version 12.0.0.0)
15. Microsoft.Office.Interop.OneNote (Assembly version 14.0.0.0)
16. Microsoft.Office.Interop.Outlook
17. Microsoft.Office.Interop.OutlookViewCtl
18. Microsoft.Office.Interop.PowerPoint
19. Microsoft.Office.Interop.Publisher
20. Microsoft.Office.Interop.SharePointDesigner
21. Microsoft.Office.Interop.SharePointDesignerPage
22. Microsoft.Office.Interop.SmartTag
23. Microsoft.Office.Interop.Visio
24. Microsoft.Office.Interop.Visio.SaveAsWeb
25. Microsoft.Office.Interop.VisOcx
26. Microsoft.Office.Interop.Word
27. Microsoft.stdformat
28. Microsoft.Vbe.Interop
29. Microsoft.Vbe.Interop.Forms
30. MSCOMCTL
31. msdatasrc
32. OFFICE
33. stdole
34. Policy.11.0.Microsoft.Office.Interop.Access
35. Policy.11.0.Microsoft.Office.Interop.Excel
36. Policy.11.0.Microsoft.Office.Interop.Graph
37. Policy.11.0.Microsoft.Office.Interop.InfoPath
38. Policy.11.0.Microsoft.Office.Interop.InfoPath.Xml
39. Policy.11.0.Microsoft.Office.Interop.MSProject
40. Policy.11.0.Microsoft.Office.Interop.Outlook
41. Policy.11.0.Microsoft.Office.Interop.OutlookViewCt l
42. Policy.11.0.Microsoft.Office.Interop.PowerPoint
43. Policy.11.0.Microsoft.Office.Interop.Publisher
44. Policy.11.0.Microsoft.Office.Interop.SmartTag
45. Policy.11.0.Microsoft.Office.Interop.Visio
46. Policy.11.0.Microsoft.Office.Interop.Visio.SaveAsW eb
47. Policy.11.0.Microsoft.Office.Interop.VisOcx
48. Policy.11.0.Microsoft.Office.Interop.Word
49. Policy.11.0.Microsoft.Vbe.Interop
50. Policy.11.0.Office
51. Policy.12.0.Microsoft.Office.Infopath
52. Policy.12.0.Microsoft.Office.Infopath.FormControl
53. Policy.12.0.Microsoft.Office.Infopath.Permission
54. Policy.12.0.Microsoft.Office.Interop.Access
55. Policy.12.0.Microsoft.Office.Interop.Access.Dao
56. Policy.12.0.Microsoft.Office.Interop.Excel
57. Policy.12.0.Microsoft.Office.Interop.Graph
58. Policy.12.0.Microsoft.Office.Interop.InfoPath
59. Policy.12.0.Microsoft.Office.Interop.InfoPath.Xml
60. Policy.12.0.Microsoft.Office.Interop.MSProject
61. Policy.12.0.Microsoft.Office.Interop.Outlook
62. Policy.12.0.Microsoft.Office.Interop.OutlookViewCt l
63. Policy.12.0.Microsoft.Office.Interop.PowerPoint
64. Policy.12.0.Microsoft.Office.Interop.Publisher
65. Policy.12.0.Microsoft.Office.Interop.SmartTag
66. Policy.12.0.Microsoft.Office.Interop.Visio
67. Policy.12.0.Microsoft.Office.Interop.Visio.SaveAsW eb
68. Policy.12.0.Microsoft.Office.Interop.VisOcx
69. Policy.12.0.Microsoft.Office.Interop.Word
70. Policy.12.0.Microsoft.Vbe.Interop
71. Policy.12.0.Office


Подскажите, в чем проблемка может быть?



0



Razvedka2020

40 / 35 / 9

Регистрация: 01.01.2014

Сообщений: 201

27.01.2018, 17:51

10

Эти библиотеки устанавливаются с пакетом MS Office и на вкладке COM называются Microsoft Exel xx.0 Object Library и Microsoft Word xx.0 Object Library соответственно, где хх.0 зависит от установленного у тебя офиса. Например для 2003 офиса это будет 10.0 (если не ошибаюсь), а для 2010 14.0
Так же для универсальности можно использовать позднее связывание:

VB.NET
1
2
Dim wApp As Object = CreateObject("word.Application")
Dim wDoc As Object = _word.Documents.Open(path)

вместо:

VB.NET
1
2
3
4
 
Private wApp As WRD.Application
wApp = New WRD.Application
Dim wDoc As WRD.Document



0



0 / 0 / 0

Регистрация: 25.01.2018

Сообщений: 19

27.01.2018, 17:57

 [ТС]

11

Значит-ли это, что в коде вместо:
Imports WRD = Microsoft.Office.Interop.Word
Imports EX = Microsoft.Office.Interop.Excel

надо написать:

Imports WRD = Microsoft.Word 12.0 Object Library
Imports EX = Microsoft.Excel 12.0 Object Library

я правильно предполагаю?



0



Razvedka2020

40 / 35 / 9

Регистрация: 01.01.2014

Сообщений: 201

27.01.2018, 18:05

12

Это значит что в обозревателе решений тебе нужно добавить ссылки на эти библиотеки или вообще отказаться от

VB.NET
1
2
Imports WRD = Microsoft.Office.Interop.Word
Imports EX = Microsoft.Office.Interop.Excel

и использовать позднее связывание



0



4278 / 3417 / 827

Регистрация: 02.02.2013

Сообщений: 3,308

Записей в блоге: 2

27.01.2018, 19:37

13

KorSar72, если у вас установлены программы Excel и Word, то у вас эти библиотеки должны быть в наличии и ссылки на них м.б. установлены как я уже писал (именно на вкладке NET)

Цитата
Сообщение от ovva
Посмотреть сообщение

См. меню VS: Project/Add Reference…/NET

Если установлено несколько версий этих программ, то в доступе будет и несколько версий этих библиотек. Для Office2003 будет v.11.
Выбор позднего связывания для вас я бы не рекомендовал просто, потому что вопросы никогда не кончатся. По крайней мере, для раннего связывания работает механизм IntelliSense.



0



0 / 0 / 0

Регистрация: 25.01.2018

Сообщений: 19

27.01.2018, 22:55

 [ТС]

14

У меня установлены только Word 2007 и Excel 2007. Других версий указанных программ нет. При этом в Microsoft Visual Basic 2010 Express (на чем я и пытаюсь написать код) нет библиотек Microsoft.Office.Interop.Word и Microsoft.Office.Interop.Excel (ни в NET, ни в COM. Установить их с официального сайта (как я писал ранее) не получается). При этом в COM имеются Microsoft.Word 12.0 Object Library и Microsoft.Excel 12.0 Object Library.
так, что взял за основу Ваш код и «прикручиваю» вариант, который посоветовал Razvedka2020. (но пока..много ошибок).



0



0 / 0 / 0

Регистрация: 25.01.2018

Сообщений: 19

28.01.2018, 01:24

 [ТС]

15

Ну я чего-то не понимаю, что твориться с моим Microsoft Visual Basic 2010 Express!

Есть код, который нормально работает, но …. только один раз. Если закрыть проект и потом снова его открыть исчезает как форма, так и кнопка в панели управления «пуск» (ну запуск проекта).
При первичном открытии указанного проекта, в коде содержится строка —

Imports Excel = Microsoft.Office.Interop.Excel

и код работает без ошибок (это не смотря на то, что я писал выше про отсутствие указанных библиотек).

Закрываю указанный проект, перезапускаю (или не перезапускаю) Microsoft Visual Basic 2010 Express, создаю новый код (или открываю любой другой проект) где содержится аналогичная строка —

Imports Excel = Microsoft.Office.Interop.Excel

— сразу получаю в списке ошибок сообщение — «Не найден адресуемый компонент Microsoft.Office.Interop.Excel».

Что такое? Криво стоит Microsoft Visual Basic 2010 Express?????

(прикладываю указанный рабочий код).



0



Razvedka2020

40 / 35 / 9

Регистрация: 01.01.2014

Сообщений: 201

28.01.2018, 10:00

16

Попробую объяснить настолько просто, насколько смогу (да простят меня Гуру форума за мою безграмотность).
Насколько я вижу из написаного тобой, твоя проблема в основном кроется в непонимании механики работы с библиотеками. При работе с библиотеками можно использовать позднее или раннее связывание. При раннем связывании ты добавляешь в проект ссылку на нужную тебе библиотеку (в твоем случае это будет Microsoft.Word 12.0 Object Library и Microsoft.Excel 12.0 Object Library. Не путай с Microsoft.Office.Interop.Word и Microsoft.Office.Interop.Excel — это пространство имен!!!). Теперь ты в проекте можешь обращаться к объектам библиотеки по полному пути, например:

VB.NET
1
2
Dim _word As Microsoft.Office.Interop.Word.Application
Dim _document As Microsoft.Office.Interop.Word.Document

Для удобства использования библиотеки и сокращения писанины ты можешь объявить пространство имен (зависит от твоих потребностей и целей):

VB.NET
1
2
3
4
5
6
7
Imports Microsoft
 
Imports Microsoft.Office
 
Imports Microsoft.Office.Interop
 
Imports Microsoft.Office.Interop.Word

И теперь обращаться к объектам библиотеки в сокращенной записи (в зависимости от того как объявил):

VB.NET
1
2
3
4
5
6
7
8
9
10
11
Dim _word As Office.Interop.Word.Application
Dim _document As Office.Interop.Word.Document
 
Dim _word As Interop.Word.Application
Dim _document As Interop.Word.Document
 
Dim _word As Word.Application
Dim _document As Word.Document
 
Dim _word As Application
Dim _document As Document

Так же ты можешь объявить свое собственное пространство имен, как это сделал Ovva:

VB.NET
1
Imports WRD = Microsoft.Office.Interop.Word

И также обращаться к объектам библиотеки в сокращенной записи, минуя Microsoft.Office.Interop.Word:

VB.NET
1
2
Dim _word As WRD.Application
Dim _document As WRD.Document

Метод раннего связывания хороший и удобный, но накладывает ограничения, например если добавишь библиотеку из Word 2013, то программа не будет работать где установлен Word 2003-2010.
При позднем связывании ты НЕ ДОБАВЛЯЕШЬ ссылку на библиотеку и НЕ ОБЪЯВЛЯЕШЬ ПРОСТРАНСТВО ИМЕН!!!
Обращаешься к объектам библиотеки через переменную типа «object»:

VB.NET
1
2
Dim _word As Object = CreateObject("word.Application")
Dim _document As Object = _word.Documents.open(Application.StartupPath & "template.doc")

Метод геморный, но зато универсальный. Главное чтобы на целевом компе стоял Word и неважно какой версии (конечно если ты не будишь использовать специфический методы из поздних версий), программа сама подхватывает имеющуюся библиотеку и работает с ней.
Надеюсь что у меня получилось доходчиво объяснить механику работы, а то из меня объясняльщик хреновый (да простят меня Гуру форума за мою безграмотность).



0



0 / 0 / 0

Регистрация: 25.01.2018

Сообщений: 19

28.01.2018, 16:25

 [ТС]

17

Спасибо всем!
И за личные ответы, и за посты на форуме. Засел за учебники.



0



4278 / 3417 / 827

Регистрация: 02.02.2013

Сообщений: 3,308

Записей в блоге: 2

29.01.2018, 13:15

18

Лучший ответ Сообщение было отмечено KorSar72 как решение

Решение

KorSar72, предлагаю вариант решения без использования Word и Excel в явном виде (они вообще могут отсутствовать на компьютере). Требуется добавить ссылку на библиотеку (NET) DocumentFormat.OpenXml. Справедливо для структуры данных примера Prim_IsxData1.doc.
Файлы xlsx записываются по адресу исходных файлов docx.

Миниатюры

Парсинг данных из файла Word и сохранение результата в Excel
 



0



0 / 0 / 0

Регистрация: 25.01.2018

Сообщений: 19

29.01.2018, 23:22

 [ТС]

19

Требуется добавить ссылку на библиотеку (NET) DocumentFormat.OpenXml.

Многоуважаемый Ovva!

В очередной раз не могу найти у себя в VB 2010 Express в (NET) — >> DocumentFormat.OpenXml.
Пробую поставить с официального сайта www.microsoft.com следующие обновления и …. библиотеки:
— OpenXMLSDKTool.msi
— OpenXMLSDKv2.msi
— office2007sp1-kb936982-fullfile-en-us.exe
— office2007sp2-kb953195-fullfile-en-us.exe
— office2007sp3-kb2526086-fullfile-en-us.exe

п.с. — я уже себя чувствую даже не нубом….((((. Че ж все такое «кривое» у меня стоит на ноуте…..

Добавлено через 1 час 11 минут
Уряяя…. появилась библиотека!

Кое-что все равно надо поправить… ошибки и предупреждения! Работаю.

Добавлено через 32 минуты
открываю код — и сразу ошибки вот в этих строках:

— Imports DocumentFormat.OpenXml.Packaging
— Imports DocumentFormat.OpenXml.Spreadsheet
— Imports DocumentFormat.OpenXml

Предупреждение 1-2-3 Пространство имен или тип, указанные в операторе Imports «DocumentFormat.OpenXml.Packaging»
«DocumentFormat.OpenXml.Spreadsheet»
«DocumentFormat.OpenXml»
не содержат открытые члены или не могут быть найдены. Убедитесь, что пространство имен или тип определены и содержат хотя бы один открытый член. Убедитесь, что имя импортируемого элемента не было использовано в других Imports.

Вот тут — https://msdn.microsoft.com/ru-… aging.aspx — написано:
Пространство имен DocumentFormat.OpenXml.Packaging — Office 2013 и более поздней версии (самая ранняя — 2010).

У меня стоит Office 2007 .. думаю в этом и кроется проблема. Надо офис ставить хотя-бы 2010.

Добавлено через 10 минут
С официального сайта:

DocumentFormat.OpenXml.Packaging Namespace — Office 2007 Other Versions —
This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

No comment! даже скачать неоткуда теперь ;(



0



4278 / 3417 / 827

Регистрация: 02.02.2013

Сообщений: 3,308

Записей в блоге: 2

30.01.2018, 00:20

20

Не понимаю что там у вас происходит. Попробуйте установить библиотеку из прилагаемого архива. Как это нужно сделать:

  • Скопируйте библиотеку (предварительно разахивировав) в папку Debug вашего проекта.
  • Определите ссылку на эту библиотеку (Project/Add Reference…/Browse…)
  • В окне Solution Explorer отметьте иконку Show All Files и в появившемся списке щелкните на узле References и выберете библиотеку DocumentFormat.OpenXml. В окне Properties будут показаны ее свойства. Установите свойство Copy Local = True.

И продолжаете отладку программы.
Вообще это все как то непонятно. Было бы неплохо, если бы вы выложили весь проект (тот с которым вы работаете). И еще вопрос а какая ОС установлена на вашем компьютере.



0



IT_Exp

Эксперт

87844 / 49110 / 22898

Регистрация: 17.06.2006

Сообщений: 92,604

30.01.2018, 00:20

20

Время прочтения: 6 мин.

В
процессе аудита часто возникает потребность в обработке данных из документов в
форматах MS Word или Excel. В своей статье я хочу поделиться опытом считывания
информации из таких файлов с использованием языка C#.

Для
работы с файлами Word и Excel
я решил выбрать библиотеки Microsoft.Office.Interop.Word и Microsoft.Office.Interop.Excel, предоставляющие программные интерфейсы для
взаимодействия с объектами MS
Word и
Excel.

Преимущества
использования этих библиотек:

  • созданы корпорацией Microsoft, следовательно, взаимодействие с объектами программ пакета MS Office реализовано наиболее оптимально,
  • нужный пакет Visual Studio Tool for Office поставляется вместе с Visual Studio (достаточно отметить его при установке VS).

Также
следует заметить, что у такого похода есть и недостаток: для того, чтобы
написанная программа работала на ПК пользователя необходимо, чтобы на нём были
установлены программы MS
Office и
MS Excel. Поэтому такой
подход плохо подходит для серверных решений. Также такая программа не будет
являться кроссплатформенной.

Добавление библиотек в проект Visual Studio

Библиотеки
поставляются вместе с пакетом Visual
Studio
Tool
for
Office (платформа .NET Framework).

Для
использования библиотеки нужно:

  • добавить ссылку на неё: в обозревателе решений необходимо кликнуть правой кнопкой мыши по пункту Ссылки (Рис. 1) и найти нужную библиотеку по ключевым словам (после добавления ссылка появится в списке),

  • указать используемое пространство имён в файле программы (в примере ему назначен алиас Word): (Рис. 2):

Пример парсинга файла MS Word

Можно прочитать основные
форматы: .doc, .docx, .rtf.

Ниже
приведён листинг с примером считывания текста из документа MS Word:

object FileName = @"C:test.doc";
object rOnly = true;
object SaveChanges = false;
object MissingObj = System.Reflection.Missing.Value;

Word.Application app = new Word.Application();
Word.Document doc = null;
Word.Range range = null;
try
{
    doc = app.Documents.Open(ref FileName, ref MissingObj, ref rOnly, ref MissingObj,
    ref MissingObj, ref MissingObj, ref MissingObj, ref MissingObj,
    ref MissingObj, ref MissingObj, ref MissingObj, ref MissingObj,
    ref MissingObj, ref MissingObj, ref MissingObj, ref MissingObj);

    object StartPosition = 0;
    object EndPositiojn = doc.Characters.Count;
    range = doc.Range(ref StartPosition, ref EndPositiojn);

    // Получение основного текста со страниц (без учёта сносок и колонтитулов)
    string MainText = (range == null || range.Text == null) ? null : range.Text;
    if (MainText != null)
    {
        /* Обработка основного текста документа*/
    }

    // Получение текста из нижних и верхних колонтитулов
    foreach (Word.Section section in doc.Sections)
    {
        // Нижние колонтитулы
        foreach (Word.HeaderFooter footer in section.Footers)
        {
            string FooterText = (footer.Range == null || footer.Range.Text == null) ? null : footer.Range.Text;
            if (FooterText != null)
            {
                /* Обработка текста */
            }
        }

        // Верхние колонтитулы
        foreach (Word.HeaderFooter header in section.Headers)
        {
            string HeaderText = (header.Range == null || header.Range.Text == null) ? null : header.Range.Text;
            if (HeaderText != null)
            {
                /* Обработка текста */
            }
        }
    }
    // Получение текста сносок
    if (doc.Footnotes.Count != 0)
    {
        foreach (Word.Footnote footnote in doc.Footnotes)
        {
            string FooteNoteText = (footnote.Range == null || footnote.Range.Text == null) ? null : footnote.Range.Text;
            if (FooteNoteText != null)
            {
                /* Обработка текста */
            }
        }
    }
} catch (Exception ex)
{
    /* Обработка исключений */
}
finally
{
    /* Очистка неуправляемых ресурсов */
    if(doc != null)
    {
        doc.Close(ref SaveChanges);
    }
    if(range != null)
    {
        Marshal.ReleaseComObject(range);
        range = null;
    }            
    if(app != null)
    {
        app.Quit();
        Marshal.ReleaseComObject(app);
        app = null;
    }
}

Примечания:

  • в коде приводится пример считывания основного текста документа, текста верхних и нижних колонтитулов, а также текста сносок,
  • в коде производится очистка неуправляемых ресурсов с использованием класса Marshal (подробнее можно почитать по ссылке )

Пример парсинга файла MS Excel

Можно прочитать основные
форматы: .xls, .xlsx.

Ниже
приведён листинг с примером считывания текста из документа MS Excel (по
ячейкам):

string FileName = @"C:UsersbeeDownloadstest.xlsx";
object rOnly = true;
object SaveChanges = false;
object MissingObj = System.Reflection.Missing.Value;

Excel.Application app = new Excel.Application();
Excel.Workbooks workbooks = null;
Excel.Workbook workbook = null;
Excel.Sheets sheets = null;
try
{
    workbooks = app.Workbooks;
    workbook  = workbooks.Open(FileName, MissingObj, rOnly, MissingObj, MissingObj,
                                MissingObj, MissingObj, MissingObj, MissingObj, MissingObj,
                                MissingObj, MissingObj, MissingObj, MissingObj, MissingObj);

    // Получение всех страниц докуента
    sheets = workbook.Sheets;
                
    foreach(Excel.Worksheet worksheet in sheets)
    {
        // Получаем диапазон используемых на странице ячеек
        Excel.Range UsedRange = worksheet.UsedRange;
        // Получаем строки в используемом диапазоне
        Excel.Range urRows = UsedRange.Rows;
        // Получаем столбцы в используемом диапазоне
        Excel.Range urColums = UsedRange.Columns;

        // Количества строк и столбцов
        int RowsCount = urRows.Count;
        int ColumnsCount = urColums.Count;
        for(int i = 1; i <= RowsCount; i++)
        {
            for(int j = 1; j <= ColumnsCount; j++)
            {
                Excel.Range CellRange = UsedRange.Cells[i, j];
                // Получение текста ячейки
                string CellText = (CellRange == null || CellRange.Value2 == null) ? null :
                                    (CellRange as Excel.Range).Value2.ToString();

                if(CellText != null)
                {
                    /* Обработка текста */
                }
            }
        }
        // Очистка неуправляемых ресурсов на каждой итерации
        if (urRows != null) Marshal.ReleaseComObject(urRows);
        if (urColums != null) Marshal.ReleaseComObject(urColums);
        if (UsedRange != null) Marshal.ReleaseComObject(UsedRange);
        if (worksheet != null) Marshal.ReleaseComObject(worksheet);
    }
} catch (Exception ex)
{
    /* Обработка исключений */
}
finally
{
    /* Очистка оставшихся неуправляемых ресурсов */
    if (sheets != null) Marshal.ReleaseComObject(sheets);
    if (workbook != null)
    {
        workbook.Close(SaveChanges);
        Marshal.ReleaseComObject(workbook);
        workbook = null;
    }

    if (workbooks != null)
    {
        workbooks.Close();
        Marshal.ReleaseComObject(workbooks);
        workbooks = null;
    }
    if (app != null)
    {
        app.Quit();
        Marshal.ReleaseComObject(app);
        app = null;
    }
}

Примечания:

  • при обработке текста каждой ячейки
    приходится заранее знать количество задействованных строк и столбцов на текущем
    листе документа,
  • такой перебор не совсем оптимален
    (временная сложность алгоритма O(n2)):
    при желании его можно ускорить (например, разбив обработку на несколько
    потоков): в данной статье приводится лишь пример получения текста из каждой
    ячейки,
  • при таком переборе ячеек необходимо
    на каждой итерации освобождать неуправляемые ресурсы, чтобы избежать утечек
    памяти (аналогично предыдущему примеру, используется класс Marshal).

Приведенные
примеры хорошо подходят для реализации приложения по обработке документов Word и
Excel
на платформе .NET Framework.

С
помощью указанных библиотек можно не только читать текст из документов, но и
создавать новые файлы форматов MS Word и
Excel.

В 2017 году пакет Microsoft Office был популярной целью для атак хакеров. Вместе с большим числом найденных уязвимостей и опубликованных proof-of-concept эксплойтов у авторов вредоносного ПО появилась необходимость в предотвращении детекта новых модификаций эксплойтов антивирусными средствами. Также стало ясно, что использования легитимных особенностей RTF-парсера более недостаточно для эффективного избегания анализа и обнаружения. Вместе с увеличением атак посредством MS Office, когда файл формата RTF используется как контейнер для эксплойта, мы нашли большое количество сэмплов, которые «эксплуатировали» RTF-парсер в Microsoft Word, чтобы «сломать» все другие имплементации RTF-парсера, включая те, которые используются в антивирусном программном обеспечении.

Для того, чтобы достичь точного парсинга, как в MS Office, нам было необходимо провести реверс-инжиниринг. Работа была начата с MS Office 2010 т.к. когда дело касается парсинга, лучше начать с более старой имплементации, а затем сравнили находки с тем, что удалось обнаружить в более новых версиях.

RTF-парсер представляет собой машину состояний (state machine) с 37 состояниями, 22 из которых уникальны:

Мы рассмотрим самые главные состояния и те, которые относятся к парсингу objdata – контрольного слова (control word), обозначающего назначение (destination) которое содержит данные «обьекта». Эти состояния:

enum

{

       PARSER_BEGIN = 0,

       PARSER_CHECK_CONTROL_WORD = 2,

       PARSER_PARSE_CONTROL_WORD = 3,

       PARSER_PARSE_CONTROL_WORD_NUM_PARAMETER = 4,

       PARSER_PARSE_HEX_DATA = 5,

       PARSER_PARSE_HEX_NUM_MSB = 7,

       PARSER_PARSE_HEX_NUM_LSB = 8,

       PARSER_PROCESS_CMD = 0xE,

       PARSER_END_GROUP = 0x10,

       // …

};

Microsoft Office поставляется без отладочных символов, поэтому я не смог восстановить оригинальные имена. Но верю, что выбрал наиболее подходящие по их функциональности.

Первое состояние, которое выполняется на открытом RTF-файле — PARSER_BEGIN. В большинстве случаев оно также выставляется после обработки очередного контрольного слова. Главная цель состояния – определить следующее состояние, исходя из встреченных символов, установленного назначения, и других значений, содержащихся в ‘this’ структуре и установленных обработчиками контрольных слов. По умолчанию следующее установленное состояние это PARSER_CHECK_CONTROL_WORD.

case PARSER_BEGIN:

        // … — checks that we dont need

        while (data.pos != data.end)

        {

                byte = *(uint8_t*)data.pos;

                data.pos++;

                if (this->bin_size > 0)

                {

                        goto unexpected_char;

                }

                // …

                if (byte == 9)

                {

                        // …

                        continue;

                }

                if (byte == 0xA || byte == 0xD)

                {

                        // …

                        break;

                }

                if (byte == ‘’)

                {

                        uint8_t byte1 = *(uint8_t*)data.pos;

                        if (byte1 == »’)

                        {

                               if (this->destination == listname ||

                                       this->destination == fonttbl ||

                                       this->destination == revtbl ||

                                       this->destination == falt ||

                                       this->destination == leveltext ||

                                       this->destination == levelnumbers ||

                                       this->destination == liststylename ||

                                       this->destination == protusertbl ||

                                       this->destination == lsdlockedexcept)

                                       goto unexpected_char;

                               state = PARSER_CHECK_CONTROL_WORD;

                               // …

                               break;

                        }

                        if (byte1 == ‘u’)

                        {

                               // …

                               break;

                        }

                        state = PARSER_CHECK_CONTROL_WORD;

                        // …

                        break;

                }

                if (byte == ‘{‘)

                {

                        create_new_group();

                        // …

                        break;

                }

                if (byte == ‘}’)

                {

                        state = PARSER_END_GROUP;

                        break;

                }

        unexpected_char:

                // it will set next state depending on destination / or go to unexpected_cmd to do more checks and magic

                // …

                if (this->destination == pict ||

                        this->destination == objdata ||

                        this->destination == objalias ||

                        this->destination == objsect ||

                        this->destination == datafield ||

                        this->destination == fontemb ||

                        this->destination == svb ||

                        this->destination == macro ||

                        this->destination == tci ||

                        this->destination == datastore ||

                        this->destination == mmconnectstrdata ||

                        this->destination == mmodsoudldata ||

                        this->destination == macrosig)

                {

                        state = PARSER_PARSE_HEX_DATA;

                        data.pos—;

                        break;

                }

                // …

                break;

        }

        break;

PARSER_CHECK_CONTROL_WORD проверит, является ли следующий символ началом контрольного слова или же это контрольный символ (control symbol), после чего установит следующее состояние соответственно результату проверки.

case PARSER_CHECK_CONTROL_WORD:

        byte = *(uint8_t*)data.pos;

        if ((byte >= ‘a’ && byte <= ‘z’) || (byte == ‘ ‘) || (byte >= ‘A’ && byte <= ‘Z’))

        {

                state = PARSER_PARSE_CONTROL_WORD;

                this->cmd_len = 0;

        }

        else

        {

                data.pos++;

                this->temp[0] = 1;

                this->temp[1] = byte;

                this->temp[2] = 0;

                state = PARSER_PROCESS_CMD;

                this->cmd_len = 1;

                break;

        }

Состояния PARSER_PARSE_CONTROL_WORD и PARSER_PARSE_CONTROL_WORD_NUM_PARAMETER сохранят Null-терминированное контрольное слово, состоящее из латинских символов, и Null-терминированный числовой параметр (если он имеется) во временный буфер фиксированного размера.

case PARSER_PARSE_CONTROL_WORD:

        pos = this->temp + 1;

        parsed = this->temp + 1;

        while (data.pos != data.end)

        {

                byte = *(uint8_t*)data.pos;

                // length of null-terminated strings cmd + num should be <= 0xFF

                if ((byte == ‘-‘) || (byte >= ‘0’ && byte <= ‘9’))

                {

                        //if parsed == temp_end

                        // goto raise_exception

                        *parsed = 0;

                        parsed++;

                        pos = parsed;

                        if (parsed >= temp_end)

                        {

                               parsed = temp_end — 1;

                               *parsed = 0;

                               state = PARSER_PROCESS_CMD;

                               this->cmd_len = pos — (this->temp + 1);

                               break;

                        }

                        data.pos++;

                        this->cmd_len = pos — (this->temp + 1);

                        *parsed = byte;

                        parsed++;

                        pos = parsed;

                        state = PARSER_PARSE_CONTROL_WORD_NUM_PARAMETER;

                        break;

                }

                if (byte == ‘ ‘)

                {

                        data.pos++;

                        if (parsed >= temp_end)

                        {

                               parsed = temp_end — 1;

                        }

                        *parsed = 0;

                        state = PARSER_PROCESS_CMD;

                        this->cmd_len = pos — (this->temp + 1);

                        break;

                }

                if ((byte >= ‘a’ && byte <= ‘z’) || (byte >= ‘A’ && byte <= ‘Z’))

                {

                        if (parsed — this->temp >= 0xFF)

                        {

                               if (parsed >= temp_end)

                               {

                                       parsed = temp_end — 1;

                               }

                               *parsed = 0;

                               state = PARSER_PROCESS_CMD;

                               this->cmd_len = pos — (this->temp + 1);

                               break;

                        }

                        //if parsed == temp_end

                        // goto raise_exception

                        *parsed = byte;

                        parsed++;

                        pos = parsed;

                        data.pos++;

                }

                else

                {

                        if (parsed >= temp_end)

                        {

                               parsed = temp_end — 1;

                        }

                        *parsed = 0;

                        state = PARSER_PROCESS_CMD;

                        this->cmd_len = pos — (this->temp + 1);

                        break;

                }

        }

        break;

case PARSER_PARSE_CONTROL_WORD_NUM_PARAMETER:

        while (data.pos != data.end)

        {

                byte = *(uint8_t*)data.pos;

                // length of null-terminated strings cmd + num should be <= 0xFF

                if (byte == ‘ ‘)

                {

                        data.pos++;

                        if (parsed >= temp_end)

                        {

                               parsed = temp_end — 1;

                        }

                        *parsed = 0;

                        state = PARSER_PROCESS_CMD;

                        break;

                }

                if (byte >= ‘0’ && byte <= ‘9’)

                {

                        if (parsed — this->temp >= 0xFF)

                        {

                               if (parsed >= temp_end)

                               {

                                       parsed = temp_end — 1;

                               }

                               *parsed = 0;

                               state = PARSER_PROCESS_CMD;

                               break;

                        }

                        //if parsed == temp_end

                        // goto raise_exception

                        *parsed = byte;

                        *parsed++;

                        data.pos++;

                }

                else

                {

                        if (parsed >= temp_end)

                        {

                               parsed = temp_end — 1;

                        }

                        *parsed = 0;

                        state = PARSER_PROCESS_CMD;

                        break;

                }

        }

        break;

case PARSER_PROCESS_CMD:

case PARSER_SKIP_DATA:

case PARSER_END_GROUP:

case PARSER_SKIP_DATA_CHECK_B:

case PARSER_SKIP_DATA_CHECK_I:

case PARSER_SKIP_DATA_CHECK_N:

case PARSER_SKIP_DATA_GET_BIN_VAL:

case PARSER_SKIP_DATA_INNER_DATA:

        this->state = state;

        cmd_parser(&data);

        state = this->state;

        break;

Этот буфер обрабатывается в состоянии PARSER_PROCESS_CMD, которое вызывает другую функцию ответственную за обработку контрольных слов и контрольных символов. Эта функция учитывает текущее состояние и устанавливает следующее.

В коде есть несколько состояний, ответственных за парсинг шестнадцатеричных данных. Для нас наиболее интересно PARSER_PARSE_HEX_DATA – как можно увидеть, это состояние устанавливается в PARSER_BEGIN, если установлено назначение objdata.

case PARSER_PARSE_HEX_DATA:

        parsed_data = this->temp;

        if (this->bin_size <= 0)

        {

                while (data.pos != data.end)

                {

                        byte = *(uint8_t*)data.pos;

                        if (byte == ‘{‘ || byte == ‘}’ || byte == ‘’)

                        {

                               state = PARSER_BEGIN;

                               if (parsed_data != this->temp)

                               {

                                       push_data(parsed_data — this->temp);

                                       parsed_data = this->temp;

                               }

                               break;

                        }

                        if (this->flag & 0x4000)

                        {

                               data.pos++;

                               continue;

                        }

                        if (byte >= ‘0’ && byte <= ‘9’)

                        {

                               val = byte — 0x30;

                        }

                        else if (byte >= ‘a’ && byte <= ‘f’)

                        {

                               val = byte — 0x57;

                        }

                        else if (byte >= ‘A’ && byte <= ‘F’)

                        {

                               val = byte — 0x37;

                        }

                        else if (byte == 9 || byte == 0xA || byte == 0xD || byte == 0x20)

                        {

                               data.pos++;

                               continue;

                        }

                        else

                        {

                               // show message that there are not enough memory

                               this->flag |= 0x4000;

                               data.pos++;

                               continue;

                        }

                        if (this->flag & 0x8000)

                        {

                               this->hex_data_byte = val << 4;

                               this->flag &= 0x7FFF;

                        }

                        else

                        {

                               if (parsed_data == temp_end)

                               {

                                       push_data(sizeof(this->temp));

                                       parsed_data = this->temp;

                               }

                               this->hex_data_byte |= val;

                               *parsed_data = this->hex_data_byte;

                               parsed_data++;

                               this->flag |= 0x8000;

                        }

                        data.pos++;

                }

        }

        else

        {

                if (this->flag & 0x4000)

                {

                        uint32_t size;

                        if (this->bin_size <= data.end — data.pos)

                        {

                               size = this->bin_size;

                        }

                        else

                        {

                               size = data.end — data.pos;

                        }

                        this->bin_size -= size;

                        data.pos += size;

                }

                else

                {

                        while (this->bin_size > 0)

                        {

                               if (parsed_data == temp_end)

                               {

                                       push_data(sizeof(this->temp));

                                       parsed_data = this->temp;

                               }

                               byte = *(uint8_t*)data.pos;

                               *parsed_data = byte;

                               parsed_data++;

                               data.pos++;

                               this->bin_size—;

                        }

                }

        }

        if (parsed_data != this->temp)

        {

                push_data(parsed_data — this->temp);

                parsed_data = this->temp;

        }

        break;

Это состояние распарсит шестнадцатеричные и бинарные данные, если они были объявлены.

Состояния PARSER_PARSE_HEX_NUM_MSB и PARSER_PARSE_HEX_NUM_LSB используются в паре для парисинга шестнадцатеричных значений (данные контрольного слова panose и контрольного символа ).

case PARSER_PARSE_HEX_NUM_MSB:

        this->flag |= 0x8000;

        this->hex_num_byte = 0;

        state = PARSER_PARSE_HEX_NUM_LSB;

case PARSER_PARSE_HEX_NUM_LSB:

        //  …

        byte = *(uint8_t*)data.pos;

        data.pos++;

        val = 0;

        if (byte — ‘0’ <= 9)

        {

                val = byte — 0x30;

        }

        else if (byte — ‘a’ <= 5)

        {

                val = byte — 0x57;

        }

        else if (byte — ‘A’ <= 5)

        {

                val = byte — 0x37;

        }

        this->hex_num_byte |= val << ((this->flag >> 0xF) << 2);

        this->flag = ((~this->flag  ^ this->flag) & 0x7FFF) ^ ~this->flag;

        if (this->flag & 0x8000)

        {

                // …

                state = PARSER_BEGIN;

        }

        else

        {

                break;

        }

        break;

Сброс состояния

Внимательно изучив код состояний PARSER_PARSE_HEX_NUM_MSB, PARSER_PARSE_HEX_NUM_LSB и PARSER_PARSE_HEX_DATA, легко заметить баг. Несмотря на то, что они используют разные переменные для декодированных шестнадцатеричных значений, состояния используют одинаковый бит для определения какой ниббл (полубайт) декодируется сейчас – верхний (MSB) или нижний (LSB). И PARSER_PARSE_HEX_NUM_MSB всегда сбрасывает этот бит на MSB.

Тем самым можно заставить байты исчезнуть из финального результата декодирования, если в контексте PARSER_PARSE_HEX_DATA вынудить смену состояния на PARSER_PARSE_HEX_NUM_MSB.

Для этого достаточно вставить XX в данные, которые идут после контрольного слова objdata. В этом случае, когда данные будут обрабатываться в PARSER_PARSE_HEX_DATA, парсер встретит и вернется в PARSER_BEGIN, а в конце концов попадет в состояние PARSER_PROCESS_CMD. Логика для контрольного символа устроена так, что назначение не будет изменено, но будет выставлено состояние PARSER_PARSE_HEX_NUM_MSB. После PARSER_PARSE_HEX_NUM_MSB и PARSER_PARSE_HEX_NUM_LSB управление снова перейдет в PARSER_BEGIN и PARSER_PARSE_HEX_DATA, т.к. назначение все еще равно objdata. После этого снова будет декодироваться верхний ниббл.

Также примечательно, что PARSER_PARSE_HEX_NUM_LSB не проверяет валидность значений, т.е. после могут идти два абсолютно любых байта.

Это можно наблюдать в следующем примере:

«f’cc»будут удалены

Когда исполнение в первый раз передается состоянию PARSER_PARSE_HEX_DATA, после того, как контрольное слово objdata обработано, MSB бит уже установлен. Давайте рассмотрим, как это происходит и как следующий пример будет обработан парсером:

Проведя некоторое время над функцией ответственной за обработку контрольных слов и контрольных символов, я нашел список всех контрольных слов и их структуры.

С этой информацией мы можем найти и взглянуть на конструктор objdata:

Как можно заметить, конструктор устанавливает бит MSB, выделяет новый буфер и заменяет указатель на старый. Таким образом, данные, декодированные между двумя контрольными словами objdata, не используются.

«d0cf11e0a1b11ae1» будут удалены

Пункт назначения

Мы знаем, что если вставить и objdata, то это изменит выходные данные. Но что по поводу других контрольных слов и символов? Ведь их в парсере больше 1500!

По большей части ничего.

Т.к. часть контрольных слов обозначают назначение, они не могут применяться – результатом будет изменение objdata на назначение контрольного слова, а для декодирования данных нам нужно назначение objdata.

Часть других контрольных слов не имеют эффекта на назначение objdata.

Единственный способ задать другое назначение таким образом, чтобы можно было вернуть назначение objdata без потери декодированных данных, это воспользоваться особыми символами – открывающая фигурная скобка ({) и закрывающая фигурная скобка (}). Эти символы обозначают начало и конец группы.

После того, как парсер встретит конец группы в PARSER_BEGIN, назначение, которое было установлено перед началом группы, будет восстановлено.

Таким образом, если после objdata вставить {aftncn FF}, то FF не попадут в данные objdata, т.к. FF теперь относятся к назначению aftncn и будут обработаны согласно его логике.

В противном случае, если вставить {aftnnalc FF}, то FF попадут в objdata т.к. назначение все еще равно objdata.

Также стоит отметить, что {objdata FF} все-еще нельзя использовать т.к. буфер не будет восстановлен.

Точный список назначений был составлен благодаря простому фаззеру.

Фиксированный буфер

Другая техника обфускации, которая приходит в голову при взгляде на код RTF-парсера, не относится к этому «MSB» багу, но также может использоваться для удаления шестнадцатеричных данных. Техника относится к размеру временного буфера и к тому, как контрольные слова и числовые параметры парсятся в состояниях PARSER_PARSE_CONTROL_WORD и PARSER_PARSE_CONTROL_WORD_NUM_PARAMETER.

Пример злоупотребления представлен на следующей картинке:

В этом примере размер данных, которые будут удалены как часть числового параметра, считается по формуле: 0xFF (размер временного буфера) — 0xB (размер контрольного слова «oldlinewrap») — 2 (null-терминаторы) = 0xF2.

Ненужные данные

Описанные ранее техники относятся к основам парсинга формата RTF, но дополнительная путаница скрывается в обработке особых контрольных слов.

Согласно спецификации, если контрольный символ * находился перед неизвестным контрольным словом или контрольным символом, то считается, что это неизвестное назначение и все данные перед скобкой (}), которая закрывает эту группу, должны быть выкинуты. В MS Office содержатся контрольные слова, которые не упоминаются в спецификации, и вызывает опасение то, что список контрольных слов будет изменен в будущем, оказывая влияние на парсинг документа в различных версиях MS Office. Когда функция, ответственная за обработку контрольных слов и контрольных символов, встретит такой случай или одно из особых контрольных слов (такие как comment, generator, nonshppict и др.), состояние будет установлено на PARSER_SKIP_DATA, а число встреченных открывающих скобок ({) на 1.

enum

{

       // …

       PARSER_SKIP_DATA = 0xF,

       // …

       PARSER_SKIP_DATA_CHECK_B = 0x13,

       PARSER_SKIP_DATA_CHECK_I = 0x14,

       PARSER_SKIP_DATA_CHECK_N = 0x15,

       PARSER_SKIP_DATA_GET_BIN_VAL = 0x16,

       PARSER_SKIP_DATA_INNER_DATA = 0x17,

       // …

};

Немного магии

Во время анализа состояний из группы PARSER_SKIP_DATA открылись факты, которые идут не только в разрез со спецификацией, но и в разрез с основным кодом парсера.

В поисках контрольного слова bin эти состояния будут пропускать байты, изменяя количество встреченных открывающих и закрывающих скобок, пока их количество не будет равно 0. Западня кроется в том, как обрабатывается числовой параметр в PARSER_SKIP_DATA_GET_BIN_VAL.

Во-первых, максимально допустимая длина числового параметра увеличена до 0xFF байт, длина контрольного слова не учитывается.

Во-вторых, числовой параметр теперь вовсе не числовой! Парсер позволяет передать не только числа, но и латинские буквы. Потом этот параметр передается в кастомную функцию strtol, тем самым позволяя задать длину данных, которые должны быть пропущены, без учета открывающих и закрывающих скобок как шестнадцатеричное число.

Благодаря этим примитивам возможны новые способы обфускации, которые еще не встречались in the wild.

Заключение

Реверс-инжиниринг показал себя как наиболее эффективный способ создания парсера, и в случае с RTF, скорее всего, не удалось бы добиться желаемого результата иначе.

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

Продукты «Лаборатории Касперского» распознают все виды RTF-обфускаций и выполняют наиболее корректную обработку документов формата RTF, обеспечивая максимальную защиту для наших пользователей.

Понравилась статья? Поделить с друзьями:
  • Что такое paragraphs word
  • Что такое palo для excel
  • Что такое ms word какой значок
  • Что такое ms word информатика тест
  • Что такое ms word информатика кратко