Search code examples
xmldelphiimportdelphi-10.2-tokyopdf-form

How to import data from XML to PDF Form using Delphi ?


I have a PDF File filled with text, arrays, images etc.. but also with textfields.

I would like to know if and how is it possible to import a correctly formatted XML File in order to fill these textfields such as Name, Surname, Address...

I simply want to do something like this action from the menu in Acrobat Reader : Edition- Form options - Import data but using Delphi programming.

I presume that I will need to open the PDF and use a function to parse the XML file in order to fill the form but for now I didn't find any good advice on the web explaining how to do such thing.

I hope my question is correct and it will be possible for you to answer me.

Best regards.


Solution

  • Here is some example code which uses the form-filling COM objects of Acrobat to fill in Acrobat fields from XML data parsed using the XML Partner library from Turbopower. It's not compilable as is because of some external dependencies, and in need of a bit of refactoring, but it should give you the general idea. The Calibrate routine adds some markings to assist field placement.

    unit AcrobatXMLu;
    
    interface
    
    uses
      [...]
      Acrobat_tlb, //  main Acrobat COM wrapper
      AFORMAUTLib_TLB;  // Acrobat Forms COM objects
    
    type
      TGenerateFlag = (gfShowAcrobat, gfUseDefaultValues, gfGenerateTestData, gfAddCalibration, gfAlwaysFillCheckboxes);
      TGenerateFlags = set of TGenerateFlag;
    
    function CreateAcrobatFieldsInner(eStartNode: TxpElement;
      const Path: String; Flags : TGenerateFlags; const OutputFile : String): Boolean;
    
    implementation
    
    function CreateAcrobatFieldsInner(eStartNode: TxpElement;
      const Path: String; Flags : TGenerateFlags; const OutputFile : String): Boolean;
    var
      List : TxpNodeList;
      X : TxpNode;
      N,
      Max : Integer;
      E : TxpElement;
      Align,
      S : String;
      AddedFields : TStringlist;
    
      procedure CreateField(E : TxpElement);
      var
        FieldName,
        FieldType,
        MappedFieldType : String;
        PageNo : Integer;
        ALeft,
        ARight,
        ATop,
        ABottom,
        AWidth,
        AHeight : Single;
        S : String;
        Field : IField;  //  Acrobat form field
        IsMultiLine : Boolean;
      begin
         FieldName := E.GetAttribute('Name');
         if CompareText(FieldName, 'Name1') = 0 then
           NoOp;
         FieldType := LowerCase(E.GetAttribute('PdfFieldType'));
         MappedFieldType := FieldType;
         if CompareText(MappedFieldType, 'checkbox') = 0 then
           MappedFieldType := 'text';
         IsMultiLine := CompareText(MappedFieldType, 'memo') = 0;
         if (CompareText(MappedFieldType, 'Text') = 0) or (CompareText(MappedFieldType, 'Memo') = 0) then
           MappedFieldType := 'text';
    
         Align := LowerCase(E.GetAttribute('Align'));
         S := E.GetAttribute('Page');
         if S = '' then
           S := TxpElement(E.ParentNode).GetAttribute('Page');
         if S <> '' then
           PageNo := StrToInt(S) - 1
         else
           PageNo := 0;
         S := GetHeritableAttribute(E, 'XPos', 'FieldXPos');
         ALeft := StrToInt(S);
    
         ATop := E.GetAttributeInt('YPos');
    
         S := GetHeritableAttribute(E, 'Width', 'FieldWidth');
         if S <> '' then
           AWidth := StrToInt(S)
         else
           AWidth := 60;
         S := GetHeritableAttribute(E, 'Height', 'FieldHeight');
         if S <> '' then
           AHeight := StrToInt(S)
         else
           AHeight := 20;
         ARight := ALeft + AWidth;
         ABottom := ATop - AHeight;
    
         try
           Field := Acrobat.Fields.Add(FieldName, MappedFieldType, PageNo, ALeft, ATop, ARight, ABottom) as IField;
           if True or (AddedFields.IndexOf(FieldName) < 0) then begin
             if CompareText(MappedFieldType, 'text') = 0 then begin
               S := GetHeritableAttribute(E, 'TextFont', 'FieldTextFont');
               if S <> '' then
                 Field.Set_TextFont(S);
               S := GetHeritableAttribute(E, 'TextSize', 'FieldTextSize');
               if S <> '' then
                 if not (CompareText(S, 'Auto') = 0) then
                   Field.Set_TextSize(StrToInt(S));
               if IsMultiLine then
                 Field.Set_IsMultiline(True);
    
               S := E.GetAttribute('DefaultValue');
               S := StringReplace(S, #10, #10#13, [rfReplaceAll]);
               if S <> '' then begin
                 Field.Set_Value(S);
               end
               else begin
                 if CompareText(FieldType, 'checkbox') = 0 then begin
                   if gfAlwaysFillCheckboxes in Flags then
                     Field.Set_Value('X')
                 end
                 else begin
                   if gfGenerateTestData in Flags then
                     Field.Set_Value(Format('(%s)', [FieldName]));
                 end;
               end;
               if Align <> '' then
                 Field.Set_Alignment(Align);
             end
             else begin
               if CompareText(MappedFieldType, 'checkbox') = 0 then begin
               end;
             end;
           end;
           if AddedFields.IndexOf(FieldName) < 0 then
             AddedFields.Add(FieldName)
         except
           ShowMessage('Error adding field ' + FieldName);
         end;
      end;
    
      procedure Calibrate;
      var
        X,
        Y,
        N,
        M,
        Page : Integer;
        Field : IField;  // Acrobat form field
    
        procedure AddField(X, Y : Integer);
        var
          S : String;
          FieldName : String;
        begin
          if X < 40  then
            S := Format('Y:%d', [Y])
          else
            S := Format('X:%d', [X]);
          FieldName := Format('X%dY%d', [X, Y]);
          Field := Acrobat.Fields.Add(FieldName, 'text', Page, X, Y, X + 40, Y - 15) as IField;
          Field.Set_TextFont('Courier');
          Field.Set_TextSize(10);
          Field.Set_Value(S);
    
        end;
      begin
        for Page := 0 to ((Acrobat.AcroApp.GetActiveDoc as CAcroAVDoc).GetPDDoc as CAcroPDDoc).GetNumPages - 1 do begin
          for N := 1 to 15 do
            AddField(40 * N, 0);
          N := 15;
          for M := 0 to 60 do
            AddField(0, N * M);
        end;
      end;
    
    begin
      AddedFields := TStringlist.Create;
      AddedFields.Sorted := True;
      Result := False;
      try
        List := eStartNode.SelectNodes(Path);
        try
          Max := List.Length - 1;
          for N := 0 to Max do begin
            X := List.Item(N);
            if not (X is TxpElement) then Continue;
            E := TxpElement(X);
            S := E.GetAttribute('YPos');
            if S <> '' then
              CreateField(E);
          end;
          Result := True;
        finally
          List.Free;
        end;
        if gfAddCalibration in Flags then
          Calibrate;
        Acrobat.DocV.GetPDDoc.Save(PDSaveFull, OutputFile);
      finally
        AddedFields.Free;
      end;
    end;
    
    end.
    

    As written, this code adds the fields defined in the XML and fills them in as it goes along. Obvious;ly it is a trivial matter to fill in existing fields instead.

    Some of the 3rd party PDF libraries for Delphi may also be able to fill in Acrobat fields. Also the command-line library PDFtk can also fill in Acrobat fields and do other things the Acrobat COM objects cannot, like "flattening" a PDF form, which effectively merges the text in the fields into the host document and so no longer editable as a form. See https://www.pdflabs.com/docs/pdftk-man-page/ for more info.