2007 г.
Процедуры и функции для работы с OpenOffice
Владимир Ермаков, Королевство Дельфи
Все в мире развивается по спирали. Раньше программисты разрабатывали механизмы взаимодействия между Delphi и MSExcel, теперь они методом проб и ошибок создают приложения для создания документов в OpenOffice. Надеюсь, что эта статья сэкономит время и усилия для решения более важных проблем, чем открытие шаблона и поиск нужной ячейки.
Автор ни в коем случае не возлагает на себя лавры разработчика-первооткрывателя. Очень многое из данной статьи лежит в интернете на разных сайтах.
Например —
www.delphimaster.ru и др.
Другие процедуры и функции были созданы прямо в процессе работы над заданием.
Все было проверено на работоспособность.
Итак, начнем.
Для удобства работы, вынесем базовые функции и процедуры в новый класс
type TopofCalc = class(TObject)
при работе с таблицами, информация о типе документа может принимать следующие состояния:
type TTipooCalc = (ttcError, ttcNone, ttcExcel, ttcOpenOffice);
данные функции определяет тип приложения
function TopofCalc.GetIsExcel: boolean;
begin
result:= (Tipoo=ttcExcel);
end;
function TopofCalc.GetIsOpenOffice: boolean;
begin
result:= (Tipoo=ttcOpenOffice);
end;
и произведена ли его загрузка
function TopofCalc.GetProgLoaded: boolean;
begin
result:= not (VarIsEmpty(Programa) or VarIsNull(Programa));
end;
function TopofCalc.GetDocLoaded: boolean;
begin
result:= not (VarIsEmpty(Document) or VarIsNull(Document));
end;
запуск приложения…
procedure TopofCalc.LoadProg;
begin
if ProgLoaded then CloseProg;
if ((UpperCase(ExtractFileExt(FileName))='.XLS') or
(UpperCase(ExtractFileExt(FileName))='.XLT')) then begin
//Excel...
Programa:= CreateOleObject('Excel.Application');
Programa.Application.EnableEvents:=false;
Programa.displayAlerts:=false;
if ProgLoaded then Tipoo:= ttcExcel;
end;
// Another filetype? Let's go with OpenOffice...
if ((UpperCase(ExtractFileExt(FileName))='.ODS') or
(UpperCase(ExtractFileExt(FileName))='.OTS')) then begin
//OpenOffice.calc...
Programa:= CreateOleObject('com.sun.star.ServiceManager');
if ProgLoaded then Tipoo:= ttcOpenOffice;
end;
//Still no program loaded?
if not ProgLoaded then begin
Tipoo:= ttcError;
raise Exception.create('TopofCalc.create failed, may be no Office is installed?');
end;
end;
проведя все необходимые проверки, мы можем создать электронную таблицу
procedure TopofCalc.NewDoc;
var ooParams: variant;
begin
if not ProgLoaded
then raise exception.create('No program loaded for the new document.');
if DocLoaded then CloseDoc;
DeskTop:= Unassigned;
if IsExcel then begin
Programa.WorkBooks.Add();
Programa.Visible:= Visible;
Document:= Programa.ActiveWorkBook;
ActiveSheet:= Document.ActiveSheet;
end;
if IsOpenOffice then begin
Desktop:= Programa.CreateInstance('com.sun.star.frame.Desktop');
ooParams:= VarArrayCreate([0, 0], varVariant);
ooParams[0]:= ooCreateValue('Hidden', not Visible);
Document:= Desktop.LoadComponentFromURL('private:factory/scalc', '_blank',
0, ooParams);
ActivateSheetByIndex(1);
end;
end;
а теперь закрыть таблицу
procedure TopofCalc.CloseDoc;
begin
if DocLoaded then begin
try
if IsOpenOffice then Document.Dispose;
if IsExcel then Document.close;
finally
//Clean up both "pointer"...
Document:= Null;
ActiveSheet:= Null;
end;
end;
end;
и само приложение
procedure TopofCalc.CloseProg;
begin
if DocLoaded then CloseDoc;
if ProgLoaded then begin
try
if IsExcel then Programa.Quit;
Programa:= Unassigned;
finally end;
end;
Tipoo:= ttcNone;
end;
вынесем последовательности команд создания таблицы в отдельную процедуру конструктора
constructor TopofCalc.CreateTable(MyTipoo: TTipooCalc; MakeVisible: boolean);
var
i: integer;
IsFirstTry: boolean;
begin
//Close all opened things first...
if DocLoaded then CloseDoc;
if ProgLoaded then CloseProg;
IsFirstTry:= true;
for i:= 1 to 2 do begin
//Try to open OpenOffice...
if (MyTipoo = ttcOpenOffice) or (MyTipoo = ttcNone)then begin
Programa:= CreateOleObject('com.sun.star.ServiceManager');
if ProgLoaded then begin
Tipoo:= ttcOpenOffice;
break;
end else begin
if IsFirstTry then begin
//Try Excel as my second choice
MyTipoo:= ttcExcel;
IsFirstTry:= false;
end else begin
//Both failed!
break;
end;
end;
end;
//Try to open Excel...
if (MyTipoo = ttcExcel) or (MyTipoo = ttcNone) then begin
Programa:= CreateOleObject('Excel.Application');
if ProgLoaded then begin
Tipoo:= ttcExcel;
break;
end else begin
if IsFirstTry then begin
//Try OpenOffice as my second choice
MyTipoo:= ttcOpenOffice;
IsFirstTry:= false;
end else begin
//Both failed!
break;
end;
end;
end;
end;
//Was it able to open any of them?
if Tipoo = ttcNone then begin
Tipoo:= ttcError;
raise exception.create('TopofCalc.create failed, may be no OpenOffice is installed?');
end;
//Add a blank document...
fVisible:= MakeVisible;
NewDoc;
end;
это – создание таблицы «с нуля». откроем существующую
procedure TopofCalc.LoadDoc;
var ooParams: variant;
begin
if FileName='' then exit;
if not ProgLoaded then LoadProg;
if DocLoaded then CloseDoc;
DeskTop:= Unassigned;
if IsExcel then begin
Document:=Programa.WorkBooks.Add(FileName);
Document.visible:=visible;
Document:= Programa.ActiveWorkBook;
ActiveSheet:= Document.ActiveSheet;
end;
if IsOpenOffice then begin
Desktop:= Programa.CreateInstance('com.sun.star.frame.Desktop');
ooParams:= VarArrayCreate([0, 0], varVariant);
ooParams[0]:= ooCreateValue('Hidden', not Visible);
Document:= Desktop.LoadComponentFromURL(FileNameToURL(FileName), '_blank', 0, ooParams);
ActivateSheetByIndex(1);
end;
if Tipoo=ttcNone then
raise exception.create('File "'+FileName+'" is not loaded. Are you install OpenOffice?');
end;
опишем еще один конструктор для открытия существующей таблицы
constructor TopofCalc.OpenTable(Name: string; MakeVisible: boolean);
begin
//Store values...
FileName:= Name;
fVisible:= MakeVisible;
//Open program and document...
LoadProg;
LoadDoc;
end;
кроме того, опишем уничтожение объекта
destructor TopofCalc.Destroy;
begin
CloseDoc;
CloseProg;
inherited;
end;
по аналогии, опишем сохранение
function TopofCalc.SaveDoc: boolean;
begin
result:= false;
if DocLoaded then begin
if IsExcel then begin
Document.Save;
result:= true;
end;
if IsOpenOffice then begin
Document.Store;
result:= true;
end;
end;
end;
печать
function TopofCalc.PrintDoc: boolean;
var ooParams: variant;
begin
result:= false;
if DocLoaded then begin
if IsExcel then begin
Document.PrintOut;
result:= true;
end;
if IsOpenOffice then begin
//NOTE: OpenOffice will print all sheets with Printable areas, but if no
//printable areas are defined in the doc, it will print all entire sheets.
//Optional parameters (wait until fully sent to printer)...
ooParams:= VarArrayCreate([0, 0], varVariant);
ooParams[0]:= ooCreateValue('Wait', true);
Document.Print(ooParams);
result:= true;
end;
end;
end;
и режим предварительного просмотра
procedure TopofCalc.ShowPrintPreview;
begin
if DocLoaded then begin
Visible:= true;
if IsExcel then
Document.PrintOut(,,,true);
if IsOpenOffice then
ooDispatch('.uno:PrintPreview', Unassigned);
end;
end;
нам также пригодится скрытие/отображение на экране
procedure TopofCalc.SetVisible(v: boolean);
begin
if DocLoaded and (v<>fVisible) then begin
if IsExcel then
Programa.Visible:= v;
if IsOpenOffice then
Document.getCurrentController.getFrame.getContainerWindow.setVisible(v);
fVisible:= v;
end;
end;
теперь, мы можем получить информацию о таблице.
Начнем с количества листов
function TopofCalc.GetCountSheets: integer;
begin
result:= 0;
if DocLoaded then begin
if IsExcel then result:= Document.Sheets.count;
if IsOpenOffice then result:= Document.getSheets.GetCount;
end;
end;
и сделаем один из листов активным.
function TopofCalc.ActivateSheetByIndex(nIndex: integer): boolean;
begin
result:= false;
if DocLoaded then begin
if IsExcel then begin
Document.Sheets[nIndex].activate;
ActiveSheet:= Document.ActiveSheet;
result:= true;
end;
//Index is 1 based in Excel, but OpenOffice uses it 0-based
if IsOpenOffice then begin
ActiveSheet:= Document.getSheets.getByIndex(nIndex-1);
result:= true;
end;
sleep(100); //Asyncronus, so better give it time to make the change
end;
end;
активным лист можно сделать не только по его индексу, но и по названию
function TopofCalc.ActivateSheetByName(SheetName: string; CaseSensitive: boolean): boolean;
var
OldActiveSheet: variant;
i: integer;
begin
result:= false;
if DocLoaded then begin
if CaseSensitive then begin
//Find the EXACT name...
if IsExcel then begin
Document.Sheets[SheetName].Select;
ActiveSheet:= Document.ActiveSheet;
result:= true;
end;
if IsOpenOffice then begin
ActiveSheet:= Document.getSheets.getByName(SheetName);
result:= true;
end;
end else begin
//Find the Sheet regardless of the case...
OldActiveSheet:= ActiveSheet;
for i:= 1 to GetCountSheets do begin
ActivateSheetByIndex(i);
if UpperCase(ActiveSheetName)=UpperCase(SheetName) then begin
result:= true;
Exit;
end;
end;
//If not found, let the old active sheet active...
ActiveSheet:= OldActiveSheet;
end;
end;
end;
getByName(string) имеет свойства для чтения и записи
function TopofCalc.GetActiveSheetName: string;
begin
if DocLoaded then begin
if IsExcel then
result:= ActiveSheet.Name;
if IsOpenOffice then
result:= ActiveSheet.GetName;
end;
end;
procedure TopofCalc.SetActiveSheetName(NewName: string);
var ooParams:variant;
begin
if DocLoaded then begin
if IsExcel then
Programa.ActiveSheet.Name:= NewName;
if IsOpenOffice then begin
ActiveSheet.setName(NewName);
//This code always changes the name of "visible" sheet, not active one!
ooParams:= VarArrayCreate([0, 0], varVariant);
ooParams[0]:= ooCreateValue('Name', NewName);
ooDispatch('.uno:RenameTable', ooParams);
end;
end;
end;
пригодится проверка на защиту листа от записи
function TopofCalc.IsActiveSheetProtected: boolean;
begin
result:= false;
if DocLoaded then begin
if IsExcel then
result:= ActiveSheet.ProtectContents;
if IsOpenOffice then
result:= ActiveSheet.IsProtected;
end;
end;
добваление листа
procedure TopofCalc.AddNewSheet(NewName: string);
var
ooSheets: variant;
begin
if DocLoaded then begin
if IsExcel then begin
Document.WorkSheets.Add;
Document.ActiveSheet.Name:= NewName;
//Active sheet has move to this new one, so I need to update the var
ActiveSheet:= Document.ActiveSheet;
end;
if IsOpenOffice then begin
ooSheets:= Document.getSheets;
ooSheets.insertNewByName(NewName, 1);
//Redefine active sheet to this new one
ActiveSheet:= ooSheets.getByName(NewName);
end;
end;
end;
перейдем от листов к ячейкам
получить значение ячейки
//OpenOffice start at cell (0,0) while Excel at (1,1)
function TopofCalc.GetCellText(row, col: integer): string;
begin
if DocLoaded then begin
if IsExcel then result:= ActiveSheet.Cells[row, col].Formula; //.Text;
if IsOpenOffice then result:= ActiveSheet.getCellByPosition(col-1, row-1).getFormula;
end;
end;
установить значение
procedure TopofCalc.SetCellText(row, col: integer; Txt: string);
begin
if DocLoaded then begin
if IsExcel then ActiveSheet.Cells[row, col].Formula:= Txt;
if IsOpenOffice then ActiveSheet.getCellByPosition(col-1, row-1).setFormula(Txt);
end;
end;
то же самое, но по имени ячейки.
Обязательно указание номера листа
function TopofCalc.GetCellTextByName(Range: string): string;
var OldActiveSheet: variant;
begin
if DocLoaded then begin
if IsExcel then begin
result:= Programa.Range[Range].Text; //Set 'Formula' but Get 'Text';
end;
if IsOpenOffice then begin
OldActiveSheet:= ActiveSheet;
//If range is in the form 'NewSheet!A1' then first change sheet to 'NewSheet'
if pos('!', Range) > 0 then begin
//Activate the proper sheet...
if not ActivateSheetByName(Copy(Range, 1, pos('!', Range)-1), false) then
raise exception.create('Sheet "'+Copy(Range, 1, pos('!', Range)-1)+
'" not present in the document.');
Range:= Copy(Range, pos('!', Range)+1, 999);
end;
result:= ActiveSheet.getCellRangeByName(Range).getCellByPosition(0,0).getFormula;
ActiveSheet:= OldActiveSheet;
end;
end;
end;
procedure TopofCalc.SetCellTextByName(Range: string; Txt: string);
var OldActiveSheet: variant;
begin
if DocLoaded then begin
if IsExcel then begin
Programa.Range[Range].formula:= Txt;
end;
if IsOpenOffice then begin
OldActiveSheet:= ActiveSheet;
//If range is in the form 'NewSheet!A1' then first change sheet to 'NewSheet'
if pos('!', Range) > 0 then begin
//Activate the proper sheet...
if not ActivateSheetByName(Copy(Range, 1, pos('!', Range)-1), false) then
raise exception.create('Sheet "'+Copy(Range, 1, pos('!', Range)-1)+
'" not present in the document.');
Range:= Copy(Range, pos('!', Range)+1, 999);
end;
ActiveSheet.getCellRangeByName(Range).getCellByPosition(0,0).SetFormula(Txt);
ActiveSheet:= OldActiveSheet;
end;
end;
end;
а так же – размера шрифта.
Можно установить его в шаблоне, а можно прямо в ходе работы программы.
procedure TopofCalc.FontSize(row,col:integer;oosize:integer);
begin
if DocLoaded then begin
if IsExcel then begin
Programa.ActiveSheet.Cells[row,col].Font.Size:=oosize;
end;
if IsOpenOffice then begin
ActiveSheet.getCellByPosition(col-1, row-1).getText.createTextCursor.CharHeight:= oosize;
end;
end;
end;
сделать шрифт жирным
procedure TopofCalc.Bold(row,col: integer);
const ooBold: integer = 150; //150 = com.sun.star.awt.FontWeight.BOLD
begin
if DocLoaded then begin
if IsExcel then begin
Programa.ActiveSheet.Cells[row,col].Font.Bold;
end;
if IsOpenOffice then begin
ActiveSheet.getCellByPosition(col-1, row-1).getText.createTextCursor.CharWeight:= ooBold;
end;
end;
end;
изменить ширину столбца
procedure TopofCalc.ColumnWidth(col, width: integer); //Width in 1/100 of mm.
begin
if DocLoaded then begin
if IsExcel then begin
//Excel use the width of '0' as the unit, we do an aproximation: Width '0' = 2 mm.
Programa.ActiveSheet.Cells[col, 1].ColumnWidth:= width/100/3;
end;
if IsOpenOffice then begin
ActiveSheet.getCellByPosition(col-1, 0).getColumns.getByIndex(0).Width:= width;
end;
end;
end;
в заключение, предлагаю функции, предназначенные именно для OpenOffice
преобразование имени
//Change 'C:\File.txt' into 'file:///c:/File.txt' (for OpenOffice OpenURL)
function TopofCalc.FileNameToURL(FileName: string): string;
begin
result:= '';
if LowerCase(copy(FileName,1,8))<>'file:///' then
result:= 'file:///';
result:= result + StringReplace(FileName, '\', '/', [rfReplaceAll, rfIgnoreCase]);
end;
создание объекта
function TopofCalc.ooCreateValue(ooName: string; ooData: variant): variant;
var
ooReflection: variant;
begin
if IsOpenOffice then begin
ooReflection:= Programa.createInstance('com.sun.star.reflection.CoreReflection');
ooReflection.forName('com.sun.star.beans.PropertyValue').createObject(result);
result.Name := ooName;
result.Value:= ooData;
end else begin
raise exception.create('ooValue imposible to create, load OpenOffice first!');
end;
end;
запуск диспатчера
procedure TopofCalc.ooDispatch(ooCommand: string; ooParams: variant);
var
ooDispatcher, ooFrame: variant;
begin
if DocLoaded and IsOpenOffice then begin
if (VarIsEmpty(ooParams) or VarIsNull(ooParams)) then
ooParams:= VarArrayCreate([0, -1], varVariant);
ooFrame:= Document.getCurrentController.getFrame;
ooDispatcher:= Programa.createInstance('com.sun.star.frame.DispatchHelper');
ooDispatcher.executeDispatch(ooFrame, ooCommand, '', 0, ooParams);
end else begin
raise exception.create('Dispatch imposible, load a OpenOffice doc first!');
end;
end;
end.