// Экспорт инвентарки по скрипту (пример) const xlNone = $FFFFEFD2; xlAutomatic = $FFFFEFF7; xlContinuous = $00000001; xlInsideHorizontal = $0000000C; xlInsideVertical = $0000000B; xlEdgeBottom = $00000009; xlEdgeLeft = $00000007; xlEdgeRight = $0000000A; xlEdgeTop = $00000008; xlThin = $00000002; var DATASET: TDataset;//DATASET передает данные(описывается здесь,но инициализируется извне, в солярисе) cds: TClientdataset; Table: TTable; ID_ORG: integer;//Код контрагента (склада) RowCount,ColCount:INTEGER; Dialog: TSaveDialog; Filename: string; WF: TMyWait; // Окно прогресса QText, S : String; STemp : Variant; //----------------------------------------- //Ershov: mFilenameExcel:string; mExcelApp,mWorkbook,mExcelAppSheet: Variant;// mRange, mCell1, mCell2, mCellAll: Variant; mArrayA:Variant;//вариантный массив удобен для массивов переменной длины и размерности mDateTimeBegA,mDateTimeEndA, mDeltaTimeA:TDateTime; mDateTimeBegB,mDateTimeEndB, mDeltaTimeB:TDateTime; //-------------------------------------- //For IDE D7 //implementation //Uses //bvMessageUnit,Variants, //ComObj;//For Ole //--------------------------- { function IsOLEObjectInstalled(Name: String): boolean; //Функция возвращает True если найден OLE-объект //uses ComObj, ActiveX; var ClassID: TCLSID;//CLSID является глобально уникальным идентификатором (GUID). Rez : HRESULT; begin // Ищем CLSID OLE-объекта //хелп по функции API CLSIDFromProgID. Rez := CLSIDFromProgID(PWideChar(WideString(Name)), ClassID); if Rez = S_OK then Result := true // Объект найден else Result := false; end; } //----------------------------------------- { function IsObjectActive(ClassName: string): Boolean; //uses ComObj, ActiveX; var ClassID: TCLSID; Unknown: IUnknown; begin try ClassID := ProgIDToClassID(ClassName); Result := GetActiveObject(ClassID, nil, Unknown) = S_OK; except // raise; Result := False; end; end; } //----------------------------------------- procedure InitOleObjectExcel; Begin //Как определить установлен ли Excel { if not IsOLEObjectInstalled('Excel.Application') then Begin ShowMessage('Не установлен Excel:Класс не зарегистрирован');exit; End; if not IsObjectActive('Excel.Application') then ShowMessage('Excel не вызван !'); } try // try // mExcelApp:= GetActiveOleObject('Excel.Application'); // except mExcelApp:= CreateOleObject('Excel.Application'); // end; except ShowMessage('Не могу запустить Excel'); Exit; end; mExcelApp.DisplayAlerts:=False;//false-отключить диалоговое окно(например,подтверждения операции или замены)Excel. try mExcelApp.Workbooks.Open(mFileNameExcel);//Open a Workbooks except ShowMessage('Не найден файл=' + mFileNameExcel);Exit; end; mExcelApp.Application.EnableEvents:=false;//Отключаем реакцию Excel на события для ускорения вывода информации end; procedure CloseOleObjectExcel; Begin // mExcelApp.Visible:=True;//Делаем Excel видимым Try mExcelApp.ActiveWorkBook.SaveAs(mFilenameExcel);//Save the active Workbook EXCEPT; bvMessageError('Закройте дубликаты,возможно уже открыт файл=' + mFileNameExcel);Exit; End;//EndTRY mExcelApp.WorkBooks.Close;//закрываем файл mExcelApp.Quit;//закрываем Эксель end; //----------------------------------------- procedure SelectSheet(NameSheet:STRING); Begin //Вариант 1:выбирается лист, которая была активна в пред.сессии Try //mExcelAppSheet:=mExcelApp.Workbooks[1].Worksheets['Вед.Расхожд.'];// mExcelAppSheet:= mExcelApp.Workbooks[1].Worksheets[NameSheet];// mExcelAppSheet.Activate;//Нужна для пред.ку EXCEPT; bvMessageWarning('Не существует листа с именем='+ NameSheet); mExcelAppSheet:= mExcelApp.ActiveSheet;//Так как такого листа не, то вывод будет на последний активный лист End;//EndTRY End; //------------------------------------------------------------------------ Procedure DebugVisual(XMassiv:Variant;XNStr,XNStolb:INTEGER); Var mIStr,mJStolb, mCodType:INTEGER; Begin //Unassigned -переменная является нетронутой, т.е. переменной еще не присвоено значение. Оно автоматически устанавливается в качестве начального значения любой переменной с типом Variant. //Null - переменная имеет неопределенное значение. Если в выражении участвует переменная со значением Null, то результат всего выражения тоже равен Null. bvMessageWarning('DebugVisual XNStr='+IntToStr(XNStr)); bvMessageWarning('DebugVisual XNStolb='+IntToStr(XNStolb)); for mIStr:= 0 to XNStr-1 do begin for mJStolb:= 0 to XNStolb -1 do begin // bvMessageWarning('DebugVisual XMassiv='+VarToStr(XMassiv[IStr,JStolb])); bvMessageWarning('DebugVisual mIStr='+IntToStr(mIStr)); bvMessageWarning('DebugVisual mJStolb='+IntToStr(mJStolb)); //------------------------ mCodType:=VarType(XMassiv[mIStr,mJStolb]); Case mCodType OF 0: bvMessageWarning('Переменная содержит значение Unassigned'); 1: bvMessageWarning('Переменная содержит значение Null'); 2,3,10,14: bvMessageWarning('DebugVisual XMassiv='+IntToStr(XMassiv[mIStr,mJStolb])); 5,6: bvMessageWarning('DebugVisual XMassiv='+FloatToStr(XMassiv[mIStr,mJStolb])); 7: bvMessageWarning('DebugVisual XMassiv='+DateTimeToStr(XMassiv[mIStr,mJStolb])); 8: bvMessageWarning('DebugVisual XMassiv='+VarToStr(XMassiv[mIStr,mJStolb]));//varOleStr 100: bvMessageWarning('DebugVisual XMassiv='+ XMassiv[mIStr,mJStolb]); else bvMessageWarning('DebugVisual не известный тип mCodType='+IntToStr(mCodType)); End;//EndCase //------------------------ end; end; bvMessageWarning('DebugVisual Finish'); end; procedure CopyDataSetTocds(XFields: string); Begin //var cds :TClientdataset; cds:=Tclientdataset.create(nil); //cds.Close; //Поиск по ключевому полю:fields и fielddefs //в конце CDS.Createdataset; //procedure Add(const Name: string; DataType: TFieldType; Size: Word; Required: Boolean) cds.fielddefs.Clear; //создаем нужные нам поля ReadDataSet(DestinationCDS :TClientDataSet; SourceDataSet :TDataSet) :Boolean //первым полем сразу создать автоинкрементное поле, оно само будет генериться cds.fielddefs.Add('RecNom',ftInteger,0,false); cds.fielddefs.Add('id',ftInteger,0,false);//Отличие: ID товара cds.fielddefs.Add('BarCode',ftString,10,false); cds.fielddefs.Add('Name',ftstring,100,false); cds.fielddefs.Add('Seria',ftString,100,false); cds.fieldDefs.Add('Nomer',ftString,20,false); cds.fielddefs.Add('Data',ftDate,0,false); cds.fielddefs.Add('ProducerN',ftstring,30,false);//Отличие:Производитель cds.fielddefs.Add('CenaRub',ftFloat,0,false); cds.fielddefs.Add('Ostat',ftFloat,0,false); cds.fielddefs.Add('RealOstat',ftFloat,0,false); cds.fielddefs.Add('NN',ftString,20,false); cds.fielddefs.Add('SumOstat',ftFloat,0,false); cds.createDataset; //bvMessageWarning('1 CopyDataSetTocds'); //--------------------------------------------- //потом возпользоваться функцией копирования таблицы в таблицу, //смотри у меня ReadDAtaset в скриптере. //cds.ReadDataSet(DestinationCDS :TClientDataSet; SourceDataSet :TDataSet) :BooleanReadDataSet(DestinationCDS :TClientDataSet; SourceDataSet :TDataSet) :BooleanReadDataSet(DestinationCDS :TClientDataSet; SourceDataSet :TDataSet) :Booleanfree; //readdataset(cds,DATASET); //TableAppend(ResultTable: Tdataset; Source: TDataSet; p0asNULL: boolean = False; Fields: string=''; UseProgress :Boolean =false) TableAppend(cds,DATASET,false,XFields,false); cds.LogChanges:= false;//быстрее заполнялось и редактировалось,иначе будет лог изменений вести End; //----------------------------------- //----------------------------------------------------- //ReadDatasetToArray есть в Common6\bvcomp\bvdb.pas //Для быстроты отладки вставил сюда //function E_ReadDataSetToArray(SourceDataset :TDataSet;ColumnsFirst :Boolean; const Fields: string):OleVariant; // возвращает VarArray function E_ReadDataSetToArray(SourceDataset :TDataSet;ColumnsFirst :Boolean; const Fields: string):Variant; // возвращает VarArray //Автор Борзов var i,k :Integer; FC :Integer; List :TStringList; i1,i2 :Integer; begin Result := Null; //assert(Sourcedataset.Active,'Не открыт набор данных');//Не compile sub script SourceDataset.Last; // FetchAll SourceDataSet.First; List := TStringList.Create; try if Fields <> '' then begin StringToList(Fields,List,';'); FC := List.Count; end else FC := SourceDataSet.FieldCount; if SourceDataSet.RecordCount > 0 then begin if ColumnsFirst then Result := VarArrayCreate([0,FC-1,0,SourceDataSet.RecordCount-1],varVariant) else Result := VarArrayCreate([0,SourceDataSet.RecordCount-1,0,FC-1],varVariant); k := 0; sourcedataset.DisableControls; try while not SourceDataSet.eof do begin for i := 0 to FC-1 do begin if ColumnsFirst then begin i1 := i; i2 := K; end else begin i1 := k; i2 := i; end; if List.Count>0 then Result[i1,i2] := SourceDataset.FieldByName(List[i]).Value else Result[i1,i2] := SourceDataSet.Fields[i].Value; end; Inc(k); SourceDataSet.next end; // DebugVisual(Result,SourceDataSet.RecordCount,i2); //Отладка finally sourcedataset.EnableControls; end; end; finally List.free end; end; //----------------------------------------------------- function E_CopyDataSetToArray(XDataset:TDataSet):Variant; // возвращает VarArray Var mNRec,mNCol:Integer; DD : Double; begin Result:= Null; RowCount:= XDATASET.RecordCount;//RowCount-число строк ColCount:= 19;//ColCount-число колонок 17 //Заполняем массив mArrayA Result:= VarArrayCreate([0, RowCount, 0, ColCount], varVariant); //--------------------- With XDATASET Do Begin DisableControls;//Для быстроты First; mNRec:=0; // wf.ProgressBarmin:=1; // wf.ProgressBarmax:=RowCount;//Борзов While Not eof Do Begin mNCol:=0; // wf.progressbarpos := dataset.recno;//Борзов // wf.progressbarpos:= mNRec; IF(not FieldByName('RecNom').IsNull)then Result[mNRec,mNCol]:= FieldbyName('RecNom').AsInteger;// N п/п mNCol:=mNCol+1; IF(not FieldByName('id').IsNull)then Result[mNRec,mNCol]:= FieldbyName('id').Value; // ID товара mNCol:=mNCol+1; IF(not FieldByName('barcode').IsNull)then Result[mNRec,mNCol]:= Trim(FieldbyName('barcode').AsString); //Штрих-код mNCol:=mNCol+1; IF(not FieldByName('Name').IsNull)then Result[mNRec,mNCol]:= Trim(FieldbyName('Name').AsString);//Наименование mNCol:=mNCol+1; IF(not FieldByName('Seria').IsNull) or (Trim(FieldByName('Seria').AsString) = '') then Result[mNRec,mNCol]:= Trim(FieldbyName('Seria').AsString);//Серия mNCol:=mNCol+1; IF(not FieldByName('Nomer').IsNull)then Result[mNRec,mNCol]:= Trim(FieldbyName('Nomer').AsString)//Номер накл. else Result[mNRec,mNCol]:= ''; mNCol:=mNCol+1; IF(not FieldByName('Data').IsNull)then Result[mNRec,mNCol]:= FieldbyName('Data').AsDateTime; //Дата прихода mNCol:=mNCol+1; //--------- Result[mNRec,mNCol]:= ''; mNCol:=mNCol+1; IF(not FieldByName('CenaRub').IsNull)then Result[mNRec,mNCol]:= FormatFloat('#,##0.00', FieldbyName('CenaRub').AsFloat); //Цена розничная mNCol:=mNCol+1; IF(not FieldByName('Ostat').IsNull)then Result[mNRec,mNCol]:= FormatFloat('0.00',FieldbyName('Ostat').value);//Остаток mNCol:=mNCol+1; IF(not FieldByName('RealOstat').IsNull)then Result[mNRec,mNCol]:= FormatFloat('0.00',FieldbyName('RealOstat').value);//Ост.Реально mNCol:=mNCol+1; IF(not FieldByName('NN').IsNull)then Result[mNRec,mNCol]:= Trim(FieldbyName('NN').AsString);//Код товара mNCol:=mNCol+1; IF(not FieldByName('SumOstat').IsNull)then Result[mNRec,mNCol]:= FormatFloat('#,##0.00', FieldbyName('SumRealOstat').AsFloat) else Result[mNRec,mNCol]:= 0.00; mNCol:=mNCol+1; //--------- mNRec:=mNRec +1; Next; End; EnableControls;// End;//EndWith end; //----------------------------------------------------- procedure DrawCellBorder(NRow,NCol:Variant; Grid : Boolean); var RCell : Variant; begin RCell:= mExcelAppSheet.Range[NRow,NCol]; with RCell.Borders(xlEdgeLeft) do begin if not Grid then LineStyle:= xlNone else begin LineStyle:= xlContinuous; Weight:= xlThin; end; End; with RCell.Borders(xlEdgeTop) do begin if not Grid then LineStyle:= xlNone else begin LineStyle:= xlContinuous; Weight:= xlThin; end; End; with RCell.Borders(xlEdgeBottom) do begin if not Grid then LineStyle:= xlNone else begin LineStyle:= xlContinuous; Weight:= xlThin; end; End; with RCell.Borders(xlEdgeRight) do begin if not Grid then LineStyle:= xlNone else begin LineStyle:= xlContinuous; Weight:= xlThin; end; End; with RCell.Borders(xlInsideVertical) do begin if not Grid then LineStyle:= xlNone else begin LineStyle:= xlContinuous; Weight:= xlThin; end; End; with RCell.Borders(xlInsideHorizontal) do begin if not Grid then LineStyle:= xlNone else begin LineStyle:= xlContinuous; Weight:= xlThin; end; End; end; procedure SolarisToExcel(ShablonFile:String); Var mBeginCol,mBeginRow,mRowCount,mColCount,i,j: integer; mNRec,mNCol:Integer; mNPolei:INTEGER; Str, Qs : String; Rows, Columns, TotalRows, i,j : Integer; RRange, RRange2, SS : Variant; Query : TQuery; Begin SelectSheet('Вед.Расхожд.');//Выбирается лист mBeginRow:=4; mBeginCol:=1; mColCount:= 19;//ColCount-число колонок (было 17) Rows:= mExcelAppSheet.UsedRange.Rows.Count; Columns:= mExcelAppSheet.UsedRange.Columns.Count; RRange:= mExcelAppSheet.Cells[mBeginRow, mBeginCol]; RRange2:= mExcelAppSheet.Cells[Rows,Columns]; mExcelAppSheet.Range[RRange, RRange2].Clear; //--------------------- //Вариант 1:Ершов,в этом скрипте { mRowCount:=DATASET.RecordCount;//RowCount-число строк mArrayA:=E_CopyDataSetToArray(DATASET);//mArrayA mNPolei:=10;DebugVisual(mArrayA,mRowCount,mNPolei); //Отладка } //---------------------------------------------------- //Вариант 4.1:Борзов, в этом скрипте { mRowCount:=DATASET.RecordCount;//mRowCount-число строк CopyDataSetTocds('recnom;barcode;name;seria;nomer;data;cenarub;ostat;realostat;nn'); //bvMessageWarning('1 SolarisToExcel'); mArrayA:=E_ReadDatasetToArray(cds,false,'recnom;InventarNom;barcode;name;seria;nomer;Data;Producer;cenarub;ostat;realostat;nn'); //mNPolei:=12;DebugVisual(mArrayA,mRowCount,mNPolei); //Отладка //bvMessageWarning('2 SolarisToExcel'); } //---------------------------------------------------- //Вариант 4.2:Борзов, в Common6\bvcomp\bvdb.pas mRowCount:= DATASET.RecordCount;//mRowCount-число строк ///// CopyDataSetTocds('recnom;Producer;barcode;name;seria;nomer;data;Producer;cenarub;ostat;realostat;nn'); ///// mArrayA:= ReadDatasetToArray(cds,false,'recnom;barcode;name;seria;nomer;Data;ProducerN;cenarub;ostat;realostat;nn'); CopyDataSetTocds('recnom;id;barcode;name;seria;nomer;data;cenarub;ostat;realostat;nn;SumOstat'); mArrayA:= ReadDatasetToArray(cds,false,'recnom;id;barcode;name;seria;nomer;Data;ProducerN;cenarub;ostat;realostat;nn;SumOstat'); // bvMessageWarning('2 SolarisToExcel'); //---------------------------------------------------- //ReadDatasetToArray есть в Common6\bvcomp\bvdb.pas //SQL запросить - фукнция BDEQueryValue; //она берет на вход SQL-текст-параметры. возвращает одно значение //BDEQueryValues - возвратит вариантный массив значений, //одно или двумерный, в зависимости от результата, этого хватит для любого запроса //База данных,с которой сейчас работает солярис,видна ему изнутри как 'dbkassa' //это более универсальное средства, чем указывать жестко алиас //функция,формирующая этот массив, уже есть, я тебе ее имя давал тоже //--------------------- // mDateTimeEndA:=Time; //mExcelApp.Cells[6,5].Value:='www.spz.ru'; //---------------------------------------------------- // mCellAll:= mExcelAppSheet.Range[RRange,mExcelAppSheet.Cells.SpecialCells($0000000B)]; // Идем к последней ячейке // DrawCellBorder(RRange,RRange2, False); // Убираем сетку // mCellAll:= mExcelAppSheet.Range[RRange, RRange2]; // mCellAll.Interior.Color:= clWhite; // xlNone - без заливки; // mCellAll.ClearContents; // Очищаем содержимое // mExcelAppSheet.Cells[mBeginRow,mBeginCol].Select; mCell1:= mExcelAppSheet.Cells[mBeginRow,mBeginCol];//Левая верхняя ячейка области,в которую выводим данные mCell2:= mExcelAppSheet.Cells[mBeginRow + mRowCount-1, mBeginCol + mColCount-1];//Правая нижняя ячейка области,в которую выводим данные mRange:= mExcelAppSheet.Range[mCell1,mCell2];//Область, в которую будем выводить данные //Если mCell2 не верна(номер ячейки > реальных данных),то в ячейке=НД(нет данных) //---------------------------------------------------- // wf.Text:='Заполнение листа'; // mDateTimeBegB:=Time; mRange.Value:= mArrayA;//непосредственно вывод данных,быстрее поячеечного присвоения на 50 % Query:= TQuery.Create(nil); Query.DatabaseName:= 'dbKassa'; with Query do begin SQL.Clear; SQL.Text:= 'select a.name from producer a join pr_tovar b '+ ' on a.id = b.producer where b.id = :P_ID'; Prepare; end; WF:= tMyWait.create('Идет экспорт инвентарной ведомости...'); WF.progressbarmax := mBeginRow + mRowCount-1; WF.progressbarpos := 0; WF.ShowProgress; for i:= mBeginRow to mBeginRow + mRowCount-1 do begin SS:= mExcelAppSheet.Cells[i,2].Value; Str:= Trim(VarToStr(SS)); mExcelAppSheet.Cells[i,2].Clear; if Str <> '' then begin Query.Close; Query.ParamByName('P_ID').Value:= StrToInt(Str); Query.Open; end; IF(not Query.Fields[0].IsNull) then mExcelAppSheet.Cells[i,8].Value:= Trim(Query.Fields[0].AsString) else mExcelAppSheet.Cells[i,8].Value:= ''; WF.IncProgress; end; Query.Close; Query.Free; mExcelApp.Visible:=True; // mExcelAppSheet.Cells[mBeginRow,mBeginCol].Activate; // Форматирование столбцов Excel // Выравнивание в ячейке 2-xlLeft, 3-xlCenter, 4-xlRight mExcelAppSheet.Columns[5].HorizontalAlignment := 4; //xlRight; mExcelAppSheet.Columns[7].HorizontalAlignment := 3; mExcelAppSheet.Columns[12].HorizontalAlignment := 4; for i:= 2 to 19 do // Делаем автоширину всех колонок mExcelAppSheet.Columns[i].AutoFit; DrawCellBorder(mCell1,mCell2, True); // mExcelAppSheet.Cells[mBeginRow,mBeginCol].Activate; // RRange:= mExcelApp.ActiveWorkBook.ActiveSheet.Range[mBeginRow,mBeginCol]; // mExcelApp.ActiveWindow.FreezePanes:= True; End; //-------------------------------------------------------------------- begin // ShowMessage('Старт '); //-------------------- //Для отладки комментировал if not (dataset is TTable) then RaiseException('Таблица имеет неожиданный тип '+dataset.classname+', требуется проверка скрипта на совместимость.'); //------------------------------ //D7,help:Dialog,TSaveDialog //зная имя нужного файла установить сразу на него курсор //т.е.при открытии окна Savedialog курсорна ходится на строке "Имя файла", //а я хочу,чтобы курсор находился на самом файле в окне выбора файлов, // чтобы оставалось его только перенести правой кнопкой мышки. //------------ //Сделать запоминание не только каталога, но и файла последнего загруженного файла //В диалоговом окне для ручного ввода предусмотрен элемент TEdit, //который при желании можно заменить на TCоmbовох. //Для этого необходимо свойству FileEditStyle придать значение fsComboBox вместо умалчиваемого fsEdit. Если выбран комбинированный список, с ним можно связать протокол выбора имен. Для этого используется свойство HistoryList: TStrings, содержимое которого будет появляться в выпадающем списке. Этот список не пополняется автоматически, //поэтому за его содержимым должна следить программа. Например: //if OpenDialogI.Execute then HistoryList.Add(OpenDialogI.FileName); // Help:TOpenDialog, property HistoryList: TStrings; Maintains a list of previously selected files: //HistoryList is maintained for compatibility with older windows-only versions of TOpenDialog. //------------ Filename:=''; Dialog:= TSaveDialog.create(nil); try //Dialog.InitialDir:='C:\My_progE\Data\Excel';//нач.каталог ExtractFilePath(Application.ExeName); Dialog.Filter:= 'Excel-файлы(*.xls)|*.xls|Все файлы(*.*)|*.*'; Dialog.Title:='Укажите Excel-файл'; //ofFileMustExist;OfOverWritePrompt;OfCreatePrompt;OfDontAddToRecent(не compile) //Dialog.Options:=Dialog.Options + [OfOverWritePrompt, ofFileMustExist]; //Dialog.Options:=Dialog.Options + [ofFileMustExist]; //------ //Если же установлено fsComboBox, в выпадающем списке появляется содержимое HistoryList. // Dialog.FileEditStyle:= fsComboBox;//fsEdit;fsComboBox if Dialog.execute then Begin // Dialog.HistoryList.Add(Dialog.FileName); // SaveDialog1.HistoryList.Add(SaveDialog1.FileName);//чтобы оно действительно играло роль "предыстории", прог­раммист должен пополнять список после успешного окончания диалога Filename:= Dialog.Filename; End; finally Dialog.free; end; if Filename <> '' then // with tMyWait.create('Идет экспорт инвентарной ведомости...') do try ForceDirectories(extractFilepath(Filename)); mFilenameExcel:=Filename; //mFilenameExcel:='C:\My_progE\Data\Excel\KostukIn.xls'; //ShowMessage('Найден файл mFileNameExcel=' + mFileNameExcel); // Чтобы было много данных:В Солярисе,Операции, инвентарная ведомость, //Оформить новую ведомость, указать дату 01.06.06 вместо сегодняшней=26.01.07 //Чтобы было мало данных: Оформить новую ведомость, создать пустую ведомость и саммому из списка товаров ее заполнить. //---------------------------------------------- //Вариант 1: InitOleObjectExcel(); SolarisToExcel(mFilenameExcel); // CloseOleObjectExcel; tMyWait.Hide;//waitform CreateHintI('Операция завершена','Передача инвентарной ведомости',20); finally CloseOleObjectExcel; tMyWait.free; end; end.