Teknisk artikel

Slå ihop PDF-formulär i Delphi: regler för dubblettfält

PDF Library for Delphi slår ihop två AcroForm-dokument med en explicit policy för fält som delar namn. MergeDocumentEx tar källdokumentets identifierare och en av tre strategier: dfsReject vägrar sammanslagningen, dfsMerge behåller det delade namnet och synkroniserar värden, och dfsAutoNumber döper om de inkommande fälten deterministiskt. Namnskanningen sker innan några objektnummer förskjuts, så en avvisad sammanslagning lämnar båda dokumenten fullt användbara

Alla som satt ihop ett PDF-ansökningspaket har stött på detta. Tre formulär, vart och ett med ett fält kallat Signature eller Date eller Total, slås ihop till en fil. I ett AcroForm är det fullständigt kvalificerade fältnamnet fältets identitet, så två fält med samma namn är inte alls två fält: att fylla i det ena fyller i det andra, och en signatur applicerad på det ena täcker en omfattning ingen avsåg

Varför avgörs namnkollisionen innan sammanslagningen?

Den äldre MergeDocument slår ihop de två AcroForm-rotfältsarrayerna och erbjuder inget val. Värre är att när resultatet är oanvändbart sker upptäckten efter att objektnummer har omnumrerats och sidträd sytts ihop, vilket lämnar anroparen med ett dokument i ett tillstånd ingetdera originalet befann sig i

MergeDocumentEx vänder på ordningen. Den samlar in namnen på fälten på toppnivå från båda dokumenten, jämför dem, och tillämpar strategin innan något flyttas. En avvisning är därför en ren no-op: måldokumentet är orört, källdokumentet är orört, och båda förblir öppna och användbara, vilket sammanslagningstestet verifierar genom att läsa ut ett fältvärde från källan igen efter en avvisad sammanslagning

Jämförelsen använder en ordnad, skiftlägeskänslig namnmängd, så kostnaden är proportionell mot det kombinerade fältantalet gånger en logaritmisk faktor snarare än mot produkten av de två antalen. Skiftlägeskänslighet är det korrekta valet här eftersom PDF-fältnamn är skiftlägeskänsliga; att utjämna dem skulle slå ihop fält specifikationen behandlar som distinkta

De tre strategierna, och när vardera är rätt

dfsReject är strategin för automatiserade pipelines som inte får producera tvetydiga dokument. Sammanslagningen returnerar noll och LastErrorCode rapporterar 705, en dedikerad kod så att dubblettnamn kan särskiljas från varje annat sammanslagningsfel och dirigeras till en specifik åtgärd, vanligtvis omdöpning av fält uppströms

dfsMerge behåller det delade namnet medvetet och synkroniserar mål- och standardvärdet in i källfältet, så en konform visare behandlar de flera widgetarna som ett logiskt namngivet fält, vilket är standardbeteende för AcroForm för ett fält med flera widgetannoteringar. Vad den inte gör är att slå ihop olika fältordböcker till ett enda objekt. Varje fält behåller sin egen sidassociation, sitt utseende och sina åtgärder, eftersom att kollapsa dem tyst skulle kasta bort formatering och beteende som hör till det inkommande dokumentet

dfsAutoNumber döper om inkommande dubbletter genom att lägga till ett numeriskt suffix som börjar på _2 och tar det första lediga. Resultatet är reproducerbart: det beror bara på de närvarande namnen, aldrig på fältobjektnummer, så att slå ihop samma par av dokument två gånger ger samma namn båda gångerna. Den egenskapen spelar roll när nedströmskod, en FDF-import eller en databasmappning refererar till fält efter namn

uses
  PDFlibrary;

var
  Lib: TPDFlib;
  TargetDoc, SourceDoc: Integer;
begin
  Lib := TPDFlib.Create;
  try
    TargetDoc := Lib.SelectedDocument;
    Lib.LoadFromFile('application-part1.pdf', '');

    SourceDoc := Lib.NewDocument;
    Lib.LoadFromFile('application-part2.pdf', '');

    Lib.SelectDocument(TargetDoc);
    if Lib.MergeDocumentEx(SourceDoc, dfsReject) = 0 then
    begin
      if Lib.LastErrorCode = 705 then
      begin
        // Båda dokumenten är fortfarande intakta - försök igen med en policy
        Log('duplicate field names; retrying with auto-numbering');
        Lib.MergeDocumentEx(SourceDoc, dfsAutoNumber);
      end;
    end;

    Lib.SaveToFile('application-complete.pdf');
  finally
    Lib.Free;
  end;
end;

Notera tvåstegsmönstret i den koden, vilket bara är möjligt eftersom avvisning är icke-destruktiv. Prova den strikta policyn först, inspektera felet, bestäm sedan. Med en sammanslagning som misslyckas halvvägs skulle reservlösningen behöva börja om genom att läsa in båda filerna på nytt

Hur det sammanslagna formuläret ser ut efteråt

Under dfsMerge ger ett målfält vid namn Shared som bär "Target value" och ett källfält med samma namn två fält, båda namngivna Shared, båda rapporterande målvärdet, eftersom mål- och standardvärdet synkroniseras in i det inkommande fältet. Det är den avsedda semantiken för ett delat namn: ett logiskt fält, flera widgetar, ett värde

Under dfsAutoNumber ger samma indata Shared och Shared_2 som separata fält med oberoende värden. Välj mellan de två genom att ställa en enda fråga: ska att fylla i den ena kontrollen fylla i den andra? För ett undertecknarnamn upprepat på varje del av ett paket, ja, och dfsMerge är rätt. För en totalsumma som betyder något annat på varje formulär, nej, och autonumrering är rätt

// Efter en sammanslagning, räkna upp vad du faktiskt fick
for I := 1 to Lib.FormFieldCount do
  Log(Format('%d: %s = %s',
    [I, Lib.GetFormFieldTitle(I), Lib.GetFormFieldValue(I)]));

Praktiska noteringar för att sätta ihop formulärpaket

Den lyckade sammanslagningen konsumerar källdokumentet: det tas bort från bibliotekets dokumentlista, vilket är varför DocumentCount sjunker från två till ett. Fortsätt inte använda källidentifieraren efteråt. Dokumentversionen höjs till den högre av de två, så att slå ihop ett PDF 2.0-formulär i ett 1.7-dokument ger en 2.0-fil

Ordning spelar roll för namn. Att slå ihop A i B och att slå ihop B i A ger olika autonumrerade resultat, eftersom dokumentet som utför sammanslagningen behåller sina namn oförändrade. När ett paket har ett kanoniskt primärt formulär, gör det till målet

Signaturfält förtjänar sitt eget övervägande. En signatur som applicerades före en sammanslagning täcker bara den revision den signerade, så sammanslagning ogiltigförklarar den i praktisk mening genom att filen har ändrats sedan signering. Sätt ihop först och signera det sammansatta dokumentet, snarare än att slå ihop signerade delar. När sammanslagningen handlar om sidinnehåll snarare än formulär är den snabbare vägen som beskrivs i snabb PDF-sammanslagning med byte-referensförskjutning det bättre verktyget

Planera slutligen datasidan av paketet tillsammans med sammanslagningen. Om fältvärden kommer från ett externt system, avgör om det systemet adresserar fält efter namn innan du väljer autonumrering, eftersom Shared_2 inte kommer att matcha en mappning som förväntar sig Shared. Import- och exportformat täcks i FDF-, XFDF- och XFA-formulärdatautbyte, och fältnivåscriptbeteende som också kan påverkas av omdöpning täcks i interaktiva formuläråtgärder och JavaScript

Formulärsammanslagning, datautbyte och signering körs i samma bibliotek för Delphi, C++Builder och Free Pascal; den fullständiga funktionslistan finns på sidan för PDF Library for Delphi