Василий Нестеров дата публикации 06-08-2002 20:06 Работаем с MS Word - статистика документов В данном небольшом материале рассматривается вопрос подсчета статистики файлов *.doc и *.rtf. Такой вопрос у меня возник, когда пришлось сделать небольшую базу данных по учету документов, куда надо было заносить и статистику документа - число знаков, слов и т.п. Открывать каждый раз Word, считать статистику и забивать ее в форму ввода было лень, так что пришла в голову мысль это дело автоматизировать. Информации по данному вопросу найти так и не удалось, так что основным источником знаний служили заголовочный файл Word2000.pas и справка по Visual Basic for Applications. Ну и, конечно, множество разных экспериментов.
Сразу оговорюсь, что я не профессиональный программист, так что в тонкости интерфейсов вникать не будем - сам в них не особо разбираюсь. Потому, не мудрствуя лукаво, просто поместим на форме компоненты WordApplication и WordDocument с палитры Servers. Для работы используются свойства и методы этих компонентов.
Встроенная статистика Word подсчитывает статистику обычного текста, обычных и концевых сносок. Для подсчета статистики используется метод компонента WordDocument ComputeStatistic(). Он имеет один параметр, характеризующий, что именно считать, представляющий из себя шестнадцатеричную константу. Константы описаны в заголовочном файле Word2000.pas, он лежит обычно в /Delphi/Ocx/Servers.
Шестнадцатеричная | Символьное обозначение | Смысл |
$00000000 | wdStatisticWords | Количество слов |
$00000001 | wdStatisticLines | Количество строк |
$00000002 | wdStatisticPages | Количество страниц |
$00000003 | wdStatisticCharacters | Знаки без пробелов |
$00000004 | wdStatisticParagraphs | Количество разделов |
$00000005 | wdStatisticCharactersWithSpaces | Знаки с пробелами |
|
Это было основное, что надо знать. Ну а теперь по порядку.
Поместив на форму упомянутые компоненты, видим, что свойств и методов у них совсем мало. В первую очередь следует определиться с методом ConnectKind компонента WordApplication. Оно может принимать различные значения, но мы оставим присваемое по умолчанию значение ckRunningOrNew. Это означает, что соединение происходит с уже работающим сервером, при его отсутствии запускается новый. Как правило, это вполне устраивает.
Первым делом откроем документ. Предварительно надо объявить переменную FileName, она будет типа OleVariant, которой присвоим строку с именем файла.
WordApplication1.Connect;
WordApplication1.Documents.Open(FileName,
EmptyParam,EmptyParam,EmptyParam,
EmptyParam,EmptyParam,EmptyParam,
EmptyParam,EmptyParam,EmptyParam,
EmptyParam,EmptyParam);
WordDocument1.ConnectTo(WordApplication1.ActiveDocument);
Обратите внимание на количество параметров-"пустышек". Их число больше того, которое обычно приводится в книжках. Ну, в моих, во всяком случае. Объясняется это тем, что "книжные" функции предназначены для MS Word 97, а такая запись для работы с Word 2000 и Word XP.
Объявив нужное количество переменных типа LongInt (в очень большом файле или при суммировании по нескольким документам в принципе может оказаться больше знаков, чем пределы обычного целого типа), можем уже и приступать к подсчету. Например, посчитаем число слов, знаков с пробелами и без пробелов обычного текста, а также количество страниц в документе. Результаты сохраним соответственно в "длинных" переменных WCount, SCount, CCount, и PCount.
WCount:=WordDocument1.ComputeStatistics($00000000);
CCount:=WordDocument1.ComputeStatistics($00000003);
SCount:=WordDocument1.ComputeStatistics($00000005);
PCount:=WordDocument1.ComputeStatistics($00000002);
Открыв нужный документ в Word'е и вызвав диалог подсчета статистики, нетрудно увидеть, что значения переменных равны параметрам вордовской статистики со сброшенным флажком "Учитывать все сноски".
Сноски в документах могут быть обычные и концевые. То есть если первые располагаются внизу данной страницы, то концевые - строго в конце документа. Кроме того, они могут отличаться и нумерацией - автоматической или заданной пользователем. Начнем с обычных сносок как с самого простого. В терминологии объектной модели Word - Footnotes. Сначала надо вычислить количество самих сносок:
ifcount:=WordDocument1.DefaultInterface.Footnotes.Count;
Подсчет статистики текста в сноске производится так:
FWCount:=WordDocument1.DefaultInterface.Footnotes.Item(ifoot).Range.ComputeStatistics($00000000);
Здесь ifoot - целое число, "нумерующее" сноску. Для того, чтобы учесть сами номера сносок, сделаем так:
FWCount:=FWCount+WordDocument1.DefaultInterface.Footnotes.Item(ifoot).Reference.ComputeStatistics($00000000);
Это мы посчитали для примера количество слов в сноске с номером ifoot и ее метке - при пользовательской нумерации в качестве "номера" может быть целое предложение. Далее начинаем перебирать их одну за другой. При этом следует учесть, что кроме статистики сносок необходимо получить и статистику их "номеров". То есть:
for ifoot:=1 to ifcount do begin
FWCount:=FWCount+
WordDocument1.DefaultInterface.Footnotes.Item(ifoot).Range.ComputeStatistics($00000000);
FCCount:=FCCount+
WordDocument1.DefaultInterface.Footnotes.Item(ifoot).Range.ComputeStatistics($00000003);
FSCount:=FSCount+
WordDocument1.DefaultInterface.Footnotes.Item(ifoot).Range.ComputeStatistics($00000005);
FCCount:=FCCount+
WordDocument1.DefaultInterface.Footnotes.Item(ifoot).Reference.ComputeStatistics($00000003);
FSCount:=FSCount+
WordDocument1.DefaultInterface.Footnotes.Item(ifoot).Reference.ComputeStatistics($00000005)+1;
if WordDocument1.DefaultInterface.Footnotes.Item(ifoot).Reference.Text<>IntToStr(ifoot)
then begin
FWCount:=FWCount+
WordDocument1.DefaultInterface.Footnotes.Item(ifoot).Reference.ComputeStatistics($00000000);
end;
end;
Прибавление единицы появляется оттого, что сумма статистики сносок и номеров не совпадает с тем, что выдает встроенная статистика Word. Между номером сноски и текстом сноски Word ставит пробел, который почему-то не учитывается. Условный оператор определяет, как пронумерована данная сноска - по умолчанию или нет. В последнем случае следует проверить количество слов в обозначении сноски. Такая схема дает результат, совпадающий со показаниями встроенной статистики. Кроме того, цикл у нас идет от 1 - так начинается нумерация сносок в MS Word, да и практически всех остальных объектов тоже.
Теперь перейдем к концевым сноскам. Теоретически все то же самое, только вместо слова "Footnotes" пишем "Endnotes". И тут наталкиваемся на сюрприз - почему-то оно считает неточно. Я в данном случае поступил так: сохраняю документ под другим именем, переконвертирую концевые сноски в обычные и далее все, как сказано выше. Сохранение документа:
WordDocument1.SaveAs(FileName, FileFormat),
где в скобках стоят два параметра типа OleVariant - имя файла и шестнадцатеричная константа, задающая формат файла. Некоторые константы:
Шестнадцатеричная | Символьное обозначение | Смысл |
$00000000 | wdFormatDocument | Документ Word |
$00000004 | wdFormatDOSText | Простой текст |
$00000006 | wdFormatRTF | Файл RTF |
|
Полный список констант формата можно найти все в том же файле Word2000.pas. И еще один интересный момент - если просто поставить в скобки обе константы, работать не будет. Следует предварительно объявить две переменных, присвоить им соответствующие значения и только потом сохранять.
Ну, а теперь, собственно, можем вернуться к сноскам. Конвертирование концевых сносок в обычные происходит так:
WordDocument1.DefaultInterface.Endnotes.Convert;
Теперь мы имеем документ, в котором содержатся только обычные сноски. С ними никаких проблем не возникает, пример, как с ними работать, см. выше. Если интересует статистика отдельно разных типов сносок, считаем предварительно статистику обычных сносок, сохраняем ее в "буферных" переменных и считаем еще раз после конвертирования. Разница даст статистику концевых сносок по отдельности. Сложив статистику сносок и простого текста, получаем статистику документа с учетом сносок так, как ее дает сам Word.
Тут по традиции несколько покритикуем Microsoft. Как оказалось, Word показывает не все, что содержится в документе. Не принимаются в расчет колонтитулы. А ведь в них может содержаться изрядный кусок текста, особенно в справках, бланках и т.п. Оказывается, Word их на самом деле считает, но нам не показывает. Вот и посмотрим, как же его можно заставить это сделать.
Колонтитулы в документе тесно связаны с несколько загадочной штукой под названием "разделы" - Sections. Каждый раздел может иметь верхние и нижние колонтитулы. Потому первым делом определяем количество абзацев.
isectct:=WordDocument1.DefaultInterface.Sections.Count;
Здесь у нас целые переменные isectct, icofct, icohct обозначают соответственно количество разделов как таковых, количество нижних и верхних колонтитулов данного раздела. Переменная isec служит "номером" раздела, переменные icof, icoh "нумеруют" соответственно нижние и верхние колонтитулы в пределах данного раздела. Количество колонтитулов в разделе определяем так:
icofct:=WordDocument1.DefaultInterface.Sections.Item(isec).Footers.Count;
icohct:=WordDocument1.DefaultInterface.Sections.Item(isec).Headers.Count;
Теперь уже можем "достать" текст из колонтитула:
CBWCount:=
WordDocument1.DefaultInterface.Sections.Item(isec).Footers.Item(icof).Range.ComputeStatistics($00000000);
В данном случае мы для примера посчитали число слов, содержащихся в нижнем колонтитуле под номером icof, принадлежащем разделу под номером isec. Теперь можем написать "двойной" цикл для подсчета статистики верхних и нижних колонтитулов. Полностью это будет выглядеть так:
isectct:=WordDocument1.DefaultInterface.Sections.Count;
for isec:=1 to isectct do begin
icofct:=WordDocument1.DefaultInterface.Sections.Item(isec).Footers.Count;
icohct:=WordDocument1.DefaultInterface.Sections.Item(isec).Headers.Count;
for icof:=1 to icofct do begin
CBWCount:=CBWCount+
WordDocument1.DefaultInterface.Sections.Item(isec).Footers.Item(icof).Range.ComputeStatistics($00000000);
CBCCount:=CBCCount+
WordDocument1.DefaultInterface.Sections.Item(isec).Footers.Item(icof).Range.ComputeStatistics($00000003);
CBSCount:=CBSCount+
WordDocument1.DefaultInterface.Sections.Item(isec).Footers.Item(icof).Range.ComputeStatistics($00000005);
end;
for icoh:=1 to icohct do begin
CHWCount:=CHWCount+
WordDocument1.DefaultInterface.Sections.Item(isec).Headers.Item(icoh).Range.ComputeStatistics($00000000);
CHCCount:=CHCCount+
WordDocument1.DefaultInterface.Sections.Item(isec).Headers.Item(icoh).Range.ComputeStatistics($00000003);
CHSCount:=CHSCount+
WordDocument1.DefaultInterface.Sections.Item(isec).Headers.Item(icoh).Range.ComputeStatistics($00000005);
end;
end;
В переменных, к которым на каждом шаге добавляются результаты статистики, после перебора всех разделов накопится суммарная статистика слов, знаков с пробелами и знаков без пробелов во всех колонтитулах.
Часто использующиеся для рисования схемок текстовые вставки с панели рисования также представляют интерес. Сам Word формально считает их "картинками", не имеющими никакой статистики - видимо, по географическому расположению в панели инструментов. В объектной модели - Shapes. Вот тут начинается самое интересное. Во-первых, все, что находится на панели рисования, является Shapes. То есть в принципе для Word'а все едино, текстовая вставка, объект WordArt или геометрическая фигура. Вместе с тем смотрится довольно нелогично, что этот самый Shape можно переконвертировать, на выбор, во Frame или InlineShape. Они уже обладают статистикой, так что, казалось бы, все в порядке. Но коварству Microsoft, кажется, вообще нет предела. Во-первых с удивлением обнаруживаем, что Shapes нумеруются индексом типа OleVariant. Что с ним дальше делать, неясно. Если просто присваивать индексу целое число, при конвертации каждого второго Shape во Frame получаем ошибку. А если обработать исключение, то будем таки иметь статистику половины вставок. Видимо, есть какие-то тонкости с четными и нечетными индексами. Во-вторых, InlineShape штука и вовсе загадочная. Никаких ошибок при конвертации не возникало, но и количество InlineShapes неизменно оказывалось нулевым. Подсчитать статистику вставок удалось только сохранив файл как RTF и расковыряв его код, но это стоит описать отдельно. Приводился же последний абзац в надежде, что кто-то с таким сталкивался и нашел способ работы с Shapes "встроенными" способами.
Ну, вот практически и все. Суммируя все, что мы получили, имеем статистику документа даже точнее встроенной. Еще пара замечаний. Перед подсчетом Word стоит "спрятать", чтобы он не маячил на экране:
WordApplication1.Visible:=False;
При подсчете статистики, особенно если в документе содержится что-то кроме простого текста, считается, что в файл внесены изменения. Потому напоследок сохраняем и закрываем документ:
WordDocument1.Save;
WordDocument1.Close;
Ну и, конечно, делаем серверу Word Disconnect, когда он станет нам уже не нужен.
А теперь предупреждение тем, кто заинтересовался данным вопросом и хочет поэкспериментировать сам. Офис слишком тесно связан с Windows, потому на сбои в его работе система реагирует крайне остро. При отладке программ подсчета статистики у меня после ошибки часто появлялся "голубой экран". То же самое происходило, если после разбора RTF - файла в поисках Shapes и превращения их в обычный текст в Word загружался неправильно "собранный" файл. Так что очень рекомендуется предварительно сохранить важные данные или поставить систему понадежнее. У меня стоит WindowsXP, который к таким сбоям оказался нечуствителен. Кроме того, пока приведенное здесь не было отлажено, после ошибок частенько летел сам офис. Так что имейте под рукой дистрибутив для запуска диагностики офиса и устранения повреждений.
[Работа с MS Word]
Обсуждение материала [ 10-08-2004 11:21 ] 2 сообщения |