github.com/pdfcpu/pdfcpu@v0.11.1/pkg/api/form.go (about) 1 /* 2 Copyright 2023 The pdfcpu Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package api 18 19 import ( 20 "bytes" 21 "encoding/csv" 22 "encoding/json" 23 "fmt" 24 "io" 25 "os" 26 "path/filepath" 27 "strconv" 28 "strings" 29 30 "github.com/pdfcpu/pdfcpu/pkg/log" 31 "github.com/pdfcpu/pdfcpu/pkg/pdfcpu/create" 32 "github.com/pdfcpu/pdfcpu/pkg/pdfcpu/form" 33 "github.com/pdfcpu/pdfcpu/pkg/pdfcpu/model" 34 "github.com/pdfcpu/pdfcpu/pkg/pdfcpu/types" 35 "github.com/pkg/errors" 36 ) 37 38 var ( 39 ErrNoFormData = errors.New("pdfcpu: missing form data") 40 ErrNoFormFieldsAffected = errors.New("pdfcpu: no form fields affected") 41 ErrInvalidCSV = errors.New("pdfcpu: invalid csv input file") 42 ErrInvalidJSON = errors.New("pdfcpu: invalid JSON encoding") 43 ) 44 45 // FormFields returns all form fields of rs. 46 func FormFields(rs io.ReadSeeker, conf *model.Configuration) ([]form.Field, error) { 47 if rs == nil { 48 return nil, errors.New("pdfcpu: FormFields: missing rs") 49 } 50 51 if conf == nil { 52 conf = model.NewDefaultConfiguration() 53 } 54 conf.Cmd = model.LISTFORMFIELDS 55 56 ctx, err := ReadValidateAndOptimize(rs, conf) 57 if err != nil { 58 return nil, err 59 } 60 61 fields, _, err := form.FormFields(ctx) 62 63 return fields, err 64 } 65 66 // RemoveFormFields deletes form fields in rs and writes the result to w. 67 func RemoveFormFields(rs io.ReadSeeker, w io.Writer, fieldIDsOrNames []string, conf *model.Configuration) error { 68 if rs == nil { 69 return errors.New("pdfcpu: RemoveFormFields: missing rs") 70 } 71 72 if conf == nil { 73 conf = model.NewDefaultConfiguration() 74 } 75 conf.Cmd = model.REMOVEFORMFIELDS 76 77 ctx, err := ReadValidateAndOptimize(rs, conf) 78 if err != nil { 79 return err 80 } 81 82 ok, err := form.RemoveFormFields(ctx, fieldIDsOrNames) 83 if err != nil { 84 return err 85 } 86 if !ok { 87 return ErrNoFormFieldsAffected 88 } 89 90 return Write(ctx, w, conf) 91 } 92 93 // RemoveFormFieldsFile deletes form fields in inFile and writes the result to outFile. 94 func RemoveFormFieldsFile(inFile, outFile string, fieldIDsOrNames []string, conf *model.Configuration) (err error) { 95 var f1, f2 *os.File 96 97 if f1, err = os.Open(inFile); err != nil { 98 return err 99 } 100 101 tmpFile := inFile + ".tmp" 102 if outFile != "" && inFile != outFile { 103 tmpFile = outFile 104 } 105 logWritingTo(outFile) 106 107 if f2, err = os.Create(tmpFile); err != nil { 108 f1.Close() 109 return err 110 } 111 112 defer func() { 113 if err != nil { 114 f2.Close() 115 f1.Close() 116 os.Remove(tmpFile) 117 return 118 } 119 if err = f2.Close(); err != nil { 120 return 121 } 122 if err = f1.Close(); err != nil { 123 return 124 } 125 if outFile == "" || inFile == outFile { 126 err = os.Rename(tmpFile, inFile) 127 } 128 }() 129 130 return RemoveFormFields(f1, f2, fieldIDsOrNames, conf) 131 } 132 133 // LockFormFields turns form fields in rs into read-only and writes the result to w. 134 func LockFormFields(rs io.ReadSeeker, w io.Writer, fieldIDsOrNames []string, conf *model.Configuration) error { 135 if rs == nil { 136 return errors.New("pdfcpu: LockFormFields: missing rs") 137 } 138 139 if conf == nil { 140 conf = model.NewDefaultConfiguration() 141 } 142 conf.Cmd = model.LOCKFORMFIELDS 143 144 ctx, err := ReadValidateAndOptimize(rs, conf) 145 if err != nil { 146 return err 147 } 148 149 ok, err := form.LockFormFields(ctx, fieldIDsOrNames) 150 if err != nil { 151 return err 152 } 153 if !ok { 154 return ErrNoFormFieldsAffected 155 } 156 157 return Write(ctx, w, conf) 158 } 159 160 // LockFormFieldsFile turns form fields of inFile into read-only and writes the result to outFile. 161 func LockFormFieldsFile(inFile, outFile string, fieldIDsOrNames []string, conf *model.Configuration) (err error) { 162 var f1, f2 *os.File 163 164 if f1, err = os.Open(inFile); err != nil { 165 return err 166 } 167 168 tmpFile := inFile + ".tmp" 169 if outFile != "" && inFile != outFile { 170 tmpFile = outFile 171 } 172 logWritingTo(outFile) 173 174 if f2, err = os.Create(tmpFile); err != nil { 175 f1.Close() 176 return err 177 } 178 179 defer func() { 180 if err != nil { 181 f2.Close() 182 f1.Close() 183 os.Remove(tmpFile) 184 return 185 } 186 if err = f2.Close(); err != nil { 187 return 188 } 189 if err = f1.Close(); err != nil { 190 return 191 } 192 if outFile == "" || inFile == outFile { 193 err = os.Rename(tmpFile, inFile) 194 } 195 }() 196 197 return LockFormFields(f1, f2, fieldIDsOrNames, conf) 198 } 199 200 // UnlockFormFields makess form fields in rs writeable and writes the result to w. 201 func UnlockFormFields(rs io.ReadSeeker, w io.Writer, fieldIDsOrNames []string, conf *model.Configuration) error { 202 if rs == nil { 203 return errors.New("pdfcpu: UnlockFormFields: missing rs") 204 } 205 206 if conf == nil { 207 conf = model.NewDefaultConfiguration() 208 } 209 conf.Cmd = model.UNLOCKFORMFIELDS 210 211 ctx, err := ReadValidateAndOptimize(rs, conf) 212 if err != nil { 213 return err 214 } 215 216 ok, err := form.UnlockFormFields(ctx, fieldIDsOrNames) 217 if err != nil { 218 return err 219 } 220 if !ok { 221 return ErrNoFormFieldsAffected 222 } 223 224 return Write(ctx, w, conf) 225 } 226 227 // UnlockFormFieldsFile makes form fields of inFile writeable and writes the result to outFile. 228 func UnlockFormFieldsFile(inFile, outFile string, fieldIDsOrNames []string, conf *model.Configuration) (err error) { 229 var f1, f2 *os.File 230 231 if f1, err = os.Open(inFile); err != nil { 232 return err 233 } 234 235 tmpFile := inFile + ".tmp" 236 if outFile != "" && inFile != outFile { 237 tmpFile = outFile 238 } 239 logWritingTo(outFile) 240 241 if f2, err = os.Create(tmpFile); err != nil { 242 f1.Close() 243 return err 244 } 245 246 defer func() { 247 if err != nil { 248 f2.Close() 249 f1.Close() 250 os.Remove(tmpFile) 251 return 252 } 253 if err = f2.Close(); err != nil { 254 return 255 } 256 if err = f1.Close(); err != nil { 257 return 258 } 259 if outFile == "" || inFile == outFile { 260 err = os.Rename(tmpFile, inFile) 261 } 262 }() 263 264 return UnlockFormFields(f1, f2, fieldIDsOrNames, conf) 265 } 266 267 // ResetFormFields resets form fields of rs and writes the result to w. 268 func ResetFormFields(rs io.ReadSeeker, w io.Writer, fieldIDsOrNames []string, conf *model.Configuration) error { 269 if rs == nil { 270 return errors.New("pdfcpu: ResetFormFields: missing rs") 271 } 272 273 if conf == nil { 274 conf = model.NewDefaultConfiguration() 275 } 276 conf.Cmd = model.RESETFORMFIELDS 277 278 ctx, err := ReadValidateAndOptimize(rs, conf) 279 if err != nil { 280 return err 281 } 282 283 ok, err := form.ResetFormFields(ctx, fieldIDsOrNames) 284 if err != nil { 285 return err 286 } 287 if !ok { 288 return ErrNoFormFieldsAffected 289 } 290 291 return Write(ctx, w, conf) 292 } 293 294 // ResetFormFieldsFile resets form fields of inFile and writes the result to outFile. 295 func ResetFormFieldsFile(inFile, outFile string, fieldIDsOrNames []string, conf *model.Configuration) (err error) { 296 var f1, f2 *os.File 297 298 if f1, err = os.Open(inFile); err != nil { 299 return err 300 } 301 302 tmpFile := inFile + ".tmp" 303 if outFile != "" && inFile != outFile { 304 tmpFile = outFile 305 } 306 logWritingTo(outFile) 307 308 if f2, err = os.Create(tmpFile); err != nil { 309 f1.Close() 310 return err 311 } 312 313 defer func() { 314 if err != nil { 315 f2.Close() 316 f1.Close() 317 os.Remove(tmpFile) 318 return 319 } 320 if err = f2.Close(); err != nil { 321 return 322 } 323 if err = f1.Close(); err != nil { 324 return 325 } 326 if outFile == "" || inFile == outFile { 327 err = os.Rename(tmpFile, inFile) 328 } 329 }() 330 331 return ResetFormFields(f1, f2, fieldIDsOrNames, conf) 332 } 333 334 // ExportForm extracts form data originating from source from rs. 335 func ExportForm(rs io.ReadSeeker, source string, conf *model.Configuration) (*form.FormGroup, error) { 336 if rs == nil { 337 return nil, errors.New("pdfcpu: ExportForm: missing rs") 338 } 339 340 if conf == nil { 341 conf = model.NewDefaultConfiguration() 342 } 343 conf.Cmd = model.EXPORTFORMFIELDS 344 345 ctx, err := ReadValidateAndOptimize(rs, conf) 346 if err != nil { 347 return nil, err 348 } 349 350 formGroup, ok, err := form.ExportForm(ctx.XRefTable, source) 351 if err != nil { 352 return nil, err 353 } 354 if !ok { 355 return nil, ErrNoFormFieldsAffected 356 } 357 358 return formGroup, nil 359 } 360 361 // ExportFormJSON extracts form data originating from source from rs and writes the result to w. 362 func ExportFormJSON(rs io.ReadSeeker, w io.Writer, source string, conf *model.Configuration) error { 363 if rs == nil { 364 return errors.New("pdfcpu: ExportFormJSON: missing rs") 365 } 366 367 if w == nil { 368 return errors.New("pdfcpu: ExportFormJSON: missing w") 369 } 370 371 if conf == nil { 372 conf = model.NewDefaultConfiguration() 373 } 374 conf.Cmd = model.EXPORTFORMFIELDS 375 376 ctx, err := ReadValidateAndOptimize(rs, conf) 377 if err != nil { 378 return err 379 } 380 381 ok, err := form.ExportFormJSON(ctx.XRefTable, source, w) 382 if err != nil { 383 return err 384 } 385 if !ok { 386 return ErrNoFormFieldsAffected 387 } 388 389 return nil 390 } 391 392 // ExportFormFile extracts form data from inFilePDF and writes the result to outFileJSON. 393 func ExportFormFile(inFilePDF, outFileJSON string, conf *model.Configuration) (err error) { 394 var f1, f2 *os.File 395 396 if f1, err = os.Open(inFilePDF); err != nil { 397 return err 398 } 399 400 if f2, err = os.Create(outFileJSON); err != nil { 401 f1.Close() 402 return err 403 } 404 logWritingTo(outFileJSON) 405 406 defer func() { 407 if err != nil { 408 f2.Close() 409 f1.Close() 410 return 411 } 412 if err = f2.Close(); err != nil { 413 return 414 } 415 if err = f1.Close(); err != nil { 416 return 417 } 418 }() 419 420 return ExportFormJSON(f1, f2, inFilePDF, conf) 421 } 422 423 func validateComboBoxValues(f form.Form) error { 424 for _, cb := range f.ComboBoxes { 425 if cb.Value == "" || cb.Editable { 426 continue 427 } 428 if len(cb.Options) > 0 { 429 if !types.MemberOf(cb.Value, cb.Options) { 430 i, err := strconv.Atoi(cb.Value) 431 if err == nil && i < len(cb.Options) { 432 return nil 433 } 434 return errors.Errorf("pdfcpu: fill field name: \"%s\" unknown value: \"%s\" - options: [%v]\n", cb.Name, cb.Value, strings.Join(cb.Options, ", ")) 435 } 436 } 437 } 438 return nil 439 } 440 441 func validateListBoxValues(f form.Form) error { 442 for _, lb := range f.ListBoxes { 443 if len(lb.Values) == 0 { 444 continue 445 } 446 if len(lb.Options) > 0 { 447 for _, v := range lb.Values { 448 if !types.MemberOf(v, lb.Options) { 449 i, err := strconv.Atoi(v) 450 if err == nil && i < len(lb.Options) { 451 return nil 452 } 453 return errors.Errorf("pdfcpu: fill field name: \"%s\" unknown value: \"%s\" - options: [%v]\n", lb.Name, v, strings.Join(lb.Options, ", ")) 454 } 455 } 456 } 457 } 458 return nil 459 } 460 461 func validateRadioButtonGroupValues(f form.Form) error { 462 for _, rbg := range f.RadioButtonGroups { 463 if rbg.Value == "" { 464 continue 465 } 466 if len(rbg.Options) > 0 { 467 if !types.MemberOf(rbg.Value, rbg.Options) { 468 i, err := strconv.Atoi(rbg.Value) 469 if err == nil && i < len(rbg.Options) { 470 return nil 471 } 472 return errors.Errorf("pdfcpu: fill field name: \"%s\" unknown value: \"%s\" - options: [%v]\n", rbg.Name, rbg.Value, strings.Join(rbg.Options, ", ")) 473 } 474 } 475 } 476 return nil 477 } 478 479 func validateOptionValues(f form.Form) error { 480 if err := validateRadioButtonGroupValues(f); err != nil { 481 return err 482 } 483 484 if err := validateComboBoxValues(f); err != nil { 485 return err 486 } 487 488 if err := validateListBoxValues(f); err != nil { 489 return err 490 } 491 492 return nil 493 } 494 495 func fillPostProc(ctx *model.Context, pp []*model.Page) error { 496 if _, _, err := create.UpdatePageTree(ctx, pp, nil); err != nil { 497 return err 498 } 499 500 return ValidateContext(ctx) 501 } 502 503 // FillForm populates the form rs with data from rd and writes the result to w. 504 func FillForm(rs io.ReadSeeker, rd io.Reader, w io.Writer, conf *model.Configuration) error { 505 if rs == nil { 506 return errors.New("pdfcpu: FillForm: missing rs") 507 } 508 509 if rd == nil { 510 return errors.New("pdfcpu: FillForm: missing rd") 511 } 512 513 if conf == nil { 514 conf = model.NewDefaultConfiguration() 515 } 516 conf.Cmd = model.FILLFORMFIELDS 517 518 ctx, err := ReadValidateAndOptimize(rs, conf) 519 if err != nil { 520 return err 521 } 522 523 // TODO not necessarily so 524 ctx.RemoveSignature() 525 526 var buf bytes.Buffer 527 if _, err := io.Copy(&buf, rd); err != nil { 528 return err 529 } 530 531 bb := buf.Bytes() 532 533 if !json.Valid(bb) { 534 return ErrInvalidJSON 535 } 536 537 formGroup := form.FormGroup{} 538 539 if err := json.Unmarshal(bb, &formGroup); err != nil { 540 return err 541 } 542 543 if len(formGroup.Forms) == 0 { 544 return ErrNoFormData 545 } 546 547 f := formGroup.Forms[0] 548 549 if err := validateOptionValues(f); err != nil { 550 return err 551 } 552 553 if log.CLIEnabled() { 554 log.CLI.Println("filling...") 555 } 556 557 ok, pp, err := form.FillForm(ctx, form.FillDetails(&f, nil), f.Pages, form.JSON) 558 if err != nil { 559 return err 560 } 561 if !ok { 562 return ErrNoFormFieldsAffected 563 } 564 565 if err := fillPostProc(ctx, pp); err != nil { 566 return err 567 } 568 569 return Write(ctx, w, conf) 570 } 571 572 // FillFormFile populates the form inFilePDF with data from inFileJSON and writes the result to outFilePDF. 573 func FillFormFile(inFilePDF, inFileJSON, outFilePDF string, conf *model.Configuration) (err error) { 574 var f0, f1, f2 *os.File 575 576 if f0, err = os.Open(inFileJSON); err != nil { 577 return err 578 } 579 580 if f1, err = os.Open(inFilePDF); err != nil { 581 f0.Close() 582 return err 583 } 584 rs := f1 585 586 tmpFile := inFilePDF + ".tmp" 587 if outFilePDF != "" && inFilePDF != outFilePDF { 588 tmpFile = outFilePDF 589 } 590 logWritingTo(outFilePDF) 591 592 if f2, err = os.Create(tmpFile); err != nil { 593 f1.Close() 594 f0.Close() 595 return err 596 } 597 598 defer func() { 599 if err != nil { 600 f2.Close() 601 f1.Close() 602 f0.Close() 603 os.Remove(tmpFile) 604 return 605 } 606 if err = f2.Close(); err != nil { 607 return 608 } 609 if err = f1.Close(); err != nil { 610 return 611 } 612 if err = f0.Close(); err != nil { 613 return 614 } 615 if outFilePDF == "" || inFilePDF == outFilePDF { 616 err = os.Rename(tmpFile, inFilePDF) 617 } 618 }() 619 620 return FillForm(rs, f0, f2, conf) 621 } 622 623 func parseFormGroup(rd io.Reader) (*form.FormGroup, error) { 624 formGroup := &form.FormGroup{} 625 626 var buf bytes.Buffer 627 if _, err := io.Copy(&buf, rd); err != nil { 628 return nil, err 629 } 630 631 bb := buf.Bytes() 632 633 if !json.Valid(bb) { 634 return nil, ErrInvalidJSON 635 } 636 637 if err := json.Unmarshal(bb, formGroup); err != nil { 638 return nil, err 639 } 640 641 if len(formGroup.Forms) == 0 { 642 return nil, ErrNoFormData 643 } 644 645 return formGroup, nil 646 } 647 648 func mergeForms(outDir, fileName string, outFiles []string, conf *model.Configuration) error { 649 outFile := filepath.Join(outDir, fileName+".pdf") 650 if err := MergeCreateFile(outFiles, outFile, false, conf); err != nil { 651 return err 652 } 653 if log.CLIEnabled() { 654 log.CLI.Println("cleaning up...") 655 } 656 for _, fn := range outFiles { 657 if err := os.Remove(fn); err != nil { 658 return err 659 } 660 } 661 return nil 662 } 663 664 func multiFillFormJSON(inFilePDF string, rd io.Reader, outDir, fileName string, merge bool, conf *model.Configuration) error { 665 formGroup, err := parseFormGroup(rd) 666 if err != nil { 667 return err 668 } 669 670 var outFiles []string 671 672 for i, f := range formGroup.Forms { 673 674 rs, err := os.Open(inFilePDF) 675 if err != nil { 676 return err 677 } 678 defer rs.Close() 679 680 ctx, err := ReadValidateAndOptimize(rs, conf) 681 if err != nil { 682 return err 683 } 684 685 ok, pp, err := form.FillForm(ctx, form.FillDetails(&f, nil), f.Pages, form.JSON) 686 if err != nil { 687 return err 688 } 689 if !ok { 690 return ErrNoFormFieldsAffected 691 } 692 693 if _, _, err := create.UpdatePageTree(ctx, pp, nil); err != nil { 694 return err 695 } 696 697 if conf.PostProcessValidate { 698 if err = ValidateContext(ctx); err != nil { 699 return err 700 } 701 } 702 703 outFile := filepath.Join(outDir, fmt.Sprintf("%s_%02d.pdf", fileName, i+1)) 704 if log.CLIEnabled() { 705 log.CLI.Printf("writing %s\n", outFile) 706 } 707 708 if err := WriteContextFile(ctx, outFile); err != nil { 709 return err 710 } 711 outFiles = append(outFiles, outFile) 712 } 713 714 if merge { 715 if err := mergeForms(outDir, fileName, outFiles, conf); err != nil { 716 return err 717 } 718 } 719 720 return nil 721 } 722 723 func parseCSVLines(rd io.Reader) ([][]string, error) { 724 // Does NOT do any fieldtype checking! 725 // Don't use unless you know your form anatomy inside out! 726 727 // The first row is expected to hold the fieldIDs/fieldNames of the fields to be filled - the only form metadata needed for this usecase. 728 // The remaining rows are the corresponding data tuples. 729 // Each row results in one separate PDF form written to outDir. 730 731 // fieldName1 fieldName2 fieldName3 fieldName4 732 // John Doe 1.1.2000 male 733 // Jane Doe 1.1.2000 female 734 // Jacky Doe 1.1.2000 non-binary 735 736 csvLines, err := csv.NewReader(rd).ReadAll() 737 if err != nil { 738 return nil, err 739 } 740 741 if len(csvLines) < 2 { 742 return nil, ErrInvalidCSV 743 } 744 745 fieldNames := csvLines[0] 746 if len(fieldNames) == 0 { 747 return nil, ErrInvalidCSV 748 } 749 750 return csvLines, nil 751 } 752 753 func multiFillFormCSV(inFilePDF string, rd io.Reader, outDir, fileName string, merge bool, conf *model.Configuration) error { 754 csvLines, err := parseCSVLines(rd) 755 if err != nil { 756 return err 757 } 758 759 fieldNames := csvLines[0] 760 var outFiles []string 761 762 for i, formRecord := range csvLines[1:] { 763 764 f, err := os.Open(inFilePDF) 765 if err != nil { 766 return err 767 } 768 defer f.Close() 769 770 ctx, err := ReadValidateAndOptimize(f, conf) 771 if err != nil { 772 return err 773 } 774 775 fieldMap, imgPageMap, err := form.FieldMap(fieldNames, formRecord) 776 if err != nil { 777 return err 778 } 779 780 ok, pp, err := form.FillForm(ctx, form.FillDetails(nil, fieldMap), imgPageMap, form.CSV) 781 if err != nil { 782 return err 783 } 784 if !ok { 785 return ErrNoFormFieldsAffected 786 } 787 788 if _, _, err := create.UpdatePageTree(ctx, pp, nil); err != nil { 789 return err 790 } 791 792 if conf.PostProcessValidate { 793 if err = ValidateContext(ctx); err != nil { 794 return err 795 } 796 } 797 798 outFile := filepath.Join(outDir, fmt.Sprintf("%s_%02d.pdf", fileName, i+1)) 799 logWritingTo(outFile) 800 if err := WriteContextFile(ctx, outFile); err != nil { 801 return err 802 } 803 outFiles = append(outFiles, outFile) 804 } 805 806 if merge { 807 if err := mergeForms(outDir, fileName, outFiles, conf); err != nil { 808 return err 809 } 810 } 811 812 return nil 813 } 814 815 // MultiFillForm populates multiples instances of inFilePDF's form with data from rd and writes the result to outDir. 816 func MultiFillForm(inFilePDF string, rd io.Reader, outDir, fileName string, format form.DataFormat, merge bool, conf *model.Configuration) error { 817 if conf == nil { 818 conf = model.NewDefaultConfiguration() 819 } 820 conf.Cmd = model.MULTIFILLFORMFIELDS 821 822 fileName = strings.TrimSuffix(filepath.Base(fileName), ".pdf") 823 824 if format == form.JSON { 825 return multiFillFormJSON(inFilePDF, rd, outDir, fileName, merge, conf) 826 } 827 828 return multiFillFormCSV(inFilePDF, rd, outDir, fileName, merge, conf) 829 } 830 831 // MultiFillFormFile populates multiples instances of inFilePDFs form with data from inFileData and writes the result to outDir. 832 func MultiFillFormFile(inFilePDF, inFileData, outDir, outFilePDF string, merge bool, conf *model.Configuration) (err error) { 833 format := form.JSON 834 if strings.HasSuffix(strings.ToLower(inFileData), ".csv") { 835 format = form.CSV 836 } 837 838 var f *os.File 839 840 if f, err = os.Open(inFileData); err != nil { 841 return err 842 } 843 844 defer func() { 845 cerr := f.Close() 846 if err == nil { 847 err = cerr 848 } 849 }() 850 851 s := "JSON" 852 if format == form.CSV { 853 s = "CSV" 854 } 855 856 outFileBase := filepath.Base(outFilePDF) 857 858 if log.CLIEnabled() { 859 log.CLI.Printf("filling multiple forms via %s based on %s data from %s into %s/%s ...\n", inFilePDF, s, inFileData, outDir, outFileBase) 860 } 861 862 return MultiFillForm(inFilePDF, f, outDir, outFileBase, format, merge, conf) 863 }