PHP

индекс
206,76

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

Некоторое время назад мы с вами обсуждали получение чистого текста из различных форматов данных: будь то 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'ам.

Реализация


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

Литература




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


+38
22 ноября 2009, 19:11
77

комментарии (31)

+1
Kakysha #
Спасибо, очень полезная серия статей, боюсь показать очевидным и невероятным, но неужели следующая статья будет про xls?
0
DonRamon #
Пока, увы, не требуется. Кроме того, я хоть и не разбирался с XLS вообще, но боюсь там будут серьёзные проблемы с формулами, что сделает реализацию достаточно дикой. Могу ошибаться.
0
DonRamon #
А вообще, есть желание переписать свои наработки (WCBFF и иже с ним, и PDF) на Python'е, который сейчас изучаю. Есть мнение, что код получится меньше и понятней.
0
lazycoder #
Есть одна правда — заключается в том, что для xls есть вплоне вменяемая библиотека — как для забивания, так и для потрошения…
http://www.codeplex.com/PHPExcel/. Использовал её для генерации прайсов.
0
DonRamon #
XLSX only. Поправьте меня, если я не прав.
+1
lazycoder #
Нет, почему же?
«Read different file formats into your spreadsheet object

* Excel 2007 (spreadsheetML)
* BIFF5 (Excel 5.0 / Excel 95), BIFF8 (Excel 97 and higher)
* PHPExcel Serialized Spreadsheet
* Excel 2003 XML format
* Symbolic Link (SYLK)
* CSV (Comma Separated Values)»
–1
DonRamon #
Признаю свою ошибку, мельком лишь пробежался.
0
lazycoder #
Да ничего, просто я её юзал для конкретных задач:)
0
lazycoder #
Да ничего, просто я её юзал для конкретных задач:)
0
Treg #
PHPExcel умеет как читать, так и писать xls разных версий
+1
volinrok #
PHPExcelReader умеет парсить XLS файлы:
sourceforge.net/projects/phpexcelreader/
0
maxcom #
XLS хорошо поддерживается в Apache POI
0
Mercury13 #
А я вот делаю импортёр XLS. В пятницу показал первые цифры. Из целой кучи команд реализовал только две — NUMBER и MULRK. Завтра буду исправлять эту недоработку :)
0
DonRamon #
Молодец, я рад, что есть люди, которые страдают занимаются подобными задачами, несмотря на кажущуюся их начальную сложность.
0
AlexeyTokar #
иногда проще купить готовый компонент в систему, чем тратить время(которое обычно — деньги) на создание собственного.

для XLS есть хороший продукт: XLSParserPro
0
DonRamon #
XLSX only. Поправьте меня, если я не прав.
0
AlexeyTokar #
не прав, так как данной библиотекой приходилось пользоваться года 3-4 назад — тогда xlsx мы не видели :)
0
DonRamon #
Ой, простите не Вам. Выше про PHPExcel. Миль пардон )
0
AlexeyTokar #
да. только что перепроверил — правильное название: Php Excel Parser Pro
–7
NEPRICHASTEN #
Всё это задротство, никогда не буду этим заниматься, ибо бред полнейший.
0
Kakysha #
да, фапать гораздо проще
0
DonRamon #
Товарищ, если вы ничем не будете заниматься, то ни за что не будете получать деньги. Так что идите дальше в свой LineAge II дрочить дальше, Вы там царь и бог, я предположу.
+1
Frikazoid #
У товарища сегодня плохое настроение. Я не поленился посмотреть на посты )
Бывает.
0
DonRamon #
Ага, особенно комментарии «чушь не пиши, а?» и «школота» добавляют ему кармы и просто реабилитируют его в моих глазах.
+1
NEPRICHASTEN #
Хотел бы принести извинения автору и читателям за свою оплошность, в результате которой у меня украли аккаунт и начали из-под него безобразничать. Постараюсь восстановить свою репутацию хорошими постами на «Хабр». Извините!
+1
victorb #
Есть мнение что для всех перечисленных форматов файлов (кроме, наверное, pdf) можно использовать open office в headless режиме, как это и сделали парни из Alfresco. Понятно, что это куда более громоздкое решение, но, как говорится, 30 гектар леса разом и под корень.
0
DonRamon #
Вы даже не представляете в каких стеснённых условиях у меня идёт разработка: save_mode, max_execution_time = 30, выключенные shell_exec и иже с ним. Но заказчик не хочет менять хостинг. Что ж работаем на том, что есть… Ясно дело, что можно использовать стороннее, отлаженное, классное. Но иногда не получается, хотя очень хочется.
0
Carry #
Большое спасибо, пригодится не только для PHP.
0
jil #
Не понятно — как выстраивается последовательность того же текста?
на слайдах он может быть абсолютно не упорядочен.
Не получится ли на выходе (в некоторых случаях) текст не поддающийся анализу?
0
DonRamon #
Это ещё предстоит выяснить. В худшем случае, придётся ещё и положение текста читать на странице и исходя из этого делать какие-либо предположения. Как уже говорил, скрипт ещё сыроват — в процессе доработки.
0
great_boba #
В свое время то же интересовался чтением из Майкросовтовских форматов.
Нашел класс ExcelReader и ExcelWriter, которые по-моему находятся в pear

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