Pull to refresh

Текст любой ценой: PPT

Reading time 3 min
Views 5K
Некоторое время назад мы с вами обсуждали получение чистого текста из различных форматов данных: будь то PDF или DOC. В одном из обсуждений был высказано предположение, что при парсинге презентаций PowerPoint я заработаю геморрой или другую страшную болезнь мягкой точки. Что ж, волей судеб мне пришлось доставать текст и из этого «сладенького» формата. Скажу честно, геморрой заработать не удалось, а вот класс для парсинга презентаций вышел.

НеМного о формате PPT


Как и DOC, PPT является надстройкой над форматом WCBFF (структурированного бинарного файлового формата), о котором вы можете прочесть в статье о MS Word, написанной мною ранее. Отмечу лишь то, что за время тестирования я нашёл несколько ошибок (а кроме того, повидал кучу «битых» файлов, из которых приходилось получать текст) в старых реализация WCBFF и DOC, поэтому советую обновить свои исходники тем, кто использует мои наработки.

Так, мы отвлеклись. Продолжим беседу о PowerPoint. В отличие от DOC, мы будем работать не с «файлами» WordDocument и 1Table, как раньше, а со специфичными для презентаций: Current User и PowerPoint Document. Наличие обоих «файлов» в «файловой системе» CBF-файла обязательно для презентаций. По ним же можно определить, что перед нами презентация, в случае ошибочного расширения.

Итак, чтобы начать получать данные из PPT стоит прочесть небольшой «файл»-запись (или «файл», состоящий из одной записи CurrentUserAtom) Current User. Эта запись содержит техническую информацию о том, кто редактировал этот файл в последний раз, но это не самое важное. В этом блоке есть информация о смещении к первой записи UserEditAtom, речь о которой пойдёт чуть ниже.

Сейчас же я расскажу, как нужно читать записи в PPT. Любая запись в презентации содержит специальный заголовок rh, который содержит техническую информацию о ней. Для этого достаточно прочесть первые 8 байт любой записи. Первое слово обычно не содержит нужной информации, а вот следующие 6 байт нам потребуются. WORD по смещению 2 (rh.recType) идентифицирует тип записи, по которому можно узнать, что делать с записью дальше. Long по смещению 4 (recLen) — длина записи без учёта заголовка в восемь байт. Такой способ записи достаточно удобен и позволяет избежать многих ошибок при разборе файла презентации

Что дальше? Вернёмся к записи UserEditAtom. Эта запись находится уже в PowerPoint Document. В последствии работать мы будем только с этим «файлом». С помощью прочтения этой и связанных с ней записей мы должны построить такую дивную штуку, как массив смещений PersistDirectory, с помощью которого мы будем искать главную структуру документа PowerPoint — DocumentContainer. Для этого мы должны прочитать текущую запись UserEditAtom, найти в ней смещение offsetPersistDirectory к текущей «live» версии PersistDirectory и смещение offsetLastEdit к следующей записи UserEditAtom. Так продолжим получать смещения, пока не наткнёмся на нули в DWORD'е offsetLastEdit.

После получения всех смещений offsetPersistDirectory мы должны создать эту самую PersistDirectory. Идём по смещения в обратном порядке и читаем записи PersistDirectoryAtom. Они содержат в себе массив записей PersistDirectoryEntry. Каждая из коих содержит номер persistId первого entry и их количество cPersist в текущей записи. После этой информации идёт массив смещений к объектам PersistDirectory. Это самый важный массив, по которому мы будем находить ссылки на все объекты презентации.

Теперь вернёмся к последнему прочитанному UserEditAtom и найдём там поле docPersistIdRef. Это номер самого важного объекта DocumentContainer в PersistDirectory. Прочитаем его. В нём хранится вагон и маленькая тележка информации о текущей презентации: колонтитулы, заметки к слайдам и главное — запись SlideListWithTextContainer, содержащая всякое разное о слайдах.

Нас будут интересовать всего три типа записей, что хранятся в этом главном блоке: TextCharsAtom, TextBytesAtom и SlidePersistAtom. С первыми два всё несложно: это unicode-текст на слайде и обычный ANSI, соответственно. Другое дело, когда вместо текста получаем ссылку на слайд SlidePersistAtom. По ней мы должны прочитать объект Drawing, который (sic!) не является объектом PPT. Да-да, внутри слайда в этом случае внедрён объект MS Drawing, с достаточно неприятной структуру вложенных записей.

Когда я в первый раз узнал об этом факте, скажу честно, я расстроился. Ещё 600 с гаком страниц документации. Но ODRAW, как оказалось, построен на тех же rh-заголовках с теми же recType'ами, что и PPT. Это позволило облегчить себе задачу и чуть-чуть смухлевать с помощью поиска в Drawing-объекте всё тех же атомов TextCharsAtom и TextBytesAtom по их recType'ам.

Реализация


Код с комментариями вы можете получить на GitHub'е. Пока ещё немножко сыро, но я думаю, что в ближайшее время найду все подводные камни. Основные ошибки выскакивают именно из-за не совсем правильно (?) прочтения PersistDirectory. Если у кого есть уточнения, то я с удовольствием их выслушаю.

Литература




Текст любой ценой


Tags:
Hubs:
+38
Comments 25
Comments Comments 25

Articles