Pull to refresh

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

Reading time 3 min
Views 2.7K
Да, вы не ошиблись, и это не дежавю. Вы наверняка когда-то (если завсегдатай) видели этот топик. С тех пор прошло много времени, а мне продолжают ходить письма с вопросами и просьбами о совете на тему чтения текстовой информации из бинарных форматов данных. А это значит, что тема до сих пор актуальна, интересна для программирующей общественности.

За этот год (а ведь и вправду прошло больше года) я поменял место работы и занимаюсь совершенно другими вещами и давно уже не программирую (много не программирую, если быть точным) на PHP. Новый проект обязал меня совершенствоваться в python'е (и ощутить его силу), поэтому однажды воскресным вечером было решено переписать и, главное, улучшить некоторые из своих библиотек для чтения текста. Сегодня я представлю на суд публики молодой opensource-проект Miette («вкусняшка», если переводить с французского), который призван (в каком-никаком будущем) читать файлы пакета Microsoft Office.

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

Естественно python во многом отличается от PHP и, на мой взгляд, имеет несколько больший функционал, поэтому и принцип построения библиотек в проекте несколько другой, нежели старая «поделка» на PHP. В данном случае было решено запретить себе, как разработчику и заказчику в одном лице, загружать какие-либо большие блоки в память. Мьетт читает данные постепенно, по требованию, как это делает сам Word. Это делает его легковесным и нетребовательным к оперативной памяти. В будущем, я постараюсь пройти исходные profiler'ем и найти узкие горлышка, которые стоит оптимизировать дальше.

Идём дальше?

Я советую просмотреть старую статью и исходники cfb и doc на PHP перед тем, как читать дальше.

Структура проекта


Проект состоит (и в последствии будет состоять) из директорий, каждая из которых содержит reader того или иного типа файлов. Сейчас существует reader на Compound File Binary File Format, который является обёрткой над данными большинства офисных файлов, и для DOC (Microsoft Word). Дальше добавится поддержка XLS и PPT.

CFB содержит два основных объекта — Reader и DirectoryEntry, над которыми построены остальные «читатели». Первый предоставляет интерфейс для работы со «вхождениями в каталог», из которых состоит CFB-хранилище. С помощью класса Reader вы сможете получить доступ к требуемому entry как по имени, так и по номеру. Для корневого вхождения («Root Entry») сделан проброс на атрибут, что, как можно заменить в классе DirectoryEntry, во многом упорядочивает и стандартизирует работу с mini FAT.

DirectoryEntry реализует минимальный интерфейс работы с файлами: read([size]), seak(offset, [whence]) и tell(). Это опять же упрощает работу со «вхождениями» и, в целом, в духе python'а. Вы всё также можете прочесть целое вхождение с помощью read() без параметра, но при чтении нескольких байт вы получите вполне выгодное решение, которые не прочитает ни одного лишнего бита. Кроме того, вы можете обратиться к left/right sibling и child «вхождениям» через соответствующие атрибуты — это делает хождение по дереву CFB удобным и ненавязчивым.

На примере DocTextReader вы можете увидеть пример работы с CFB. Как можно заметить, в отличие от PHP-реализации мы стараемся прочитать меньший объём данных в оперативную память, постоянно перемещаясь по doc-файлу. Нам на помощь приходят дополнительные методы DirectoryEntry get_byte, get_short и get_long, которые читают соответствующее количество байтов с определённого места. Осуществлён проброс основных «вхождений» 0/1Table и WordDocument в качестве атрибутов класса.

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

Пример использования


Ну и напоследок пример использования библиотеки.

from doc.text import DocTextReader

doc = DocTextReader('parus.doc')
root_entry = doc.root_entry
word_document = doc.get_entry_by_name('WordDocument')
one_table = root_entry.child.left_sibling.left_sibling

fc_clx = self.word_document.get_long(0x01a2)

one_table.seek(fc_clx)
print one_table.read(1)
print one_table.tell() # fc_clx + 1

print doc.read()


P.S. Надеюсь я и Miette вас не разочаруют. Следите за обновлениями на GitHub'е :)
Tags:
Hubs:
+35
Comments 17
Comments Comments 17

Articles