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  }