diff --git a/.gitignore b/.gitignore index 04816ec..0705def 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ test/Generated* test/InputFiles3* test/test1/* newdir2/ +src/__recovery /releases src/docto.exe diff --git a/companion/app/Services/CommandInfoService.php b/companion/app/Services/CommandInfoService.php index 09ed17d..7a0d65d 100644 --- a/companion/app/Services/CommandInfoService.php +++ b/companion/app/Services/CommandInfoService.php @@ -727,9 +727,12 @@ public static function Explanations() "dddeletefiles" => ['cmd' => '--deletefiles', 'desc' => "Delete the converted file after conversion."], "ddbookmarksource" => ['cmd' => '--bookmarksource', 'desc' => "Where to get Bookmarks from for PDF. This parameter is only relevant for PDF files."], "ddexportmarkup" => ['cmd' => '--ExportMarkup', 'desc' => "Export Comments and other markup from Word Document to PDF"], - "ddnosubdirs" => ['cmd' => '--no-subdirs', 'desc' => "Don't rescurse subdirs. Only convert files in requested dir. "], + "ddnosubdirs" => ['cmd' => '--no-subdirs', 'desc' => "Don't recurse sub-dirs. Only convert files in requested dir. "], "dduseISO190051" => ['cmd' => '--use-iso19005-1', 'desc' => 'Output PDFs from Word as ISO standard 19005-1 for self contained PDFS. Sometimes also refered to as (PDF/A)[https://en.wikipedia.org/wiki/PDF/A]'], "ddinputfilter" => ['cmd' => '--inputfilter', 'desc' => 'Input filter to use to find documents. eg Project*.doc* will match ProjectA.doc, Project123.doc, ProjectABC.docx '], + "ddsheets" => ['cmd' => '--sheets', 'desc' => 'Output specific sheet from a workbook. Sheet will be output with a specific named file. MyWorksheet_(SheetName) This does not work with xlCSV'], + "ddallsheets" => ['cmd' => '--allsheets', 'desc' => 'Output all sheets in a workbook. Each sheet will be output to a separate named file. MyWorksheet_(SheetName)'], + ]; diff --git a/companion/app/Services/DocToCommandBuilder.php b/companion/app/Services/DocToCommandBuilder.php new file mode 100644 index 0000000..a4fbb7e --- /dev/null +++ b/companion/app/Services/DocToCommandBuilder.php @@ -0,0 +1,29 @@ +add(config('services.docto.path')); + } + + public function add($param, $value = null){ + $this->params[] = [$param,$value]; + return $this; + } + + public function build(){ + $cmd = ''; + foreach($this->params as $paramset){ + $cmd .= ' ' . $paramset[0] ; + if (isset($paramset[1])){ + $cmd .= ' "' . $paramset[1] . '" '; + } + } + return $cmd; + } + } diff --git a/companion/app/Services/FileGatherService.php b/companion/app/Services/FileGatherService.php index b7db42e..dbc3f80 100644 --- a/companion/app/Services/FileGatherService.php +++ b/companion/app/Services/FileGatherService.php @@ -7,20 +7,30 @@ class FileGatherService { - public static function GatherFiles(Collection $list, $tempDirName) + /** + * Gather files will take files from specific subdir of the resource InputFiles directory and + * copy them to a temporary directory wehre they can be used for a test. + * @param Collection $list + * @param $tempDirName + * @return Collection + * @throws \League\Flysystem\FilesystemException + */ + public static function GatherFiles(Collection | string $list, $tempDirName) { + if (is_string($list)){ + $list = collect([$list]); + } + // remove exisitn files if (\Illuminate\Support\Facades\Storage::exists($tempDirName)){ \Illuminate\Support\Facades\Storage::deleteDirectory($tempDirName); } $tempDirPath = Storage::path($tempDirName); - $list->each(function ($dir) use ($tempDirName, $tempDirPath){ - $inputfilesdir = \Illuminate\Support\Facades\Storage::path('inputfiles\\' . $dir ); + $list->each(function ($inputdir) use ($tempDirName, $tempDirPath){ + $inputfilesdir = \Illuminate\Support\Facades\Storage::disk('inputfiles')->path($inputdir ); $cmd = "xcopy \"$inputfilesdir\" \"$tempDirPath\\\" "; $result = \Illuminate\Support\Facades\Process::run( $cmd ); - echo "\n $cmd \n"; - echo "\n" . $result->output() . "\n"; }); return collect(Storage::listContents($tempDirName)); } diff --git a/companion/app/Services/ResourceFileService.php b/companion/app/Services/ResourceFileService.php index df0e207..0e236a2 100644 --- a/companion/app/Services/ResourceFileService.php +++ b/companion/app/Services/ResourceFileService.php @@ -5,6 +5,8 @@ class ResourceFileService { public static function LoadResourceFile($fn) { + + $fullfn = docto_path('\\src\\res\\' . $fn); return file_get_contents($fullfn); @@ -18,6 +20,7 @@ public static function LoadResourceFiles(){ if (strlen($fn) > 2 ){ // ignore . .. //echo $fileinfo['filename'] . ':' . strlen($fileinfo['filename']); if (strpos( $fn , '__history') !== false){continue;} + if (strpos( $fn , '__recovery') !== false){continue;} $info = pathinfo($fn); $AllContent[$info['filename']] = ['filename'=> $fn, 'contents' => static::LoadResourceFile($fn)]; diff --git a/companion/config/filesystems.php b/companion/config/filesystems.php index 0e7d695..63fb5fc 100644 --- a/companion/config/filesystems.php +++ b/companion/config/filesystems.php @@ -42,6 +42,12 @@ 'throw' => false, ], + 'inputfiles' => [ + 'driver' => 'local', + 'root' => resource_path('inputfiles'), + 'throw' => false, + ], + 'public' => [ 'driver' => 'local', diff --git a/companion/config/services.php b/companion/config/services.php index d7fcf9a..d5bda7e 100644 --- a/companion/config/services.php +++ b/companion/config/services.php @@ -15,7 +15,8 @@ */ 'docto' => [ - 'path' => env('DOCTO_PATH','..\\exe\\32\\docto.exe'), + // 'path' => env('DOCTO_PATH','..\\exe\\32\\docto.exe'), + 'path' => env('DOCTO_PATH','..\\exe\\64\\docto.exe'), ], 'mailgun' => [ diff --git a/companion/resources/generator_templates/AllParameters.md b/companion/resources/generator_templates/AllParameters.md index 89c3408..9d5bc41 100644 --- a/companion/resources/generator_templates/AllParameters.md +++ b/companion/resources/generator_templates/AllParameters.md @@ -213,6 +213,27 @@ If you wish for the converted PDF to be opened after creation. No value req. Only convert certain pages in the document. +### Convert all or Specific Individual Sheets +_Excel_ + +By default when doing a conversion with XL, the first sheet is the only sheet that gets +output. Often this is what you want, however if you wish to have a specific sheet output +you can specify that or you can output all sheets and they will be given individual names. + +> --allsheets + +Output all sheets. Seperate files will be created for each sheet and named appropriatly. +eg. If you have a workbook.xls with Sheet1 and MySheet and you convert to pdf. You will get +2 files named workbook_(Sheet1).pdf and workbook_(MySheet).pdf + +> --sheets "1,2" + +You can specify to only convert certain sheets, eg 1 and 2 or "Sheet1,MySheet" + +````cmd +Note: --sheets does not work with -t xlCSV . You must use --allsheets and output all. +```` + ### Document Properties > --no-IncludeDocProperties --no-DocProp [no value required] diff --git a/companion/resources/inputfiles/docx/DocXFile.docx b/companion/resources/inputfiles/docx/DocXFile.docx new file mode 100644 index 0000000..c2e376e Binary files /dev/null and b/companion/resources/inputfiles/docx/DocXFile.docx differ diff --git a/companion/resources/inputfiles/multisheet/Book1 MultiSheet Test.xlsx b/companion/resources/inputfiles/multisheet/Book1 MultiSheet Test.xlsx new file mode 100644 index 0000000..8081666 Binary files /dev/null and b/companion/resources/inputfiles/multisheet/Book1 MultiSheet Test.xlsx differ diff --git a/companion/resources/inputfiles/password/CullohillApplePie_Protected.doc b/companion/resources/inputfiles/password/CullohillApplePie_Protected.doc new file mode 100644 index 0000000..4d8527a Binary files /dev/null and b/companion/resources/inputfiles/password/CullohillApplePie_Protected.doc differ diff --git a/companion/resources/inputfiles/plain/Ballymaloe Mincemeat Tart.doc b/companion/resources/inputfiles/plain/Ballymaloe Mincemeat Tart.doc new file mode 100644 index 0000000..01a6e15 Binary files /dev/null and b/companion/resources/inputfiles/plain/Ballymaloe Mincemeat Tart.doc differ diff --git a/companion/resources/inputfiles/plain/Ballymaloe+Hot+Buttered+Lobster (2).doc b/companion/resources/inputfiles/plain/Ballymaloe+Hot+Buttered+Lobster (2).doc new file mode 100644 index 0000000..472fd9d Binary files /dev/null and b/companion/resources/inputfiles/plain/Ballymaloe+Hot+Buttered+Lobster (2).doc differ diff --git a/companion/resources/inputfiles/plain/Ballymaloe+Hot+Buttered+Lobster 3 .doc b/companion/resources/inputfiles/plain/Ballymaloe+Hot+Buttered+Lobster 3 .doc new file mode 100644 index 0000000..9829de7 Binary files /dev/null and b/companion/resources/inputfiles/plain/Ballymaloe+Hot+Buttered+Lobster 3 .doc differ diff --git a/companion/resources/inputfiles/plain/Bookmarks+Chocolate+and+Hazelnut+Tart.doc b/companion/resources/inputfiles/plain/Bookmarks+Chocolate+and+Hazelnut+Tart.doc new file mode 100644 index 0000000..ea9c6b0 Binary files /dev/null and b/companion/resources/inputfiles/plain/Bookmarks+Chocolate+and+Hazelnut+Tart.doc differ diff --git a/companion/resources/inputfiles/plain/CullohillApplePie - Copy.doc b/companion/resources/inputfiles/plain/CullohillApplePie - Copy.doc new file mode 100644 index 0000000..388aee1 Binary files /dev/null and b/companion/resources/inputfiles/plain/CullohillApplePie - Copy.doc differ diff --git a/companion/resources/inputfiles/single/ASingleFile.doc b/companion/resources/inputfiles/single/ASingleFile.doc new file mode 100644 index 0000000..e9d0f8d Binary files /dev/null and b/companion/resources/inputfiles/single/ASingleFile.doc differ diff --git a/companion/tests/Feature/DocX/CompatibilityPestTest.php b/companion/tests/Feature/DocX/CompatibilityPestTest.php new file mode 100644 index 0000000..387eeb6 --- /dev/null +++ b/companion/tests/Feature/DocX/CompatibilityPestTest.php @@ -0,0 +1,141 @@ +add('-WD') + ->add('-f', $testinputfilesdir_temp ) + ->add('-o', $testoutputdir_temp ) + ->add('-t', 'wdformatPDF') + ->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); + expect($outputDirFiles->count())->tobe(1); + }); + + + + it ('can convert doc to docx with compatibility',function (){ + + $inputfiledir = 'inputfiles_docx'. uniqid(); + $outputfiledir = 'outputfiles_docx' . uniqid(); + // setup + $testinputfilesdir_temp = Storage::path($inputfiledir); + $testoutputdir_temp = Storage::path($outputfiledir); + + Storage::createDirectory($outputfiledir); + + $dirfiles = \App\Services\FileGatherService::GatherFiles('plain', $inputfiledir); + + $doctocmd = \App\Services\DocToCommandBuilder::docto() + ->add('-WD') + ->add('-f', $testinputfilesdir_temp ) + ->add('-o', $testoutputdir_temp ) + ->add('-ox', '.docx') + ->add('-t', 'wdFormatDocumentDefault') + ->add('--COMPATIBILITY', '65535') + ->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())->toBeGreaterThan(0); + expect($outputDirFiles->count())->tobe(5); + + // ensure -ox parameter is used. + $file1 = $outputDirFiles->first(); + expect(str($file1)->endsWith('.docx'))->toBeTrue(); + + + }); + + it ('can convert doc to docx with -c compatibility',function (){ + + $inputfiledir = 'inputfiles_docx'. uniqid(); + $outputfiledir = 'outputfiles_docx' . uniqid(); + // setup + $testinputfilesdir_temp = Storage::path($inputfiledir); + $testoutputdir_temp = Storage::path($outputfiledir); + + Storage::createDirectory($outputfiledir); + + $dirfiles = \App\Services\FileGatherService::GatherFiles('plain', $inputfiledir); + + $doctocmd = \App\Services\DocToCommandBuilder::docto() + ->add('-WD') + ->add('-f', $testinputfilesdir_temp ) + ->add('-o', $testoutputdir_temp ) + ->add('-ox', '.docx') + ->add('-t', 'wdFormatDocumentDefault') + ->add('-c', '65535') + ->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())->toBeGreaterThan(0); + expect($outputDirFiles->count())->tobe(5); + + // ensure -ox parameter is used. + $file1 = $outputDirFiles->first(); + expect(str($file1)->endsWith('.docx'))->toBeTrue(); + + + }); + + + it ('can convert doc to alternative compatibility',function ($marker,$compatibility) { + + $inputfiledir = 'inputfiles_docx'. uniqid(); + $outputfiledir = 'outputfiles_docx' . uniqid(); + // setup + $testinputfilesdir_temp = Storage::path($inputfiledir); + $testoutputdir_temp = Storage::path($outputfiledir); + + Storage::createDirectory($outputfiledir); + + $dirfiles = \App\Services\FileGatherService::GatherFiles('plain', $inputfiledir); + + $doctocmd = \App\Services\DocToCommandBuilder::docto() + ->add('-WD') + ->add('-f', $testinputfilesdir_temp ) + ->add('-o', $testoutputdir_temp ) + ->add('-t', 'wdFormatDocumentDefault') + ->add('--COMPATIBILITY', $compatibility) + ->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())->toBeGreaterThan(0); + expect($outputDirFiles->count())->tobe(5); + + + })->with( + [ + ['wdCurrent', 65535], + ['wdWord2003', 11], + ['wdWord2007', 12], + ['wdWord2010', 14], + ['wdWord2013', 15], + ]); diff --git a/companion/tests/Feature/Remove/RemoveInputFilePestTest.php b/companion/tests/Feature/Remove/RemoveInputFilePestTest.php index 696ec73..2480e72 100644 --- a/companion/tests/Feature/Remove/RemoveInputFilePestTest.php +++ b/companion/tests/Feature/Remove/RemoveInputFilePestTest.php @@ -29,14 +29,78 @@ $dirfilescount = $dirfiles->count(); // do conversion $docto = config('services.docto.path'); - $doctocmd = "$docto -WD -f $testinputfilesdir_temp -fx .doc -o $testoutputdir_temp -t wdFormatPDF -R true"; + // $doctocmd = "$docto -WD -f $testinputfilesdir_temp -fx .doc -o $testoutputdir_temp -t wdFormatPDF -R true"; + $doctocmd = \App\Services\DocToCommandBuilder::docto() + ->add('-WD') + ->add('-f',$testinputfilesdir_temp) + ->add('-fx','.doc') + ->add('-o',$testoutputdir_temp) + ->add('-t wdFormatPDF') + ->add('-R','true') // the important one fo rthis test + ->build(); // echo $doctocmd; $output = \Illuminate\Support\Facades\Process::run($doctocmd); - +// print_r($output); // check files have been converted and originoals have been created. - expect(collect(\Illuminate\Support\Facades\Storage::listContents('inputfilestemp'))->count())->tobe($dirfilescount - $docfilecount); - expect(collect(\Illuminate\Support\Facades\Storage::listContents('outputtemp2'))->count())->tobe( $docfilecount); + expect(collect(\Illuminate\Support\Facades\Storage::AllFiles('inputfilestemp'))->count())->tobe($dirfilescount - $docfilecount); + expect(collect(\Illuminate\Support\Facades\Storage::AllFiles('outputtemp2'))->count())->tobe( $docfilecount); + + + }); + + + +it('doesnt delete files from directory', function (){ + // setup + // $testinputfilesdir = \Illuminate\Support\Facades\Storage::path('inputfiles\\plain'); + $testinputfilesdir_temp = \Illuminate\Support\Facades\Storage::path('inputfilestemp'); + + + $testinputfilesdir_temp = \Illuminate\Support\Facades\Storage::path('inputfilestemp'); + + $testoutputdir_temp = \Illuminate\Support\Facades\Storage::path('outputtemp2'); + + \Illuminate\Support\Facades\Storage::createDirectory('outputtemp2'); + + // $cmd = "xcopy \"$testinputfilesdir\" \"$testinputfilesdir_temp\\\" "; + // echo "\n". $cmd; + // $result = \Illuminate\Support\Facades\Process::run( $cmd ); + + //echo "\n" . $result->output() . "\n"; + // $dirfiles = collect(\Illuminate\Support\Facades\Storage::listContents('inputfilestemp')); + $dirfiles = \App\Services\FileGatherService::GatherFiles(collect(['plain']),'inputfilestemp'); + $docfiles = $dirfiles->filter(function ($item){ + return str($item->path())->endsWith('.doc'); + }); + expect($docfiles->count())->toBeGreaterThan(0); + $docfilecount = count($docfiles->toArray()); + + $dirfilescount = $dirfiles->count(); + // do conversion + $docto = config('services.docto.path'); + // $doctocmd = "$docto -WD -f $testinputfilesdir_temp -fx .doc -o $testoutputdir_temp -t wdFormatPDF -R true"; + $doctocmd = \App\Services\DocToCommandBuilder::docto() + ->add('-WD') + ->add('-f',$testinputfilesdir_temp) + ->add('-fx','.doc') + ->add('-o',$testoutputdir_temp) + ->add('-t wdFormatPDF') + // ->add('-R','true') // the important one fo rthis test + ->build(); + // echo $doctocmd; + $output = \Illuminate\Support\Facades\Process::run($doctocmd); + + expect(collect(\Illuminate\Support\Facades\Storage::AllFiles('inputfilestemp'))->count())->toBeGreaterThan(0); + // check files have been converted and originoals have been created. + expect(collect(\Illuminate\Support\Facades\Storage::AllFiles('inputfilestemp'))->count())->tobe( $docfilecount); + $outputDirFiles = collect(\Illuminate\Support\Facades\Storage::AllFiles('outputtemp2')); + expect($outputDirFiles->count())->toBeGreaterThan( 0); + expect($outputDirFiles->count())->tobe( $docfilecount); + // This will delete all files in the storage/tests directory. + // this happens once a test run to prevent files from building up. + // could be done a bit more organised if desired. + \Illuminate\Support\Facades\Storage::deleteDirectory('\\'); }); diff --git a/companion/tests/Feature/VersionPestTest.php b/companion/tests/Feature/VersionPestTest.php new file mode 100644 index 0000000..ec4a341 --- /dev/null +++ b/companion/tests/Feature/VersionPestTest.php @@ -0,0 +1,29 @@ +add('-h') + ->build(); + + $result = \Illuminate\Support\Facades\Process::run($doctocmd); + // print_r($output->output()); + $outputString = $result->output(); + + expect($outputString)->toContain('DocTo Version: 1.16.45'); + + + }); + diff --git a/companion/tests/Feature/XLS/MultiSheet/MultiSheetPestTest.php b/companion/tests/Feature/XLS/MultiSheet/MultiSheetPestTest.php new file mode 100644 index 0000000..1e6f752 --- /dev/null +++ b/companion/tests/Feature/XLS/MultiSheet/MultiSheetPestTest.php @@ -0,0 +1,237 @@ +count())->tobe(0); + $dirfiles = \App\Services\FileGatherService::GatherFiles(collect(['multisheet']), $inputfiledir); + + $doctocmd = \App\Services\DocToCommandBuilder::docto() + ->add('-XL') + ->add('-f', $testinputfilesdir_temp .'\\Book1 MultiSheet Test.xlsx' ) + ->add('-o', $testoutputdir_temp .'\\Book1 MultiSheet Test.pdf') + ->add('-t', 'xlPDF') + ->add('--sheets', 'Tab3') + ->add('-L 10') + ->build(); + + $output = \Illuminate\Support\Facades\Process::run($doctocmd); + // print_r($output); + $outputDirFiles = collect(\Illuminate\Support\Facades\Storage::allFiles($outputfiledir)); + + expect($outputDirFiles->count())->toBeGreaterThan(0); + expect($outputDirFiles->count())->tobe(1); + // Sheet named + $sheetNamed = $outputDirFiles->filter(function ($item) { + // echo $item . "\n"; + if (str($item)->contains('Book1 MultiSheet Test_(Sheet1).pdf') ) return true; + if (str($item)->contains('Book1 MultiSheet Test_(Sheet2).pdf') ) return true; + Return false; + }); + + + $sheetNamed3 = $outputDirFiles->filter(function ($item) { + // echo $item . "\n"; + if (str($item)->contains('Book1 MultiSheet Test_(Tab3).pdf') ) return true; + + }); + + expect($sheetNamed->count())->tobe(0); + expect($sheetNamed3->count())->tobe(1); + + // Storage::deleteDirectory($outputfiledir); + }); + + + it('can pdf each sheet in a multi sheet xls', function () { + + $inputfiledir = 'inputfilesxls'; + $outputfiledir = 'outputfilesxls' . uniqid(); + // setup + $testinputfilesdir_temp = Storage::path($inputfiledir); + $testoutputdir_temp = Storage::path($outputfiledir); + + Storage::createDirectory($outputfiledir); + + $dirfiles = \App\Services\FileGatherService::GatherFiles(collect(['multisheet']), $inputfiledir); + + $doctocmd = \App\Services\DocToCommandBuilder::docto() + ->add('-XL') + ->add('-f', $testinputfilesdir_temp .'\\Book1 MultiSheet Test.xlsx' ) + ->add('-o', $testoutputdir_temp .'\\Book1 MultiSheet Test.pdf') + ->add('-t', 'xlPDF') + ->add('--sheets', '1,2') + ->add('-L 10') + ->build(); + + $output = \Illuminate\Support\Facades\Process::run($doctocmd); + // print_r($output); + $outputDirFiles = collect(\Illuminate\Support\Facades\Storage::allFiles($outputfiledir)); + + expect($outputDirFiles->count())->toBeGreaterThan(0); + expect($outputDirFiles->count())->tobe(2); + // Sheet named + $sheetNamed = $outputDirFiles->filter(function ($item) { + // echo $item . "\n"; + if (str($item)->contains('Book1 MultiSheet Test_(Sheet1).pdf') ) return true; + if (str($item)->contains('Book1 MultiSheet Test_(Sheet2).pdf') ) return true; + Return false; + }); + + + $sheetNamed3 = $outputDirFiles->filter(function ($item) { + // echo $item . "\n"; + if (str($item)->contains('Book1 MultiSheet Test_(Tab3).pdf') ) return true; + + }); + + expect($sheetNamed->count())->tobe(2); + expect($sheetNamed3->count())->tobe(0); + + Storage::deleteDirectory('outputtempxls2'); + }); + + + it('can pdf output all sheets multi sheet xls', function () { + + $inputfiledir = 'inputfilesxls'; + $outputfiledir = 'outputfilesxls' . uniqid(); + // setup + $testinputfilesdir_temp = Storage::path($inputfiledir); + $testoutputdir_temp = Storage::path($outputfiledir); + + Storage::createDirectory($outputfiledir); + + $dirfiles = \App\Services\FileGatherService::GatherFiles(collect(['multisheet']), $inputfiledir); + + $doctocmd = \App\Services\DocToCommandBuilder::docto() + ->add('-XL') + ->add('-f', $testinputfilesdir_temp .'\\Book1 MultiSheet Test.xlsx' ) + ->add('-o', $testoutputdir_temp .'\\Book1 MultiSheet Test.pdf') + ->add('-t', 'xlPDF') + ->add('--allsheets') + ->add('-L 10') + ->build(); + + $output = \Illuminate\Support\Facades\Process::run($doctocmd); + // print_r($output); + $outputDirFiles = collect(\Illuminate\Support\Facades\Storage::allFiles($outputfiledir)); + + expect($outputDirFiles->count())->toBeGreaterThan(0); + expect($outputDirFiles->count())->tobe(3); + // Sheet named + $sheetNamed = $outputDirFiles->filter(function ($item) { + // echo $item . "\n"; + if (str($item)->contains('Book1 MultiSheet Test_(Sheet1).pdf') ) return true; + if (str($item)->contains('Book1 MultiSheet Test_(Sheet2).pdf') ) return true; + if (str($item)->contains('Book1 MultiSheet Test_(Tab3).pdf') ) return true; + // this is not expected to match. Emtpy sheets dont output + if (str($item)->contains('Book1 MultiSheet Test_(Sheet4).pdf') ) return true; + Return false; + }); + + expect($sheetNamed->count())->tobe(3); + + + Storage::deleteDirectory($outputfiledir); + }); + + it('outputs correct all sheets multi sheet xls', function () { + + $inputfiledir = 'inputfilesxls'; + $outputfiledir = 'outputfilesxls' . uniqid(); + // setup + $testinputfilesdir_temp = Storage::path($inputfiledir); + $testoutputdir_temp = Storage::path($outputfiledir); + + Storage::createDirectory($outputfiledir); + + $dirfiles = \App\Services\FileGatherService::GatherFiles(collect(['multisheet']), $inputfiledir); + + $doctocmd = \App\Services\DocToCommandBuilder::docto() + ->add('-XL') + ->add('-f', $testinputfilesdir_temp .'\\Book1 MultiSheet Test.xlsx' ) + ->add('-o', $testoutputdir_temp .'\\TabTest.csv') + ->add('-t', 'xlCSV') + ->add('--allsheets') + ->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())->toBeGreaterThan(0); + expect($outputDirFiles->count())->tobe(4); + + + // Sheet named + $fileText = file_get_contents(Storage::path($outputfiledir . '\\TabTest_(Tab3).csv')); + expect(str($fileText)->contains('This is Tab3'))->toBeTrue(); + expect(str($fileText)->contains('This is Sheet 1'))->not()->toBeTrue(); + + // Sheet named + $fileText = file_get_contents(Storage::path($outputfiledir . '\\TabTest_(Sheet1).csv')); + expect(str($fileText)->contains('This is Sheet 1'))->toBeTrue(); + expect(str($fileText)->contains('This is Tab3'))->not()->toBeTrue(); + + + + }); + + + + // these formats only output single sheet. + it('outputs correct single sheet multi sheet xls', function ($format, $ext) { + + $inputfiledir = 'inputfilesxls'; + $outputfiledir = 'outputfilesxls' . uniqid(); + // setup + $testinputfilesdir_temp = Storage::path($inputfiledir); + $testoutputdir_temp = Storage::path($outputfiledir); + + Storage::createDirectory($outputfiledir); + + $dirfiles = \App\Services\FileGatherService::GatherFiles(collect(['multisheet']), $inputfiledir); + + $doctocmd = \App\Services\DocToCommandBuilder::docto() + ->add('-XL') + ->add('-f', $testinputfilesdir_temp .'\\Book1 MultiSheet Test.xlsx' ) + ->add('-o', $testoutputdir_temp .'\\TabTest.' . $ext) + ->add('-t', $format) + ->add('--allsheets') + ->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())->toBeGreaterThan(0); + expect($outputDirFiles->count())->tobe(1); + + + + })->with([ + ['xlUnicodeText', 'txt'], + ['xlCSVWindows', 'csv'], + ['xlTextWindows', 'txt'], + ]); diff --git a/companion/tests/Feature/output/OutputDirCreationPestTest.php b/companion/tests/Feature/output/OutputDirCreationPestTest.php index ba3a454..d1f40a7 100644 --- a/companion/tests/Feature/output/OutputDirCreationPestTest.php +++ b/companion/tests/Feature/output/OutputDirCreationPestTest.php @@ -9,7 +9,10 @@ $docto = config('services.docto.path'); $inputdir = \Illuminate\Support\Facades\Storage::path($gatherdir); $fulloutputdir = \Illuminate\Support\Facades\Storage::path($outputdir); - $cmd = "$docto -WD -f $inputdir -o $fulloutputdir -t wdFormatHTML"; + // $cmd = "$docto -WD -f $inputdir -o $fulloutputdir -t wdFormatHTML"; + $cmd = \App\Services\DocToCommandBuilder::docto()->add('-WD')->add('-f',$inputdir) + ->add('-o',$fulloutputdir) + ->add('-t wdFormatHTML')->build(); \Illuminate\Support\Facades\Log::debug($cmd); \Illuminate\Support\Facades\Log::debug($fulloutputdir); $output = Process::run($cmd); @@ -17,7 +20,7 @@ }); - test('will not create extention directory', function () { + test('will not create extension directory', function () { $gatherdir = uniqid(); $outputdir = uniqid(); $files = \App\Services\FileGatherService::GatherFiles(collect(['single']),$gatherdir); @@ -26,7 +29,11 @@ $fulloutputdir = \Illuminate\Support\Facades\Storage::path($outputdir); // a bug caused docto to create and ouput to extension directory. $pdfdir = $fulloutputdir . '\\.pdf'; - $cmd = "$docto -WD -f $inputdir -o $fulloutputdir -t wdFormatPDF"; + // $cmd = "$docto -WD -f $inputdir -o $fulloutputdir -t wdFormatPDF"; + $cmd = \App\Services\DocToCommandBuilder::docto()->add('-WD') + ->add('-f',$inputdir) + ->add('-o',$fulloutputdir) + ->add('-t wdFormatPDF')->build(); \Illuminate\Support\Facades\Log::debug($cmd); \Illuminate\Support\Facades\Log::debug($fulloutputdir); $output = Process::run($cmd); @@ -36,3 +43,22 @@ + + test('can create non existant directory with bulder', function () { + $gatherdir = uniqid(); + $outputdir = uniqid(); + $files = \App\Services\FileGatherService::GatherFiles(collect(['single']),$gatherdir); + $docto = config('services.docto.path'); + $inputdir = \Illuminate\Support\Facades\Storage::path($gatherdir); + $fulloutputdir = \Illuminate\Support\Facades\Storage::path($outputdir); + $cmd = \App\Services\DocToCommandBuilder::docto()->add('-WD') + ->add('-f',$inputdir) + ->add('-o',$fulloutputdir) + ->add('-t wdFormatHTML')->build(); + // $cmd = "$docto -WD -f $inputdir -o $fulloutputdir -t wdFormatHTML"; + \Illuminate\Support\Facades\Log::debug($cmd); + \Illuminate\Support\Facades\Log::debug($fulloutputdir); + $output = Process::run($cmd); + expect(\Illuminate\Support\Facades\Storage::exists($outputdir))->toBeTrue(); + +}); diff --git a/pages/all/HelpLog.md b/pages/all/HelpLog.md index 90790e7..fe0dc62 100644 --- a/pages/all/HelpLog.md +++ b/pages/all/HelpLog.md @@ -117,10 +117,15 @@ Long Parameters: --PDF-no-BitmapMissingFonts Do not bitmap missing fonts, fonts will be substituted. --use-ISO190051 - Create PDF to the ISO 19005-1 standard. + Create PDF to the ISO 19005-1 standard. + --enable-macroautorun --enable-wordvbaauto - By Default any autorun vba will not run, use this parameter if you wish vba to Autorun. Word Only. - + --enable-xlvbaauto + By Default any autorun vba will not run, use this parameter if you wish vba to Autorun. Word / Excel Only. + --sheets + Select which sheets to save. Can be comma seperated list of sheet names or indexes. Excel Only. Unavailable for xlCSV + --allsheets + If converting to CSV default behaviour is to convert first sheet. This will convert all with appropriate names Experimental: @@ -137,6 +142,7 @@ ERROR CODES: 203 : Unknown switch in command 204 : Input File does not exist 205 : Invalid Parameter Value +210 : DocTo Error 220 : Word or COM Error 221 : Word not Installed 301 : Not Implemented diff --git a/pages/all/HelpLog.txt.md b/pages/all/HelpLog.txt.md index 6ecb041..e4be18a 100644 --- a/pages/all/HelpLog.txt.md +++ b/pages/all/HelpLog.txt.md @@ -112,10 +112,15 @@ Long Parameters: --PDF-no-BitmapMissingFonts Do not bitmap missing fonts, fonts will be substituted. --use-ISO190051 - Create PDF to the ISO 19005-1 standard. + Create PDF to the ISO 19005-1 standard. + --enable-macroautorun --enable-wordvbaauto - By Default any autorun vba will not run, use this parameter if you wish vba to Autorun. Word Only. - + --enable-xlvbaauto + By Default any autorun vba will not run, use this parameter if you wish vba to Autorun. Word / Excel Only. + --sheets + Select which sheets to save. Can be comma seperated list of sheet names or indexes. Excel Only. Unavailable for xlCSV + --allsheets + If converting to CSV default behaviour is to convert first sheet. This will convert all with appropriate names Experimental: @@ -132,6 +137,7 @@ ERROR CODES: 203 : Unknown switch in command 204 : Input File does not exist 205 : Invalid Parameter Value +210 : DocTo Error 220 : Word or COM Error 221 : Word not Installed 301 : Not Implemented diff --git a/readme.md b/readme.md index cf1e421..1b978bb 100644 --- a/readme.md +++ b/readme.md @@ -128,8 +128,8 @@ https://webapps.stackexchange.com/questions/74859/what-format-does-word-online-u ## Command Line Help Help - Docto Version:%s - Office Version : %s + DocTo Version: %s + Office Version: %s Open Source: https://github.com/tobya/DocTo/ Description: DocTo converts Word Documents and Excel Spreadsheets to other formats. @@ -140,19 +140,22 @@ https://webapps.stackexchange.com/questions/74859/what-format-does-word-online-u -H This message --HELP -? - -WD Use Word for Converstion (Default). Help '-h -wd' + -WD Use Word for Conversion (Default). Help '-h -wd' --word -XL Use Excel for Conversion. Help '-h -xl' --excel -PP Use Powerpoint for Conversion. help '-h -pp' --powerpoint - -VS Use Visio for Conversion. + -VS Use Visio for Conversion. --visio -F Input File or Directory --inputfile - -FX Input file search for if -f is directory. Can use .rtf test*.txt etc + -FX Input Extension to search for if directory. (.rtf .txt etc) Default ".doc*" (will find ".docx" also) --inputextension + --inputfilter + Filter Files to input. Property*.doc will match Property1.doc, + Property2.doc etc -O Output File or Directory to place converted Docs --outputfile -OX Output Extension if -F is Directory. Please include '.' eg. '.pdf' . @@ -162,7 +165,6 @@ https://webapps.stackexchange.com/questions/74859/what-format-does-word-online-u Available from https://docs.microsoft.com/en-us/dotnet/api/microsoft.office.interop.word.wdsaveformat or https://docs.microsoft.com/en-us/dotnet/api/microsoft.office.interop.excel.xlfileformat - or https://docs.microsoft.com/en-us/office/vba/api/powerpoint.presentation.saveas See current List Below. --format -TF Force Format. -T value if an integer, is checked against current list @@ -219,9 +221,9 @@ https://webapps.stackexchange.com/questions/74859/what-format-does-word-online-u --no-subdirs Only convert specified directory. Do not recurse sub directories --ExportMarkup Value for wdExportItem - default wdExportDocumentContent. use wdExportDocumentWithMarkup to export all word comments with pdf - --no-IncludeDocProperties + --no-IncludeDocProperties --no-DocProp - Do not include Document Properties in the exported pdf file. + Do not include Document Properties in the exported pdf file. --PDF-OpenAfterExport If you wish for a converted PDF to be opened after creation. No value req. --PDF-FromPage @@ -237,11 +239,19 @@ https://webapps.stackexchange.com/questions/74859/what-format-does-word-online-u --PDF-No-DocStructureTags Do not include DocStructureTags to help screen readers. --PDF-no-BitmapMissingFonts - Do not bitmap missing fonts, fonts will be substituted. - --use-ISO190051 + Do not bitmap missing fonts, fonts will be substituted. + --use-ISO190051 Create PDF to the ISO 19005-1 standard. + --enable-macroautorun + --enable-wordvbaauto + --enable-xlvbaauto + By Default any autorun vba will not run, use this parameter if you wish vba to Autorun. Word / Excel Only. + --sheets + Select which sheets to save. Can be comma seperated list of sheet names or indexes. Excel Only. + --allsheets + If converting to CSV default behaviour is to convert first sheet. This will convert all with appropriate names Experimental: @@ -260,8 +270,9 @@ https://webapps.stackexchange.com/questions/74859/what-format-does-word-online-u 205 : Invalid Parameter Value 220 : Word or COM Error 221 : Word not Installed + 301 : Not Implemented 400 : Unknown Error - + # Parameter Overview ## Usage diff --git a/src/ExcelUtils.pas b/src/ExcelUtils.pas index eff9453..c11ba22 100644 --- a/src/ExcelUtils.pas +++ b/src/ExcelUtils.pas @@ -14,7 +14,10 @@ ****************************************************************) interface -uses Classes,Sysutils, MainUtils, ResourceUtils, ActiveX, ComObj, WinINet, Variants, Excel_TLB_Constants,StrUtils; +uses Classes,Sysutils, MainUtils, ResourceUtils, ActiveX, ComObj, WinINet, Variants, +DynamicFileNameGenerator, DocToExceptions, + + Excel_TLB_Constants,StrUtils; type @@ -23,11 +26,25 @@ interface ExcelApp : OleVariant; FExcelVersion : String; + olevar_FromPage, olevar_ToPage : OleVariant; + + function SingleFileExecuteConversion(fileToConvert, OutputFilename: String; OutputFileFormat: Integer): TConversionInfo; + procedure SaveAsPDF(OutputFilename : string) ; + procedure SaveAsXPS(OutputFilename: string); + procedure SaveAsCSV(OutputFilename: string); + procedure ExportWorkSheetasPDF(ws :OleVariant; FileNameGen : TDynamicFileNameGenerator); + procedure ExportWorkbookasPDF(OutputFileName: String); + + function isWorkSheetEmpty(WorkSheet : OleVariant): boolean; + procedure CheckWorkSheetIndexValid(index : integer); + public constructor Create() ; function CreateOfficeApp() : boolean; override; function DestroyOfficeApp() : boolean; override; + function ExecuteConversion(fileToConvert: String; OutputFilename: String; OutputFileFormat : Integer): TConversionInfo; override; + function AvailableFormats() : TStringList; override; function FormatsExtensions(): TStringList; override; function OfficeAppVersion() : String; override; @@ -54,6 +71,15 @@ function TExcelXLSConverter.AvailableFormats() : TStringList; { TWordDocConverter } +procedure TExcelXLSConverter.CheckWorkSheetIndexValid(index: integer); +begin + + if (index = 0) then + begin + raise ESheetIndexOutOfBounds.Create('Excel Worksheets start at 1. 0 is not valid index'); + end; +end; + constructor TExcelXLSConverter.Create; begin inherited; @@ -88,7 +114,10 @@ function TExcelXLSConverter.ExecuteConversion(fileToConvert: String; OutputFilen var NonsensePassword :OleVariant; FromPage, ToPage : OleVariant; + activeSheet, oldEnableEvents, oldAutoSecurity : OleVariant; + dynamicoutputDir, dynamicoutputFile, dynamicoutputExt, dynamicOutputFileName, dynamicSheetName : String; ExitAction :TExitAction; + Sheet : integer; begin //Excel is particuarily sensitive to having \\ at end of filename, eg it won't create file. //so we remove any double \\ @@ -97,7 +126,21 @@ function TExcelXLSConverter.ExecuteConversion(fileToConvert: String; OutputFilen ExitAction := aSave; Result.InputFile := fileToConvert; Result.Successful := false; + + + // disable Macros before opening file. + if (fDontUseAutoVBA) then + begin + oldEnableEvents := ExcelApp.EnableEvents; + ExcelApp.EnableEvents := false; + oldAutoSecurity := ExcelApp.AutomationSecurity; + ExcelApp.AutomationSecurity := 3; // msoAutomationSecurityForceDisable + end; + + try + NonsensePassword := 'tfm554!ghAGWRDD'; + try ExcelApp.Workbooks.Open( FileToConvert, //FileName , EmptyParam, //UpdateLinks , @@ -155,53 +198,124 @@ function TExcelXLSConverter.ExecuteConversion(fileToConvert: String; OutputFilen aSave: // Go ahead and save begin + Result := SingleFileExecuteConversion(fileToConvert, OutputFilename, OutputFileFormat); - //Unlike Word, in Excel you must call a different function to save a pdf and XPS. - if OutputFileFormat = xlTypePDF then + // To avoid pop ups it is important to save the sheet. However not if the macros have run. + if (fDontUseAutoVBA) then begin + ExcelApp.ActiveWorkBook.save; + end ; - if pdfPrintToPage > 0 then - begin - logdebug('PrintFromPage: ' + inttostr(pdfPrintFromPage),debug); - logdebug('PrintToPage: ' + inttostr(pdfPrintToPage),debug); + ExcelApp.ActiveWorkBook.Saved := true; + // Saved has previously been set to true. + // should close without dialog. + ExcelApp.ActiveWorkbook.Close(); - FromPage := pdfPrintFromPage; - ToPage := pdfPrintToPage; - end else - begin - FromPage := EmptyParam; - ToPage := EmptyParam; - end ; + end; + end; - ExcelApp.Application.DisplayAlerts := False ; - ExcelApp.activeWorkbook.ExportAsFixedFormat(XlFixedFormatType_xlTypePDF, - OutputFilename, - EmptyParam, //Quality - IncludeDocProps, // IncludeDocProperties, - False,// IgnorePrintAreas, - FromPage , // From, - ToPage, // To, - pdfOpenAfterExport, // OpenAfterPublish, (default false); - EmptyParam// FixedFormatExtClassPtr - ) ; + finally + if (fDontUseAutoVBA) then + begin + ExcelApp.EnableEvents := oldEnableEvents; + ExcelApp.AutomationSecurity := oldAutoSecurity; + end; + end; + +end; + + +procedure TExcelXLSConverter.ExportWorkbookasPDF(OutputFileName: String); +var +activeWkBk: OleVariant; +begin + + activeWkBk := ExcelApp.ActiveWorkbook; + + + ExcelApp.Application.DisplayAlerts := False ; + activeWkBk.ExportAsFixedFormat(XlFixedFormatType_xlTypePDF, + OutputFileName, + EmptyParam, // Quality + IncludeDocProps, // IncludeDocProperties, + False, // IgnorePrintAreas, + olevar_FromPage , // From, + olevar_ToPage, // To, + pdfOpenAfterExport, // OpenAfterPublish, (default false); + EmptyParam // FixedFormatExtClassPtr + ) ; + fOutputFiles.Add(OutputFileName); + +end; - ExcelApp.ActiveWorkBook.Saved := True +procedure TExcelXLSConverter.ExportWorkSheetasPDF(ws: OleVariant; FileNameGen: TDynamicFileNameGenerator); +begin + if self.isWorkSheetEmpty(ws) then + begin + logInfo('The worksheet "' + ws.Name + '" is Empty and will not be output', STANDARD); + + end else + begin + + + ExcelApp.Application.DisplayAlerts := False ; + ws.ExportAsFixedFormat(XlFixedFormatType_xlTypePDF, + FileNameGen.Generate(ws.Name), + EmptyParam, // Quality + IncludeDocProps, // IncludeDocProperties, + False, // IgnorePrintAreas, + olevar_FromPage , // From, + olevar_ToPage, // To, + pdfOpenAfterExport, // OpenAfterPublish, (default false); + EmptyParam // FixedFormatExtClassPtr + ) ; + end; + + fOutputFiles.Add(FileNameGen.Generate(ws.Name)); + +end; + +//Useful Links: +// https://docs.microsoft.com/en-us/office/vba/api/excel.workbooks.open +// https://docs.microsoft.com/en-us/office/vba/api/excel.workbook.exportasfixedformat + +function TExcelXLSConverter.SingleFileExecuteConversion(fileToConvert: String; OutputFilename: String; OutputFileFormat : Integer): TConversionInfo; +var + NonsensePassword :OleVariant; + FromPage, ToPage : OleVariant; + activeSheet : OleVariant; + dynamicoutputDir, dynamicoutputFile, dynamicoutputExt, dynamicOutputFileName, dynamicSheetName : String; + ExitAction :TExitAction; + Sheet : integer; +begin + + logdebug('SingleFileExecuteConversion',VERBOSE); + + //Unlike Word, in Excel you must call a different function to save a pdf and XPS. + if OutputFileFormat = xlTypePDF then + begin + + SaveAsPDF(OutputFilename); end else if OutputFileFormat = xlTypeXPS then begin - ExcelApp.Application.DisplayAlerts := False ; - ExcelApp.activeWorkbook.ExportAsFixedFormat(XlFixedFormatType_xlTypeXPS, OutputFilename ); - ExcelApp.ActiveWorkBook.save; + SaveAsXPS(OutputFilename); end else if OutputFileFormat = xlCSV then begin - //CSV pops up alert. must be hidden for automation - ExcelApp.Application.DisplayAlerts := False ; - ExcelApp.activeWorkbook.SaveAs( OutputFilename, OutputFileFormat); - ExcelApp.ActiveWorkBook.saved := true; + + + + // to get sheets + // Sheets(Array("Sheet4", "Sheet5")) or Sheets(3) or Sheets(Array(1,2)) + ExcelApp.Application.DisplayAlerts := False ; + SaveAsCSV( OutputFilename); + +// ExcelApp.activeWorkbook.SaveAs( OutputFilename, OutputFileFormat); + // ExcelApp.ActiveWorkBook.saved := true; end else begin @@ -215,10 +329,8 @@ function TExcelXLSConverter.ExecuteConversion(fileToConvert: String; OutputFilen // Close Excel Sheet. Result.Successful := true; Result.OutputFile := OutputFilename; - ExcelApp.ActiveWorkbook.Close(); - end; - end; + end; @@ -232,6 +344,16 @@ function TExcelXLSConverter.FormatsExtensions: TStringList; result := Extensions; end; +function TExcelXLSConverter.isWorkSheetEmpty(WorkSheet: OleVariant): boolean; +begin + + // this will tell if a sheet is empty. + Result := ExcelApp.WorksheetFunction.CountA(WorkSheet.Cells) = 0; + +end; + + + function TExcelXLSConverter.OfficeAppVersion(): String; begin FExcelVersion := ReadOfficeAppVersion; @@ -244,4 +366,226 @@ function TExcelXLSConverter.OfficeAppVersion(): String; result := FExcelVersion; end; + +procedure TExcelXLSConverter.SaveAsPDF(OutputFilename : string) ; +var + + FromPage, ToPage, SheetList, ExcelSheets : OleVariant; + Sheet1,Sheet2,Sheet3 , Workbook , SheetsArray: OleVariant; + activeSheet, WorkSheets, ws, wsName : OleVariant; + I,j, sheetNumber :integer; + sheetName : olevariant; + FileNameGen: TDynamicFileNameGenerator; +begin + + + logdebug('Save as pdf',debug); + + ExcelApp.Application.DisplayAlerts := False ; + + if pdfPrintToPage > 0 then + begin + logdebug('PrintFromPage: ' + inttostr(pdfPrintFromPage),debug); + logdebug('PrintToPage: ' + inttostr(pdfPrintToPage),debug); + + olevar_FromPage := pdfPrintFromPage; + olevar_ToPage := pdfPrintToPage; + + end else + begin + olevar_FromPage := EmptyParam; + olevar_ToPage := EmptyParam; + + end ; + + WorkSheets := ExcelApp.Worksheets; + + + logDebug('count:' + inttostr(WorkSheets.Count), verbose); + FileNameGen := TDynamicFileNameGenerator.Create(OutputFilename); + + if fSelectedSheets_All then + begin + logDebug('in fSelectedSheets_All'); + + for I := 1 to WorkSheets.Count do + begin + + + ws := WorkSheets.Item[I]; + + logDebug('worksheet:' + ws.Name, VERBOSE); + + ExportWorkSheetasPDF(ws,FileNameGen); + + + end; + + end else if SelectedSheets.Count > 0 then + begin + logdebug('SelectedSheets.Count:' + inttostr( SelectedSheets.Count), debug); + + logDebug( SelectedSheets.Text,VERBOSE); + + + + + + for j := 0 to SelectedSheets.Count -1 do + begin + + sheetName := SelectedSheets[j]; + logDebug( sheetName,VERBOSE); + + if (TryStrToInt(sheetName,sheetNumber) )then + begin + logdebug( 'TryStrToInt',VERBOSE); + self.CheckWorkSheetIndexValid(sheetNumber); + ws := WorkSheets.Item[sheetNumber]; + end else + begin + logdebug( 'not TryStrToInt',VERBOSE); + ws := WorkSheets.Item[sheetName]; + + end; + + + + + + + + logDebug('worksheetxx:' + ws.Name, VERBOSE); + + ExportWorkSheetasPDF(ws,FileNameGen); + + + end; + + end else + begin + + self.ExportWorkbookasPDF(OutputFileName); + end; + + + + + + ExcelApp.ActiveWorkBook.Saved := True + +end; + + +procedure TExcelXLSConverter.SaveAsXPS(OutputFilename : string) ; +begin + + if (SelectedSheets.Count > 0) or (fSelectedSheets_All = true) then + begin + raise ENotImplemented.Create('--sheets, --allsheets is not available for conversion to XPS'); + end; + + ExcelApp.Application.DisplayAlerts := False ; + ExcelApp.activeWorkbook.ExportAsFixedFormat(XlFixedFormatType_xlTypeXPS, OutputFilename ); + +end; + +// Save to 1 or move csv files +procedure TExcelXLSConverter.SaveAsCSV(OutputFilename: string); +var + FromPage, ToPage : OleVariant; + activeSheet, sheetName : OleVariant; + sheetNumber : integer; + dynamicoutputDir, dynamicoutputFile, dynamicoutputExt, dynamicOutputFileName, dynamicSheetName : String; + ExitAction :TExitAction; + Sheet, ix : integer; + FileNameGen : TDynamicFileNameGenerator; +begin + // LogDebug('output to csv format'); + + + ExcelApp.Application.DisplayAlerts := False ; + + if SelectedSheets.Count > 0 then + begin + raise EInvalidParameterCombination.Create('--sheet cannot be used with xlCSV. Use --allsheets instead'); + end; + + + FileNameGen := TDynamicFileNameGenerator.Create(OutputFilename); + +// ******************************************************** +// Very Strange Behaviour +// +// When The sheets are output in order going from 1 to Worksheet.Count then each +// sheet is output correctly as a CSV file. However if an attempt is made to +// output a single sheet, either by index or sheet name the first sheet is +// always output. It makes no sense, the code is identical. I have tried many +// times. Therefore I am going to just leave it and always output all. + + + + // output all sheets with seperate names + if (fSelectedSheets_All) or (SelectedSheets.Count > 0) then + begin + + + + for Sheet := 1 to ExcelApp.ActiveWorkbook.WorkSheets.Count do + begin + // LogDebug('CSV Loop'); + activeSheet := ExcelApp.ActiveWorkbook.Sheets[Sheet]; + dynamicSheetName := activeSheet.Name; + + // LogDebug(dynamicSheetName); + + dynamicOutputFilename := FileNameGen.Generate(dynamicSheetName); + + // LogDebug(dynamicOutputFileName); + + activeSheet.SaveAs( dynamicoutputFilename, OutputFileFormat); + fOutputFiles.Add(dynamicoutputFilename); + end; + end + (* end + // If we have been given Sheetnames or ids, just output them. + else if SelectedSheets.Count > 0 then + + begin + + + + // This code doesnt work, it should but it doesnt, it always outputs the + // first sheet. + + for ix := 0 to SelectedSheets.Count -1 do + begin + LogDebug('CSV Loop:' + SelectedSheets[ix]); + sheetName := SelectedSheets[ix]; + + // Check if number requested and set sheetNumber + // ------------- + activeSheet := ExcelApp.ActiveWorkbook.Sheets[sheetName]; + dynamicSheetName := activeSheet.Name; + + LogDebug(dynamicSheetName); + + dynamicOutputFilename := FileNameGen.Generate(dynamicSheetName); + + LogDebug(dynamicOutputFileName); + + activeSheet.SaveAs( dynamicoutputFilename, OutputFileFormat); + end; + + end else *) + // Do default which is usually first sheet, with name provided. + else + begin + ExcelApp.activeWorkbook.SaveAs( OutputFilename, OutputFileFormat); + fOutputFiles.Add(OutputFilename); + end; + + +end; + end. diff --git a/src/Exceptions/DocToExceptions.pas b/src/Exceptions/DocToExceptions.pas new file mode 100644 index 0000000..b091805 --- /dev/null +++ b/src/Exceptions/DocToExceptions.pas @@ -0,0 +1,14 @@ +unit DocToExceptions; + +interface +uses Classes, ActiveX, ComObj, WinINet, Variants, sysutils, Types, StrUtils, TypInfo; + +type + + EDocToException = class(Exception); + ESheetIndexOutOfBounds = class(EDocToException); + EInvalidParameterCombination = class(EDocToException); + +implementation + +end. diff --git a/src/ExtraFiles.res b/src/ExtraFiles.res index c334f37..c93bc94 100644 Binary files a/src/ExtraFiles.res and b/src/ExtraFiles.res differ diff --git a/src/MainUtils.pas b/src/MainUtils.pas index 1902b10..f793e98 100644 --- a/src/MainUtils.pas +++ b/src/MainUtils.pas @@ -9,17 +9,17 @@ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -Intereting article +Interesting article https://support.microsoft.com/en-gb/topic/considerations-for-server-side-automation-of-office-48bcfe93-8a89-47f1-0bce-017433ad79e2 ****************************************************************) interface uses classes, Windows, sysutils, ActiveX, ComObj, WinINet, Variants, iduri, - Types, ResourceUtils, StrUtils, + Types, ResourceUtils, StrUtils, DocToExceptions, PathUtils, ShellAPI, datamodssl, Word_TLB_Constants; Const VERBOSE = 10; - DEBUG = 9; + DEBUG = 9; HELP = 8; CHATTY = 5; STANDARD = 2; @@ -33,8 +33,8 @@ interface MSVISIO = 4; - DOCTO_VERSION = '1.15.45'; // dont use 0x - choco needs incrementing versions. - DOCTO_VERSION_NOTE = ' x64 Release '; + DOCTO_VERSION = '1.16.45'; // dont use 0x - choco needs incrementing versions. + DOCTO_VERSION_NOTE = ' (Test Version XLS Multisheet B) '; type @@ -71,6 +71,9 @@ TDocumentConverter = class FKeepIRM: boolean; FDocStructureTags: boolean; FBitmapMissingFonts: boolean; + fSelectedSheets: TStrings; + + procedure SetCompatibilityMode(const Value: Integer); @@ -95,6 +98,7 @@ TDocumentConverter = class procedure SetKeepIRM(const Value: boolean); procedure SetDocStructureTags(const Value: boolean); procedure SetBitmapMissingFonts(const Value: boolean); + procedure Setsheets(const Value: TStrings); protected @@ -141,6 +145,10 @@ TDocumentConverter = class FOutputIsFile: Boolean; FOutputIsDir: Boolean; + + fOutputFiles : TStrings; + fSelectedSheets_All : boolean; + procedure SetInputFile(const Value: String); procedure SetOutputFile(const Value: String); procedure SetOutputFileFormat(const Value: Integer); @@ -184,6 +192,8 @@ TDocumentConverter = class property pdfPrintToPage : integer read FpdfPrintToPage; property useISO190051 : boolean read FuseISO190051; property pdfOptimizeFor : integer read fpdfOptimizeFor write fpdfOptimizeFor; + property SelectedSheets : TStrings read fSelectedSheets write Setsheets; + property ExportMarkup : integer read fExportMarkup; property IncludeDocProps : boolean read FIncludeDocProps write SetIncludeDocProps; @@ -218,6 +228,7 @@ TDocumentConverter = class function CheckShouldIgnore(DocumentPath : String): Boolean; + public Constructor Create(); @@ -518,6 +529,9 @@ constructor TDocumentConverter.Create; FBitmapMissingFonts := true; FInputFiles := TStringList.Create; fDontUseAutoVBA := true; + fSelectedSheets := TStringList.Create; + fSelectedSheets_All := false; + fOutputFiles := TStringlist.Create; end; @@ -539,6 +553,7 @@ destructor TDocumentConverter.Destroy; FInputFiles.Free; + fSelectedSheets.Free; if assigned(FNetHandle) then begin @@ -674,7 +689,8 @@ function TDocumentConverter.Execute: string; if ConversionInfo.Successful then begin - logInfo('File Converted: ' + ConversionInfo.OutputFile); + // logInfo('File Converted: ' + ConversionInfo.OutputFile); + logInfo('Files Converted: ' + fOutputFiles.Text); // Check if file needs to be deleted. if RemoveFileOnConvert then @@ -748,12 +764,41 @@ function TDocumentConverter.Execute: string; end; end; + on E: ENotImplemented do + begin + ErrorMessage := StringReplace(E.Message,#13,'--',[rfReplaceAll]); + if (HaltOnWordError) then + begin + LogError( FileToConvert ); + HaltWithError(301,E.ClassName + ' ' + ErrorMessage ); + end + else + begin + LogError(E.ClassName + ' ' + ErrorMessage + ' ' + FileToConvert + ':' + FileToCreate); + + end; + end; + on E: EDocToException do + begin + ErrorMessage := StringReplace(E.Message,#13,'--',[rfReplaceAll]); + if (HaltOnWordError) then + begin + LogError( FileToConvert ); + HaltWithError(210,E.ClassName + ' ' + ErrorMessage ); + end + else + begin + LogError(E.ClassName + ' ' + ErrorMessage + ' ' + FileToConvert + ':' + FileToCreate); + + end; + end; on E: Exception do begin ErrorMessage := StringReplace(E.Message,#13,'--',[rfReplaceAll]); if (HaltOnWordError) then begin - HaltWithError(220,E.ClassName + ' ' + ErrorMessage + ' ' + FileToConvert + ':' + FileToCreate); + LogError( FileToConvert ); + HaltWithError(220,E.ClassName + ' ' + ErrorMessage ); end else begin @@ -1243,6 +1288,20 @@ procedure TDocumentConverter.LoadConfig(Params: TStrings); HaltWithConfigError(205,'Invalid value for --PDF-OPTIMIZEFOR :' + value); end; END + else if (id = '--SHEETS') then + begin + fSelectedSheets.DelimitedText := value; + if fSelectedSheets.Count = 0 then + begin + HaltWithConfigError(205,'Expecting > 0 selected sheets: ' + value); + end; + end + else if (id = '--ALLSHEETS') then + begin + fSelectedSheets_All := true; + + dec(iParam); + end else if (id = '--EXPORTMARKUP') then begin if (WordConstants.Exists(value)) then @@ -1275,6 +1334,7 @@ procedure TDocumentConverter.LoadConfig(Params: TStrings); FBitmapMissingFonts := false; dec(iParam); end + else if (id = '-W') or (id = '--WEBHOOK') then begin @@ -1287,15 +1347,12 @@ procedure TDocumentConverter.LoadConfig(Params: TStrings); end else if (id = '--ENABLE-MACROAUTORUN') or - (id = '--ENABLE-WORDVBAAUTO') + (id = '--ENABLE-WORDVBAAUTO') or + (id = '--ENABLE-XLVBAAUTO') then begin fDontUseAutoVBA := false; - if (OfficeAppName <> 'Word')then - begin - // Excel Application.EnableEvents = False - // HaltWithError(301,'Parameter ' + id + ' not Implemented for ' + OfficeAppName ); - end; + end else if (id = '-X') or @@ -1642,6 +1699,8 @@ procedure TDocumentConverter.WriteOfficeAppVersion(Version: String); LogDebug('Writing Version to File:' + ConfigFileName,VERBOSE); end; + + procedure TDocumentConverter.SetBitmapMissingFonts(const Value: boolean); begin FBitmapMissingFonts := Value; @@ -1851,6 +1910,11 @@ procedure TDocumentConverter.SetRemoveFileOnConvert(const Value: boolean); +procedure TDocumentConverter.Setsheets(const Value: TStrings); +begin + fSelectedSheets := Value; +end; + procedure TDocumentConverter.SetSkipDocsWithTOC(const Value: Boolean); begin FSkipDocsWithTOC := Value; diff --git a/src/docto.dpr b/src/docto.dpr index a880827..5b540d7 100644 --- a/src/docto.dpr +++ b/src/docto.dpr @@ -35,7 +35,9 @@ uses Excel_TLB_Constants in 'Excel_TLB_Constants.pas', PowerPoint_TLB_Constants in 'PowerPoint_TLB_Constants.pas', VisioUtils in 'VisioUtils.pas', - Visio_TLB in 'Visio_TLB.pas'; + Visio_TLB in 'Visio_TLB.pas', + DynamicFileNameGenerator in 'shared\DynamicFileNameGenerator.pas', + DocToExceptions in 'Exceptions\DocToExceptions.pas'; var i, Converter : integer; diff --git a/src/docto.dproj b/src/docto.dproj index cfaf3d7..a00d2dc 100644 --- a/src/docto.dproj +++ b/src/docto.dproj @@ -226,6 +226,7 @@ 1033 CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=0.3.2.15;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= (None) + -XL -f "Book1WithMacros.xlsm" -o c:/DriveETemp/sheet222macdro.pdf -t xlPDF -l 10 --enable-xlvbaauto ../exe @@ -297,6 +298,8 @@ + + @@ -387,56 +390,80 @@ True - + .\ true - + + + docto.exe + true + + + .\ true - + .\ true - + + + .\ + true + + + + + .\ + true + + + .\ true + + + .\ + true + + docto.exe true - - + + .\ true - + .\ true - + .\ true - - + + .\ true @@ -453,8 +480,8 @@ true - - + + .\ true @@ -465,8 +492,14 @@ true - - + + + .\ + true + + + + .\ true @@ -477,6 +510,12 @@ true + + + .\ + true + + .\ @@ -495,12 +534,31 @@ true - - + + + .\ true - + + + .\ + true + + + + + .\ + true + + + + + docto.exe + true + + + .\ true @@ -517,6 +575,197 @@ true + + + .\ + true + + + + + .\ + true + + + + + .\ + true + + + + + .\ + true + + + + + .\ + true + + + + + .\ + true + + + + + .\ + true + + + + + .\ + true + + + + + .\ + true + + + + + .\ + true + + + + + .\ + true + + + + + .\ + true + + + + + .\ + true + + + + + .\ + true + + + + + .\ + true + + + + + .\ + true + + + + + .\ + true + + + + + .\ + true + + + + + .\ + true + + + + + .\ + true + + + + + .\ + true + + + + + .\ + true + + + + + .\ + true + + + + + .\ + true + + + + + .\ + true + + + + + .\ + true + + + + + .\ + true + + + + + true + + + + + .\ + true + + + + + .\ + true + + + + + .\ + true + + + + + .\ + true + + .\ @@ -529,12 +778,30 @@ true + + + .\ + true + + .\ true + + + .\ + true + + + + + .\ + true + + .\ diff --git a/src/docto.dproj.local b/src/docto.dproj.local index 77c4153..ea67e01 100644 --- a/src/docto.dproj.local +++ b/src/docto.dproj.local @@ -6,12 +6,18 @@ 2021/12/02 22:25:13.000.761,=D:\Development\GitHub\DocTo\src\New1.bat 2021/12/02 22:25:32.000.527,D:\Development\GitHub\DocTo\src\New1.bat=D:\Development\GitHub\DocTo\test\TestDocTo_Quiet.bat 2023/09/11 17:39:20.000.384,=C:\Development\github\docto\src\res\xlsFormats.txt + 2025/11/18 17:53:47.000.458,=C:\Development\github\docto\src\Unit1.pas + 2025/11/18 17:54:21.000.396,C:\Development\github\docto\src\shared\DynamicFileNameGenerator.pas=C:\Development\github\docto\src\Unit1.pas + 2025/11/19 14:43:02.000.361,=C:\Development\github\docto\src\Unit1.pas + 2025/11/19 14:43:31.000.175,C:\Development\github\docto\src\Exceptions\DocToExceptions.pas=C:\Development\github\docto\src\Unit1.pas + + @@ -31,6 +37,8 @@ + + diff --git a/src/res/HelpLog.txt b/src/res/HelpLog.txt index 6ecb041..eecaa26 100644 --- a/src/res/HelpLog.txt +++ b/src/res/HelpLog.txt @@ -112,10 +112,15 @@ Long Parameters: --PDF-no-BitmapMissingFonts Do not bitmap missing fonts, fonts will be substituted. --use-ISO190051 - Create PDF to the ISO 19005-1 standard. + Create PDF to the ISO 19005-1 standard. + --enable-macroautorun --enable-wordvbaauto - By Default any autorun vba will not run, use this parameter if you wish vba to Autorun. Word Only. - + --enable-xlvbaauto + By Default any autorun vba will not run, use this parameter if you wish vba to Autorun. Word / Excel Only. + --sheets + Select which sheets to save. Can be comma seperated list of sheet names or indexes. Excel Only. PDF Only + --allsheets + If converting to CSV default behaviour is to convert first sheet. This will convert all with appropriate names. PDF, CSV only Experimental: @@ -132,6 +137,7 @@ ERROR CODES: 203 : Unknown switch in command 204 : Input File does not exist 205 : Invalid Parameter Value +210 : DocTo Error 220 : Word or COM Error 221 : Word not Installed 301 : Not Implemented diff --git a/src/shared/DynamicFileNameGenerator.pas b/src/shared/DynamicFileNameGenerator.pas new file mode 100644 index 0000000..5c808f6 --- /dev/null +++ b/src/shared/DynamicFileNameGenerator.pas @@ -0,0 +1,87 @@ +unit DynamicFileNameGenerator; + +interface + +uses Classes, MainUtils, ResourceUtils, ActiveX, ComObj, WinINet, Variants, sysutils, Types, StrUtils; + +type + +TDynamicFileNameGenerator = Class(TObject) + private + FIgnoreTag: Boolean; + procedure SetIgnoreTag(const Value: Boolean); +protected + + fOrigionalFilename : string; + dynamicoutputDir :string; + dynamicoutputFile :string; + dynamicoutputExt :string; + fFilenameParts : TStringList; + procedure Deconstruct(); +public + Constructor Create(FileName: String); + function Generate(tag: String ) : String; + function SafeFileName(FileName: String ) : String; + property IgnoreTag : Boolean read FIgnoreTag write SetIgnoreTag; +End; + + +implementation + +{ TDynamicFileNameGenerator } + +constructor TDynamicFileNameGenerator.Create(FileName: String); +begin + + FIgnoreTag := false; + fOrigionalFilename := Filename; + self.Deconstruct; +end; + +procedure TDynamicFileNameGenerator.Deconstruct; + + +begin + dynamicoutputDir := ExtractFilePath(fOrigionalFilename); // includes last \ + dynamicoutputFile := ChangefileExt ( ExtractFileName(fOrigionalFilename),''); + dynamicoutputExt := ExtractFileExt(fOrigionalFilename); + + +end; + +function TDynamicFileNameGenerator.Generate(tag: String): String; +var +dynamicFileName : String; +dynamicOutputFilename : string; + +begin + dynamicFileName := SafeFileName(tag); + if FIgnoreTag then + begin + result := fOrigionalFilename; + end else + begin + result := dynamicoutputDir + dynamicoutputFile + '_(' + tag + ')' + dynamicoutputExt; + end; + +end; + +function TDynamicFileNameGenerator.SafeFileName(FileName: String): String; +begin + Filename := StringReplace(Filename,'&','_',[rfReplaceAll,rfIgnoreCase]); + Filename := StringReplace(Filename,'/','_',[rfReplaceAll,rfIgnoreCase]); + Filename := StringReplace(Filename,'\\','_',[rfReplaceAll,rfIgnoreCase]); + Filename := StringReplace(Filename,'<','_',[rfReplaceAll,rfIgnoreCase]); + Filename := StringReplace(Filename,'>','_',[rfReplaceAll,rfIgnoreCase]); + Filename := StringReplace(Filename,':','_',[rfReplaceAll,rfIgnoreCase]); + Filename := StringReplace(Filename,'?','_',[rfReplaceAll,rfIgnoreCase]); + Filename := StringReplace(Filename,'*','_',[rfReplaceAll,rfIgnoreCase]); + result := Filename; +end; + +procedure TDynamicFileNameGenerator.SetIgnoreTag(const Value: Boolean); +begin + FIgnoreTag := Value; +end; + +end. diff --git a/test/inputfilesxl/Book1 Single Sheet.xlsx b/test/inputfilesxl/Book1 Single Sheet.xlsx new file mode 100644 index 0000000..591c871 Binary files /dev/null and b/test/inputfilesxl/Book1 Single Sheet.xlsx differ diff --git a/test/inputfilesxl/Book1WithMacros.xlsm b/test/inputfilesxl/Book1WithMacros.xlsm index e71827a..f94a4ac 100644 Binary files a/test/inputfilesxl/Book1WithMacros.xlsm and b/test/inputfilesxl/Book1WithMacros.xlsm differ diff --git a/test/inputfilesxl/Week 1 Test.xls b/test/inputfilesxl/Week 1 Test.xls index a10f1d6..0abb076 100644 Binary files a/test/inputfilesxl/Week 1 Test.xls and b/test/inputfilesxl/Week 1 Test.xls differ diff --git a/test/inputfilesxl/wk1test-MultiSheet.xls b/test/inputfilesxl/wk1test-MultiSheet.xls new file mode 100644 index 0000000..129dd4c Binary files /dev/null and b/test/inputfilesxl/wk1test-MultiSheet.xls differ diff --git a/test/inputfilesxl/wk1test.xls b/test/inputfilesxl/wk1test.xls new file mode 100644 index 0000000..eaca836 Binary files /dev/null and b/test/inputfilesxl/wk1test.xls differ diff --git a/test/testxlsfile.bat b/test/testxlsfile.bat index d5bd189..9ee0855 100644 --- a/test/testxlsfile.bat +++ b/test/testxlsfile.bat @@ -1,2 +1,2 @@ REM Load csv files convert to xls -"../exe/32/docto.exe" -XL -f "C:\dev\github\docto\test\inputfilesxl\Week 1 Test.xls" -o "C:\dev\github\docto\test\GeneratedFiles" -T xlPDF -l 10 \ No newline at end of file +"../exe/32/docto.exe" -XL -f "C:\dev\github\docto\test\inputfilesxl\Week 1 Test.xls" -o "C:\dev\github\docto\test\GeneratedFiles" -T xlPDF -l 10 diff --git a/test/testxlspass.bat b/test/testxlspass.bat index 5084bab..aab0e29 100644 --- a/test/testxlspass.bat +++ b/test/testxlspass.bat @@ -1,2 +1,3 @@ REM Load csv files convert to xls -"../exe/32/docto.exe" -XL -f "C:\dev\github\docto\test\inputfilesxl\Book1 with password.xls" -o "C:\dev\github\docto\test\GeneratedFiles" -T xlpdf -l 10 \ No newline at end of file +REM will skip password protected file +"../exe/32/docto.exe" -XL -f "C:\development\github\docto\test\inputfilesxl\Book1 with password.xls" -o "C:\development\github\docto\test\GeneratedFiles" -T xlpdf -l 10 \ No newline at end of file