diff --git a/agents.md b/agents.md new file mode 100644 index 0000000..9da8721 --- /dev/null +++ b/agents.md @@ -0,0 +1,171 @@ +# DocTo — Agent Guide + +## Project Overview + +DocTo is a Windows command-line utility written in **Delphi (Object Pascal)** that +converts Microsoft Office documents (Word `.doc`/`.docx`, Excel `.xls`/`.xlsx`, +PowerPoint `.ppt`/`.pptx`) to other formats (PDF, CSV, TXT, RTF, etc.) via COM +Automation. Microsoft Word, Excel, or PowerPoint must be installed on the host machine. + + +- Repository: https://github.com/tobya/DocTo +- Website: https://tobya.github.io/DocTo/ +## Tech Stack + +| Layer | Technology | +|-------|-----------| +| Language | Delphi (tested with 10.3; compatible with XE4+) | +| Office integration | Windows COM / Office Interop (Word, Excel, PowerPoint, Visio) | +| Build system | Delphi IDE / `.dproj` project file | +| Tests | Batch scripts (`.bat`) in `/test/` | PHP Pest Tests in `/companion` +| Docs / companion / Test site | Markdown + PHP (`/pages/`, `/companion/`) | + +## Repository Layout + +``` +docTo/ +├── src/ # All Delphi source files (.pas, .dpr, .dproj) +│ ├── docto.dpr # Project file (entry point) +│ ├── MainUtils.pas # Core TDocumentConverter class and shared utilities +│ ├── baseConfig.pas # Abstract TParamLoader base class for CLI params +│ ├── configInput.pas # -F / --inputfile parameter handler +│ ├── configOutput.pas # -OX / --outputextension parameter handler +│ ├── WordUtils.pas # Word COM interop helpers +│ ├── ExcelUtils.pas # Excel COM interop helpers +│ ├── PowerPointUtils.pas # PowerPoint COM interop helpers +│ ├── PathUtils.pas # Path/directory utilities +│ ├── ResourceUtils.pas # String resource helpers +│ ├── datamodSSL.* # Data module for SSL/webhook support +│ ├── shared/ # Shared/common units +│ ├── Exceptions/ # Custom exception types +│ └── res/ # Resource files +├── test/ # Manual test scripts and fixture files +│ ├── testDocTo.bat # Main test runner batch script +│ ├── InputFiles/ # Sample Word/RTF/CSV/XLS input files +│ ├── inputfilesxl/ # Excel-specific input fixtures +│ ├── inputfilespp/ # PowerPoint-specific input fixtures +│ ├── GeneratedFiles/ # Output directory for test conversions +│ └── GeneratedTestputFiles/ +├── .github/ +│ ├── workflows/ # GitHub Actions (greetings bot) +│ └── ISSUE_TEMPLATE/ +├── pages/ # GitHub Pages / documentation content +├── companion/ # Companion tooling +├── exe/ # Pre-built binaries +├── readme.md +└── changes.md +``` + +## Architecture + +### Core Pattern: TParamLoader + +CLI parameters follow a **registration/dispatch** pattern: + +- `TParamLoader` (`baseConfig.pas`) — abstract base class with three responsibilities: + - `RegisterParams(List)` — adds the parameter key(s) it handles (e.g. `-F`, `--INPUTFILE`) to a lookup list + - `Load(Converter, Param, Value)` — applies the parsed value to the `TDocumentConverter` instance + - `ShouldDec` — whether parsing should decrement the argument index after processing +- Each parameter has a dedicated subclass (e.g. `TParamInput`, `TParamOutputExtension`) +- `TDocumentConverter` (`MainUtils.pas`) is the central domain object passed through all param loaders + +### Converters + +Three COM-based converter paths: +- **Word** (default): use `-WD` flag or omit; format constants from `wdSaveFormat` +- **Excel**: use `-XL` flag; format constants from `xlFileFormat` +- **PowerPoint**: use `-PP` flag + +### Logging + +Log levels are integers: `1` ERRORS, `2` STANDARD (default), `5` CHATTY, `9` DEBUG, `10` VERBOSE. +Use `Converter.logdebug(msg, LEVEL)` for diagnostic output. + +## Building + +- **Compiler**: Embarcadero Delphi (tested with 10.3+; XE4 and XE7 also supported) +- **Platform**: Windows only — relies on COM, Word/Excel/PowerPoint interop +- Open `src/docto.dproj` in the Delphi IDE and build, or use the Delphi command-line compiler (`dcc32`) +- Output is a single `docto.exe` binary + +No external package manager or build script is present. The project has no Linux/macOS build path. + +## Code Structure + +- Ensure that If blocks always have a begin end section for all branches even if not strictly neccessary. + +## Testing + +Tests are `.bat` scripts in `/test/`. They call the compiled `docto.exe` and verify output files are produced. Run them directly from a Windows command prompt with Office installed: + +Tests are manual batch scripts in `test/`: + +```bat +# Run the main test suite (requires Word/Excel/PowerPoint installed) +.\test\testDocTo.bat +``` + +- Input fixtures live in `test/InputFiles/`, `test/inputfilesxl/`, `test/inputfilespp/` +- Outputs are written to `test/GeneratedFiles/` and `test/GeneratedTestputFiles/` +- There is no automated unit-test framework; correctness is verified by inspecting generated files + +There is no automated test runner — tests must be run manually on a machine with Microsoft Office installed. + +Additional Tests are written as Pest Tests in companion Laravel PHP site in the `/companion/` dir + +## Key Concepts for Agents + +- **Application flags**: `-WD` (Word), `-XL` (Excel), `-PP` (PowerPoint), `-VS` (Visio). Word is the default. +- **Three required parameters**: `-F` (input file/dir), `-O` (output file/dir), `-T` (format type, e.g. `wdFormatPDF`). +- **Format types**: Passed as named constants (e.g. `wdFormatPDF`, `xlCSV`) or integers matching the Office Interop enums. +- **COM errors**: Office automation can raise `EOleException`. The `-X` flag controls whether DocTo halts or continues on COM errors. +- **TLB constants**: `Word_TLB_Constants.pas`, `Excel_TLB_Constants.pas`, and `PowerPoint_TLB_Constants.pas` define the Office format enum values. + +## Contribution Guidelines + +- Open an issue before large PRs to avoid wasted effort. +- The main development branch is `DocTo` (note: not `main`). +- Looking for help with: Delphi/VBA features, PHP/Laravel/Pest tests, and documentation. +- PRs are welcome. + +When adding a new conversion feature, add a corresponding test case to `testDocTo.bat` and provide a sample input file under the appropriate `InputFiles*` directory. + +## Error Codes + +| Code | Meaning | +|------|---------| +| 200 | Invalid file format specified | +| 201 | Insufficient inputs (need -F, -O, -T at minimum) | +| 202 | Switch requires a value | +| 203 | Unknown switch | +| 204 | Input file does not exist | +| 205 | Invalid parameter value | +| 220 | Word/Excel/PowerPoint COM error | +| 221 | Word/Excel/PowerPoint not installed | +| 400 | Unknown error | + +## Adding a New CLI Parameter + +1. Create a new unit in `src/` (e.g. `configMyParam.pas`) +2. Declare a class that extends `TParamLoader` +3. Implement `RegisterParams` — add the short and long flag names +4. Implement `Load` — read `Value` and set the appropriate field on `TDocumentConverter` +5. Implement `ShouldDec` — return `false` unless the param consumes an extra token +6. Register the new class in the parameter dispatch table in `MainUtils.pas` +7. Add documentation for the new flag to `readme.md` under "Command Line Help" + +## Key Conventions + +- Path handling: always call `ExpandFileName` on user-supplied paths to resolve relative references; use `IncludeTrailingBackslash` for directory paths +- Input validation: use `Converter.HaltWithError(code, message)` to exit with a defined error code +- COM errors: wrap COM calls in try/except and honour the `-X` (halt-on-error) flag +- String constants and user-visible messages should be placed in `ResourceUtils.pas` (resource strings), not inlined +- The main branch is named `DocTo` (not `main`) + +## External References + +- Word SaveAs format constants: https://docs.microsoft.com/en-us/dotnet/api/microsoft.office.interop.word.wdsaveformat +- Excel file format constants: https://docs.microsoft.com/en-us/dotnet/api/microsoft.office.interop.excel.xlfileformat +- Word compatibility mode values: https://msdn.microsoft.com/en-us/library/office/ff192388.aspx +- Releases: https://github.com/tobya/DocTo/releases +- Wiki & examples: https://github.com/tobya/DocTo/wiki diff --git a/companion/app/Console/Commands/docto/CreateConfigObject.php b/companion/app/Console/Commands/docto/CreateConfigObject.php new file mode 100644 index 0000000..bbde743 --- /dev/null +++ b/companion/app/Console/Commands/docto/CreateConfigObject.php @@ -0,0 +1,40 @@ +argument('name'); + $paramlist = str( $this->option('paramlist'))->explode(','); + $pasfile = Blade::render('docto.pasfile.configObject',['paramlist'=>$paramlist, 'name'=>$name]); + $storageDisk = Storage::build([ + 'driver' => 'local', + 'root' => base_path('../src/ParamObjects'), + ]); + $storageDisk->put('config' . $name . '.pas', $pasfile); + $this->info('Create New Parameter Config file. ' . 'config' . $name . '.pas' ); + } +} diff --git a/companion/resources/views/docto/pasfile/configObject.blade.php b/companion/resources/views/docto/pasfile/configObject.blade.php new file mode 100644 index 0000000..b0a87a0 --- /dev/null +++ b/companion/resources/views/docto/pasfile/configObject.blade.php @@ -0,0 +1,41 @@ +unit config{{$name}}; + +interface + +uses classes, MainUtils, System.Contnrs, SysUtils, + baseConfig; + +type +TParam{{$name}} = class(TParamLoader) +public + + procedure Load(Converter : TDocumentConverter; Param, Value : String); override; + function ShouldDec : Boolean; override; + class procedure RegisterParameters(List : TStrings); +end; + +implementation + +{ TParam{{$name}} } + +procedure TParam{{$name}}.Load(Converter: TDocumentConverter; Param, Value: String); +begin + // +end; + +class procedure TParam{{$name}}.RegisterParameters(List: TStrings); +begin + + @foreach($paramlist as $param ) + List.AddPair('{{$param}}', TParam{{$name}}.ClassName, TObject(TParam{{$name}})); + @endforeach + +end; + + +function TParam{{$name}}.ShouldDec: Boolean; +begin + Result := false; +end; + +end. diff --git a/companion/tests/Feature/Input/InputFilterPestTest.php b/companion/tests/Feature/Input/InputFilterPestTest.php index 3cff123..8dacd59 100644 --- a/companion/tests/Feature/Input/InputFilterPestTest.php +++ b/companion/tests/Feature/Input/InputFilterPestTest.php @@ -57,7 +57,7 @@ ->build(); $output = \Illuminate\Support\Facades\Process::run($doctocmd); - // print_r($output->output()); + $outputDirFiles = collect(\Illuminate\Support\Facades\Storage::allFiles($outputfiledir)); expect($outputDirFiles->count())->toBeGreaterThan(0); diff --git a/companion/tests/Feature/Input/InputSubDirPestTest.php b/companion/tests/Feature/Input/InputSubDirPestTest.php new file mode 100644 index 0000000..65165ac --- /dev/null +++ b/companion/tests/Feature/Input/InputSubDirPestTest.php @@ -0,0 +1,80 @@ +count() + $subdirfiles->count(); + + $doctocmd = \App\Services\DocToCommandBuilder::docto() + ->add('-WD') + ->add('-f', $testinputfilesdir_temp ) + ->add('-o', $testoutputdir_temp ) + ->add('-t', 'wdFormatPDF') + ->add('-L',10) + ->build(); + + $output = \Illuminate\Support\Facades\Process::run($doctocmd); + // print_r($output->output()); + $outputDirFiles = collect(\Illuminate\Support\Facades\Storage::allFiles($outputfiledir)); + + + expect($outputDirFiles->count())->tobe($allfilestotal); + + // ensure -ox parameter is used. + $file1 = $outputDirFiles->first(); + expect(str($file1)->endsWith('.pdf'))->toBeTrue(); + +}); + +it('can get all files in base dir but not subdir', function ($command){ + $inputfiledir = 'inputfiles'. uniqid(); + $outputfiledir = 'outputfiles_docz' . uniqid(); + // setup + $testinputfilesdir_temp = Storage::path($inputfiledir); + $testoutputdir_temp = Storage::path($outputfiledir); + + Storage::createDirectory($outputfiledir); + + $dirfiles = \App\Services\FileGatherService::GatherFiles('plain', $inputfiledir); + $subdirfiles = \App\Services\FileGatherService::GatherFiles('plain', $inputfiledir . '\\subdir'); + + $allfilestotal = $dirfiles->count() + $subdirfiles->count(); + + $doctocmd = \App\Services\DocToCommandBuilder::docto() + ->add('-WD') + ->add('-f', $testinputfilesdir_temp ) + ->add('-o', $testoutputdir_temp ) + ->add('-t', 'wdFormatText') + ->add($command) // should not load files from /subdir + ->add('-L',10) + ->build(); + + $output = \Illuminate\Support\Facades\Process::run($doctocmd); + // print_r($output->output()); + $outputDirFiles = collect(\Illuminate\Support\Facades\Storage::allFiles($outputfiledir)); + + + expect($dirfiles->count())->toBe(5); + expect($outputDirFiles->count())->toBe($dirfiles->count()); + expect($outputDirFiles->count())->toBeLessThan($allfilestotal); + + // ensure -ox parameter is used. + $file1 = $outputDirFiles->first(); + expect(str($file1)->endsWith('.txt'))->toBeTrue(); + +})->with([ + ['--NO-RECURSE'], + ['--NO-SUBDIR'], + ['--NO-SUBDIRS'], + +]); diff --git a/companion/tests/Feature/VersionPestTest.php b/companion/tests/Feature/VersionPestTest.php index de0e480..5b5f6ab 100644 --- a/companion/tests/Feature/VersionPestTest.php +++ b/companion/tests/Feature/VersionPestTest.php @@ -23,7 +23,7 @@ $outputString = $result->output(); // find at begining out output - expect(str($outputString)->take(100)->toString())->toContain('DocTo Version: 1.16.46'); + expect(str($outputString)->take(100)->toString())->toContain('DocTo Version: 1.16'); }); diff --git a/src/ExtraFiles.res b/src/ExtraFiles.res index 29a996c..727ef9b 100644 Binary files a/src/ExtraFiles.res and b/src/ExtraFiles.res differ diff --git a/src/MainUtils.pas b/src/MainUtils.pas index 735688f..9c9780d 100644 --- a/src/MainUtils.pas +++ b/src/MainUtils.pas @@ -33,7 +33,7 @@ interface MSVISIO = 4; - DOCTO_VERSION = '1.16.2'; // dont use 0x - choco needs incrementing versions. + DOCTO_VERSION = '1.16.6'; // dont use 0x - choco needs incrementing versions. DOCTO_VERSION_NOTE = ' x64 Release '; type @@ -80,7 +80,6 @@ TDocumentConverter = class procedure SetIgnore_MACOSX(const Value: boolean); procedure SetEncoding(const Value: Integer); procedure SetSkipDocsWithTOC(const Value: Boolean); - procedure HaltWithConfigError(ErrorNo: Integer; Msg: String); procedure SetList_ErrorDocs(const Value: Boolean); procedure SetList_ErrorDocs_Seconds(const Value: Integer); @@ -99,6 +98,7 @@ TDocumentConverter = class procedure SetDocStructureTags(const Value: boolean); procedure SetBitmapMissingFonts(const Value: boolean); procedure Setsheets(const Value: TStrings); + function GetParamHandlers: TStrings; protected @@ -155,9 +155,6 @@ TDocumentConverter = class procedure SetOutputFileFormatString(const Value: String); procedure SetOutputLog(const Value: Boolean); procedure SetOutputLogFile(const Value: String); - function IsValidFormat(FormatID : Integer): Boolean; - - procedure HaltWithError(ErrorNo:Integer; Msg : String); procedure SetLogToFile(const Value: Boolean); procedure SetLogFilename(const Value: String); procedure ListFiles(const PathName, FileName: string; const SubDir: boolean; outFiles: TStrings); @@ -174,14 +171,7 @@ TDocumentConverter = class procedure SetIsDirOutput(const Value: Boolean); procedure SetIsFileOutput(const Value: Boolean); procedure SetLogLevel(const Value: integer); - property InputIsFile : Boolean read FInputIsFile write SetIsFileInput; - property InputIsDir : Boolean read FInputIsDir write SetIsDirInput; - property OutputIsFile : Boolean read FOutputIsFile write SetIsFileOutput; - property OutputIsDir : Boolean read FOutputIsDir write SetIsDirOutput; - property OutputIsStdOut : Boolean read FOutputIsStdOut write SetOutputIsStdOut; - property DoSubDirs : Boolean read FDoSubDirs write SetDoSubDirs; - property OutputExt : string read FOutputExt write SetOutputExt; - property LogLevel : integer read FLogLevel write SetLogLevel; + property RemoveFileOnConvert: boolean read FRemoveFileOnConvert write SetRemoveFileOnConvert; property Ignore_MACOSX : boolean read FIgnore_MACOSX write SetIgnore_MACOSX; property List_ErrorDocs : Boolean read FList_ErrorDocs write SetList_ErrorDocs ; @@ -203,7 +193,6 @@ TDocumentConverter = class property WordConstants : TResourceStrings read getWordConstants; - property OfficeAppName : String read FOfficeAppName write FOfficeAppName; // Events @@ -256,14 +245,14 @@ TDocumentConverter = class procedure LogError(Msg: String); function ConvertErrorText(Msg: String) : String; function CallWebHook(Params: String) : string; - FUNCTION AfterConversion(InputFile, OutputFile: String):string; - Function OnConversionError(InputFile, OutputFile, Error: String):string; + function AfterConversion(InputFile, OutputFile: String):string; + function OnConversionError(InputFile, OutputFile, Error: String):string; Procedure LoadFileList(); procedure LogResourceHelp(HelpResName : String); procedure LogVersionInfo(ForceReload : boolean = true); - + procedure HaltWithError(ErrorNo:Integer; Msg : String); procedure LogWordFormats(); procedure LogExcelFormats(); @@ -282,7 +271,12 @@ TDocumentConverter = class Property LogToFile : Boolean read FLogToFile write SetLogToFile; property LogFilename: String read FLogFilename write SetLogFilename; Property Version : String read FVersionString; + property LogLevel : integer read FLogLevel write SetLogLevel; property HaltOnWordError : Boolean read FHaltOnWordError write SetHaltOnWordError; + property OfficeAppName : String read FOfficeAppName write FOfficeAppName; + function IsValidFormat(FormatID : Integer): Boolean; + procedure HaltWithConfigError(ErrorNo: Integer; Msg: String); + function LookupFormatByName(const FormatName: String): Integer; property SkipDocsWithTOC : Boolean read FSkipDocsWithTOC write SetSkipDocsWithTOC; property SkipDocsExist : Boolean read FSkipDocsExist write FSkipDocsExist; property InputExtension: String read GetExtension write SetExtension; @@ -291,6 +285,15 @@ TDocumentConverter = class property BookMarkSource: Integer read FBookMarkSource; property pdfExportRange : Integer read FPdfExportRange_Word write SetPDfExportRange ; + property InputIsFile : Boolean read FInputIsFile write SetIsFileInput; + property InputIsDir : Boolean read FInputIsDir write SetIsDirInput; + property OutputIsFile : Boolean read FOutputIsFile write SetIsFileOutput; + property OutputIsDir : Boolean read FOutputIsDir write SetIsDirOutput; + property OutputIsStdOut : Boolean read FOutputIsStdOut write SetOutputIsStdOut; + property DoSubDirs : Boolean read FDoSubDirs write SetDoSubDirs; + property OutputExt : string read FOutputExt write SetOutputExt; + + property ParamHandlers : TStrings read GetParamHandlers; property IsWord : Boolean read getIsWord; property IsExcel : Boolean read getIsExcel; @@ -305,6 +308,9 @@ TDocumentConverter = class implementation +uses baseConfig, ConfigOutput, ConfigInput, configLogLevel, configFormat, + configNoRecurse ,configCompatibility +; { TConsoleLog } @@ -689,8 +695,14 @@ function TDocumentConverter.Execute: string; if ConversionInfo.Successful then begin - // logInfo('File Converted: ' + ConversionInfo.OutputFile); - logInfo('Files Converted: ' + sLineBreak + fOutputFiles.Text); + logInfo('File Converted: ' + ConversionInfo.OutputFile); + + // when excel converts sheets each is a seperate file and + // listed in fOutputFiles + if(fOutputFiles.Count > 0) then + begin + logInfo('Files Converted: ' + fOutputFiles.Text); + end; // Check if file needs to be deleted. if RemoveFileOnConvert then @@ -847,6 +859,17 @@ function TDocumentConverter.IsValidFormat(FormatID: Integer): Boolean; end; end; +function TDocumentConverter.LookupFormatByName(const FormatName: String): Integer; +var + idx : Integer; +begin + idx := Formats.IndexOfName(FormatName); + if idx > -1 then + Result := StrToInt(Formats.Values[FormatName]) + else + Result := -1; +end; + function TDocumentConverter.ChooseConverter(Params: TStrings) : integer; var f , iParam, idx: integer; pstr : string; @@ -935,9 +958,12 @@ procedure TDocumentConverter.LoadConfig(Params: TStrings); HelpStrings: TResourceStrings; tmpext : String; valueBool : Boolean; - X: Integer; - Sval : String; + X, O: Integer; + Sval : string; + ParamHandlerClass : TClass; + ParamHandler : TParamLoader; + paramHandleridx : integer; begin // Initialise iParam := 0; @@ -961,6 +987,7 @@ procedure TDocumentConverter.LoadConfig(Params: TStrings); + While iParam <= Params.Count -1 do begin @@ -983,9 +1010,33 @@ procedure TDocumentConverter.LoadConfig(Params: TStrings); // jump to next id + value inc(iParam,2); + logdebug(Self.ParamHandlers.Values[id],errors); + if Self.ParamHandlers.Values[id] <> '' then + begin + + + // retrieve the Handler from list. + paramHandleridx := ParamHandlers.IndexOfName(id); + + // Retrieve insance of class from handler list. + ParamHandlerClass := TCLASS(ParamHandlers.Objects[paramHandleridx]); -if (id = '-XL') or + // Create instance of class. + LogDebug(ParamHandlerClass.ClassName + 'before cast',ERRORS); + ParamHandler := TParamLoader(ParamHandlerClass.Create()); + + // load parameters + LogDebug(ParamHandler.ClassName + ' ' + id + ' :: ' + value,ERRORS); + ParamHandler.Load(Self,id,Value); + + if ParamHandler.ShouldDec then + begin + dec(iParam); + end; + + end + else if (id = '-XL') or (id = '--EXCEL') or (id = '-WD') or (id = '--WORD') or @@ -1027,21 +1078,6 @@ procedure TDocumentConverter.LoadConfig(Params: TStrings); end - else if (id = '-OX') or - (id = '--OUTPUTEXTENSION') then - begin - - //If the first character isn't . add it. - if value[1] = '.' then - begin - FOutputExt := value; - end - else - begin - FOutputExt := '.' + value; - end; - - end else if (id = '-F') or (id = '--INPUTFILE') then begin @@ -1089,14 +1125,6 @@ procedure TDocumentConverter.LoadConfig(Params: TStrings); end; end - else if (id = '--NO-RECURSE') or - (id = '--NO-SUBDIR') or - (id = '--NO-SUBDIRS') then - begin - FDoSubDirs := false; - LogInfo('Loading files from directory but not subdirectories',CHATTY); - dec(iparam); - end else if (id = '--STDOUT') then BEGIN OutPutIsStdOut := true; @@ -1109,15 +1137,7 @@ procedure TDocumentConverter.LoadConfig(Params: TStrings); begin InputExtension := value; end - else if ( id = '-L') - OR (id = '--LOGLEVEL') then - begin - if isNumber(value) then - begin - LogLevel := strtoint(value); - LogInfo('Log Level Set To:' + IntToStr(LogLevel),LogLevel); - end - end + else if (id = '-Q') or (id = '--QUIET') then begin @@ -1126,38 +1146,7 @@ procedure TDocumentConverter.LoadConfig(Params: TStrings); // Doesn't require a value dec(iParam); end - else if (id = '-T') or (id = '-TF') or - (id = '--FORMAT') or (id = '--FORCEFORMAT') then - begin - if IsNumber(value) then - begin - FOutputFileFormat := strtoint(value); - //If not forcing, and the format is invalid by list, then raise error. - if (not (id = '-TF')) and ( not IsValidFormat(FOutputFileFormat)) then - begin - HaltWithConfigError(200, 'File Format ' + value + ' is invalid, please see help. -h. To force use, use -TF'); - end; - end - else // string format such as 'XLcsv' - begin - FOutputFileFormatString := value; - - idx := formats.IndexOfName(FOutputFileFormatString); - if idx > -1 then - begin - OutputFileFormat := strtoint(formats.Values[OutputFileFormatString]); - - end - else if idx = -1 then - begin - HaltWithConfigError(200,'File Format ' + OutputFileFormatString + ' is an invalid ' + OfficeAppName + ' file extension , please see help. -h'); - - end; - end; - logdebug('Type Integer is: ' + inttostr(FOutputFileFormat), VERBOSE); - - end else if (id = '-C') or (id = '--COMPATIBILITY') then begin @@ -1731,6 +1720,19 @@ function TDocumentConverter.GetExtension: String; +function TDocumentConverter.GetParamHandlers: TStrings; +begin + Result := TStringList.Create; + + TParamInput.RegisterParameters(Result); + TParamOutputExtension.RegisterParameters(Result); + TParamLogLevel.RegisterParameters(Result); + TParamFormat.RegisterParameters(Result); + TParamNoRecurse.RegisterParameters(Result); + TParamCompatibility.RegisterParameters(Result); + +end; + function TDocumentConverter.getIsExcel: Boolean; begin Result := MSExcel = FAppID; diff --git a/src/ParamObjects/baseConfig.pas b/src/ParamObjects/baseConfig.pas new file mode 100644 index 0000000..e105800 --- /dev/null +++ b/src/ParamObjects/baseConfig.pas @@ -0,0 +1,53 @@ +unit baseConfig; + +interface + +uses Classes,System.Contnrs, + + MainUtils; + +type + + + +TParamLoader = class abstract + private + fParamID : string; + protected + function GetParamID: String; virtual; + procedure SetParamID(const Value: String); virtual; + + +public + + + procedure Load(Converter : TDocumentConverter; Param, Value : String); virtual; abstract; + function ShouldDec : Boolean; virtual; abstract; + + class procedure RegisterParameters(List : TStrings); + + Property ParamID : String read GetParamID Write SetParamID; + +end; + + +implementation + +{ TParamLoader } + +function TParamLoader.GetParamID: String; +begin + result := FParamID; +end; + +class procedure TParamLoader.RegisterParameters(List: TStrings); +begin + // +end; + +procedure TParamLoader.SetParamID(const Value: String); +begin + FParamID := Value; +end; + +end. diff --git a/src/ParamObjects/configCompatibility.pas b/src/ParamObjects/configCompatibility.pas new file mode 100644 index 0000000..5beb598 --- /dev/null +++ b/src/ParamObjects/configCompatibility.pas @@ -0,0 +1,40 @@ +unit configCompatibility; + +interface + +uses classes, MainUtils, System.Contnrs, SysUtils, + baseConfig; + +type +TParamCompatibility = class(TParamLoader) +public + + procedure Load(Converter : TDocumentConverter; Param, Value : String); override; + function ShouldDec : Boolean; override; + class procedure RegisterParameters(List : TStrings); +end; + +implementation + +{ TParamCompatibility } + +procedure TParamCompatibility.Load(Converter: TDocumentConverter; Param, Value: String); +begin + // +end; + +class procedure TParamCompatibility.RegisterParameters(List: TStrings); +begin + + List.AddPair('-C', TParamCompatibility.ClassName, TObject(TParamCompatibility)); + List.AddPair('--COMPATIBILITY', TParamCompatibility.ClassName, TObject(TParamCompatibility)); + +end; + + +function TParamCompatibility.ShouldDec: Boolean; +begin + Result := false; +end; + +end. diff --git a/src/ParamObjects/configFormat.pas b/src/ParamObjects/configFormat.pas new file mode 100644 index 0000000..b726210 --- /dev/null +++ b/src/ParamObjects/configFormat.pas @@ -0,0 +1,70 @@ +unit configFormat; + +interface + +uses classes, MainUtils, System.Contnrs, SysUtils, + baseConfig; + +type +TParamFormat = class(TParamLoader) +public + + procedure Load(Converter : TDocumentConverter; Param, Value : String); override; + function ShouldDec : Boolean; override; + class procedure RegisterParameters(List : TStrings); +end; + +implementation + +{ TParamFormat } + +procedure TParamFormat.Load(Converter: TDocumentConverter; Param, Value: String); +var + ForceFormat : Boolean; + FormatInt : Integer; +begin + ForceFormat := (Param = '-TF') or (Param = '--FORCEFORMAT'); + + if IsNumber(Value) then + begin + FormatInt := StrToInt(Value); + Converter.OutputFileFormat := FormatInt; + if (not ForceFormat) and (not Converter.IsValidFormat(FormatInt)) then + begin + Converter.HaltWithConfigError(200, 'File Format ' + Value + + ' is invalid, please see help. -h. To force use, use -TF'); + end; + end + else // string format such as 'wdFormatPDF', 'xlCSV' + begin + Converter.OutputFileFormatString := Value; + FormatInt := Converter.LookupFormatByName(Value); + if FormatInt > -1 then + begin + Converter.OutputFileFormat := FormatInt; + end + else + begin + Converter.HaltWithConfigError(200, 'File Format ' + Value + + ' is an invalid ' + Converter.OfficeAppName + ' file extension , please see help. -h'); + end; + end; + + Converter.LogDebug('Type Integer is: ' + IntToStr(Converter.OutputFileFormat), VERBOSE); +end; + +class procedure TParamFormat.RegisterParameters(List: TStrings); +begin + List.AddPair('-T', TParamFormat.ClassName, TObject(TParamFormat)); + List.AddPair('--FORMAT', TParamFormat.ClassName, TObject(TParamFormat)); + List.AddPair('-TF', TParamFormat.ClassName, TObject(TParamFormat)); + List.AddPair('--FORCEFORMAT', TParamFormat.ClassName, TObject(TParamFormat)); +end; + + +function TParamFormat.ShouldDec: Boolean; +begin + Result := false; +end; + +end. diff --git a/src/ParamObjects/configInput.pas b/src/ParamObjects/configInput.pas new file mode 100644 index 0000000..55a1d2d --- /dev/null +++ b/src/ParamObjects/configInput.pas @@ -0,0 +1,90 @@ +unit configInput; + +interface + +uses classes, MainUtils,System.Contnrs, Sysutils, + baseConfig; + +type +TParamInput = class(TParamLoader) +public + + + procedure Load(Converter : TDocumentConverter; Param, Value : String); override; + function ShouldDec : Boolean; override; + + class procedure RegisterParameters(List : TStrings); +end; + +implementation + +{ TParamOutput } + +procedure TParamInput.Load(Converter: TDocumentConverter; Param, + Value: String); +var tmppath : string; +begin + + // Before doing anything else expand file name to remove any relative paths. + Converter.InputFile := ExpandFileName(Value); + + Converter.logdebug('[TPARAMINput]Input File is: ' + Converter.InputFile,CHATTY); + + tmppath := ExtractFilePath(Converter.InputFile); + + // If we are given a filename with no path, get currentdir and add to file. + if (tmppath = '') then + begin + tmppath := GetCurrentDir(); + tmppath := IncludeTrailingBackslash(tmppath); + Converter.InputFile := tmppath + Converter.InputFile; + end; + + if (FileExists(Converter.InputFile) = false) and (DirectoryExists(Converter.InputFile) = false) then + begin + Converter.HaltWithError(204,'EInput file ' + Converter.InputFile + ' does not exist.'); + end + else if (FileExists(Converter.InputFile)) then + begin + Converter.InputIsFile := true; + Converter.InputIsDir := false; + end + else if (DirectoryExists(Converter.InputFile)) then + begin + Converter.InputIsFile := false; + Converter.InputIsDir := true; + + // Create Absolute path from any relative path + Converter.InputFile := ExpandFileName(Converter.InputFile); + end; + + + // Set Output Directory to Input Directry at this stage. This ensure if no + // output directory (-o) is specified, then it will default to same as + // input dir. If output has been supplied as param it will overwrite later. + if Converter.OutputFile = '' then + begin + Converter.OutputFile := IncludeTrailingBackslash(tmppath); + Converter.OutputIsDir := true; + end; + + + + +end; + + +class procedure TParamInput.RegisterParameters(List: TStrings); +begin + List.AddPair('-F',TParamInput.Classname,TOBJECT(TParamInput) ); + List.AddPair('--INPUTFILE',TParamInput.Classname,TOBJECT(TParamInput) ); +end; + + + +function TParamInput.ShouldDec: Boolean; +begin + Result := false; +end; + +end. diff --git a/src/ParamObjects/configLogLevel.pas b/src/ParamObjects/configLogLevel.pas new file mode 100644 index 0000000..32d5767 --- /dev/null +++ b/src/ParamObjects/configLogLevel.pas @@ -0,0 +1,44 @@ +unit configLogLevel; + +interface + +uses classes, MainUtils, System.Contnrs, SysUtils, + baseConfig; + +type +TParamLogLevel = class(TParamLoader) +public + + procedure Load(Converter : TDocumentConverter; Param, Value : String); override; + function ShouldDec : Boolean; override; + class procedure RegisterParameters(List : TStrings); +end; + +implementation + +{ TParamLogLevel } + +procedure TParamLogLevel.Load(Converter: TDocumentConverter; Param, + Value: String); +begin + if IsNumber(Value) then + begin + Converter.LogLevel := StrToInt(Value); + Converter.LogInfo('Log Level Set To:' + IntToStr(Converter.LogLevel), Converter.LogLevel); + Converter.LogInfo('TConfigLogLevel Sets:' + IntToStr(Converter.LogLevel), Converter.LogLevel); + end; +end; + +class procedure TParamLogLevel.RegisterParameters(List: TStrings); +begin + List.AddPair('-L', TParamLogLevel.ClassName, TObject(TParamLogLevel)); + List.AddPair('--LOGLEVEL', TParamLogLevel.ClassName, TObject(TParamLogLevel)); +end; + + +function TParamLogLevel.ShouldDec: Boolean; +begin + Result := false; +end; + +end. diff --git a/src/ParamObjects/configNoRecurse.pas b/src/ParamObjects/configNoRecurse.pas new file mode 100644 index 0000000..b7818a4 --- /dev/null +++ b/src/ParamObjects/configNoRecurse.pas @@ -0,0 +1,46 @@ +unit configNoRecurse; + +interface + +uses classes, MainUtils, System.Contnrs, SysUtils, + baseConfig; + +type +TParamNoRecurse = class(TParamLoader) +public + + procedure Load(Converter : TDocumentConverter; Param, Value : String); override; + function ShouldDec : Boolean; override; + class procedure RegisterParameters(List : TStrings); +end; + +implementation + +{ TParamNoRecurse } + +procedure TParamNoRecurse.Load(Converter: TDocumentConverter; Param, Value: String); +begin + Converter.DoSubDirs := false; + + Converter.LogInfo('Loading files from directory but not subdirectories',CHATTY); + +end; + +class procedure TParamNoRecurse.RegisterParameters(List: TStrings); +begin + + List.AddPair('--NO-RECURSE', TParamNoRecurse.ClassName, TObject(TParamNoRecurse)); + List.AddPair('--NO-SUBDIR', TParamNoRecurse.ClassName, TObject(TParamNoRecurse)); + + List.AddPair('--NO-SUBDIRS', TParamNoRecurse.ClassName, TObject(TParamNoRecurse)); + + +end; + + +function TParamNoRecurse.ShouldDec: Boolean; +begin + Result := true; +end; + +end. diff --git a/src/ParamObjects/configOutput.pas b/src/ParamObjects/configOutput.pas new file mode 100644 index 0000000..2c7ab97 --- /dev/null +++ b/src/ParamObjects/configOutput.pas @@ -0,0 +1,54 @@ +unit configOutput; + +interface + +uses classes, MainUtils,System.Contnrs, + baseConfig; + +type +TParamOutputExtension = class(TParamLoader) +public + + procedure Load(Converter : TDocumentConverter; Param, Value : String); override; + function ShouldDec : Boolean; override; + + class procedure RegisterParameters(List : TStrings); + +end; + +implementation + +{ TParamOutput } + +procedure TParamOutputExtension.Load(Converter: TDocumentConverter; Param, + Value: String); +begin + + //If the first character isn't . add it. + if value[1] = '.' then + begin + Converter.OutputExt := value; + end + else + begin + Converter.OutputExt := '.' + value; + end; + + +end; + +class procedure TParamOutputExtension.RegisterParameters(List: TStrings); +begin + List.AddPair('-OX',TParamOutputExtension.Classname, TObject(TParamOutputExtension)); + List.AddPair('--OUTPUTEXTENSION',TParamOutputExtension.Classname, TObject(TParamOutputExtension)); + +end; + + + +function TParamOutputExtension.ShouldDec: Boolean; +begin + Result := false; +end; + +end. diff --git a/src/docto.dpr b/src/docto.dpr index 565389c..8e80259 100644 --- a/src/docto.dpr +++ b/src/docto.dpr @@ -37,7 +37,14 @@ uses VisioUtils in 'VisioUtils.pas', Visio_TLB in 'Visio_TLB.pas', DynamicFileNameGenerator in 'shared\DynamicFileNameGenerator.pas', - DocToExceptions in 'Exceptions\DocToExceptions.pas'; + DocToExceptions in 'Exceptions\DocToExceptions.pas', + baseConfig in 'ParamObjects\baseConfig.pas', + configInput in 'ParamObjects\configInput.pas', + configOutput in 'ParamObjects\configOutput.pas', + configLogLevel in 'ParamObjects\configLogLevel.pas', + configFormat in 'ParamObjects\configFormat.pas', + configNoRecurse in 'ParamObjects\configNoRecurse.pas', + configCompatibility in 'ParamObjects\configCompatibility.pas'; var i, Converter : integer; diff --git a/src/docto.dproj b/src/docto.dproj index 61848f1..59d1e46 100644 --- a/src/docto.dproj +++ b/src/docto.dproj @@ -8,7 +8,7 @@ None DCC32 18.8 - Win64 + Win32 3 Win32 @@ -305,6 +305,10 @@ + + + + diff --git a/src/docto.dproj.local b/src/docto.dproj.local index 25748df..fbd059d 100644 --- a/src/docto.dproj.local +++ b/src/docto.dproj.local @@ -12,46 +12,51 @@ 2025/11/19 14:43:31.000.175,C:\Development\github\docto\src\Unit1.pas=C:\Development\github\docto\src\Exceptions\DocToExceptions.pas - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + - - - - - - + - - - - - - - - - - - - - - - - diff --git a/src/docto.res b/src/docto.res index d95ca05..3923ef8 100644 Binary files a/src/docto.res and b/src/docto.res differ