Киби.ru

Живой сайт об актуальных проектах

User Tools

Site Tools


science_and_life:isxodniki

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
science_and_life:isxodniki [2017/02/21 20:32] 77.37.156.52science_and_life:isxodniki [2017/03/13 18:20] (current) – старая версия восстановлена (2008/12/11 13:01) kibi
Line 1: Line 1:
-Наш сервис предоставляет настоящие лайки на фото заказчиков, которые готовы платить за качество.  +====== исходники ====== 
-  + 
-Именно для этого мы и набираем удалённых сотрудников, которые будут выполнять работу, то есть ставить лайки и получить за это деньги.  +<code delphi> 
-  +{excel2djvu - конвертор xls файлов с содержанием журнала в HTML формат для создания DJVU архива. 
-Чтобы стать нашим удалённым сотрудником и начать ставить лайки, зарабатывая при этом 45 рублей за 1 поставленный лайк,  + 
-  +Copyright (C) 2006 by Alexander Sorkin aka Kibi 
-Вам достаточно просто зарегистрироваться на нашем сервисе. > like.zarplatt.ru <  + 
-  +This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 
-Вывод заработанных средств ежедневно в течении нескольких минут.+ 
 +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 
 + 
 +You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. 
 + 
 +                              11.11.2006 
 +                              Alexander Sorkin aka Kibi 
 +                              kibizoid@gmail.com 
 +                              http://kibi.ru 
 + 
 +****************************************************************************************} 
 + 
 +// Исходный код основан на примере работы с Excel человека по имени Злой и опубликованного  
 +//для свободного использования на сайте Королевство Дельфи. 
 + 
 +unit Unit1; 
 + 
 +interface 
 + 
 +uses 
 +  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, 
 +  ComObj, ActiveX, shellapi, FileCtrl, 
 +{$IFDEF VER120} 
 +  OleCtrls, // Почем-то так написано у меня - по-моему для D5 это лишнее. 
 +            // Это склероз или диабет, не помню! 
 +{$ENDIF} 
 +  Excel8TLB, StdCtrls, ExtCtrls, ComCtrls, SHDocVw; 
 + 
 + 
 +type 
 +  TForm1 = class(TForm) 
 +    StatusBar1: TStatusBar; 
 +    OpenDialog1: TOpenDialog; 
 +    PageControl2: TPageControl; 
 +    TabSheet1: TTabSheet; 
 +    Panel1: TPanel; 
 +    html_preview: TWebBrowser; 
 +    TabSheet2: TTabSheet; 
 +    tree_preview: TTreeView; 
 +    SaveDialog1: TSaveDialog; 
 +    Panel2: TPanel; 
 +    PageControl1: TPageControl; 
 +    simple_tab: TTabSheet; 
 +    grp_simple_volume: TGroupBox; 
 +    btn_simple_volume: TButton; 
 +    complex_tab: TTabSheet; 
 +    btn_Initiate_Data: TButton; 
 +    chk_debug_mode: TCheckBox; 
 +    btn_Collect_Data: TButton; 
 +    GroupBox1: TGroupBox; 
 +    Button1: TButton; 
 +    Button2: TButton; 
 +    ErrorLog: TListView; 
 +    TabSheet3: TTabSheet; 
 +    single_volume_tree: TTreeView; 
 +    local_progress: TProgressBar; 
 +    global_progress: TProgressBar; 
 +    edt_volumes: TComboBox; 
 +    btn_Create_Global_Content: TButton; 
 +    Label1: TLabel; 
 +    Bevel1: TBevel; 
 +    chk_make_bookmarks: TCheckBox; 
 +    chk_make_authors: TCheckBox; 
 +    chk_make_rubriks: TCheckBox; 
 +    chk_make_content: TCheckBox; 
 +    procedure FormDestroy(Sender: TObject); 
 +    procedure Initiate_Data(Sender: TObject); 
 +    procedure Local_Content_Create(Sender: TObject); 
 +    procedure FormCreate(Sender: TObject); 
 +    procedure Global_Content_Create(Sender: TObject); 
 +    procedure btn_simple_volumeClick(Sender: TObject); 
 +    procedure btn_Collect_DataClick(Sender: TObject); 
 +    procedure Button1Click(Sender: TObject); 
 +    procedure Button2Click(Sender: TObject); 
 +  private 
 +    { Private declarations } 
 +    FIXLSApp: Excel8TLB._Application; 
 +    FIWorkbook,FIWorkbook2: Excel8TLB._Workbook; 
 +    AllContent, fileDJVU_Bookmarks, file_Global_Volumes_Content, file_All_Authors_Index, file_All_Rubriks_Index:TStream; 
 +    // Все ОНО тут! 
 +    // Оформлено отдельными процедурами из противности. 
 +    // Впрочем, отсюда проще взять для последующей утилизации. 
 +    procedure CreateExcel (NewInstance: boolean); 
 +    procedure ShowExcel; 
 +    //procedure HideExcel; 
 +    procedure ReleaseExcel; 
 +    procedure ShowTree; 
 +    procedure MakeContent; 
 +    procedure ExportHTML(all:boolean); 
 +    procedure CreateBook; 
 +    procedure CreateTOC; 
 +    procedure Load_complex_rubriks; 
 +    procedure Load_rubriks_groups; 
 +    procedure CreateBatches; 
 +    function compare_rubriks(rubriki, test_string:string; var other_rubriks:string; var pos_found:integer):boolean; 
 +    procedure sort_articles; 
 +    procedure ShowMessageInLog(year, volume, page, msg:string); 
 +  public 
 +    { Public declarations } 
 +    // Свойство, спросите Вы? Так это же ООП! 
 +    property IXLSApp: Excel8TLB._Application read FIXLSApp; 
 +    property IWorkbook: Excel8TLB._Workbook read FIWorkbook; 
 +    property IWorkbook2: Excel8TLB._Workbook read FIWorkbook2; 
 +  end; 
 +Const Make_Single = false; 
 +      Make_Global = true; 
 +      min_year = 1990; 
 +      max_year = 2005; 
 +      max_toc_triplet = 7; 
 +      max_complex_rubriks = 5; 
 + 
 +Type ContentElement = class 
 +     public 
 +        year_vol_str : string; 
 +        str_vol : string; 
 +        rubrika : string; 
 +        author : string; 
 +        title : string; 
 +        page : string; 
 +        link : string; 
 +        volume : string; 
 +        year : string; 
 +        function ColoredResult:string; 
 +        constructor Create(rubrika, author, title, page, link, volume, year: string); 
 +     end; 
 + 
 +Type TMyContent = record 
 +        rubrika : string; 
 +        author : string; 
 +        title : string; 
 +        page : string; 
 +        link : string; 
 +        volume : string; 
 +        year : string; 
 +        used_as_slave: boolean; 
 +        used_as_article: array [1..max_complex_rubriks] of boolean; 
 +     end; 
 +     index_element = record 
 +        name : string; 
 +        index : integer; 
 +     end; 
 +     rubr_index_element = record 
 +        name : string; 
 +        index : integer; 
 +        other_rubrik : string; 
 +     end; 
 +     authors_element = record 
 +        name:string; 
 +        index: integer; 
 +        count: integer; 
 +        triplet: string; 
 +        surname: string; 
 +     end; 
 +     rubriks_element = record 
 +        name:string; 
 +        index: integer; 
 +        count: integer; 
 +        sinonim: string; 
 +        common: integer; 
 +        group_number: integer; 
 +        number : integer; 
 +     end; 
 +     triplets_element = record 
 +        triplet:string; 
 +        index: integer; 
 +        count: integer; 
 +        letter: string; 
 +        toc_triplets_index: integer;// связь с массивом toc_triplets - к какому диапазону принадлежит. 
 +     end; 
 +     sinonims_element = record 
 +        rubrika: string; 
 +        sinonim: string; 
 +     end; 
 +     rubriks_type_element = record 
 +        rubrika:string; 
 +        is_table_rubrik:integer; // 1 - если рубрика табличная, 0 - списочная 
 +     end; 
 +     rubriks_group_element = record 
 +        group: string;          // название группы 
 +        rubriks: TStringList;   // перечень рубрик в группе 
 +     end; 
 +  type TRubr_Info = class(TObject) 
 +       public 
 +          info : rubriks_element; 
 +          constructor Create(init_info: rubriks_element); 
 +       end; 
 +var 
 +  Form1: TForm1; 
 +  VolumeContent: array of TMyContent; 
 +  Rubriki:TStringList; 
 +  Rubriks_groups:array of rubriks_group_element; 
 +  prog_path: string; 
 +  simple_mode: boolean; 
 +implementation 
 + 
 +{$R *.DFM} 
 +constructor TRubr_Info.Create(init_info: rubriks_element); 
 +begin 
 +   self.info := init_info; 
 +end; 
 +function ContentElement.ColoredResult:string; 
 +begin 
 +// 
 +end; 
 + 
 +constructor ContentElement.Create(rubrika, author, title, page, link, volume, year: string); 
 +begin 
 +        self.rubrika := rubrika; 
 +        self.author := author; 
 +        self.title := title; 
 +        self.page := page; 
 +        self.link := link; 
 +        self.volume := volume; 
 +        self.year := year; 
 +        if ((year = '1992') and (volume = '05')) then begin 
 +           year_vol_str := '1992_05_06'; 
 +           str_vol := '05-06'; 
 +        end else begin 
 +           year_vol_str := format('%s_%s',[self.year, self.volume]); 
 +           str_vol := self.volume; 
 +        end; 
 +end; 
 + 
 + 
 +procedure TForm1.CreateExcel(NewInstance: boolean); 
 +var IU: IUnknown; 
 +    isCreate: boolean; 
 +begin 
 +  // Tricks: 
 +  //        в случает вызова Excel-а, как сервера автоматизации: 
 +  //          - все автозагружаемые книги не загружаются (и слава богу!); 
 +  //          - создается процесс, но Excel в случае создания нового процесса, 
 +  //            естественно, невидим. 
 +  if not Assigned(IXLSApp) then begin // а зачем его создавать, если уже есть? 
 +    isCreate := NewInstance or 
 +      (not SUCCEEDED( GetActiveObject(Excel8TLB.CLASS_Application_, nil, IU) ) ); 
 +    if isCreate then 
 +      FIXLSApp := CreateComObject(Excel8TLB.CLASS_Application_) as Excel8TLB._Application 
 +    else 
 +      FIXLSApp := IU as Excel8TLB._Application; 
 +  end; 
 +end; 
 +procedure TForm1.ShowMessageInLog(year, volume, page, msg:string); 
 +var newItem:TListItem; 
 +begin 
 +   newItem := ErrorLog.Items.Add; 
 +   newItem.Caption := (inttostr(ErrorLog.Items.Count)); 
 +   newItem.SubItems.Add (format('%s, №%s',[year,volume])); 
 +   newItem.SubItems.Add (page); 
 +   newItem.SubItems.Add (msg); 
 +end; 
 +procedure TForm1.ShowExcel; 
 +begin 
 +  // Tricks: 
 +  //        Приведение к TOLEEnum сделано только для того, чтобы не видеть 
 +  //        warning-ов типа "Constant expression violates subrange bounds" и т.п. 
 +  // 
 +  //        lcid - что такое? Что-то насчет локализации, уже не помню. Надо уточнить! 
 +  //        Но вы всегда можете спокойно передать 0 вместо lcid. 
 +  // 
 +  //        Так насчет показать Excel... Показать просто. Главное корректно показать. 
 +  //        Вроде бы, достаточно просто Visible := true. Черта с два! 
 +  //        Комментируем строки с условием на минимизацию и ScreenUpdating. 
 +  //        Делаем CreateExcel, ShowExcel, минимизируем его (мышью - чем еще?), 
 +  //        возвращаемся в приложение и делаем еще раз ShowExcel! И что? 
 +  //        Насколько я понял его поведение, Visible в таком случае сфокусирует туда, 
 +  //        но окно-то не раскроется. Поэтому проверка на xlMinimize. 
 +  // 
 +  //        Что здесь делает ScreenUpdating? Многие спросят. Делаем: 
 +  //        - раскомментим строки с условием на минимайз; 
 +  //        - CreateExcel; 
 +  //        - ShowExcel; 
 +  //        - закрываем (мышью) Excel (можно через Файл\Закрыть); 
 +  //        - смотрим в процессы - Excel не выгрузился (так оно и понятно - интерфейс не освободили); 
 +  //        - Главное! Делаем ShowExcel - имеем право. 
 +  //        При последнем действии Excel у меня активизируется, но не перерисовывается. 
 +  //        Эту проблему последняя строка и решает. Этот баг я называю "прозрачный Excel"
 +  //        А мы тут полупрозрачные окна рисуем. Наверняка, MS скоро в WinAPI это положит! 
 +  if Assigned(IXLSApp) then begin 
 +    IXLSApp.Visible[0] := true; 
 +    //IXLSApp.Visible[0] := false; 
 +    if IXLSApp.WindowState[0] = TOLEEnum(Excel8TLB.xlMinimized) then 
 +      IXLSApp.WindowState[0] := TOLEEnum(Excel8TLB.xlNormal); 
 +    IXLSApp.ScreenUpdating[0] := true; 
 +    IXLSApp.DisplayAlerts[0] := true; 
 +  end; 
 +end; 
 + 
 +(*procedure TForm1.HideExcel; 
 +begin 
 +  if Assigned(IXLSApp) then begin 
 +    IXLSApp.Visible[0] := false; 
 +  end; 
 +end;*) 
 + 
 +procedure TForm1.ReleaseExcel; 
 +begin 
 +  // Tricks: 
 +  //        Верни пользователю его Excel!!! 
 +  //        Считаем, что если остается хотя бы одна открытая книга, значит пользователь ее хотел 
 +  //        бы потом увидеть. А то ж можно сделать Excel-у Hide и Release. И что? 
 +  //        Если там осталась книга, которая правилась - процесс останется, но в списке приложений 
 +  //        его не увидишь - только в списке процессов. 
 +  //        Так решим эту проблему следующим образом: 
 +  if Assigned(IXLSApp) then begin 
 +    // если есть книга и мы невидимы 
 +    if (IXLSApp.Workbooks.Count > 0) and (not IXLSApp.Visible[0]) then begin 
 +      // положим аккуратненько (правильно написал это слово?) его вниз, 
 +      IXLSApp.WindowState[0] := TOLEEnum(xlMinimized); 
 +      // а потом покажем, 
 +      IXLSApp.Visible[0] := true; 
 +      // ну, и после вернем фокус форме и приложению, а то после Visible - у эксела SetFocus 
 +      // происходит. 
 +      // Собственно говоря, набор свойств Visible, ScreenUpdating, Activate (еще остановимся) и 
 +      // WindowState меня периодически пугает своей неоднозначностью. 
 +      // Дельфийная альтернатива Visible, SetFocus, WindowState работает четко
 +      if not(csDestroying in ComponentState) then Self.SetFocus; // а это объяснять необходимо? 
 +      // Объясню вопросом. Какой SetFocus, если приложение (форма) закрывается? 
 +      Application.BringToFront; 
 +    end; 
 +  end; 
 +  // Tricks: 
 +  // Главное мое правило: поюзал интерфейс - сам его и освободи. 
 +  // Эта привычка выработана давно. Освобождаю даже локально объявленные переменные-интерфейсы. 
 +  // То есть блок finally всегда присутствует с такими присваиваниями. 
 +  // Совет начинающим от начинающего: если чего-то не выгружается - проверь, освобождаются ли 
 +  // все использованные интерфейсы, особенно те, которые в полях классов и глобальных переменных. 
 +  // Кстати, последнее - дурной стиль. 
 +  // Максималист, я знаю. По мне, дурной стиль всякая лишняя глобальная переменная, будь то 
 +  // экземпляр класса, целое число или интерфейс. Я даже сношу описания типа var Form1: TForm1. 
 +  // Для использования Excel у меня существует несколько классов. 
 +  // Это классы XL Report - низкоуровневый набор компонентов, обеспечивающий 
 +  // создание одного отчета по нескольким TDataSet. А вся подсистема отчетов описана еще одним 
 +  // классом, который умеет создать эти самые наборы данных под конкретный отчет, хранить 
 +  // описания этих запросов (Это запросы. Параметризованные, конечно). К тому же, все это 
 +  // завязано на COM и ActiveX Scripting. Это не оттого, что я такой крутой. Просто пора 
 +  // давно понять, что начинать строить настоящие приложения надо с создания интерфейса 
 +  // МоеПриложение.Application. Это не мода, это, по-моему, уже необходимость. 
 +  FIXLSApp := nil; 
 +end; 
 + 
 +procedure TForm1.FormCreate(Sender: TObject); 
 +begin 
 +  prog_path:=ExtractFilePath(ParamStr(0)); 
 +  if not DirectoryExists(prog_path+'bookmarks/') then ForceDirectories(prog_path+'bookmarks/'); 
 +  CreateExcel(false); 
 +  ShowExcel; 
 +  Application.BringToFront; 
 +  if fileexists(prog_path+'!simple_mode.txt') then simple_mode := true else simple_mode := false; 
 +  if not simple_mode then CreateBook; 
 +  if simple_mode then begin 
 +     Simple_tab.TabVisible:=true; 
 +     complex_tab.TabVisible:=false; 
 +     pagecontrol1.ActivePage:=Simple_tab; 
 +  end else begin 
 +     Simple_tab.TabVisible:=false; 
 +     complex_tab.TabVisible:=true; 
 +     pagecontrol1.ActivePage:=complex_tab; 
 +  end; 
 +  Rubriki:=TStringList.Create; 
 +//  Rubriki.LoadFromFile(prog_path+'rubriki.txt'); 
 +end; 
 + 
 +procedure TForm1.FormDestroy(Sender: TObject); 
 +begin 
 +  FIWorkbook.Close(EmptyParam, EmptyParam, EmptyParam,0); 
 +  FIWorkbook:= nil; 
 +  FIWorkbook2:= nil; 
 +  ReleaseExcel; // святое дело 
 +  Rubriki.Free; 
 +end; 
 + 
 +procedure TForm1.CreateBook; 
 +var FullFileName: string; 
 +    x:integer; 
 +    Value: OLEVariant; 
 +begin 
 +  if simple_mode then begin 
 +     OpenDialog1.InitialDir:=prog_path; 
 +     if OpenDialog1.Execute then begin 
 +       FullFileName:=OpenDialog1.FileName; 
 +     end; 
 +  end else FullFileName := prog_path + 'content.xls'; 
 +  if Assigned(IXLSApp) and (not Assigned(IWorkbook) ) then 
 +    try 
 +      try 
 +        FIWorkbook := IXLSApp.Workbooks.Open(FullFileName, 
 +             EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, 
 +             EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, false, 0); 
 +        edt_volumes.Items.Clear; 
 +        edt_volumes.Sorted:=false; 
 +        for x:=1 to IWorkbook.Names.Count do begin // прочесть из экселя список уже существующих именованных диапазонов 
 +           Value:=IWorkbook.Names.Item(x,EmptyParam,EmptyParam).Name; 
 +           edt_volumes.Items.Add(Copy(VarToStr(Value), length('vol_')+1, length(VarToStr(Value))-length('vol_'))); 
 +           // префикс к номеру в списке нам не нужен - чтобы оставались чистые yyyy_mm (имя в экселе не может начинаться с цифры) 
 +        end; 
 +        edt_volumes.Sorted:=true; 
 +        if edt_volumes.Items.Count>0 then edt_volumes.ItemIndex:=0; 
 +      finally 
 +        ShowExcel; 
 +        Application.BringToFront; 
 +      end; 
 +    except 
 +      raise Exception.Create('Не могу открыть книгу!'); 
 +    end; 
 +end; 
 + 
 +procedure TForm1.Initiate_Data(Sender: TObject); 
 +
 +  Простановка именованных диапазонов 
 +  Исходные данные - уже открытый XLS файл, лист "Содержание" 
 +  Конечный результат - именованные диапазоны в XLS файле 
 +  Происходит сброс всех именных диапазонов и сохранение обработанного файла 
 + 
 +  Именно тут надо сделать разбор списка авторов и построение списка "Автор - ссылки на статьи", 
 +  а так же списка "Рубрика - ссылки на статьи" 
 + 
 +  Сам процесс разбора - поэтапный 
 +     - построение неупорядоченного списка 1 автор - 1 ссылка на статью 
 +     - сортировка созданного списка по авторам / статьям 
 +     экспорт в текстовый файл статистики - автор/количество статей - для проверки и построения списка исключений 
 +     + статистика, сколько ссылок у одной рубрики - нужна при составлении дерева рубрик 
 +     + регалии хранить не требуется - автор имеет только фамилию, имя и отчество 
 + 
 +  Запользование этого списка идёт уже на этапе построения авторского указателя и рубрикатора соответственно 
 +  потому как в одной строке не получается хранить больше 1000 символов и 500 ссылок в одну строку на укладываются :( 
 + 
 +  Оглавление по авторам - также три уровня 
 +     - первый уровень - таблица на одну страницу - алфавит с уточнением - диапазонами из трёх букв 
 +       Количество диапазонов - фиксированное - для равномерного заполнения первой страницы 
 +     - второй уровень - полный перечень авторов для каждого диапазона 
 +     - третий - перечень статей для каждого автора (возможно с фоткой и краткой биографией) 
 + 
 + 
 +
 +var vYear, vMonth, vAuthor, vRubrika, Values, prevValue, outValues: OLEVariant; 
 +    ISheet, ISheet_auth_index, ISheet_rubr_index, ISheet_authors, ISheet_rubriks, 
 +    ISheet_triplet, ISheet_sinonims, ISheet_rubriks_type: Excel8TLB._Worksheet; 
 +    IRange: Excel8TLB.Range; 
 +    //IR1, IR2: IxlRange; 
 +    articles: array of TMyContent; 
 +    auth_index:array of index_element; 
 +    authors: array of authors_element; 
 +    triplets: array of triplets_element; 
 +    rubr_index: array of rubr_index_element; 
 +    rubriks: array of rubriks_element; 
 +    sinonims: array of sinonims_element; 
 +    rubriks_type: array of rubriks_type_element; 
 +    x, first,last:integer; 
 +    stroka,prevStroka,prevVolume:string; 
 +    auth_index_counter, // счётчик авторских статей 
 +    authors_counter, // счётчик уникальных авторов 
 +    triplet_counter:integer; // счётчик триплетов 
 +    rubr_index_counter, // счётчик статей с рубриками 
 +    rubriks_counter: integer; // счётчик уникальных рубрик 
 + 
 +  function get_sinonim(rubrika:string):string; 
 +  // вычисляем синоним рубрики (по предварительно составленному вручную списку) 
 +  // список синонимов находится в массиве sinonims 
 +  var x:integer; 
 +  begin 
 +     for x:=0 to length(sinonims)-1 do begin 
 +        if sinonims[x].rubrika=rubrika then begin 
 +           result:=sinonims[x].sinonim; 
 +           exit; 
 +        end; 
 +     end; 
 +     result:=rubrika; 
 +  end; 
 +  function get_rubrik_type(calculated_type:integer; rubrika:string):integer; 
 +  // получаем тип рубрики (по вычисленному типу и предварительно составленному вручную списку) 
 +  // список типов рубрик от пользователя находится в массиве rubriks_type 
 +  var x:integer; 
 +  begin 
 +     for x:=0 to length(rubriks_type)-1 do begin 
 +        if rubriks_type[x].rubrika = rubrika then begin 
 +           result := rubriks_type[x].is_table_rubrik; 
 +           exit; 
 +        end; 
 +     end; 
 +     result := calculated_type; 
 +  end; 
 +procedure process_rubriks(rubriki:string; link:integer); 
 +  function other_rubriks(rubriks:TStringList; i: integer):string; 
 +  var x: integer; 
 +      temp_str:string; 
 +  begin 
 +     temp_str := ''; 
 +     for x:=0 to rubriks.Count-1 do begin 
 +        if x<i then begin // запомнить все рубрики до i - "материнские" рубрики - рубрики более высокого уровня 
 +           if temp_str = '' 
 +              then temp_str := rubriks[x] 
 +              else temp_str := temp_str + ' | ' + rubriks[x]; 
 +        end; 
 +     end; 
 +     result := temp_str; 
 +  end; 
 +  var x:integer; 
 +      temp:string; 
 +      mas:TStringList; 
 +  begin 
 +     if rubriki='' then exit; // пропускаем статьи без рубрик 
 + 
 +     rubriki:=StringReplace(rubriki' | ', '@', [rfReplaceAll]); // получено в вычищенном виде при сборке 
 + 
 +     mas:=TStringList.Create; 
 +     temp:=''; 
 +     for x:=1 to length(rubriki) do begin // разбиваем строку на подстроки, разделитель "@" 
 +       if rubriki[x]='@' then begin 
 +          mas.Add(temp); 
 +          temp:=''; 
 +       end else temp:=temp+rubriki[x]; 
 +     end; 
 +     mas.Add(temp); // добавить последний найденный элемент 
 +     for x := 0 to mas.Count-1 do begin 
 +        setlength(rubr_index,rubr_index_counter+1); // завести место под новый элемент 
 +        rubr_index[rubr_index_counter].name:= get_sinonim(mas[x]); 
 +        rubr_index[rubr_index_counter].index:= link; 
 +        rubr_index[rubr_index_counter].other_rubrik := other_rubriks(mas,x); // сопутствующие рубрики 
 +        inc(rubr_index_counter); 
 +     end; 
 +     mas.Free; 
 +  end; 
 +function extract_surname(name:string):string; 
 +  {фамилия вычленяется следующим образом - берётся конец строки до пробела "М. Иванов", "Михаил Иванов"
 +  Итого, алгоритм критичен к отсутствию пробела после точки - это можно исправить, но тогда не будут появляться разные авторы 
 +  "М.Иванов" и "М. Иванов"
 +  var temp:string; 
 +      x:integer; 
 +  begin 
 +     temp:=''; 
 +     for x:=length(name) downto 1 do begin 
 +        if name[x]=' ' then break; 
 +        temp := name[x] + temp; 
 +     end; 
 +     extract_surname:=temp; 
 +  end; 
 +function check_name(name:string):boolean; 
 +// проверка качества имени - имя должно быть только из русских букв 
 +var x:integer; 
 +    test_str:string; 
 +begin 
 +   for x:=1 to length(name) do begin 
 +      if not (name[x] in ['а'..'я','А'..'Я','ё','Ё','.',' ','''','-']) then begin 
 +         result := false; 
 +         exit; 
 +      end; 
 +   end; 
 +   test_str := extract_surname(name); 
 +   if length(test_str)<3 then begin // триплет должен иметь три символа 
 +      result := false; 
 +      exit; 
 +   end; 
 +   for x:=1 to length(test_str) do begin 
 +      if not (test_str[x] in ['А'..'Я','Ё','''','-']) then begin // ограничения на символы в фамилии 
 +         result := false; 
 +         exit; 
 +      end; 
 +   end; 
 + 
 +   result := true; 
 +end; 
 +function process_authors(rubrika, authors:string; link:integer):boolean; 
 +//link - номер статьи в основном списке (на листе "Содержание"
 +  var x:integer; 
 +      temp:string; 
 +      mas:TStringList; 
 +      ok: boolean; 
 +  begin 
 +     ok := true; 
 +     if authors='' then begin result := ok; exit end; // пропускаем статьи без авторов 
 +     if pos('Рефераты',rubrika)>0 then begin result := ok; exit end; // пропускаем авторов рефератов 
 +     if pos('Вкладк',rubrika)>0 then begin result := ok; exit end; // пропускаем вкладки 
 +     if pos('Обложк',rubrika)>0 then begin result := ok; exit end; // пропускаем обложки 
 +     temp := copy (authors,1,5); 
 + 
 +     authors := StringReplace(authors,' и ','@',[rfReplaceAll, rfIgnoreCase]); 
 +     authors := StringReplace(authors,', ','@',[rfReplaceAll, rfIgnoreCase]); 
 + 
 +     mas:=TStringList.Create; 
 +     temp:=''; 
 +     for x:=1 to length(authors) do begin // разбиваем строку на подстроки, разделитель "@" 
 +       if authors[x]='@' then begin 
 + 
 +          if (length(temp)>0) and (temp[1]=AnsiUpperCase(temp[1])) and  // добавлять только имена (начинаются с заглавной буквы) 
 +             (pos('кадемии',temp)=0) and // никаких "кадемии" в имени нет 
 +             (pos('осударствен',temp)=0) and // никаких "осударствен" в имени нет 
 +             (pos('Украины',temp)=0) // никаких "Украины" в имени нет 
 +             then mas.Add(temp); 
 +          temp:=''; 
 +       end else temp:=temp+authors[x]; 
 +     end; 
 +     if (temp<>'') and (temp[1]=AnsiUpperCase(temp[1])) and  // добавлять только имена (начинаются с заглавной буквы) 
 +        (pos('кадемии',temp)=0) and // никаких "кадемии" в имени нет 
 +        (pos('осударствен',temp)=0) and // никаких "осударствен" в имени нет 
 +        (pos('Украины',temp)=0) // никаких "Украины" в имени нет 
 +        then mas.Add(temp); 
 +     for x := 0 to mas.Count-1 do begin 
 +        setlength(auth_index,auth_index_counter+1); // завести место под новый элемент 
 +        if not check_name(mas[x]) then ok := false; 
 +        auth_index[auth_index_counter].name:= mas[x]; 
 +        auth_index[auth_index_counter].index:= link; 
 +        inc(auth_index_counter); 
 +     end; 
 +     mas.Free; 
 +     result := ok; 
 +  end; 
 +  function detect_rubriks_group(rubrik_name:string):integer; 
 +  var x:integer; 
 +      n: integer; 
 +      results: integer; 
 +  begin 
 +     results:=0; 
 +     for x:=1 to length(Rubriks_Groups)-1 do begin 
 +        if Rubriks_Groups[x].rubriks.Find(rubrik_name, n) then begin 
 +           results := x; 
 +           break; 
 +        end 
 +     end; 
 +     detect_rubriks_group := results; 
 +  end; 
 +  function detect_common_rubriks(i:integer; rubrik_name:string):integer; 
 +     Const min_vol = 1; 
 +           max_vol = 12; 
 +           test_vol = max_vol + 1; 
 +           magic_number = 6; // количество номеров с рубрикой в году, 
 +                             // при котором рубрика считается регулярной 
 +     var x:integer; 
 +         user_year, user_vol:integer; 
 +         all_volumes:array[min_year..max_year, min_vol..max_vol+1] of integer; 
 +  begin 
 +    // Заполнить таблицу ссылок (all_volumes) для рубрики 
 +    FillChar(all_volumes,sizeof(all_volumes),0); // инициализировать таблицу номеров 
 +    for x := rubriks[i].index to rubriks[i].index+rubriks[i].count-1 do begin // прочесать все статьи для рубрики x 
 +       try 
 +          user_year := strtoint(articles[rubr_index[x].index].year); // заполучить год, в котором статья 
 +          user_vol := strtoint(articles[rubr_index[x].index].volume); // заполучить номер, в котором статья 
 +       except 
 +          user_year := 0; 
 +          user_vol := 0; 
 +       end; 
 +       if (user_year >= min_year) and (user_year <= max_year) and 
 +       (user_vol >= min_vol) and (user_vol <= max_vol) 
 +       then begin 
 +          if all_volumes[user_year, user_vol] = 0 then begin 
 +             all_volumes[user_year, user_vol] := 1; // в этом номере есть статьи нужной рубрики 
 +             inc(all_volumes[user_year, test_vol]); // увеличить годовой счётчик 
 +          end; 
 +       end; 
 +    end; 
 +    for x:=min_year to max_year do begin 
 +       if all_volumes[x, test_vol] >= magic_number then begin 
 + 
 +          detect_common_rubriks:=get_rubrik_type(1,rubrik_name); exit; 
 + 
 +       end; 
 +    end; 
 +    detect_common_rubriks:=get_rubrik_type(0,rubrik_name); 
 +  end; 
 +begin 
 +  sort_articles; // отсортировать статьи 
 +  Load_rubriks_groups; // загрузить список групп рубрик 
 +  // загрузить полный список статей 
 + 
 +  auth_index_counter := 1; 
 +  rubr_index_counter := 1; 
 +  global_progress.Min:=0; 
 +  global_progress.max:=15; // основные этапы 
 +  global_progress.Position:=local_progress.Min; 
 +  global_progress.Step:=1; 
 +  if Assigned(IWorkbook) then 
 +    try 
 +      ISheet := IWorkbook.Worksheets.Item['Содержание'] as Excel8TLB._Worksheet; 
 +      if not simple_mode then begin 
 +         ISheet_auth_index := IWorkbook.Worksheets.Item['Авторы'] as Excel8TLB._Worksheet; 
 +         ISheet_auth_index.Cells.Clear; // очистить лист "Авторы" 
 +         ISheet_rubr_index := IWorkbook.Worksheets.Item['Рубрики'] as Excel8TLB._Worksheet; 
 +         ISheet_rubr_index.Cells.Clear; // очистить лист "Рубрики" 
 +         ISheet_authors := IWorkbook.Worksheets.Item['Авторы (свод)'] as Excel8TLB._Worksheet; 
 +         ISheet_authors.Cells.Clear; // очистить лист "Авторы (свод)" 
 +         ISheet_rubriks := IWorkbook.Worksheets.Item['Рубрики (свод)'] as Excel8TLB._Worksheet; 
 +         ISheet_rubriks.Cells.Clear; // очистить лист "Рубрики (свод)" 
 +         ISheet_triplet := IWorkbook.Worksheets.Item['Триплеты'] as Excel8TLB._Worksheet; 
 +         ISheet_triplet.Cells.Clear; // очистить лист "Триплеты" 
 +         // загрузить список типов рубрик (табличная/списочная) 
 +         ISheet_rubriks_type := IWorkbook.Worksheets.Item['Типы рубрик'] as Excel8TLB._Worksheet; 
 +         IRange := ISheet_rubriks_type.UsedRange[0]; 
 +         Values := IRange.Value; 
 +         SetLength(Rubriks_type,IRange.Rows.Count+1); 
 +         global_progress.StepIt; 
 +         local_progress.Min:=0; 
 +         local_progress.max:=IRange.Rows.Count; 
 +         local_progress.Position:=local_progress.Min; 
 +         local_progress.Step:=1; 
 +         statusbar1.simpletext:='Загрузка листа "Типы рубрик"...'; 
 +         for x := 1 to IRange.Rows.Count do begin 
 +            local_progress.stepit; 
 +            with Rubriks_type[x] do begin 
 +               rubrika := Values[x, 1]; 
 +               if (Values[x, 2] = 'таблица') then is_table_rubrik := 1 else is_table_rubrik := 0; 
 +            end; 
 +         end; 
 +         ISheet_rubriks_type := nil; 
 + 
 +         // загрузить список синонимов рубрик 
 +         ISheet_sinonims := IWorkbook.Worksheets.Item['Синонимы'] as Excel8TLB._Worksheet; 
 +         IRange := ISheet_sinonims.UsedRange[0]; 
 +         Values := IRange.Value; 
 +         SetLength(Sinonims,IRange.Rows.Count+1); 
 +         global_progress.StepIt; 
 +         local_progress.Min:=0; 
 +         local_progress.max:=IRange.Rows.Count; 
 +         local_progress.Position:=local_progress.Min; 
 +         local_progress.Step:=1; 
 +         statusbar1.simpletext:='Загрузка листа "Синонимы"...'; 
 +         for x := 1 to IRange.Rows.Count do begin 
 +            local_progress.stepit; 
 +            with Sinonims[x] do begin 
 +               rubrika := Values[x, 1]; 
 +               sinonim := Values[x, 2]; 
 +            end; 
 +         end; 
 +         ISheet_sinonims := nil; 
 +      end; 
 +      try 
 +        for x:=1 to IWorkbook.Names.Count do begin 
 +           IWorkbook.Names.Item(1,EmptyParam,EmptyParam).delete; {удалить все именованные диапазоны} 
 +        end; 
 +        edt_volumes.Items.Clear; 
 +        edt_volumes.Sorted:=false; 
 + 
 + 
 +        // загрузить лист "Содержание" 
 +        IRange := ISheet.UsedRange[0];  // прочесть весь лист 
 +        Values := IRange.Value; 
 +        SetLength(Articles,IRange.Rows.Count+1); // установить длину массива статей 
 +        // загрузить массив статей 
 +        global_progress.StepIt; 
 +        local_progress.Min:=1; 
 +        local_progress.max:=IRange.Rows.Count; 
 +        local_progress.Position:=local_progress.Min; 
 +        local_progress.Step:=1; 
 +        statusbar1.simpletext:='Загрузка массива статей...'; 
 +        for x:=1 to IRange.Rows.Count do begin 
 +          local_progress.StepIt; 
 +          with Articles[x] do begin 
 +             rubrika := Values[x, 1]; 
 +             author := Values[x, 2]; 
 +             title := Values[x, 3]; 
 +             page := Values[x, 4]; 
 +             link := Values[x, 5]; 
 +             volume := Values[x, 6]; 
 +             year := Values[x, 7]; 
 +          end; 
 +        end; 
 +        with Articles[2] do begin 
 +           stroka := year + '_' + volume; first:=2; 
 +           if not simple_mode then begin 
 +              if not process_authors(rubrika,author,2) then // вычленить авторов из описания и положить в список авторов 
 +                 ShowMessageInLog(year,volume,page,format('Некорректное имя автора - "%s"',[author])); 
 +              process_rubriks(rubrika,2); // вычленить рубрики из описания и положить в список рубрик 
 +           end; 
 +        end; 
 + 
 +        global_progress.StepIt; 
 +        local_progress.Min:=3; 
 +        local_progress.max:=IRange.Rows.Count; 
 +        local_progress.Position:=local_progress.Min; 
 +        local_progress.Step:=1; 
 +        statusbar1.simpletext:='Построение списка номеров...'; 
 +        for x:=3 to IRange.Rows.Count do begin 
 +           local_progress.StepIt; 
 +           statusbar1.simpletext:='Построение списка номеров (строка '+inttostr(x)+')...'; 
 +           with Articles[x] do begin 
 +              prevVolume := articles[x-1].volume; 
 +              prevStroka := stroka; 
 +              if not simple_mode then begin 
 +                 if not process_authors(rubrika,author,x) then // вычленить авторов из описания и положить в список авторов 
 +                    ShowMessageInLog(year, volume, page, format('Некорректное имя автора - "%s"',[author])); 
 +                 process_rubriks(Rubrika,x); // вычленить рубрики из описания и положить в список рубрик 
 +              end; 
 +              stroka := Year + '_' + volume; 
 +              if (stroka <> prevStroka) then begin // сменился блок без разделителя 
 +                 statusbar1.simpletext:='Построение списка номеров... ('+prevStroka+')'; 
 +                 last:=x-1; 
 +                 IWorkbook.Names.Add('vol_'+prevStroka, 
 +                                     EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam, 
 +                                     '=Содержание!R'+IntToStr(first)+'C1:R'+IntToStr(last)+'C7',EmptyParam); 
 +                 edt_volumes.Items.Add(prevStroka); 
 +                 first:=x; 
 +              end; 
 +              if (prevVolume = '') and (volume = '') then break; 
 +           end; 
 +        end; 
 +        last:=IRange.Rows.Count; 
 +        if prevStroka<>'_' then begin IWorkbook.Names.Add('vol_'+prevStroka, 
 +                                  EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam,EmptyParam, 
 +                                  '=Содержание!R'+IntToStr(first)+'C1:R'+IntToStr(last)+'C7',EmptyParam); 
 +           edt_volumes.Items.Add(prevStroka); 
 +        end; 
 +        if not simple_mode then begin 
 + 
 +        // выгрузить инфу на лист ISheet_auth_index 
 +        outValues := VarArrayCreate([1, length(auth_index), 1, 2], varVariant); 
 +        for x:=1 to length(auth_index)-1 do begin 
 +           outValues[x, 1] := auth_index[x].name; 
 +           outValues[x, 2] := auth_index[x].index; 
 +        end; 
 +        IRange:= ISheet_auth_index.Range['A1','B'+inttostr(length(auth_index)-1)]; 
 +        IRange.Value := outValues; 
 +        VarClear(outValues); 
 + 
 +        global_progress.StepIt; 
 +        sort_articles; 
 + 
 +        // выгрузить инфу на лист ISheet_rubr_index 
 +        outValues := VarArrayCreate([1, length(rubr_index), 1, 3], varVariant); 
 +        for x:=1 to length(rubr_index)-1 do begin 
 +           outValues[x, 1] := rubr_index[x].name; 
 +           outValues[x, 2] := rubr_index[x].index; 
 +           outValues[x, 3] := rubr_index[x].other_rubrik; 
 +        end; 
 +        IRange:= ISheet_rubr_index.Range['A1','C'+inttostr(length(rubr_index)-1)]; 
 +        IRange.Value := outValues; 
 +        VarClear(outValues); 
 + 
 +        global_progress.StepIt; 
 +        sort_articles; 
 + 
 +        // построение списка уникальных авторов 
 +        IRange := ISheet_auth_index.UsedRange[0];  // прочесть весь лист 
 +        Values := IRange.Value; 
 + 
 +        // то самое место, где жутко тормозит 
 +        vAuthor := Values[1, 1]; first:=1; //last:=0; 
 +        statusbar1.simpletext:='Построение списка авторов...'; 
 +        authors_counter := 1; 
 + 
 +        global_progress.StepIt; 
 +        local_progress.Min:=2; 
 +        local_progress.max:=IRange.Rows.Count; 
 +        local_progress.Position:=local_progress.Min; 
 +        local_progress.Step:=1; 
 +        for x:=2 to IRange.Rows.Count do begin // построить список авторов 
 +            local_progress.StepIt; 
 +            prevValue := vAuthor; 
 +            vAuthor := Values[x, 1]; 
 +            if (vartostr(vAuthor) <> vartostr(prevValue)) then begin // сменился блок 
 +               last := x-1; 
 +               stroka:=extract_surname(VarToStr(prevValue)); 
 + 
 +               setlength(authors,authors_counter+1); // завести место под новый элемент 
 +               with authors[authors_counter] do begin 
 +                  name := vartostr(prevValue); 
 +                  index := first; 
 +                  count := last-first+1; 
 +                  triplet:= copy(stroka,1,3); 
 +                  surname:= stroka; 
 +               end; 
 +               inc(authors_counter); 
 +               first := x; 
 +            end; 
 +        end; 
 +        last := IRange.Rows.Count; 
 +        stroka:=extract_surname(VarToStr(vAuthor)); 
 + 
 +        setlength(authors,authors_counter+1); // завести место под новый элемент 
 +        with authors[authors_counter] do begin 
 +           name := vartostr(vAuthor); 
 +           index := first; 
 +           count := last-first+1; 
 +           triplet:= copy(stroka,1,3); 
 +           surname:= stroka; 
 +        end; 
 +        //inc(authors_counter); 
 + 
 +        // выгрузить инфу на лист ISheet_authors 
 +        outValues := VarArrayCreate([1, length(authors), 1, 5], varVariant); 
 +        for x:=1 to length(authors)-1 do begin 
 +           outValues[x, 1] := authors[x].name; 
 +           outValues[x, 2] := authors[x].index; 
 +           outValues[x, 3] := authors[x].count; 
 +           outValues[x, 4] := authors[x].triplet; 
 +           outValues[x, 5] := authors[x].surname; 
 +        end; 
 +        IRange:= ISheet_authors.Range['A1','E'+inttostr(length(authors)-1)]; 
 +        IRange.Value := outValues; 
 +        VarClear(outValues); 
 + 
 +        sort_articles; 
 +        global_progress.StepIt; 
 + 
 +        // построение списка уникальных рубрик 
 +        IRange := ISheet_rubr_index.UsedRange[0];  // прочесть весь лист 
 +        Values := IRange.Value; 
 + 
 +        // загрузить индекс рубрик назад 
 +        global_progress.StepIt; 
 +        local_progress.Min:=2; 
 +        local_progress.max:=IRange.Rows.Count; 
 +        local_progress.Position:=local_progress.Min; 
 +        local_progress.Step:=1; 
 +        Setlength(Rubr_index,IRange.Rows.Count+1); 
 +        for x := 1 to IRange.Rows.Count do begin 
 +          local_progress.stepit; 
 +          with Rubr_index[x] do begin 
 +             name := Values[x, 1]; 
 +             index := Values[x, 2]; 
 +          end; 
 +        end; 
 + 
 +        vRubrika := Values[1, 1]; first:=1; //last:=0; 
 +        statusbar1.simpletext:='Построение списка рубрик...'; 
 +        rubriks_counter := 1; 
 + 
 +        global_progress.StepIt; 
 +        local_progress.Min:=2; 
 +        local_progress.max:=IRange.Rows.Count; 
 +        local_progress.Position:=local_progress.Min; 
 +        local_progress.Step:=1; 
 +        for x:=2 to IRange.Rows.Count do begin // построить список рубрик 
 +            local_progress.StepIt; 
 +            prevValue := vRubrika; 
 +            vRubrika := Values[x, 1]; 
 +            if (vartostr(vRubrika) <> vartostr(prevValue)) then begin // сменился блок 
 +               last := x-1; 
 +               stroka:=get_sinonim(VarToStr(prevValue)); 
 +               setlength(rubriks,rubriks_counter+1); // завести место под новый элемент 
 +               with rubriks[rubriks_counter] do begin 
 +                  name := vartostr(prevValue); 
 +                  index := first; 
 +                  count := last-first+1; 
 +                  common := detect_common_rubriks(rubriks_counter, stroka); // определить, регулярная ли рубрика 
 +                  group_number := detect_rubriks_group(stroka); // определить группу рубрики 
 +               end; 
 +               inc(rubriks_counter); 
 +               first := x; 
 +            end; 
 +        end; 
 +        last := IRange.Rows.Count; 
 +        stroka:=get_sinonim(VarToStr(vRubrika)); 
 +        // обработать последний элемент 
 +        setlength(rubriks,rubriks_counter+1); // завести место под новый элемент 
 +        with rubriks[rubriks_counter] do begin 
 +           name := vartostr(vRubrika); 
 +           index := first; 
 +           count := last-first+1; 
 +           common := detect_common_rubriks(rubriks_counter, stroka); // определить, регулярная ли рубрика 
 +           group_number := detect_rubriks_group(stroka); // определить группу рубрики 
 +        end; 
 +        //inc(rubriks_counter); 
 + 
 +        // выгрузить инфу на лист ISheet_rubriks 
 +        outValues := VarArrayCreate([1, length(rubriks), 1, 5], varVariant); 
 +        for x:=1 to length(rubriks)-1 do begin 
 +           outValues[x, 1] := rubriks[x].name; 
 +           outValues[x, 2] := rubriks[x].index; 
 +           outValues[x, 3] := rubriks[x].count; 
 +           outValues[x, 4] := rubriks[x].common; 
 +           outValues[x, 5] := rubriks[x].group_number; 
 + 
 +        end; 
 +        IRange:= ISheet_rubriks.Range['A1','E'+inttostr(length(rubriks)-1)]; 
 +        IRange.Value := outValues; 
 +        VarClear(outValues); 
 + 
 +        sort_articles; 
 +        global_progress.StepIt; 
 + 
 +        // построение списка триплетов (трёхбуквенных комбинаций) 
 +        IRange := ISheet_authors.UsedRange[0];  // прочесть весь лист 
 +        Values := IRange.Value; 
 + 
 +        vAuthor := Values[1, 4]; first:=1; //last:=0; 
 +        statusbar1.simpletext:='Построение списка триплетов...'; 
 +        triplet_counter:=1; 
 + 
 +        global_progress.StepIt; 
 +        local_progress.Min:=2; 
 +        local_progress.max:=IRange.Rows.Count; 
 +        local_progress.Position:=local_progress.Min; 
 +        local_progress.Step:=1; 
 +        for x:=2 to IRange.Rows.Count do begin // построить список триплетов 
 +            local_progress.StepIt; 
 +            prevValue := vAuthor; 
 +            vAuthor := Values[x, 4]; 
 +            if vartostr(vAuthor) <> vartostr(prevValue) then begin // сменился блок 
 +               last := x-1; 
 +               stroka:=extract_surname(VarToStr(prevValue)); 
 + 
 +               setlength(triplets,triplet_counter+1); // завести место под новый элемент 
 +               with triplets[triplet_counter] do begin 
 +                  triplet := vartostr(prevValue); 
 +                  index := first; 
 +                  count := last-first+1; 
 +                  letter:= copy(stroka,1,1); 
 +               end; 
 +               inc(triplet_counter); 
 +               first := x; 
 +            end; 
 +        end; 
 +        last := IRange.Rows.Count; 
 +        stroka:=extract_surname(VarToStr(vAuthor)); 
 + 
 +        setlength(triplets,triplet_counter+1); // завести место под новый элемент 
 +        with triplets[triplet_counter] do begin 
 +           triplet := vartostr(vAuthor); 
 +           index := first; 
 +           count := last-first+1; 
 +           letter:= copy(stroka,1,1); 
 +        end; 
 +        //inc(triplet_counter); 
 + 
 +        // выгрузить инфу на лист ISheet_triplet 
 +        outValues := VarArrayCreate([1, length(triplets), 1, 4], varVariant); 
 +        for x:=1 to length(triplets)-1 do begin 
 +           outValues[x, 1] := triplets[x].triplet; 
 +           outValues[x, 2] := triplets[x].index; 
 +           outValues[x, 3] := triplets[x].count; 
 +           outValues[x, 4] := triplets[x].letter; 
 +        end; 
 +        IRange:= ISheet_triplet.Range['A1','D'+inttostr(length(triplets)-1)]; 
 +        IRange.Value := outValues; 
 +        VarClear(outValues); 
 + 
 +        sort_articles; 
 +        end; // simple_mode 
 +        global_progress.StepIt; 
 +        statusbar1.simpletext:='Сохранение файла...'; 
 + 
 +        local_progress.Position := local_progress.max; 
 +        edt_volumes.Sorted:=true; 
 +        if edt_volumes.Items.Count>0 then edt_volumes.ItemIndex:=0; 
 +          //0: Value := ISheet.Cells.Item[2, 1].Value; 
 +          //1: Value := ISheet.Range['A2', EmptyParam].Value; 
 +          //2: Value := ISheet.Range['TestCell', EmptyParam].Value; 
 +          //3: Value := IWorkbook.Names.Item('TestCell', EmptyParam, EmptyParam).RefersToRange.Value; 
 +          //ShowMessageInLog(Value); 
 +      finally 
 +        IRange := nil; 
 +        ISheet := nil; 
 +        ISheet_auth_index := nil; 
 +        ISheet_rubr_index := nil; 
 +        ISheet_authors := nil; 
 +        ISheet_rubriks := nil; 
 +        ISheet_triplet := nil; 
 +        IWorkBook.Save(0); 
 +        setlength(auth_index,0); 
 +        setlength(rubr_index,0); 
 +        setlength(authors,0); 
 +        setlength(rubriks,0); 
 +        setlength(triplets,0); 
 +        setlength(sinonims,0); 
 +        statusbar1.simpletext:='Готово'; 
 +        global_progress.StepIt; 
 +      end; 
 +    except 
 +      raise Exception.Create('Не могу прочитать данные!'); 
 +    end; 
 +    local_progress.Position := local_progress.Max; 
 +    global_progress.Position := global_progress.Max; 
 +end; 
 + 
 +Procedure TForm1.sort_articles; 
 +var ISheet: Excel8TLB._Worksheet; 
 +begin 
 +  if Assigned(IWorkbook) then begin 
 +     ISheet := IWorkbook.Worksheets.Item['Содержание'] as Excel8TLB._Worksheet; 
 +     try 
 +       try 
 +         ISheet.Columns.Sort(ISheet.Range['G1',emptyparam],xlAscending,ISheet.Range['F1',emptyparam],EmptyParam, 
 +         xlAscending,ISheet.Range['D1',emptyparam],xlAscending, 
 +         xlYes, EmptyParam, EmptyParam, 
 +         xlTopToBottom, xlStroke, 
 +         true, true, 
 +         true); 
 +      finally 
 +         ISheet := nil; 
 +       end; 
 +     except 
 +       raise Exception.Create('Не могу отсортировать статьи!'); 
 +     end; 
 + 
 +     if not simple_mode then begin 
 +     ISheet := IWorkbook.Worksheets.Item['Авторы'] as Excel8TLB._Worksheet; 
 +     try 
 +       try 
 +         ISheet.Columns.Sort(ISheet.Range['A1',emptyparam],xlAscending,ISheet.Range['B1',emptyparam],EmptyParam, 
 +         xlAscending,ISheet.Range['C1',emptyparam],xlAscending, 
 +         xlNo, EmptyParam, EmptyParam, 
 +         xlTopToBottom, xlStroke, 
 +         true, true, 
 +         true); 
 +       finally 
 +         ISheet := nil; 
 +       end; 
 +     except 
 +       raise Exception.Create('Не могу отсортировать авторов!'); 
 +     end; 
 + 
 +     ISheet := IWorkbook.Worksheets.Item['Рубрики'] as Excel8TLB._Worksheet; 
 +     try 
 +       try 
 +         ISheet.Columns.Sort(ISheet.Range['A1',emptyparam],xlAscending,ISheet.Range['B1',emptyparam],EmptyParam, 
 +         xlAscending,ISheet.Range['C1',emptyparam],xlAscending, 
 +         xlNo, EmptyParam, EmptyParam, 
 +         xlTopToBottom, xlStroke, 
 +         true, true, 
 +         true); 
 +       finally 
 +         ISheet := nil; 
 +       end; 
 +     except 
 +       raise Exception.Create('Не могу отсортировать рубрики!'); 
 +     end; 
 + 
 + 
 +     ISheet := IWorkbook.Worksheets.Item['Авторы (свод)'] as Excel8TLB._Worksheet; 
 +     try 
 +       try 
 +         ISheet.Columns.Sort(ISheet.Range['D1',emptyparam],xlAscending,ISheet.Range['E1',emptyparam],EmptyParam, 
 +         xlAscending,ISheet.Range['F1',emptyparam],xlAscending, 
 +         xlNo, EmptyParam, EmptyParam, 
 +         xlTopToBottom, xlStroke, 
 +         true, true, 
 +         true); 
 +       finally 
 +         ISheet := nil; 
 +       end; 
 +     except 
 +       raise Exception.Create('Не могу отсортировать свод авторов!'); 
 +     end; 
 + 
 +     ISheet := IWorkbook.Worksheets.Item['Рубрики (свод)'] as Excel8TLB._Worksheet; 
 +     try 
 +       try 
 +         ISheet.Columns.Sort(ISheet.Range['E1',emptyparam],xlAscending,ISheet.Range['A1',emptyparam],EmptyParam, 
 +         xlAscending,ISheet.Range['F1',emptyparam],xlAscending, 
 +         xlNo, EmptyParam, EmptyParam, 
 +         xlTopToBottom, xlStroke, 
 +         true, true, 
 +         true); 
 +       finally 
 +         ISheet := nil; 
 +       end; 
 +     except 
 +       raise Exception.Create('Не могу отсортировать свод рубрик!'); 
 +     end; 
 + 
 + 
 +     end; // simple_mode 
 +  end; 
 +end; 
 + 
 +procedure TForm1.Load_complex_rubriks; 
 +var Values: OLEVariant; 
 +    ISheet: Excel8TLB._Worksheet; 
 +    IRange: Excel8TLB.Range; 
 +    i: integer; 
 +begin 
 +   // загрузить список сборных рубрик 
 +   ISheet := IWorkbook.Worksheets.Item['Сборные рубрики'] as Excel8TLB._Worksheet; 
 +   IRange := ISheet.UsedRange[0]; 
 +   local_progress.Min:=0; 
 +   local_progress.max:=IRange.Rows.Count; 
 +   local_progress.Position:=local_progress.Min; 
 +   local_progress.Step:=1; 
 +   statusbar1.simpletext:='Загрузка списка сборных рубрик...'; 
 +   Values := IRange.Value; 
 +   //global_progress.StepIt; 
 +   Rubriki.Clear; 
 +   for i := 1 to IRange.Rows.Count do begin 
 +      local_progress.stepit; 
 +      Rubriki.Add(Values[i,1]); 
 +   end; 
 +   ISheet := nil; 
 +   statusbar1.simpletext:=''; 
 +end; 
 + 
 + 
 +{заполняет структуру Rubriks_groups на основе информации с листа "Группы рубрик"
 +procedure TForm1.Load_rubriks_groups; 
 +var Values: OLEVariant; 
 +    ISheet: Excel8TLB._Worksheet; 
 +    IRange: Excel8TLB.Range; 
 +    i,j: integer; 
 +    temp_str: string; 
 +begin 
 +   ISheet := IWorkbook.Worksheets.Item['Группы рубрик'] as Excel8TLB._Worksheet; 
 +   IRange := ISheet.UsedRange[0]; 
 +   local_progress.Min:=0; 
 +   local_progress.max:=IRange.Columns.Count; 
 +   local_progress.Position:=local_progress.Min; 
 +   local_progress.Step:=1; 
 +   statusbar1.simpletext:='Загрузка списка типов рубрик...'; 
 +   Values := IRange.Value; 
 +   setlength(Rubriks_groups,IRange.Columns.Count+1); 
 +   Rubriks_groups[0].group := 'Другие рубрики'; 
 +   Rubriks_groups[0].rubriks := TStringList.Create; // создать контейнер для рубрик 
 +   for i := 1 to IRange.Columns.Count do begin 
 +      local_progress.stepit; 
 +      with Rubriks_groups[i] do begin 
 +         group := Values[1,i]; // получить название группы 
 +         rubriks := TStringList.Create; // создать контейнер для рубрик 
 +         rubriks.Sorted := true; 
 +         for j:=2 to IRange.Rows.Count do begin // загрузить рубрики 
 +            temp_str := Values[j,i]; 
 +            if (temp_str <> '') then rubriks.Add(temp_str) else break; 
 +         end; 
 +      end; 
 +   end; 
 +   ISheet := nil; 
 +   statusbar1.simpletext:=''; 
 +end; 
 + 
 +procedure TForm1.Local_Content_Create(Sender: TObject); 
 +var year_vol:string; 
 +    x:integer; 
 +    year, load_year_vol:string; 
 +   procedure LoadContent(year_vol:string); 
 +   var Values: OLEVariant; 
 +       ISheet: Excel8TLB._Worksheet; 
 +       IRange: Excel8TLB.Range; 
 +       i,j: integer; 
 +       stroka: string; 
 +   begin 
 +      try 
 +         ISheet := IWorkbook.Worksheets.Item['Содержание'] as Excel8TLB._Worksheet; 
 +         IRange := ISheet.Range['vol_'+year_vol, EmptyParam]; 
 +         Values := IRange.Value; 
 +         SetLength(VolumeContent,IRange.Rows.Count); 
 +         for i := 1 to IRange.Rows.Count do begin 
 +            with VolumeContent[i-1] do begin 
 +              rubrika := Values[i, 1]; 
 +              author := Values[i, 2]; 
 +              title := Values[i, 3]; 
 +              page := Values[i, 4]; 
 +              link := Values[i, 5]; 
 +              volume := Values[i, 6]; 
 +              year := Values[i, 7]; 
 +              used_as_slave := false; 
 +              for j:=1 to max_complex_rubriks do used_as_article[j] := false; 
 +              stroka:= format('%s * %s * %s * %s * %s * %s * %s', [rubrika, author, title, page, link, volume, year]); 
 +           end; 
 +         end; 
 +      finally 
 +         IRange := nil; 
 +         ISheet := nil; 
 +      end; 
 +   end; 
 +begin 
 +  if Assigned(IWorkbook) then begin 
 +     Load_complex_rubriks; // загрузить список сборных рубрик 
 +     local_progress.Min:=0; 
 +     local_progress.max:=edt_volumes.Items.Count; 
 +     local_progress.Position:=local_progress.Min; 
 +     local_progress.Step:=1; 
 +     for x:=0 to edt_volumes.Items.Count-1 do begin 
 +        AllContent:=TMemoryStream.Create; 
 +        local_progress.StepIt; 
 +        load_year_vol:=edt_volumes.Items[x]; 
 +        year_vol:=load_year_vol; 
 +        year := copy(year_vol, 1, 4); 
 +        if (year_vol = '1992_05') then year_vol := '1992_05_06'; 
 +        statusbar1.simpletext:=format('Создание закладок для %s.djvu...',[year_vol]); 
 +        fileDJVU_Bookmarks:=TFileStream.Create(prog_path+'bookmarks\'+year_vol+'.htm', fmCreate); 
 +        LoadContent(load_year_vol); 
 +        MakeContent; 
 +        ExportHTML(make_single); 
 +        fileDJVU_Bookmarks.CopyFrom(AllContent,0); // скопировать всё оглавление в файл с оглавлением 
 +        fileDJVU_Bookmarks.Free; 
 +        AllContent.Free; 
 +     end; 
 +  end; 
 +  statusbar1.simpletext:='Готово'; 
 +end; 
 + 
 +procedure TForm1.CreateBatches; 
 +var bat_file: TStringList; 
 +    sed_path: string; 
 +    x:integer; 
 +    year, year_vol: string; 
 +function test_path:boolean; 
 +var x:integer; 
 +    results:boolean; 
 +begin 
 +  results := true; 
 +  for x:=1 to length(prog_path) do begin 
 +     if (prog_path[x] in ['А'..'Я', 'а'..'я', 'Ё', 'ё', ' ']) then 
 +     begin 
 +        results := false; 
 +        break; 
 +     end; 
 +  end; 
 +  test_path := results; 
 +end; 
 +begin 
 +  if not test_path then begin 
 +  Application.MessageBox( 
 +    'Путь к рабочей папке содержит нежелательные символы - автоматическая конвертация в DJVU будет невозможна.'+#13#10+#13#10+ 
 +    'Имена папок должны быть набраны латиницей и не содержать пробелов', 
 +    'Предупреждение о проблемах', 
 +    MB_OK); 
 +  end; 
 +  bat_file:=TStringList.Create; 
 + 
 +  // !insert_bookmarks.bat 
 + 
 +  for x:=0 to edt_volumes.Items.Count-1 do begin 
 +     year_vol:=edt_volumes.Items[x]; 
 +     year := copy(year_vol, 1, 4); 
 +     if (year_vol = '1992_05') then year_vol := '1992_05_06'; 
 +     bat_file.Add(format('@call !insert_one.bat %s %s',[year, year_vol])); 
 +  end; 
 +  bat_file.SaveToFile(prog_path+'!insert_bookmarks.bat'); 
 + 
 +  // !insert_one.bat 
 + 
 +  bat_file.Clear; 
 +  bat_file.Add('@echo off'); 
 +  bat_file.Add('echo Inserting bookmarks into %1\%2.djvu'); 
 +  bat_file.Add('bookmark.exe "bookmarks\%2.htm" "%1\%2.djvu"'); 
 +  bat_file.Add('del "%1\%2.djvu"'); 
 +  bat_file.Add('ren "%1\%2.new.djvu" "%2.djvu"'); 
 +  bat_file.SaveToFile(prog_path+'!insert_one.bat'); 
 + 
 +  // !do_all.bat 
 + 
 +  bat_file.Clear; 
 +  bat_file.Add('@call !process_one.bat all_authors'); 
 +  bat_file.Add('@call !process_one.bat all_rubriks'); 
 +  bat_file.Add('@call !process_one.bat all_content'); 
 +  bat_file.Add('@call !insert_bookmarks.bat'); 
 +  bat_file.SaveToFile(prog_path+'!do_all.bat'); 
 + 
 +  // !Process_one.bat 
 + 
 +  bat_file.Clear; 
 +  bat_file.Add('@ECHO OFF'); 
 +  bat_file.Add('echo Converting PDF to DJVU...'); 
 +  bat_file.Add('"pdftodjvu.exe" --dpi=300 --threshold-level=80 --fg-quality=quasilossless --bg-subsample=3 '+ 
 +  '--quality=75 --words --convert-links --fgbg-sep --fg-colors=256 --fg-image-colors=40 --pages-per-dict=20 '+ 
 +  '--force --profile=clean300 --ignore-color-profiles "' + prog_path + '%1.pdf" "' + prog_path + '%1.djvu"'); 
 +  bat_file.Add('echo Extracting links from DJVU...'); 
 +  bat_file.Add('djvused.exe "%1.djvu" -e output-ant > "%1.txt"'); 
 +  bat_file.Add('echo Correcting links...'); 
 + 
 +  // вычислить строку для коррекции ссылок внутри DJVU - коррекция бага Acrobat'
 +  sed_path := prog_path;                                        // G:\NiZh\read_excel\ 
 +  sed_path:=StringReplace(sed_path, ':', '|', [rfReplaceAll]);  // G|\NiZh\read_excel\ 
 +  sed_path:=StringReplace(sed_path, '\', '/', [rfReplaceAll]);  // G|/NiZh/read_excel/ 
 +  sed_path:='../file:///' + sed_path;                           // ../file:///G|/NiZh/read_excel/ 
 +  sed_path:=StringReplace(sed_path, '/', '\/', [rfReplaceAll]); // ..\/file:\/\/\/G|\/NiZh\/read_excel\/ 
 + 
 +  bat_file.Add('sed -e "s/' + sed_path + '//" "%1.txt" > "%1.temp.txt"'); 
 +  bat_file.Add('echo Inserting links into DJVU...'); 
 +  bat_file.Add('djvused.exe "%1.djvu" -f "%1.temp.txt" -s'); 
 +  bat_file.Add('echo Inserting bookmarks into DJVU...'); 
 +  bat_file.Add('bookmark.exe all_bookmarks.htm %1.djvu'); 
 +  bat_file.Add('del "%1.djvu"'); 
 +  bat_file.Add('ren "%1.new.djvu" "%1.djvu"'); 
 +  bat_file.Add('echo Deleting temporary files...'); 
 +  bat_file.Add('del "%1.txt"'); 
 +  bat_file.Add('del "%1.temp.txt"'); 
 +  bat_file.Add('echo %1.djvu is ready.'); 
 +  bat_file.SaveToFile(prog_path+'!process_one.bat'); 
 +  bat_file.Free; 
 +end; 
 + 
 +procedure TForm1.ShowTree; 
 +var i: integer; 
 +    srcNode:TTreeNode; 
 +    stroka:string; 
 +begin 
 +        tree_preview.Items.Clear; 
 +        tree_preview.Items:=single_volume_tree.Items; // перенести из дерева оглавления структуру 
 +        srcNode:=tree_preview.TopItem; 
 +        while srcNode <> nil do begin 
 +           if pos('_',srcNode.Text)=0 then 
 +              i:=StrToInt(srcNode.text) 
 +           else 
 +              i := strToInt(copy(SrcNode.Text,1,pos('_',SrcNode.Text)-1)); 
 +           stroka := ''; 
 +           if (VolumeContent[i].rubrika <> '') then stroka := '[' + VolumeContent[i].rubrika + '] '; 
 +           if (VolumeContent[i].author <> '') then stroka := stroka + '(' + VolumeContent[i].author + ') — '; 
 +           if (VolumeContent[i].title <> '') then stroka := stroka + VolumeContent[i].title; 
 +           if length(stroka)>255 then stroka:=copy(stroka,1,250)+'...'; 
 +           srcNode.text:=stroka;    // и подменить цифры на осмысленный текст 
 +           srcNode:=srcNode.GetNext; 
 +        end; 
 +end; 
 + 
 +procedure TForm1.MakeContent; 
 +var i,y: integer; 
 +    Node, lastNode:TTreeNode; 
 +    stroka, stroka_spec:string; 
 +    special_rubrika, slave, master:boolean; 
 +    other_rubriks:string; 
 +    pos_found:integer; 
 + 
 +   procedure connect_slaves(Node:TTreeNode; master_link: string); 
 +   // собирает все подчинённые статьи (подвёрстки) для данной мастер-статьи 
 +   // на входе - узел мастер-статьи и мастер-ссылка 
 +   var x:integer; 
 +       stroka : string; 
 +       master_flag : boolean; 
 +       counter : integer; 
 +       article_number:integer; 
 +   begin 
 +      master_flag:=true; 
 +      stroka:=master_link; 
 +      counter:=0; 
 +      Delete(stroka,1,1); // получить соответствующий слэйв-линк 
 +      stroka:= 'д' + stroka; 
 +      for x:=0 to length(VolumeContent)-1 do begin 
 +         with VolumeContent[x] do begin 
 +            if link = master_link then inc(counter); // подсчитать кол-во мастер-линков (должен быть один) 
 +            if link = stroka then begin  // найден соответсвующий слэйв 
 +               single_volume_tree.Items.AddChild(Node, IntToStr(x)); 
 +               used_as_slave:=true; 
 +               master_flag:=false; 
 +            end; 
 +         end; 
 +      end; 
 +      // получить номер статьи для вывода сообщения 
 +      if pos('_',Node.Text)=0 then begin 
 +         article_number := StrToInt(Node.Text); 
 +      end else begin 
 +         article_number := strToInt(copy(Node.Text,1,pos('_',Node.Text)-1)); 
 +      end; 
 + 
 +      if master_flag then begin // проверка на "orphan master" - одинокие мастер-статьи 
 +         with VolumeContent[article_number] do begin 
 +            ShowMessageInLog(year, volume, page, format('Не найдено ни одной подвёрстки (%s) для [%s %s %s] (%s)',[stroka, rubrika, author, title, link])); 
 +         end; 
 +      end; 
 +      if counter<>1 then begin 
 +         with VolumeContent[article_number] do begin 
 +            ShowMessageInLog(year, volume, page, format('Главная статья имеет уже использованный номер связи %s [%s %s %s] (%s)',[master_link, rubrika, author, title, link])); 
 +         end; 
 +      end; 
 +   end; 
 + 
 +   procedure connect_subrubriks(Node:TTreeNode; root_rubrika: string); 
 +   // собирает все статьи из сборной рубрики 
 +   // на входе - узел главной статьи и имя сборной рубрики 
 +   var x:integer; 
 +       stroka:string; 
 +       lastNode:TTreeNode; 
 +       master:boolean; 
 +       other_rubriks:string; 
 +       rubr_position:integer; 
 +   begin 
 +      rubr_position := 0; 
 +      for x:=0 to length(VolumeContent)-1 do begin 
 +         with VolumeContent[x] do begin 
 +            if compare_rubriks(rubrika,root_rubrika,other_rubriks,rubr_position) then begin // найдена статья для спецрубрики 
 +               lastNode:=single_volume_tree.Items.AddChild(Node, IntToStr(x)+'_'+root_rubrika); 
 +               used_as_article[rubr_position] := true; 
 +               if (link='') then begin // определить мастер/слэйв 
 +                  master:=false; 
 +               end else begin 
 +                  stroka:=Copy(link,1,1); 
 +                  master:=stroka='а'; 
 +               end; 
 +               if master then connect_slaves(lastNode,link); 
 +            end; 
 +         end; 
 +      end; 
 +   end; 
 +    { 
 +    Нужно модифицировать алгоритм сборки оглавления: 
 +    статья, у которой две сборные рубрики, появляется в оглавлении дважды. 
 +    => признак used_as_article недостаточен для решения. 
 +    нужен массив флагов для всех сборных рубрик "used_as_article #x" 
 + 
 +    Для того, чтобы не делать массив флагов зависимым от списка сборных рубрик, 
 +    надо использовать принцип - "использовано как сборная рубрика для первой рубрики" и 
 +    ограничить количество контролируемых сборных рубрик, например пятью. 
 + 
 +    used_as_article: array [0..5] of boolean; 
 +    for i:=0 to 5 do used_as_slave[i]:=false; 
 +    used_as_article[0] - использована как обычная статья (не из сборных рубрик) 
 +    used_as_article[1] - использована как статья первой спецрубрики и т.д. 
 + 
 +    } 
 +begin 
 +        single_volume_tree.Items.Clear; 
 +        Node:=single_volume_tree.TopItem; 
 +        for i:=0 to length(VolumeContent)-1 do begin 
 +           with VolumeContent[i] do begin 
 +              // определить мастер/слэйв 
 +              if (link='') then begin 
 +                 slave:=false; 
 +                 master:=false; 
 +              end else begin 
 +                 stroka:=Copy(link,1,1); 
 +                 master:=stroka='а'; 
 +                 slave:=stroka='д'; 
 +              end; 
 +              // Узнать, относится ли рубрика к списку специальных 
 +              special_rubrika:=false; 
 +              for y:=0 to Rubriki.Count-1 do begin 
 +                 if compare_rubriks(rubrika,Rubriki.Strings[y],other_rubriks, pos_found) then begin 
 +                    special_rubrika:=true; 
 +                    // проверить, что найдена сборная рубрика, по которой статья ещё не собиралась 
 +                    if not used_as_article[pos_found] then begin 
 +                       stroka_spec:=Rubriki.Strings[y]; // запомнить название спецрубрики, т.к. рубрика может быть сложносочинённой 
 +                       break; 
 +                    end else begin pos_found:=0; stroka_spec:=''; end; 
 +                 end; 
 +              end; 
 +              // на выходе - признак, что есть спецрубрика. 
 +              // stroka_spec - только для случая, когда найдена неиспользованная рубрика 
 +              // pos_found - тоже только тогда, когда найдена неиспользованная рубрика 
 +              if special_rubrika then begin 
 +                 // если найдена спецрубрика и она неиспользованная - подключить спецрубрики 
 +                 if pos_found > 0 then begin 
 +                    if not used_as_article[pos_found] then begin // спецрубрики строятся даже по слэйвам (обложки, вкладки) 
 +                       lastNode:=single_volume_tree.Items.Add(Node, IntToStr(i)+'_'+stroka_spec); // добавить спецрубрику 
 +                       connect_subrubriks(lastNode,stroka_spec); 
 +                    end; 
 +                 end; 
 +              end else begin 
 +                 if not (used_as_article[1]) and not (used_as_slave) and not slave then begin 
 +                    lastNode:=single_volume_tree.Items.Add(Node, IntToStr(i)); // добавить статью 
 +                    used_as_article[1]:=true; 
 +                    if master then connect_slaves(lastNode,link); 
 +                 end; 
 +              end; 
 +           end; 
 +        end; 
 +        // проверка на "orphan slaves" - одинокие подвёрстки 
 +        for i:=0 to length(VolumeContent)-1 do begin 
 +           with VolumeContent[i] do begin 
 +              if (link='') then begin // определить мастер/слэйв 
 +                 slave:=false; 
 +              end else begin 
 +                 stroka:=Copy(link,1,1); 
 +                 slave:=stroka='д'; 
 +              end; 
 +              if not (used_as_slave) and slave then begin 
 +                 stroka:=link; 
 +                 Delete(stroka,1,1); // получить соответствующий мастер-линк 
 +                 stroka:= 'а' + stroka; 
 +                 ShowMessageInLog(year, volume, page, format('Нет главной статьи (%s) для подвёрстки [%s %s %s] (%s)',[stroka, rubrika, author, title, link])); 
 +              end; 
 +           end; 
 +        end; 
 +end; 
 + 
 +function TForm1.compare_rubriks(rubriki, test_string:string; var other_rubriks:string; var pos_found:integer):boolean; 
 +//сравнение рубрик - rubriki - набор рубрик для статьи (разделён " | ") 
 +//                   test_string - искомая рубрика 
 +//        возвращает other_rubriks - набор рубрик без искомой рубрики (разделитель " | ") 
 +//        возвращает pos_found - порядковый номер найденной рубрики в наборе рубрик 
 +var temp:string; 
 +    position:integer; 
 +    counter:integer; 
 +begin 
 +   other_rubriks:=''; 
 +   pos_found := 0; 
 +   counter := 0; 
 +   if rubriki=test_string then begin result:=true; pos_found := 1; exit end; 
 +   if Pos('|',rubriki)=0 then begin result:=false; exit end; 
 + 
 +   rubriki:=StringReplace(rubriki, ' | ', '|', [rfReplaceAll]); // получено в вычищенном виде при сборке 
 + 
 +   result:=false; 
 +   repeat 
 +      position:=Pos('|',rubriki); 
 +      if position>0 then begin 
 +         temp:=Copy(rubriki,1,position-1); // скопировать кусок от начала до найденного "|" 
 +         Delete(Rubriki,1,position); // удалить в рубриках всё, до найденного "|", включая разделитель 
 +         inc(counter); 
 +         if temp=test_string 
 +            then begin // рубрика обнаружена 
 +               pos_found := counter; 
 +               result:=true; 
 +            end else begin // записать найденную рубрику в список "другие рубрики" 
 +               if other_rubriks='' 
 +                  then other_rubriks:=temp 
 +                  else other_rubriks:=format('%s | %s',[other_rubriks, temp]); 
 +            end; 
 +      end; 
 +   until position=0; 
 +   temp:=rubriki; // проверить оставшийся кусок 
 +   inc(counter); 
 +   if temp=test_string 
 +      then begin  // рубрика обнаружена 
 +         pos_found := counter; 
 +         result:=true 
 +      end else begin // записать найденную рубрику в список "другие рубрики" 
 +         if other_rubriks='' 
 +            then other_rubriks:=temp 
 +            else other_rubriks:=format('%s | %s',[other_rubriks, temp]); 
 +      end; 
 +end; 
 + 
 +procedure TForm1.ExportHTML(all:boolean); 
 +var oldLevel:integer; 
 +    curNode:TTreeNode; 
 +    x, y: integer; 
 +    pos_found:integer; 
 +    file_content_name: string; 
 +    special_rubrika:boolean; 
 +    stroka,stroka_spec,other_rubriks:string; 
 +    Flags: OLEVariant; 
 +    One_Volume_Content: TStringList; 
 +    article_number: integer; 
 +    rubrika_name: string; 
 +    year_vol_str, str_vol : string; 
 +    CurElement : ContentElement; 
 +begin 
 +    curNode:=single_volume_tree.TopItem; 
 +    oldLevel:=-1; 
 +    One_Volume_Content := TStringList.Create; 
 +    if all then begin 
 +       with VolumeContent[1] do begin 
 +          if (year = '1992') and  (volume = '05') then begin 
 +             year_vol_str := '1992_05_06'; 
 +             str_vol := '05-06'; 
 +          end else begin 
 +             year_vol_str := year + '_' + volume; 
 +             str_vol := volume; 
 +          end; 
 +          One_Volume_Content.Add(format('<ul><h1 STYLE="{clear: left; PAGE-BREAK-BEFORE: always}"><a href="%1:s/%2:s.djvu" name="nomer_%2:s">Наука и Жизнь №%0:s, %1:s</a></h1></ul>',[str_vol,year,year_vol_str])); 
 +          if FileExists(prog_path+format('face/%0:s.jpg',[year_vol_str])) 
 +             then One_Volume_Content.Add(format('<a href="%0:s/%1:s.djvu"><img src="face/%1:s.jpg" align=right hspace=10 border=0></a>',[year, year_vol_str])) 
 +             else One_Volume_Content.Add(format('<a href="%0:s/%1:s.djvu"><img src="face/unknown.jpg" align=right hspace=10 border=0></a>',[year, year_vol_str])); 
 +       end; 
 +    end else begin 
 +       One_Volume_Content.Add('<LINK REL="stylesheet" TYPE="text/css" HREF="all_content.css" TITLE="style">'); 
 +    end; 
 +    while curNode <> nil do begin 
 +       if oldLevel<curNode.Level then begin // проставить <ul> 
 +          for x:=1 to curNode.Level-oldLevel do begin 
 +             One_Volume_Content.Add('<ul>'); 
 +          end; 
 +       end; 
 +       if oldLevel>curNode.Level then begin // проставить </ul> 
 +          for x:=1 to oldLevel-curNode.Level do begin 
 +             One_Volume_Content.Add('</ul>'); 
 +          end; 
 +       end; 
 +       // тут будет передаваться инфа о том, какая рубрика в данной статье является сборной. 
 +       // передаваться в виде s1 (s2, s3, s4, s5 и т.д.) 
 +       if pos('_',curNode.Text)=0 then begin 
 +          article_number := StrToInt(curNode.Text); 
 +          rubrika_name :=''; 
 +       end else begin 
 +          article_number := strToInt(copy(CurNode.Text,1,pos('_',curNode.Text)-1)); 
 +          rubrika_name := copy(CurNode.Text,pos('_',curNode.Text)+1,length(curnode.text)-pos('_',curNode.Text)+1); 
 +       end; 
 +       with VolumeContent[article_number] do begin 
 +          CurElement := ContentElement.Create(rubrika, author, title, page, link, volume, year); 
 +          if (year = '1992') and  (volume = '05') then begin 
 +             year_vol_str := '1992_05_06'; 
 +             str_vol := '05-06'; 
 +          end else begin 
 +             year_vol_str := year + '_' + volume; 
 +             str_vol := volume; 
 +          end; 
 +          if rubrika_name <> '' then begin 
 +             special_rubrika:=true; // рубрика относится к списку специальных 
 +             compare_rubriks(rubrika,rubrika_name,other_rubriks,pos_found); 
 +             stroka_spec:=rubrika_name; // запомнить название спецрубрики, т.к. рубрика может быть сложносочинённой 
 +          end else begin 
 +             special_rubrika:=false; 
 +          end; 
 +          if chk_debug_mode.Checked then begin // режим вывода отладочного оглавления 
 +             if special_rubrika and (curNode.Level=0) then begin 
 +                stroka:=format('[%s] <font color=red> - сборная рубрика</font>',[stroka_spec]) 
 +             end else begin; // вывести всю инфу по ссылке 
 +                stroka:=''; 
 +                if (rubrika <> '') then stroka := '[<font color=blue>' + rubrika + '</font>] '; 
 +                if (author <> '') then stroka := stroka + '(<font color=green>' + author + '</font>) — '; 
 +                if (title <> '') then stroka := stroka + title + ' '; 
 +                if stroka = '' then stroka := '<font color=blue><i>нет информации</i></font> '; 
 +                if (link <> ''
 +                   then stroka := stroka + '(стр. '+ page + 'связь "<font color=red>' + link + '</font>")' 
 +                   else stroka := stroka + '(стр. '+ page + ')'; 
 +             end; 
 +          end else begin // обычный режим вывода оглавления 
 +             if all then begin // вывод сборного оглавления 
 +                if special_rubrika then begin // спецрубрика 
 +                   if (curNode.Level=0) then // спецрубрика в корне 
 +                      stroka:=format('[<font color=blue>%s</font>]',[stroka_spec]) 
 +                   else begin // статья в спецрубрике 
 +                      if stroka_spec <> rubrika then begin // статья со сложносочинённой рубрикой 
 +                         if (author='') then begin // other_rubriks 
 +                            if (title=''
 +                               then stroka:=format('[<font color=blue>%s</font>]',[other_rubriks]) 
 +                               else stroka:=format('[<font color=blue>%s</font>] — %s',[other_rubriks, title]); 
 +                         end else begin 
 +                            if (title=''
 +                               then stroka:=format('[<font color=blue>%s</font>] (<font color=green>%s</font>)',[other_rubriks, author]) 
 +                               else stroka:=format('[<font color=blue>%s</font>] (<font color=green>%s</font>) — %s',[other_rubriks, author, title]); 
 +                         end; 
 +                      end else begin 
 +                         if (author='') then begin 
 +                            if (title=''
 +                               then stroka:=stroka_spec 
 +                               else stroka:=title; 
 +                         end else begin 
 +                            if (title=''
 +                               then stroka:=format('(<font color=green>%s</font>)',[author]) 
 +                               else stroka:=format('(<font color=green>%s</font>) — %s',[author, title]); 
 +                         end; 
 +                      end; 
 +                   end; 
 +                end else begin // обычная рубрика 
 +                   if (rubrika='') then begin 
 +                      if (author='') then begin 
 +                         if (title='') then stroka:='-=*=-' // заменитель для пустой ссылки 
 +                         else stroka:=title; 
 +                      end else begin 
 +                         if (title='') then stroka:=format('(<font color=green>%s</font>)',[author]) 
 +                         else stroka:=format('(<font color=green>%s</font>) — %s',[author, title]); 
 +                      end; 
 +                   end else begin // есть название рубрики 
 +                      if (author='') then begin // но нет автора 
 +                         if (title='') then stroka:=format('[<font color=blue>%s</font>]',[rubrika]) 
 +                         else stroka:=format('[<font color=blue>%s</font>] — %s',[rubrika,title]); 
 +                      end else begin 
 +                         if (title='') then stroka:=format('(<font color=green>%s</font>)',[author]) 
 +                         else stroka:=format('(<font color=green>%s</font>) — %s',[author, title]); 
 +                      end; 
 +                   end; 
 +                end; 
 +             end else begin // вывод оглавления для одного номера 
 +                if special_rubrika then begin // спецрубрика 
 +                   if (curNode.Level=0) then // спецрубрика в корне 
 +                      stroka:=format('[%s]',[stroka_spec]) 
 +                   else begin // статья в спецрубрике 
 +                      if stroka_spec <> rubrika then begin // статья со сложносочинённой рубрикой 
 +                         if (author='') then begin // other_rubriks 
 +                            if (title=''
 +                               then stroka:=format('[%s]',[other_rubriks]) 
 +                               else stroka:=format('[%s] — %s',[other_rubriks, title]); 
 +                         end else begin 
 +                            if (title=''
 +                               then stroka:=format('[%s] — %s',[other_rubriks, author]) 
 +                               else stroka:=format('[%s] — %s — %s',[other_rubriks, author, title]); 
 +                         end; 
 +                      end else begin 
 +                         if (author='') then begin 
 +                            if (title=''
 +                               then stroka:=stroka_spec 
 +                               else stroka:=title; 
 +                         end else begin 
 +                            if (title=''
 +                               then stroka:=author 
 +                               else stroka:=format('%s — %s',[author, title]); 
 +                         end; 
 +                      end; 
 +                   end; 
 +                end else begin // обычная рубрика 
 +                   if (rubrika='') then begin 
 +                      if (author='') then begin 
 +                         if (title='') then stroka:='-=*=-' // заменитель для пустой ссылки 
 +                         else stroka:=title; 
 +                      end else begin 
 +                         if (title='') then stroka:=author 
 +                         else stroka:=format('%s — %s',[author, title]); 
 +                      end; 
 +                   end else begin // есть название рубрики 
 +                      if (author='') then begin // но нет автора 
 +                         if (title='') then stroka:=format('[%s]',[rubrika]) 
 +                         else stroka:=format('[%s] — %s',[rubrika,title]); 
 +                      end else begin 
 +                         if (title='') then stroka:=author 
 +                         else stroka:=format('%s — %s',[author, title]); 
 +                      end; 
 +                   end; 
 +                end; 
 +             end; 
 +          end; 
 +          if all 
 +             then stroka:=format('<li><a href="%0:s/%1:s.djvu#%2:s">%3:s</a></li>',[year,year_vol_str,page,stroka]) 
 +             else stroka:=format('<li><a href="#%s">%s</a></li>',[page,stroka]); 
 +          One_Volume_Content.Add(stroka); 
 +          CurElement.Free; 
 +       end; 
 +       oldLevel:=curNode.Level; 
 +       curNode:=curNode.GetNext; 
 +    end; 
 +    for x:=1 to oldLevel+1 do begin // проставить завершающие </ul> 
 +       One_Volume_Content.Add('</ul>'); 
 +    end; 
 +    //if all then begin 
 +       One_Volume_Content.SaveToStream(AllContent); 
 +    {end else begin 
 +       file_content_name:=prog_path+edt_volumes.Items[edt_volumes.ItemIndex]+'.htm'; 
 +       One_Volume_Content.SaveToFile(file_content_name); 
 +       // открыть созданный Html во встроенном редакторе 
 +       Flags := 0; 
 +       html_preview.Navigate(WideString(file_content_name), Flags, Flags, Flags, Flags); 
 +       if chk_embed_bookmarks.Checked then begin 
 +          ShellExecute(GetDesktopWindow(), 'open', PChar(prog_path + 'make.bat'), 
 +          PChar(file_content_name + ' ' + edt_volumes.Items[edt_volumes.ItemIndex] + '.djvu ' + edt_volumes.Items[edt_volumes.ItemIndex] + '.new.djvu'), 
 +          nil, SW_HIDE); 
 +       end; 
 +    end;  } 
 +    One_Volume_Content.Free; 
 +end; 
 + 
 +procedure TForm1.CreateTOC; // создание содержания для глобального оглавления по номерам 
 +                            // используется инфа из edt_volumes 
 +                            // результат записывается в file_Global_Volumes_Content 
 +Const min_vol = 1; 
 +      max_vol = 12; 
 +      test_vol = max_vol + 1; 
 +var MyContentTOC:TStringList; 
 +    x,y,year_count:integer; 
 +    user_year, user_vol:integer; 
 +    str_vol, img_vol: string; 
 +    style:string; 
 +    all_volumes:array[min_year..max_year, min_vol..max_vol+1] of boolean; 
 +    djvu_name:string; 
 +    year_vol_str:string; 
 +begin 
 +    MyContentTOC:=TStringList.Create; 
 +    FillChar(all_volumes,sizeof(all_volumes),0); // инициализировать таблицу номеров 
 +    for x := 0 to edt_volumes.Items.Count-1 do begin // загрузить инфу по номерам и годам 
 +       try 
 +          user_year := StrToInt(copy(edt_volumes.Items[x],1,4)); 
 +          user_vol := StrToInt(copy(edt_volumes.Items[x],6,2)); 
 +       except 
 +          ShowMessageInLog('----', '--', '---', format('Год или номер журнала являются некорректными (%s)',[edt_volumes.Items[x]])); 
 +       end; 
 +       if (user_year >= min_year) and (user_year <= max_year) and 
 +       (user_vol >= min_vol) and (user_vol <= max_vol) 
 +       then begin 
 +          all_volumes[user_year, user_vol] := true; // есть конкретный номер 
 +          all_volumes[user_year, test_vol] := true; // есть хоть один номер за определённый год 
 +       end; 
 +    end; 
 +    // Создать заголовок 
 +    MyContentTOC.Add('<LINK REL="stylesheet" TYPE="text/css" HREF="all_content.css" TITLE="style">'); 
 +    MyContentTOC.Add(format('<ul><H1>Наука и Жизнь %d - %d</H1>',[min_year, max_year])); 
 +    if chk_debug_mode.Checked then begin 
 +       MyContentTOC.Add('<div align=right><font size=-1><i>Оглавление создано в режиме проверки, формат статей:'); 
 +       MyContentTOC.Add('<br><b>[<font color=blue>Рубрика 1 | Рубрика 2</font>] - (<font color=green>Автор</font>) - Название статьи (страница, <font color=red>связь</font>)</b></i></font></div>'); 
 +    end; 
 +    // создать оглавление первого уровня (таблица номеров) 
 +    MyContentTOC.Add('<table class="toc" style="font-size=-2" width=100% height=80% cellspacing=0 cellpadding=0>'); 
 +    MyContentTOC.Add('<tr align=center><td class="hd">&nbsp;</td>'); 
 +    for y := min_vol to max_vol do begin 
 +       if y<10 
 +          then MyContentTOC.Add(format('<td class="hd">№0%d</td>',[y])) 
 +          else MyContentTOC.Add(format('<td class="hd">№%d</td>',[y])); 
 +    end; 
 +    MyContentTOC.Add('</tr>'); 
 +    for x := max_year downto min_year do begin 
 +       if (x mod 2) = 0 then style := 'odd' else style := 'even'; 
 +       if all_volumes[x,test_vol] 
 +          then MyContentTOC.Add(format('<tr align=center class="%0:s"><td class="toc"><a href="#year_%1:d">%1:d год</a></td>',[style,x])) 
 +          else MyContentTOC.Add(format('<tr align=center class="%0:s"><td class="toc">%1:d год</td>',[style,x])); 
 +       for y := min_vol to max_vol do begin 
 +          if y<10 
 +             then year_vol_str := format('%d_0%d',[x,y]) 
 +             else year_vol_str := format('%d_%d',[x,y]); 
 +          if (year_vol_str = '1992_05') and (all_volumes[x,y]) then begin // сдвоенный номер 1992 05-06 
 +             year_vol_str := '1992_05_06'; 
 +             MyContentTOC.Add(format('<td class="toc" colspan=2><a href="#nomer_%s">-=&gt;&lt;=-</a></td>',[year_vol_str])); 
 +             continue; // перейти к следующему номеру 
 +          end; 
 + 
 +          if (x = 1992) and (y = 6) and (all_volumes[x,y-1]) then continue; // пропустить номер 1992 6 
 + 
 +          if all_volumes[x,y] then begin 
 +             MyContentTOC.Add(format('<td class="toc"><a href="#nomer_%s">-=&gt;&lt;=-</a></td>',[year_vol_str])) 
 +          end else begin 
 +             djvu_name := format('%s%d/%s.djvu',[prog_path,x,year_vol_str]); 
 +             if (chk_debug_mode.checked) and (fileexists(djvu_name)) // в режиме проверки для имеющихся в DJVU номеров создавать ссылки в таблице номеров 
 +                then MyContentTOC.Add(format('<td class="toc"><a href="%s">djvu</a></td>',[djvu_name])) 
 +                else MyContentTOC.Add('<td class="toc">&nbsp;</td>'); 
 +          end; 
 +       end; 
 +       MyContentTOC.Add('</tr>'); 
 +    end; 
 +    MyContentTOC.Add('</table></ul>'); 
 + 
 +    // создать оглавление второго уровня (годовые подшивки) 
 +    MyContentTOC.Add('<ul>'); 
 +    year_count:=0; 
 +    for x:=max_year downto min_year do begin 
 +       if all_volumes[x, test_vol] then begin // если есть номера для данного года 
 +          inc(year_count); 
 +          if (year_count mod 2) = 1 
 +             then MyContentTOC.Add(format('<H2 STYLE="{clear: left; PAGE-BREAK-BEFORE: always;}">Наука и Жизнь, %0:d год</H2><A NAME="year_%0:d"></A>',[x])) 
 +             else MyContentTOC.Add(format('<H2 STYLE="{clear: left;}">Наука и Жизнь, %0:d год</H2><A NAME="year_%0:d"></A>',[x])); 
 +          for y:=min_vol to max_vol do begin 
 +             if y=7 then MyContentTOC.Add('<br STYLE="{clear: left;}">'); 
 +             if (x = 1992) and (y = 6) and (all_volumes[x,y-1]) then continue; // пропустить номер 1992 6 
 +             if all_volumes[x,y] then begin // если есть конкретный номер, вывести для него ссылку 
 +                if y<10 
 +                   then year_vol_str := format('%d_0%d',[x,y]) 
 +                   else year_vol_str := format('%d_%d',[x,y]); 
 + 
 +                if y < 10 then str_vol := format('0%d',[y]) else str_vol := format('%d',[y]); 
 + 
 +                if (year_vol_str = '1992_05') then begin 
 +                   str_vol := '05-06'; 
 +                   year_vol_str := '1992_05_06'; 
 +                end; 
 + 
 +                if FileExists(format('%sface/%s_mini.jpg',[prog_path,year_vol_str])) 
 +                   then img_vol := format('%s_mini.jpg',[year_vol_str]) 
 +                   else img_vol:='unknown_mini.jpg'; 
 +                MyContentTOC.Add(format( 
 +                   '<table border=0 align=left><tr align=center><td><a href="#nomer_%3:s">'
 +                   '<img src="face/%2:s" border=0></a></td></tr><tr align=center><td>'
 +                   '<a href="#nomer_%3:s">№%1:s, %0:d</a></td></tr></table>', 
 +                   [x,str_vol,img_vol,year_vol_str])); 
 +             end else begin // номера нет - вывести затычку 
 +                if y < 10 then str_vol := format('0%d',[y]) else str_vol := format('%d',[y]); 
 +                img_vol := 'absent_mini.gif'; 
 +                MyContentTOC.Add(format( 
 +                   '<table border=0 align=left><tr align=center><td>'
 +                   '<img src="face/%2:s" border=0></td></tr><tr align=center><td>'
 +                   '&nbsp;</td></tr></table>', 
 +                   [x,str_vol,img_vol])); 
 +             end; 
 +          end; 
 +       end; 
 +    end; 
 +    MyContentTOC.Add('</ul>'); 
 +    MyContentTOC.SaveToStream(file_Global_Volumes_Content); 
 +    MyContentTOC.Free; 
 +end; 
 + 
 +procedure TForm1.Global_Content_Create(Sender: TObject); 
 +  Procedure per_volumes; // создание глобального оглавления по номерам (all_content.htm) 
 +  var Values: OLEVariant; 
 +    ISheet: Excel8TLB._Worksheet; 
 +    IRange: Excel8TLB.Range; 
 +    i,z,j: integer; 
 +    Flags: OLEVariant; 
 +  begin 
 +  if Assigned(IWorkbook) then 
 +    //try 
 +      ISheet := IWorkbook.Worksheets.Item['Содержание'] as Excel8TLB._Worksheet; 
 +      file_Global_Volumes_Content:=TFileStream.Create(prog_path+'all_content.htm', fmCreate); 
 +      AllContent:=TMemoryStream.Create; 
 +      tree_preview.items.Clear; 
 +      try 
 +        local_progress.Min:=0; 
 +        local_progress.max:=edt_volumes.Items.Count; 
 +        local_progress.Position:=local_progress.Min; 
 +        local_progress.Step:=1; 
 +        for z:=edt_volumes.Items.Count-1 downto 0 do begin // пройтись по всем выпускам 
 +           local_progress.StepIt; 
 +           IRange := ISheet.Range['vol_'+edt_volumes.Items[z], EmptyParam]; 
 +           Values := IRange.Value; 
 +           SetLength(VolumeContent,IRange.Rows.Count); 
 +           for i := 1 to IRange.Rows.Count do begin 
 +             with VolumeContent[i-1] do begin 
 +                rubrika := Values[i, 1]; 
 +                author := Values[i, 2]; 
 +                title := Values[i, 3]; 
 +                page := Values[i, 4]; 
 +                link := Values[i, 5]; 
 +                volume := Values[i, 6]; 
 +                year := Values[i, 7]; 
 +                used_as_slave := false; 
 +                for j :=1 to max_complex_rubriks do used_as_article[j] := false; 
 +                //stroka:= format('%s | %s | %s | %s | %s | %s | %s', [rubrika, author, title, page, link, volume, year]); 
 +             end; 
 +           end; 
 +           MakeContent; 
 +           ExportHTML(make_global); 
 +        end; 
 +        local_progress.StepIt; 
 +        CreateTOC; 
 +        local_progress.Position:=local_progress.Min; 
 +        file_Global_Volumes_Content.CopyFrom(AllContent,0); // скопировать всё оглавление в файл с оглавлением 
 +      finally 
 +        IRange := nil; 
 +        ISheet := nil; 
 +        AllContent.Free; 
 +        file_Global_Volumes_Content.Free; 
 +        tree_preview.Items.Clear; 
 +        single_volume_tree.Items.Clear; 
 +        // открыть созданный Html во встроенном редакторе 
 +        Flags := 0; 
 +        html_preview.Navigate(WideString(prog_path+'all_content.htm'), Flags, Flags, Flags, Flags); 
 +      end; 
 +    //except 
 +      //raise Exception.Create('Не могу прочитать данные в массив!'); 
 +    //end; 
 +  end; 
 +  Procedure per_authors; // создание глобального авторского указателя (all_authors.htm) 
 +//  type all_triplets_element: 
 +  var Values: OLEVariant; 
 +    ISheet, ISheet_auth_index, ISheet_authors, ISheet_triplet: Excel8TLB._Worksheet; 
 +    articles: array of TMyContent; 
 +    auth_index:array of index_element; 
 +    authors: array of authors_element; 
 +    triplets: array of triplets_element; 
 +    odd_even:boolean; 
 +    min_tripl, max_tripl: integer; // индексы для простановки диапазонов триплетов 
 +    toc_triplets:array [ord('А')..ord('Я'), 0..max_toc_triplet] of string; // столбец '0' - признак, что столбец заполнен 
 +    prevIndex, page_counter, rows_counter : integer; 
 + 
 +    vYear, vMonth, vAuthor, prevValue, vRubrika: OLEVariant; 
 +    x,first,last:integer; 
 +    stroka, prevStroka:string; 
 +    auth_index_counter, // счётчик авторских статей 
 +    authors_counter, // счётчик уникальных авторов 
 +    triplet_counter:integer; // счётчик триплетов 
 +    Authors_TOC:TStringList; // место для создания главной таблицы (верхний уровень) 
 +    Authors_Names:TStringList; // место для создания списка фамилий (средний уровень) 
 +    Authors_Articles:TStringList;// место для создания блоков ссылок на статьи (нижний уровень) 
 +    IRange: Excel8TLB.Range; 
 +    i,j,z: integer; 
 +    Flags: OLEVariant; 
 +    year_vol_str, str_vol: string; 
 + 
 +  begin 
 +  if Assigned(IWorkbook) then 
 +    try 
 +      ISheet := IWorkbook.Worksheets.Item['Содержание'] as Excel8TLB._Worksheet; 
 +      ISheet_auth_index := IWorkbook.Worksheets.Item['Авторы'] as Excel8TLB._Worksheet; 
 +      ISheet_authors := IWorkbook.Worksheets.Item['Авторы (свод)'] as Excel8TLB._Worksheet; 
 +      ISheet_triplet := IWorkbook.Worksheets.Item['Триплеты'] as Excel8TLB._Worksheet; 
 + 
 +      Authors_TOC := TStringList.Create; // место для создания главной таблицы (верхний уровень) 
 +      Authors_Names := TStringList.Create; // место для создания списка фамилий (средний уровень) 
 +      Authors_Articles := TStringList.Create; // место для создания блоков ссылок на статьи (нижний уровень) 
 + 
 +      file_All_Authors_Index:=TFileStream.Create(prog_path+'all_authors.htm', fmCreate); 
 +      try 
 +        // создать конечный перечень - имя автора (заголовок, ссылка вида #author_), перечень статей этого автора. 
 + 
 +        // загрузить лист "Содержание" 
 +        IRange := ISheet.UsedRange[0]; 
 +        Values := IRange.Value; 
 +        SetLength(Articles,IRange.Rows.Count+1); 
 +        local_progress.Min:=0; 
 +        local_progress.max:=IRange.Rows.Count; 
 +        local_progress.Position:=local_progress.Min; 
 +        local_progress.Step:=1; 
 +        statusbar1.simpletext:='Загрузка листа "Содержание"...'; 
 +        for i := 1 to IRange.Rows.Count do begin 
 +          local_progress.stepit; 
 +          with Articles[i] do begin 
 +             rubrika := Values[i, 1]; 
 +             author := Values[i, 2]; 
 +             title := Values[i, 3]; 
 +             page := Values[i, 4]; 
 +             link := Values[i, 5]; 
 +             volume := Values[i, 6]; 
 +             year := Values[i, 7]; 
 +          end; 
 +        end; 
 + 
 +        // загрузить лист "Авторы" 
 +        IRange := ISheet_auth_index.UsedRange[0]; 
 +        Values := IRange.Value; 
 +        Setlength(auth_index,IRange.Rows.Count+1); 
 +        local_progress.Min:=0; 
 +        local_progress.max:=IRange.Rows.Count; 
 +        local_progress.Position:=local_progress.Min; 
 +        local_progress.Step:=1; 
 +        statusbar1.simpletext:='Загрузка листа "Авторы"...'; 
 +        for i := 1 to IRange.Rows.Count do begin 
 +          local_progress.stepit; 
 +          with auth_index[i] do begin 
 +             name := Values[i, 1]; 
 +             index := Values[i, 2]; 
 +          end; 
 +        end; 
 + 
 +        // загрузить лист "Авторы (свод)" 
 +        IRange := ISheet_authors.UsedRange[0]; 
 +        Values := IRange.Value; 
 +        Setlength(authors,IRange.Rows.Count+1); 
 +        local_progress.Min:=0; 
 +        local_progress.max:=IRange.Rows.Count; 
 +        local_progress.Position:=local_progress.Min; 
 +        local_progress.Step:=1; 
 +        statusbar1.simpletext:='Загрузка листа "Авторы (свод)"...'; 
 +        for i := 1 to IRange.Rows.Count do begin 
 +          local_progress.stepit; 
 +          with authors[i] do begin 
 +             name := Values[i, 1]; 
 +             index := Values[i, 2]; 
 +             count:= Values[i, 3]; 
 +             triplet:= Values[i, 4]; 
 +             surname:= Values[i, 5]; 
 +          end; 
 +        end; 
 + 
 +        // загрузить лист "Триплеты" 
 +        IRange := ISheet_triplet.UsedRange[0]; 
 +        Values := IRange.Value; 
 +        Setlength(triplets,IRange.Rows.Count+1); 
 +        local_progress.Min:=0; 
 +        local_progress.max:=IRange.Rows.Count; 
 +        local_progress.Position:=local_progress.Min; 
 +        local_progress.Step:=1; 
 +        statusbar1.simpletext:='Загрузка листа "Триплеты"...'; 
 +        for i := 1 to IRange.Rows.Count do begin 
 +          local_progress.stepit; 
 +          with triplets[i] do begin 
 +             triplet := Values[i, 1]; 
 +             index := Values[i, 2]; 
 +             count:= Values[i, 3]; 
 +             letter:= Values[i, 4]; 
 +             if not (letter[1] in ['А'..'Я']) then letter:='Ъ'; // подменить ошибочные триплеты на Ъ 
 +          end; 
 +        end; 
 + 
 +        Authors_TOC.add('<LINK REL="stylesheet" TYPE="text/css" HREF="all_content.css" TITLE="style">'); 
 +        Authors_TOC.add('<ul>'); // ведущий UL 
 +         // построить список всех триплетов 
 +        local_progress.Min:=0; 
 +        local_progress.max:=length(triplets)-1; 
 +        local_progress.Position:=local_progress.Min; 
 +        local_progress.Step:=1; 
 +        statusbar1.simpletext:='Построение списка триплетов...'; 
 +        x:=0; 
 +        //odd_even:=true; 
 +        // загрузить массив триплетов 
 +        for i:= ord('А') to ord('Я') do begin 
 +           for x:=0 to max_toc_triplet do begin 
 +              toc_triplets[i,x] := ''; // обнулить массив. 
 +           end; 
 +        end; 
 +        { найти диапазон для очередной буквы 
 +          по найденному диапазону триплетов построить 8 интервалов 
 +          если кол-во триплетов меньше или равно max_toc_triplet - "тупой" алгоритм 
 +          если кол-во триплетов больше max_toc_triplet - "хитрый" алгоритм 
 +          приписать триплетам соостветствующие индексы интервалов 
 +        } 
 +        first:=1; prevStroka:=triplets[1].letter; 
 +        for i := 1 to length(triplets)-1 do begin 
 +           if (prevStroka <> triplets[i].letter) then begin // пошла новая буква 
 +              last:=i-1; // конец диапазона - предыдущий триплет 
 +              toc_triplets[ord(prevStroka[1]),0] := '*'; // проставить признак, что такая буква есть. 
 +              if (last-first+1)<=max_toc_triplet then begin // кол-во триплетов меньше или равно max_toc_triplet - "тупой" алгоритм 
 +                 for x:=1 to last-first+1 do begin 
 +                    toc_triplets[ord(prevStroka[1]),x]:=triplets[first+x-1].triplet; 
 +                    triplets[first+x-1].toc_triplets_index:=x; // приписать триплетам соостветствующие индексы интервалов 
 +                 end; 
 +              end else begin // хитрый алгоритм 
 +                 for x:=1 to max_toc_triplet do begin 
 +                    min_tripl:=first+round((last-first+1)/max_toc_triplet*(x-1)); 
 +                    max_tripl:=first+round((last-first+1)/max_toc_triplet*(x))-1; 
 +                    for z:=min_tripl to max_tripl do begin 
 +                       triplets[z].toc_triplets_index:=x; // приписать триплетам соостветствующие индексы интервалов 
 +                    end; 
 +                    if min_tripl = max_tripl 
 +                       then toc_triplets[ord(prevStroka[1]),x] := triplets[min_tripl].triplet 
 +                       else toc_triplets[ord(prevStroka[1]),x] := triplets[min_tripl].triplet + ' - ' + triplets[max_tripl].triplet; 
 +                 end; 
 +              end; 
 +              first:=i; // запомнить начало нового диапазона 
 +           end; 
 +           prevStroka := triplets[i].letter; 
 +        end; 
 +        i:=length(triplets); 
 +        last:=i-1; // конец диапазона - последний элемент 
 +        toc_triplets[ord(prevStroka[1]),0] := '*'; // проставить признак, что такая буква есть. 
 +        if (last-first+1)<=max_toc_triplet then begin // кол-во триплетов меньше или равно max_toc_triplet - "тупой" алгоритм 
 +           for x:=1 to last-first+1 do begin 
 +              toc_triplets[ord(prevStroka[1]),x]:=triplets[first+x-1].triplet; 
 +              triplets[first+x-1].toc_triplets_index:=x; // приписать триплетам соостветствующие индексы интервалов 
 +           end; 
 +        end else begin // хитрый алгоритм 
 +           for x:=1 to max_toc_triplet do begin 
 +              min_tripl:=first+round((last-first+1)/max_toc_triplet*(x-1)); 
 +              max_tripl:=first+round((last-first+1)/max_toc_triplet*(x))-1; 
 +              for z:=min_tripl to max_tripl do begin 
 +                 triplets[z].toc_triplets_index:=x; // приписать триплетам соостветствующие индексы интервалов 
 +              end; 
 +              if min_tripl = max_tripl 
 +                 then toc_triplets[ord(prevStroka[1]),x] := triplets[min_tripl].triplet 
 +                 else toc_triplets[ord(prevStroka[1]),x] := triplets[min_tripl].triplet + ' - ' + triplets[max_tripl].triplet; 
 +           end; 
 +        end; 
 +        odd_even := true; 
 +        Authors_TOC.add('<h1>Авторский указатель</h1>'); 
 +        Authors_TOC.Add('<table class="toc" style="font-size=-2" width=100% cellspacing=0 cellpadding=5>'); 
 +        for i:= ord('А') to ord('Я') do begin 
 +           if toc_triplets[i,0] = '' then continue; // пропустить пустые буквы 
 +           odd_even := not odd_even; 
 +           if odd_even 
 +              then Authors_TOC.Add('<tr class="even"><td class="hd">' + chr(i) + '</td>'
 +              else Authors_TOC.Add('<tr class="odd"><td class="hd">' + chr(i) + '</td>'); 
 +           for x:=1 to max_toc_triplet do begin 
 +              if (toc_triplets[i,x] <> ''
 +                 then Authors_TOC.Add(format('<td class="toc"><a href="#triplet_%1:d_%2:d">%0:s</a></td>',[toc_triplets[i,x],i,x])) 
 +                 else Authors_TOC.Add('<td class="toc">&nbsp;</td>'); 
 +           end; 
 +           Authors_TOC.Add('</tr>'); 
 +           local_progress.StepIt; 
 +        end; 
 +        Authors_TOC.Add('</table>'); 
 + 
 +        // построить список всех фамилий 
 +        Authors_Names.add('<div STYLE="{PAGE-BREAK-BEFORE: always}"></div>'); 
 +        local_progress.Min:=0; 
 +        local_progress.max:=length(triplets)-1; 
 +        local_progress.Position:=local_progress.Min; 
 +        local_progress.Step := 1; 
 +        statusbar1.simpletext := 'Построение списка фамилий...'; 
 +        z := 0; 
 +        page_counter := 0; 
 +        odd_even := false; 
 +        i := 1; prevStroka := triplets[i].letter + '_' + IntToStr(triplets[i].toc_triplets_index); 
 +        Authors_Names.Add(format('<h2><a name="triplet_%1:d_%2:d">%0:s</a></h2>',[toc_triplets[ord(triplets[i].letter[1]),triplets[i].toc_triplets_index],ord(triplets[i].letter[1]),triplets[i].toc_triplets_index])); 
 +        inc(page_counter,27); // высота заголовка - 27 пунктов 
 +        Authors_Names.Add('<table class="toc" style="font-size=-2" width=100% cellspacing=0 cellpadding=5><tr class="odd">'); 
 +        for i:= 1 to length(triplets)-1 do begin 
 +           if (prevStroka <> triplets[i].letter + '_' + IntToStr(triplets[i].toc_triplets_index)) then begin // пошёл новый интервал 
 +              for x:=z to 2 do begin 
 +                 Authors_Names.Add('<td class="toc">&nbsp;</td>'); // дописать пустые ячейки до трёх 
 +              end; 
 +              Authors_Names.Add('</tr></table>'); 
 +              if (page_counter>220-27) then begin // высота страницы - 290 пунктов 
 +                 z := 0; 
 +                 page_counter := 0; 
 +                 odd_even:=false; 
 +                 Authors_Names.Add(format('<h2 style="{page-break-before: always}"><a name="triplet_%1:d_%2:d">%0:s</a></h2>',[toc_triplets[ord(triplets[i].letter[1]),triplets[i].toc_triplets_index],ord(triplets[i].letter[1]),triplets[i].toc_triplets_index])); 
 +              end else 
 +                 Authors_Names.Add(format('<h2><a name="triplet_%1:d_%2:d">%0:s</a></h2>',[toc_triplets[ord(triplets[i].letter[1]),triplets[i].toc_triplets_index],ord(triplets[i].letter[1]),triplets[i].toc_triplets_index])); 
 +              inc(page_counter,27); // высота заголовка - 27 пунктов 
 +              rows_counter := 0; 
 +              Authors_Names.Add('<table class="toc" style="font-size=-2" width=100% cellspacing=0 cellpadding=5><tr class="odd">'); 
 +              z := 0; 
 +              odd_even:=false; 
 +           end; 
 +           for x := triplets[i].index to (triplets[i].index + triplets[i].count - 1) do begin // вывести все фамилии для триплета 
 +              if z = 3 then begin // запустить новую строку 
 +                 inc(page_counter,9);  // высота строки - 9 пунктов 
 +                 inc(rows_counter); 
 +                 z := 0; 
 +                 if odd_even 
 +                    then Authors_Names.Add('</tr><tr class="odd">'
 +                    else Authors_Names.Add('</tr><tr class="even">'); 
 +                 odd_even:=not odd_even; 
 +              end; 
 +              if (page_counter>220) and (rows_counter>1) then begin // высота страницы - 290 пунктов 
 +                 Authors_Names.Add('</tr></table>'); 
 +                 z := 0; 
 +                 page_counter := 0; 
 +                 odd_even:=false; 
 +                 Authors_Names.Add(format('<h2 style="{page-break-before: always}">%0:s</h2>',[toc_triplets[ord(triplets[i].letter[1]),triplets[i].toc_triplets_index]])); 
 +                 inc(page_counter,27); // высота заголовка - 27 пунктов 
 +                 Authors_Names.Add('<table class="toc" style="font-size=-2" width=100% cellspacing=0 cellpadding=5><tr class="odd">'); 
 +              end; 
 +              inc(z); 
 +              Authors_Names.Add(format('<td class="toc" width="33%%"><a href="#author_%1:d">%0:s</a></td>',[authors[x].name, x])); 
 +           end; 
 +           local_progress.StepIt; 
 +           prevStroka := triplets[i].letter + '_' + IntToStr(triplets[i].toc_triplets_index); 
 +        end; 
 +        Authors_Names.Add('</tr></table>'); 
 + 
 +        // построить список нижнего уровня 
 +        local_progress.Min:=0; 
 +        local_progress.max:=length(authors)-1; 
 +        local_progress.Position:=local_progress.Min; 
 +        local_progress.Step:=1; 
 +        statusbar1.simpletext:='Построение поавторского списка статей...'; 
 +        Authors_Articles.add('<div STYLE="{PAGE-BREAK-BEFORE: always}"></div>'); 
 +        for i := 1 to length(authors)-1 do begin 
 +           stroka := format('<h2><a name="author_%0:d">%1:s</a></h2>',[i, authors[i].name]);; 
 +           Authors_Articles.add(stroka); 
 +           local_progress.StepIt; 
 +           Authors_Articles.add('<ul>'); 
 +           for x:=authors[i].index + authors[i].Count - 1 downto authors[i].index do begin // пройтись по всем записям индекса автора 
 +              with articles[auth_index[x].index] do begin 
 + 
 +                 if (year = '1992') and  (volume = '05') then begin 
 +                    year_vol_str := '1992_05_06'; 
 +                    str_vol := '05-06'; 
 +                 end else begin 
 +                    year_vol_str := year + '_' + volume; 
 +                    str_vol := volume; 
 +                 end; 
 + 
 +                 stroka:=format('<li><a href="%0:s/%1:s.djvu#%2:s">%0:s №%3:s — ',[year, year_vol_str, page, str_vol]); 
 +                 if (rubrika <> '') then stroka := stroka + '[<font color=blue>' + rubrika + '</font>] '; 
 +                 if (author <> '') then stroka := stroka + '(<font color=green>' + author + '</font>) — '; 
 +                 if (title <> '') then stroka := stroka + title + ' '; 
 +                 stroka := stroka + '</a>'; 
 + 
 +                 Authors_Articles.Add(stroka); 
 +              end; 
 +           end; 
 +           Authors_Articles.add('</ul>'); 
 +        end; 
 +        Authors_Articles.add('</ul>'); // конечный /UL 
 +        statusbar1.simpletext:='Готово'; 
 + 
 +        Authors_TOC.SaveToStream(file_All_Authors_Index); 
 +        Authors_Names.SaveToStream(file_All_Authors_Index); 
 +        Authors_Articles.SaveToStream(file_All_Authors_Index); 
 +      finally 
 +        IRange := nil; 
 +        ISheet := nil; 
 +        ISheet_auth_index := nil; 
 +        ISheet_authors := nil; 
 +        ISheet_triplet := nil; 
 +        setlength(articles,0); 
 +        setlength(auth_index,0); 
 +        setlength(authors,0); 
 +        setlength(triplets,0); 
 +        file_All_Authors_Index.Free; 
 +        Authors_TOC.Free; // освободить место для создания главной таблицы (верхний уровень
 +        Authors_Names.Free; // освободить место для создания списка фамилий (средний уровень) 
 +        Authors_Articles.Free; // освободить место для создания блоков ссылок на статьи (нижний уровень) 
 +        { 
 +        // открыть созданный Html во встроенном редакторе 
 +        Flags := 0; 
 +        html_preview.Navigate(WideString(prog_path+'all_content.htm'), Flags, Flags, Flags, Flags); 
 +        } 
 +      end; 
 +    except 
 +      raise Exception.Create('Не могу прочитать данные в массив!'); 
 +    end; 
 +  end; 
 +  Procedure per_rubrics; // создание глобального рубрикатора (all_rubrics.htm) 
 +  var Values: OLEVariant; 
 +    ISheet, ISheet_rubr_index, ISheet_rubriks: Excel8TLB._Worksheet; 
 +    articles: array of TMyContent; 
 +    rubr_index: array of rubr_index_element; 
 +    rubriks: array of rubriks_element; 
 + 
 +    Sorted_rubriks:TStringList; 
 +    Rubrik_Object:TRubr_Info; 
 + 
 +    odd_even:boolean; // хрень для полосатых таблиц 
 +    page_counter, rows_counter : integer; 
 +    x:integer; 
 +    user_max_year, user_min_year : integer; // пределы для отображения таблиц ссылок 
 +    stroka:string; 
 +    Rubriks_TOC:TStringList; // место для создания главной таблицы (верхний уровень) 
 +    Rubriks_Names:TStringList; // место для создания списка фамилий (средний уровень) 
 +    Rubriks_Articles:TStringList;// место для создания блоков ссылок на статьи (нижний уровень) 
 +    IRange: Excel8TLB.Range; 
 +    i,z: integer; 
 +    year_vol_str, str_vol:string; 
 +    prev_group: integer; 
 +    procedure show_table_rubrik(i:integer); // вывод таблицы со ссылками для регулярных рубрик 
 +       Const min_vol = 1; 
 +             max_vol = 12; 
 +             test_vol = max_vol + 1; 
 +       var x,y:integer; 
 +           user_year, user_vol:integer; 
 +           style:string; 
 +           all_volumes:array[min_year..max_year, min_vol..max_vol+1] of integer; 
 +              // если рубрики в номере нет, то значение равно нулю, 
 +              // в противном случае - это номер страницы 
 +              // последний столбец - количество номеров с рубриками в определённом году. 
 +    begin 
 +    // определить min_year, max_year для конкретной рубрики 
 +    // 
 + 
 +    // Заполнить таблицу ссылок (all_volumes) для рубрики 
 +    FillChar(all_volumes,sizeof(all_volumes),0); // инициализировать таблицу номеров 
 +    for x := rubriks[i].index to rubriks[i].index+rubriks[i].count-1 do begin // прочесать все статьи для рубрики x 
 +       user_year := strtoint(articles[rubr_index[x].index].year); // заполучить год, в котором статья 
 +       user_vol := strtoint(articles[rubr_index[x].index].volume); // заполучить номер, в котором статья 
 +       if (user_year >= min_year) and (user_year <= max_year) and 
 +       (user_vol >= min_vol) and (user_vol <= max_vol) 
 +       then begin 
 +          if all_volumes[user_year, user_vol] = 0 then // если это первая статья данной рубрики в номере 
 +             all_volumes[user_year, user_vol] := strtoint(articles[rubr_index[x].index].page); // запомнить номер страницы 
 +          inc(all_volumes[user_year, test_vol]); // увеличить счётчик количества номеров в этом году 
 +       end; 
 +    end; 
 + 
 +    // Создать заголовок таблицы ссылок для рубрики 
 +    //Rubriks_Articles.Add(format('<H1 STYLE="{PAGE-BREAK-BEFORE: always}">%s %d - %d</H1>',[rubriks[i].name, min_year, max_year])); 
 +    Rubriks_Articles.Add('<table class="toc" style="font-size=-2" width=100% cellspacing=0 cellpadding=0>'); 
 +    Rubriks_Articles.Add('<tr align=center><td class="hd">&nbsp;</td>'); 
 +    for y := min_vol to max_vol do begin 
 +       if y<10 
 +          then Rubriks_Articles.Add(format('<td class="hd">№0%d</td>',[y])) 
 +          else Rubriks_Articles.Add(format('<td class="hd">№%d</td>',[y])); 
 +    end; 
 +    Rubriks_Articles.Add('</tr>'); 
 +    // просчитать пределы годов отображения 
 +    for x := max_year downto min_year do begin 
 +       user_max_year := x; 
 +       if (all_volumes[x, test_vol] >0) then break; 
 +    end; 
 +    for x := min_year to max_year do begin 
 +       user_min_year := x; 
 +       if (all_volumes[x, test_vol] >0) then break; 
 +    end; 
 +    // вывести основную часть таблицы 
 +    for x := user_max_year downto user_min_year do begin 
 +       if (x mod 2) = 0 then style := 'odd' else style := 'even'; // полосатая таблица 
 +       Rubriks_Articles.Add(format('<tr align=center class="%0:s"><td class="toc">%1:d год</td>',[style,x])); 
 +       for y := min_vol to max_vol do begin 
 +          if y < 10 
 +             then year_vol_str := format('%d_0%d',[x,y]) 
 +             else year_vol_str := format('%d_%d',[x,y]); 
 +          if (year_vol_str = '1992_05') then begin // сдвоенный номер 1992 05-06 
 +             year_vol_str := '1992_05_06'; 
 +             if (all_volumes[x,y]>0) 
 +                then Rubriks_Articles.Add(format('<td class="toc" colspan=2><a href="%0:d/%1:s.djvu#%2:d">-=&gt;&lt;=-</a></td>',[x, year_vol_str, all_volumes[x,y]])) 
 +                else Rubriks_Articles.Add('<td class="toc" colspan=2>&nbsp;</td>'); 
 +             continue; // перейти к следующему номеру 
 +          end; 
 +          if (x = 1992) and (y = 6) then continue; // пропустить номер 1992 6 
 +          if (all_volumes[x,y]>0) 
 +             then Rubriks_Articles.Add(format('<td class="toc"><a href="%0:d/%1:s.djvu#%2:d">-=&gt;&lt;=-</a></td>',[x, year_vol_str, all_volumes[x,y]])) 
 +             else Rubriks_Articles.Add('<td class="toc">&nbsp;</td>'); 
 +       end; 
 +       Rubriks_Articles.Add('</tr>'); 
 +    end; 
 +    Rubriks_Articles.Add('</table>'); 
 + 
 +    end; 
 +  begin // собственно начало процедуры per_rubriks 
 +  if Assigned(IWorkbook) then 
 +    try 
 +      ISheet := IWorkbook.Worksheets.Item['Содержание'] as Excel8TLB._Worksheet; 
 +      ISheet_Rubr_index := IWorkbook.Worksheets.Item['Рубрики'] as Excel8TLB._Worksheet; 
 +      ISheet_Rubriks := IWorkbook.Worksheets.Item['Рубрики (свод)'] as Excel8TLB._Worksheet; 
 + 
 +      Rubriks_TOC := TStringList.Create; // место для создания главной таблицы (верхний уровень) 
 +      Rubriks_Names := TStringList.Create; // место для создания списка рубрик (средний уровень) 
 +      Rubriks_Articles := TStringList.Create; // место для создания блоков ссылок на статьи (нижний уровень) 
 + 
 +      file_All_Rubriks_Index:=TFileStream.Create(prog_path+'all_rubriks.htm', fmCreate); 
 +      try 
 +        // создать конечный перечень - имя автора (заголовок, ссылка вида #Rubrika_), перечень статей в этой рубрике. 
 + 
 +        // загрузить лист "Содержание" 
 +        IRange := ISheet.UsedRange[0]; 
 +        Values := IRange.Value; 
 +        SetLength(Articles,IRange.Rows.Count+1); 
 +        local_progress.Min:=0; 
 +        local_progress.max:=IRange.Rows.Count; 
 +        local_progress.Position:=local_progress.Min; 
 +        local_progress.Step:=1; 
 +        statusbar1.simpletext:='Загрузка листа "Содержание"...'; 
 +        for i := 1 to IRange.Rows.Count do begin 
 +          local_progress.stepit; 
 +          with Articles[i] do begin 
 +             rubrika := Values[i, 1]; 
 +             author := Values[i, 2]; 
 +             title := Values[i, 3]; 
 +             page := Values[i, 4]; 
 +             link := Values[i, 5]; 
 +             volume := Values[i, 6]; 
 +             year := Values[i, 7]; 
 +          end; 
 +        end; 
 + 
 +        // загрузить лист "Рубрики" 
 +        IRange := ISheet_Rubr_index.UsedRange[0]; 
 +        Values := IRange.Value; 
 +        Setlength(Rubr_index,IRange.Rows.Count+1); 
 +        local_progress.Min:=0; 
 +        local_progress.max:=IRange.Rows.Count; 
 +        local_progress.Position:=local_progress.Min; 
 +        local_progress.Step:=1; 
 +        statusbar1.simpletext:='Загрузка листа "Рубрики"...'; 
 +        for i := 1 to IRange.Rows.Count do begin 
 +          local_progress.stepit; 
 +          with Rubr_index[i] do begin 
 +             name := Values[i, 1]; 
 +             index := Values[i, 2]; 
 +          end; 
 +        end; 
 + 
 +        // загрузить лист "Рубрики (свод)" 
 +        IRange := ISheet_Rubriks.UsedRange[0]; 
 +        Values := IRange.Value; 
 +        Setlength(Rubriks,IRange.Rows.Count+1); 
 +        local_progress.Min:=0; 
 +        local_progress.max:=IRange.Rows.Count; 
 +        local_progress.Position:=local_progress.Min; 
 +        local_progress.Step:=1; 
 +        statusbar1.simpletext:='Загрузка листа "Рубрики (свод)"...'; 
 +        for i := 1 to IRange.Rows.Count do begin 
 +          local_progress.stepit; 
 +          with Rubriks[i] do begin 
 +             name := Values[i, 1]; 
 +             index := Values[i, 2]; 
 +             count := Values[i, 3]; 
 +             common := Values[i, 4]; 
 +             group_number := Values[i, 5]; 
 +             number := i; 
 +          end; 
 +        end; 
 + 
 +        // создать список рубрик, упорядоченный по алфавиту  "название рубрики" | "Исходный номер рубрики" 
 +        Sorted_rubriks := TStringList.Create; 
 +        for i := 1 to length(rubriks)-1 do begin 
 +           Rubrik_Object := TRubr_Info.Create(rubriks[i]); 
 +           sorted_rubriks.AddObject(Rubrik_Object.info.name, Rubrik_Object); 
 +        end; 
 +        sorted_rubriks.Sort; 
 + 
 +        // построить первую страницу (перечень групп рубрик и ссылка на алфавитный список рубрик) 
 + 
 +        Rubriks_TOC.add('<LINK REL="stylesheet" TYPE="text/css" HREF="all_content.css" TITLE="style">'); 
 + 
 +        Rubriks_TOC.add('<ul>'); // ведущий UL 
 +        odd_even := true; 
 +        //Rubriks_TOC.add('<h1>Рубрикатор</h2>'); 
 +        Rubriks_TOC.add('<h2><a href="#Group_1">Группы рубрик</a></h2>'); 
 +        Rubriks_TOC.add('<table class="stealth" style="font-size=-2" width=100% cellspacing=0 cellpadding=5>'); 
 +        x := 1; 
 +        repeat 
 +           Rubriks_TOC.add('<tr align="center" valign="bottom" class="even">'); 
 +           for i:=0 to 2 do begin 
 +              if (x+i > length(Rubriks_Groups)-1) then continue; 
 +              Rubriks_TOC.add(format('<td><a href="#Group_%0:d"><img src="face/group_%0:d.png" border=0></a></td>', [x+i])); 
 +           end; 
 +           Rubriks_TOC.add('</tr><tr align="center" valign="top" class="even">'); 
 +           for i:=0 to 2 do begin 
 +              if (x+i > length(Rubriks_Groups)-1) then continue; 
 +              Rubriks_TOC.add(format('<td><a href="#Group_%0:d">%1:s</a>', [x+i, Rubriks_Groups[x+i].group])); 
 +           end; 
 +           Rubriks_TOC.add('</tr>'); 
 +           inc(x,3); 
 +        until (x > length(Rubriks_Groups)-1); 
 +        Rubriks_TOC.add('</table>'); 
 +        Rubriks_TOC.add('<h2><a href="#alphabet">Алфавитный список рубрик</a></h2>'); 
 +        Rubriks_TOC.add('<table class="stealth" style="font-size=-2" width=100% cellspacing=0 cellpadding=5>'); 
 +        Rubriks_TOC.add('<tr class="even"><td>'); 
 + 
 +        // построить список всех рубрик (группированный по темам) 
 +        Rubriks_Names.add('<div STYLE="{PAGE-BREAK-BEFORE: always}"></div>'); 
 +        local_progress.Min:=0; 
 +        local_progress.max:=length(rubriks)-1; 
 +        local_progress.Position:=local_progress.Min; 
 +        local_progress.Step := 1; 
 +        statusbar1.simpletext := 'Построение списка рубрик по группам...'; 
 +        z := 0; 
 +        page_counter := 0; 
 +        odd_even := false; 
 +        (*// на странице уже есть заголовок и список групп - надо учесть. 
 +        inc(page_counter, 27); // высота заголовка - 27 пунктов 
 +        inc(page_counter, 5 * (length(Rubriks_Groups)-1) ); // строки в списке - 5 пунктов 
 +        *) 
 +        prev_group:=0; // первая группа 
 + 
 +        Rubriks_Names.Add('<table class="toc" style="font-size=-2" width=100% cellspacing=0 cellpadding=5><tr class="odd">'); 
 +        for i:= 1 to length(rubriks)-1 do begin 
 +           if (Rubriks[i].group_number<>prev_group) then begin // сменилась группа рубрик 
 +              if (rows_counter>0) then // игнорируем пустые таблицы 
 +                 for x:=z to 2 do begin // закончить последнюю строку в таблице 
 +                    Rubriks_Names.Add('<td class="toc">&nbsp;</td>'); // дописать пустые ячейки до трёх 
 +                 end; 
 +              Rubriks_Names.Add('</tr></table>'); // закрыть предыдущую таблицу 
 +              z := 0; // снова инициализировать счётчик столбцов 
 +              rows_counter := 0; 
 +              //page_counter := 0; // снова инициализировать счётчик высоты 
 +              odd_even := false; // снова инициализировать полосатость 
 +              prev_group := Rubriks[i].group_number; // сменить текущую группу 
 +              Rubriks_Names.add(format('<h2 style="{page-break-before: always}"><a name="Group_%d">%s</a></h2>',[prev_group, Rubriks_Groups[prev_group].group])); 
 +              //inc(page_counter,27); // высота заголовка - 27 пунктов 
 +              page_counter := 27; 
 +              Rubriks_Names.Add('<table class="toc" style="font-size=-2" width=100% cellspacing=0 cellpadding=5><tr class="odd">'); // открыть новую таблицу 
 +           end; 
 + 
 +           if z = 3 then begin // запустить новую строку 
 +              inc(page_counter,9);  // высота строки - 9 пунктов 
 +              inc(rows_counter); 
 +              z := 0; 
 +              if odd_even 
 +                 then Rubriks_Names.Add('</tr><tr class="odd">'
 +                 else Rubriks_Names.Add('</tr><tr class="even">'); 
 +              odd_even:=not odd_even; 
 +           end; 
 +           if (page_counter>210) and (rows_counter>1) then begin // высота страницы - 210 пунктов 
 +              Rubriks_Names.Add('</tr></table>'); 
 +              z := 0; 
 +              rows_counter := 0; 
 +              page_counter := 0; 
 +              odd_even:=false; 
 +              Rubriks_Names.add(format('<h2 style="{page-break-before: always}">%s</h2>',[Rubriks_Groups[prev_group].group])); // повторить заголовок 
 +              inc(page_counter,27); // высота заголовка - 27 пунктов 
 +              Rubriks_Names.Add('<table class="toc" style="font-size=-2" width=100% cellspacing=0 cellpadding=5><tr class="odd">'); 
 +           end; 
 +           inc(z); 
 +           Rubriks_Names.Add(format('<td class="toc" width="33%%"><a href="#Rubrika_%1:d">%0:s</a></td>',[Rubriks[i].name, i])); 
 +           local_progress.StepIt; 
 +        end; 
 +        for x:=z to 2 do begin // закончить последнюю строку в таблице 
 +           Rubriks_Names.Add('<td class="toc">&nbsp;</td>'); // дописать пустые ячейки до трёх 
 +        end; 
 +        Rubriks_Names.Add('</tr></table>'); 
 + 
 +        // построить список всех рубрик, без разбивки по группам рубрик (просто по алфавиту) 
 + 
 +        local_progress.Min:=0; 
 +        local_progress.max:=sorted_rubriks.Count-1; 
 +        local_progress.Position:=local_progress.Min; 
 +        local_progress.Step := 1; 
 +        statusbar1.simpletext := 'Построение алфавитного списка рубрик...'; 
 +        z := 0; 
 +        page_counter := 0; 
 +        odd_even := false; 
 +        prev_group :=ord(upcase(sorted_rubriks.Strings[0][1])); 
 +        Rubriks_Names.add('<h2 style="{page-break-before: always}"><a name="alphabet">Алфавитный список рубрик</a></h2>'); 
 +        inc(page_counter,27); // высота заголовка - 27 пунктов 
 +        Rubriks_Names.add(format('<h2><a name="abc_%d">%s</a></h2>',[prev_group, upcase(sorted_rubriks.Strings[0][1])])); 
 +        inc(page_counter,27); // высота заголовка - 27 пунктов 
 +        Rubriks_TOC.add(format('Рубрики на: <a href="#abc_%d">%s</a>',[ord(upcase(sorted_rubriks.Strings[0][1])),upcase(sorted_rubriks.Strings[0][1])])); 
 +        Rubriks_Names.Add('<table class="toc" style="font-size=-2" width=100% cellspacing=0 cellpadding=5><tr class="odd">'); 
 +        for i:= 0 to sorted_rubriks.Count-1 do begin 
 +           if (ord(upcase(sorted_rubriks.Strings[i][1]))<>prev_group) then begin // сменилась группа рубрик 
 +              Rubriks_TOC.add(format(', <a href="#abc_%d">%s</a>',[ord(upcase(sorted_rubriks.Strings[i][1])),upcase(sorted_rubriks.Strings[i][1])])); 
 +              for x:=z to 2 do begin // закончить последнюю строку в таблице 
 +                 Rubriks_Names.Add('<td class="toc">&nbsp;</td>'); // дописать пустые ячейки до трёх 
 +              end; 
 +              Rubriks_Names.Add('</tr></table>'); // закрыть предыдущую таблицу 
 +              //inc(page_counter,9);  // высота строки - 9 пунктов 
 +              z := 0; // снова инициализировать счётчик столбцов 
 +              rows_counter := 0; 
 +              odd_even := false; // снова инициализировать полосатость 
 +              prev_group := ord(upcase(sorted_rubriks.Strings[i][1])); // сменить текущую группу 
 +              if (page_counter>220) then begin 
 +                 page_counter := 0; 
 +                 Rubriks_Names.add(format('<h2 STYLE="{PAGE-BREAK-BEFORE: always}"><a name="abc_%d">%s</a></h2>',[prev_group, upcase(sorted_rubriks.Strings[i][1])])); 
 +              end else Rubriks_Names.add(format('<h2><a name="abc_%d">%s</a></h2>',[prev_group, upcase(sorted_rubriks.Strings[i][1])])); 
 +              inc(page_counter,30); // высота заголовка - 27 пунктов 
 +              Rubriks_Names.Add('<table class="toc" style="font-size=-2" width=100% cellspacing=0 cellpadding=5><tr class="odd">'); // открыть новую таблицу 
 +           end; 
 + 
 +           if z = 3 then begin // запустить новую строку 
 +              inc(page_counter,10);  // высота строки - 9 пунктов 
 +              inc(rows_counter); 
 +              z := 0; 
 +              if odd_even 
 +                 then Rubriks_Names.Add('</tr><tr class="odd">'
 +                 else Rubriks_Names.Add('</tr><tr class="even">'); 
 +              odd_even:=not odd_even; 
 +           end; 
 +           if (page_counter>220) and (rows_counter>1) then begin // высота страницы - 210 пунктов 
 +              Rubriks_Names.Add('</tr></table>'); 
 +              z := 0; 
 +              rows_counter := 0; 
 +              page_counter := 0; 
 +              odd_even:=false; 
 +              //Rubriks_Names.add('<div STYLE="{PAGE-BREAK-BEFORE: always}"></div>'); 
 +              Rubriks_Names.add(format('<h2 STYLE="{PAGE-BREAK-BEFORE: always}">%s</h2>',[upcase(sorted_rubriks.Strings[i][1])])); 
 +              Rubriks_Names.Add('<table class="toc" style="font-size=-2" width=100% cellspacing=0 cellpadding=5><tr class="odd">'); 
 +              inc(page_counter,30); // высота заголовка - 27 пунктов 
 +           end; 
 +           inc(z); 
 + 
 +           Rubriks_Names.Add(format('<td class="toc" width="33%%"><a href="#Rubrika_%1:d">%0:s</a></td>',[sorted_rubriks.Strings[i], TRubr_Info(sorted_rubriks.Objects[i]).info.number])); 
 +           local_progress.StepIt; 
 +        end; 
 +        for x:=z to 2 do begin // закончить последнюю строку в таблице 
 +           Rubriks_Names.Add('<td class="toc">&nbsp;</td>'); // дописать пустые ячейки до трёх 
 +        end; 
 +        Rubriks_Names.Add('</tr></table>'); 
 +        Rubriks_TOC.add('</td></tr></table>'); 
 + 
 +        // построить список нижнего уровня 
 +        local_progress.Min:=0; 
 +        local_progress.max:=length(Rubriks)-1; 
 +        local_progress.Position:=local_progress.Min; 
 +        local_progress.Step:=1; 
 +        statusbar1.simpletext:='Построение поавторского списка статей...'; 
 +        Rubriks_Articles.add('<div STYLE="{PAGE-BREAK-BEFORE: always}"></div>'); 
 +        prev_group := 0; 
 +        for i := 1 to length(Rubriks)-1 do begin 
 +           local_progress.StepIt; 
 +           if (Rubriks[i].group_number<>prev_group) then begin // сменилась группа рубрик 
 +              prev_group := Rubriks[i].group_number; // сменить текущую группу 
 +              Rubriks_Articles.add(format('<h1 style="{page-break-before: always}">%s</h1>',[Rubriks_Groups[prev_group].group])); 
 +           end; 
 +           if rubriks[i].common=1 then begin // регулярная рубрика 
 +              // style="{page-break-before: always}" 
 +              stroka := format('<h2><a name="Rubrika_%0:d">%1:s</a></h2>',[i, Rubriks[i].name]);; 
 +              Rubriks_Articles.add(stroka); 
 +              show_table_rubrik(i); 
 +           end else begin // обычная рубрика 
 +              stroka := format('<h2><a name="Rubrika_%0:d">%1:s</a></h2>',[i, Rubriks[i].name]);; 
 +              Rubriks_Articles.add(stroka); 
 +              Rubriks_Articles.add('<ul>'); 
 +              for x:=Rubriks[i].index + Rubriks[i].Count - 1 downto Rubriks[i].index do begin // пройтись по всем записям индекса автора 
 +                 with articles[Rubr_index[x].index] do begin 
 + 
 +                    if (year = '1992') and  (volume = '05') then begin 
 +                       year_vol_str := '1992_05_06'; 
 +                       str_vol := '05-06'; 
 +                    end else begin 
 +                       year_vol_str := year + '_' + volume; 
 +                       str_vol := volume; 
 +                    end; 
 + 
 +                    stroka:=format('<li><a href="%0:s/%1:s.djvu#%2:s">%0:s №%3:s — ',[year, year_vol_str, page, str_vol]); 
 +                    if (rubrika <> '') then stroka := stroka + '[<font color=blue>' + rubrika + '</font>] '; 
 +                    if (author <> '') then stroka := stroka + '(<font color=green>' + author + '</font>) — '; 
 +                    if (title <> '') then stroka := stroka + title + ' '; 
 +                    stroka := stroka + '</a>'; 
 +                    Rubriks_Articles.Add(stroka); 
 +                 end; 
 +              end; 
 +              Rubriks_Articles.add('</ul>'); 
 +           end; 
 +        end; 
 +        Rubriks_Articles.add('</ul>'); // конечный /UL 
 +        statusbar1.simpletext:='Готово'; 
 + 
 +        Rubriks_TOC.SaveToStream(file_All_Rubriks_Index); 
 +        Rubriks_Names.SaveToStream(file_All_Rubriks_Index); 
 +        Rubriks_Articles.SaveToStream(file_All_Rubriks_Index); 
 +      finally 
 +        IRange := nil; 
 +        ISheet := nil; 
 +        ISheet_Rubr_index := nil; 
 +        ISheet_Rubriks := nil; 
 +        setlength(articles,0); 
 +        setlength(Rubr_index,0); 
 +        setlength(Rubriks,0); 
 +        file_All_Rubriks_Index.Free; 
 +        Rubriks_TOC.Free; // освободить метсто для создания главной таблицы (верхний уровень) 
 +        Rubriks_Names.Free; // освободить мнсто для создания списка фамилий (средний уровень) 
 +        Rubriks_Articles.Free; // освободить место для создания блоков ссылок на статьи (нижний уровень) 
 +        { 
 +        // открыть созданный Html во встроенном редакторе 
 +        Flags := 0; 
 +        html_preview.Navigate(WideString(prog_path+'all_content.htm'), Flags, Flags, Flags, Flags); 
 +        } 
 +      end; 
 +    except 
 +      raise Exception.Create('Не могу прочитать данные в массив!'); 
 +    end; 
 +    end; 
 +procedure make_bookmarks; 
 +    var Bookmarks_Content: TStringList; 
 +    x, y: integer; 
 +    year_vol_str, str_vol: string; 
 +Const min_vol = 1; 
 +      max_vol = 12; 
 +begin 
 +    Bookmarks_Content := TStringList.Create; 
 +    Bookmarks_Content.Add('<ul>'); 
 +    Bookmarks_Content.Add('<li><a href="all_content.djvu">Сквозное оглавление</a></li>'); 
 +    Bookmarks_Content.Add('<li><a href="all_authors.djvu">Авторский указатель</a></li>'); 
 +    Bookmarks_Content.Add('<li><a href="all_rubriks.djvu">Рубрикатор</a></li>'); 
 +    Bookmarks_Content.Add('<li><a href="#">Архив</a></li>'); 
 +    Bookmarks_Content.Add('<ul>'); 
 +    for x := max_year downto min_year do begin 
 +       Bookmarks_Content.Add(format('<li><a href="#">%d год</a></li>',[x])); 
 +       Bookmarks_Content.Add('<ul>'); 
 +       for y := min_vol to max_vol do begin 
 +          if y<10 
 +             then begin 
 +                year_vol_str := format('%d_0%d',[x,y]); 
 +                str_vol := format('0%d',[y]) 
 +             end else begin 
 +                year_vol_str := format('%d_%d',[x,y]); 
 +                str_vol := format('%d',[y]) 
 +             end; 
 +          if (year_vol_str = '1992_05') then begin // сдвоенный номер 1992 05-06 
 +             year_vol_str := '1992_05_06'; 
 +             str_vol := '05-06'; 
 +          end; 
 +          if (x = 1992) and (y = 6) then continue; // пропустить номер 1992 6 
 +          Bookmarks_Content.Add(format('<li><a href="%0:d/%1:s.djvu">%0:d №%2:s</a></li>',[x, year_vol_str, str_vol])); 
 +       end; 
 +       Bookmarks_Content.Add('</ul>'); 
 +    end; 
 +    Bookmarks_Content.Add('</ul>'); 
 +    Bookmarks_Content.Add('<li><a href="search.exe">Сквозной поиск</a></li>'); 
 +    Bookmarks_Content.Add('</ul>'); 
 +    Bookmarks_Content.SaveToFile('all_bookmarks.htm'); 
 +end; 
 +begin 
 +  global_progress.Min:=0; 
 +  global_progress.max:=6; 
 +  global_progress.Position:=global_progress.Min; 
 +  global_progress.Step:=1; 
 +  statusbar1.simpletext:='Создание BAT файлов...'; 
 +  CreateBatches; // создать BAT файлы для конвертирования PDF в DJVU 
 +  Load_complex_rubriks; // загрузить список сборных рубрик 
 +  Load_rubriks_groups; // загрузить список групп рубрик 
 +  global_progress.StepIt; 
 + 
 +  if chk_make_bookmarks.Checked then begin 
 +     statusbar1.simpletext:='Создание закладок для DJVU...'; 
 +     Local_Content_Create(self); 
 +  end; 
 +  global_progress.StepIt; 
 + 
 +  if chk_make_content.Checked then begin 
 +     statusbar1.simpletext:='Создание глобального оглавления по номерам...'; 
 +     per_volumes; // создать глобальное оглавление по номерам (all_content.htm) 
 +  end; 
 +  global_progress.StepIt; 
 + 
 +  if chk_make_authors.Checked then begin 
 +     statusbar1.simpletext:='Создание авторского указателя...'; 
 +     per_authors; // создать глобальный авторский указатель (all_authors.htm) 
 +  end; 
 +  global_progress.StepIt; 
 + 
 +  if chk_make_rubriks.Checked then begin 
 +     statusbar1.simpletext:='Создание рубрикатора...'; 
 +     per_rubrics; // создать глобальный рубрикатор (all_rubrics.htm) 
 +  end; 
 +  global_progress.StepIt; 
 + 
 +  statusbar1.simpletext:='Создание закладок для файлов all_?????.htm...'; 
 +  make_bookmarks; // создать закладки для all_   .htm 
 +  global_progress.StepIt; 
 +  statusbar1.simpletext:='Готово'; 
 +  local_progress.Position := local_progress.Max; 
 +  global_progress.Position := global_progress.Max; 
 +end; 
 + 
 +procedure TForm1.btn_simple_volumeClick(Sender: TObject); 
 +begin 
 +   btn_simple_volume.OnClick := Local_Content_Create; 
 +   CreateBook; 
 +   btn_simple_volume.Caption := 'Построить оглавление заново'; 
 +   form1.Refresh; 
 +   chk_debug_mode.Checked := true; 
 +   Initiate_Data(Sender); 
 +   grp_simple_volume.Caption := edt_volumes.Items[0]; 
 +   Local_Content_Create(Sender); 
 +end; 
 + 
 +procedure TForm1.btn_Collect_DataClick(Sender: TObject); 
 +type temp_content_element = record 
 +        rubrika : string; 
 +        author : string; 
 +        title : string; 
 +        page : string; 
 +        link : string; 
 +        volume : string; 
 +        year : string; 
 +        responsible : string; 
 +     end; 
 +var FullFileName: string; 
 +    x, n, position:integer; 
 +    Values: OLEVariant; 
 +    IRange: Excel8TLB.Range; 
 +    ISheet: Excel8TLB._Worksheet; 
 +    Content: array of temp_content_element; 
 +    x_volume : string; 
 +    x_year : string; 
 +    x_responsible : string; 
 +    use_interpolation: boolean; 
 +procedure check_content(FullFileName:string; x:integer; var rubrika,author,title,page,link,volume,year,responsible:string); 
 +   var test:integer; 
 +   begin 
 +      if use_interpolation then begin 
 +         if trim(rubrika+author+title+page+link)='' then exit; // пропустить пустые вызовы 
 +      end else begin 
 +         if trim(rubrika+author+title+page+link+volume+year+responsible)='' then exit; // пропустить пустые вызовы 
 +      end; 
 +      if (trim(page)='') or (trim(volume)='') or (trim(year)='') or (trim(responsible)=''
 +         then ShowMessageInLog(year, volume, page, format('Отсутствует одно или несколько обязательных полей (страница, год, номер, ответственный), см. файл %s, строку %d',[FullFileName,x])); 
 +      if (trim(rubrika + author + title) = ''
 +         then ShowMessageInLog(year, volume, page, format('Не указано ни рубрики, ни автора, ни названия статьи, см. файл %s, строку %d',[FullFileName,x])); 
 +      try 
 +         test:=strtoint(year); 
 +      except 
 +         ShowMessageInLog(year, volume, page, format('Ошибка в формате года журнала %s, см. файл %s, строку %d',[year,FullFileName,x])); 
 +         test:=min_year; // чтобы не сработало следующее сообщение об ошибке 
 +      end; 
 +      if (test>max_year) or (test<min_year) 
 +         then ShowMessageInLog(year, volume, page, format('Год журнала %d, находится вне диапазона %d-%d, см. файл %s, строку %d',[test,min_year,max_year,FullFileName,x])); 
 +      try 
 +         test:=strtoint(volume); 
 +      except 
 +         ShowMessageInLog(year, volume, page, format('Ошибка в формате номера журнала %s, смфайл %s, строку %d',[volume,FullFileName,x])); 
 +         test:=1; // чтобы не сработало следующее сообщение об ошибке 
 +      end; 
 +      if (test>12) or (test<1) 
 +         then ShowMessageInLog(year, volume, page, format('Номер журнала %d, находится вне диапазона 01-12, смфайл %s, строку %d',[test,FullFileName,x])); 
 +      try 
 +         test:=strtoint(page); 
 +      except 
 +         ShowMessageInLog(year, volume, page, format('Ошибка в формате номера страницы %s, смфайл %s, строку %d',[page,FullFileName,x])); 
 +         test:=1; // чтобы не сработало следующее сообщение об ошибке 
 +      end; 
 +      if (test<1) or (test>200) 
 +         then ShowMessageInLog(year, volume, page, format('Номер страницы %d, находится вне диапазона 1-200, см. файл %s, строку %d',[test,FullFileName,x])); 
 +      if rubrika = 'Рефераты. Садоводу — на заметку' 
 +         then rubrika := 'Рефераты | Садоводу — на заметку'; 
 +      if rubrika = 'Вкладка' 
 +         then rubrika := 'Вкладки'; 
 +      if rubrika = 'Обложки' 
 +         then rubrika := 'Обложка'; 
 +      if rubrika = 'Рефераты. Садоводу — на заметку' 
 +         then rubrika := 'Рефераты | Садоводу — на заметку'; 
 +      if title = 'Маленькие хитрости' then begin 
 +         title := ''; 
 +         if rubrika = '' 
 +            then rubrika := 'Маленькие хитрости' 
 +            else rubrika := rubrika + ' | Маленькие хитрости'; 
 +         end; 
 +   end; 
 +function cure_volume(stroka:string):string; 
 +   begin 
 +      if length(stroka)=1 
 +        then result := '0' + stroka 
 +        else result := stroka; 
 +   end; 
 +function cure_page(stroka:string):string; 
 +   begin 
 +      try 
 +         stroka:=inttostr(strtoint(stroka)); 
 +      except 
 +      end; 
 +      result:=stroka; 
 +   end; 
 +function cure(stroka:string):string; 
 +   var temp: string; 
 +   begin 
 +      temp:=StringReplace(stroka, ', ' ', [rfReplaceAll]); 
 +      temp:=StringReplace(temp, '...', '…', [rfReplaceAll]); 
 +      temp:=StringReplace(temp, '|', ' | ', [rfReplaceAll]); 
 +      temp:=StringReplace(temp, ' ,', ',', [rfReplaceAll]); 
 +      temp:=StringReplace(temp, ' .', '.', [rfReplaceAll]); 
 +      temp:=StringReplace(temp, ',', ', ', [rfReplaceAll]); 
 +      temp:=StringReplace(temp, '.', '. ', [rfReplaceAll]); 
 +      temp:=StringReplace(temp, '. -', '.-', [rfReplaceAll]); 
 +      temp:=StringReplace(temp, '––', '—', [rfReplaceAll]); 
 +      temp:=StringReplace(temp, ', ' ', [rfReplaceAll]); 
 +      temp:=StringReplace(temp, ', ,', ',', [rfReplaceAll]); 
 +      temp:=StringReplace(temp, '. .', '.', [rfReplaceAll]); 
 +      result:=trim(temp); 
 +   end; 
 +begin 
 +  OpenDialog1.InitialDir:=prog_path; 
 +  OpenDialog1.Options:=OpenDialog1.Options + [ofAllowMultiSelect]; 
 +  if not OpenDialog1.Execute then exit; // свалить, коли не открыли ничего 
 + 
 +  if Application.MessageBox( 
 +    'Использовать интерполяцию года-номера-ответственного по первой статье в файлах?', 
 +    'Выбор способа обработки XLS файлов', 
 +    MB_YESNO) = IDYES then use_interpolation:=true else use_interpolation:=false; 
 +  statusbar1.simpletext:='Объединение файлов...'; 
 +  local_progress.Min:=0; 
 +  local_progress.max:=OpenDialog1.Files.Count; 
 +  local_progress.Position:=local_progress.Min; 
 +  local_progress.Step:=1; 
 +  global_progress.Min:=0; 
 +  global_progress.max:=3; 
 +  global_progress.Position:=global_progress.Min; 
 +  global_progress.Step:=1; 
 +  for n:=0 to OpenDialog1.Files.Count-1 do begin 
 +     FullFileName := OpenDialog1.Files[n]; 
 +     local_progress.StepIt; 
 +     statusbar1.simpletext:='Чтение файла '+extractfilename(fullfilename)+'...'; 
 +     if Assigned(IXLSApp) and (not Assigned(IWorkbook2))then 
 +       try 
 +         try 
 +           FIWorkbook2 := IXLSApp.Workbooks.Open(FullFileName, 
 +                EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, 
 +                EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, false, 0); 
 +           ISheet := IWorkbook2.Worksheets.Item['Содержание'] as Excel8TLB._Worksheet; 
 +           IRange := ISheet.UsedRange[0];  // прочесть весь лист 
 +           Values := IRange.Value; 
 +           position := length(Content); 
 +           setlength(Content, position + IRange.Rows.Count-1); 
 + 
 +           // взять значение номера, года и ответственного их первой строки 
 +           if use_interpolation then begin 
 +              x_volume := Values[2,6]; 
 +              x_volume := cure(x_volume); 
 +              x_volume := cure_volume(x_volume); 
 +              x_year := Values[2,7]; 
 +              x_year := cure(x_year); 
 +              x_responsible := Values[2,8]; 
 +              x_responsible := cure(x_responsible); 
 +           end; 
 +           for x:=2 to IRange.Rows.Count do begin 
 +              with Content[position + x - 2] do begin 
 +                 rubrika := Values[x,1]; 
 +                 rubrika := cure(rubrika); 
 +                 author := Values[x,2]; 
 +                 author := cure(author); 
 +                 title := Values[x,3]; 
 +                 title := cure(title); 
 +                 page := Values[x,4]; 
 +                 page := cure(page); 
 +                 page := cure_page(page); 
 +                 link := Values[x,5]; 
 +                 link := cure(link); 
 +                 if use_interpolation then begin 
 +                    volume := x_volume; 
 +                    year := x_year; 
 +                    responsible := x_responsible; 
 +                 end else begin 
 +                    volume := Values[x,6]; 
 +                    volume := cure(volume); 
 +                    volume := cure_volume(volume); 
 +                    year := Values[x,7]; 
 +                    year := cure(year); 
 +                    responsible := Values[x,8]; 
 +                    responsible := cure(responsible); 
 +                 end; 
 +                 check_content(FullFileName,x,rubrika,author,title,page,link,volume,year,responsible); 
 +              end; 
 +           end; 
 +         finally 
 +           IWorkBook2.Close(EmptyParam,EmptyParam,EmptyParam,0); 
 +           FIWorkbook2 := nil; 
 +           ISheet := nil; 
 +         end; 
 +       except 
 +         raise Exception.Create('Не могу открыть книгу!'); 
 +       end; 
 +     end; 
 +  try 
 +     statusbar1.simpletext:='Выгрузка в файл "content.xls"...'; 
 +     ISheet := IWorkbook.Worksheets.Item['Содержание'] as Excel8TLB._Worksheet; 
 +     IDispatch(IRange) := ISheet.UsedRange[0].Rows.Item[1, EmptyParam]; // первая строка области 
 +     Values := IRange.Value; // сохранить первую строку 
 +     ISheet.Cells.ClearContents; // очистить лист "Содержание" 
 +     IRange.Value := Values; // записать назад первую строку 
 +     //IRange := nil; 
 +     //VarClear(Values); 
 + 
 +     // выгрузить инфу на лист ISheet_auth_index 
 +     global_progress.StepIt; 
 +     Values := VarArrayCreate([1, length(Content)+1, 1, 8], varVariant); 
 +     local_progress.Min:=0; 
 +     local_progress.max:=length(Content); 
 +     local_progress.Position:=local_progress.Min; 
 +     local_progress.Step:=1; 
 +     for x:=0 to length(Content)-1 do begin 
 +        local_progress.StepIt; 
 +        with Content[x] do begin 
 +           if page = '' then continue; 
 +           Values[x+1,1] := rubrika; 
 +           Values[x+1,2] := author; 
 +           Values[x+1,3] := title; 
 +           Values[x+1,4] := page; 
 +           Values[x+1,5] := link; 
 +           Values[x+1,6] := volume; 
 +           Values[x+1,7] := year; 
 +           Values[x+1,8] := responsible; 
 +        end; 
 +     end; 
 +     IRange:= ISheet.Range['A2','H'+inttostr(length(Content)+1)]; 
 +     IRange.Value := Values; 
 +     VarClear(Values); 
 +  finally 
 +     setlength(Content,0); 
 +     IRange := nil; 
 +     ISheet := nil; 
 +     ShowExcel; 
 +     Application.BringToFront; 
 +     sort_articles; // отсортировать статьи 
 +  end; 
 +  global_progress.stepit; 
 +  statusbar1.simpletext:='Сохранение "content.xls"...'; 
 +  IWorkbook.Save(0); 
 + 
 +  local_progress.Position := local_progress.Max; 
 +  global_progress.Position := global_progress.Max; 
 +  statusbar1.simpletext:='Ok'; 
 +end; 
 + 
 +procedure TForm1.Button1Click(Sender: TObject); 
 +begin 
 +errorlog.Items.Clear; 
 +end; 
 + 
 +procedure TForm1.Button2Click(Sender: TObject); 
 +var otchet : TStringList; 
 +    x: integer; 
 +begin 
 +   otchet := TStringList.Create; 
 +   SaveDialog1.InitialDir := prog_path; 
 +   if SaveDialog1.Execute then begin 
 +      for x:=0 to ErrorLog.Items.Count-1 do begin 
 +         with ErrorLog.Items.Item[x] do 
 +            otchet.Add(format('%s: %s, %s, %s',[Caption, SubItems.Strings[0], SubItems.Strings[1], SubItems.Strings[2]])); 
 +      end; 
 +      otchet.SaveToFile(SaveDialog1.FileName); 
 +   end; 
 +   otchet.Free; 
 +end; 
 + 
 +end. 
 + 
 +// Особая позиция! Только для служебного "пользования"
 +// 
 +// Я люблю Microsoft. Всей своей мелкой душонкой, всем своим бренным телом и еще, чем только могу. 
 +// Я люблю эту фирму за то, что периодически веселит меня изменениями в логике методов интерфейсов 
 +// ее приложений. За то, что мне не скучно жить от все увеличивающихся сервис-паков и сервис-релизов. 
 +// За редактор VBA я тоже ее люблю, а особенно за ToolTips в нем и F5/F8/F9 в отладчике. И за язык 
 +// этот самый я ее люблю. И за "правильные" коллекции в ExcelTLB с прекрасным механизмом поиска в них. 
 +// И за обработку исключений, и за их периодическую генерацию. А еще люблю за то, что один и тот 
 +// же код может работать, а может и не работать. Все ж зависит от влажности воздуха и атмосферного 
 +// давления, от фазы луны и расположения звезд. Именно от этого перестает выгружаться Outlook с 
 +// парами десятков потоков. Он, ведь, практически не использует ресурсов? От этого также валится 
 +// Word, забыв сохранить последний мой файл. А Excel от этого только устойчивей. Он под натиском 
 +// обработчиков событий просто вешает своих клиентов, потом отрубает их и говорит, что его нельзя 
 +// закрывать, потому что просто нельзя! 
 +// Так я люблю Microsoft, такой сильною любовью, что если б была она женщиной... 
 +// Но видно не выжила б эта женщина в наших широтах с нашим климатом и под НАШИМИ звездами. 
 +// 
 +// А еще мне нравится справка по VBA. Особенно, в Excel 2000. И на кого ее рассчитывали? 
 +// Или они думали, что я буду набирать что-то, типа "Покажи мне, пожалуйста, как использовать 
 +// свойство SubTotals у объекта PivotField?" 
 +// 
 +//                                                                                Злой. 
 + 
 + 
 +</code> 
 + 
 + 
science_and_life/isxodniki.1487698377.txt.gz · Last modified: 2017/02/21 20:32 by 77.37.156.52

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki