Teknisk artikkel

Slå sammen PDF-skjemaer i Delphi: regler for duplikatfelt

PDF Library for Delphi slår sammen to AcroForm-dokumenter med en eksplisitt policy for felt som deler et navn. MergeDocumentEx tar imot kildedokumentidentifikatoren og én av tre strategier: dfsReject avviser sammenslåingen, dfsMerge beholder det delte navnet og synkroniserer verdier, og dfsAutoNumber omdøper de innkommende feltene deterministisk. Navneskanningen skjer før noen objektnumre skifter, så en avvist sammenslåing lar begge dokumentene forbli fullt brukbare

Alle som har satt sammen en PDF-søknadspakke, har støtt på dette. Tre skjemaer, hvert med et felt kalt Signature eller Date eller Total, blir slått sammen til én fil. I en AcroForm er det fullt kvalifiserte feltnavnet identiteten til feltet, så to felt med det samme navnet er ikke to felt i det hele tatt: å fylle ut det ene fyller ut det andre, og en signatur påført det ene dekker et omfang ingen hadde tenkt

Hvorfor avgjøres navnekollisjonen før sammenslåingen?

Den eldre MergeDocument setter sammen de to AcroForm-rot-feltarrayene og tilbyr ikke noe valg. Verre, når resultatet er ubrukelig, skjer oppdagelsen etter at objektnumre er omnummerert og sidetrær er sydd sammen, noe som etterlater den kallende parten med et dokument i en tilstand ingen av originalene var i

MergeDocumentEx snur rekkefølgen. Den samler inn toppnivå-feltnavnene fra begge dokumentene, sammenligner dem, og anvender strategien før noe flytter på seg. En avvisning er derfor en ren no-op: måldokumentet er urørt, kildedokumentet er urørt, og begge forblir åpne og brukbare, noe sammenslåingstesten verifiserer ved å lese en feltverdi tilbake ut av kilden etter en avvist sammenslåing

Sammenligningen bruker et ordnet, versalfølsomt navnesett, så kostnaden er proporsjonal med det kombinerte feltantallet ganger en logaritmisk faktor snarere enn med produktet av de to antallene. Versalfølsomhet er det riktige valget her fordi PDF-feltnavn er versalfølsomme; å folde dem ville slått sammen felt spesifikasjonen behandler som distinkte

De tre strategiene, og når hver er riktig

dfsReject er strategien for automatiserte pipeliner som ikke må produsere tvetydige dokumenter. Sammenslåingen returnerer null, og LastErrorCode rapporterer 705, en dedikert kode slik at duplikatnavn kan skilles fra enhver annen sammenslåingsfeil og rutes til en spesifikk løsning, som regel omdøping av felt tidligere i kjeden

dfsMerge beholder det delte navnet bevisst og synkroniserer målverdien og standardverdien inn i kildefeltet, slik at en samsvarende visningsklient behandler de flere widgetene som ett logisk navngitt felt, som er standard AcroForm-oppførsel for et felt med flere widget-annotasjoner. Det den ikke gjør, er å folde forskjellige feltordbøker inn i ett enkelt objekt. Hvert felt beholder sin egen sidetilknytning, utseende og handlinger, fordi å slå dem sammen stille ville kastet bort formatering og oppførsel som hører til det innkommende dokumentet

dfsAutoNumber omdøper innkommende duplikater ved å legge til et numerisk suffiks som starter på _2 og tar det første ledige. Resultatet er reproduserbart: det avhenger bare av navnene som er til stede, aldri av feltobjektnumre, så å slå sammen det samme dokumentparet to ganger gir de samme navnene begge gangene. Den egenskapen betyr noe når nedstrømskode, en FDF-import eller en databasekartlegging refererer til felt etter navn

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
        // Begge dokumentene er fremdeles intakte - prøv på nytt 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;

Legg merke til totrinnsmønsteret i den koden, som bare er mulig fordi avvisning er ikke-destruktivt. Prøv den strenge policyen først, inspiser feilen, avgjør deretter. Med en sammenslåing som feiler halvveis, måtte reserveløsningen startet på nytt ved å laste inn begge filene igjen

Hvordan det sammenslåtte skjemaet ser ut etterpå

Under dfsMerge produserer et målfelt kalt Shared som bærer «Target value», og et kildefelt med det samme navnet, to felt, begge kalt Shared, begge rapporterende målverdien, fordi målverdien og standardverdien synkroniseres inn i det innkommende feltet. Det er den tiltenkte semantikken for et delt navn: ett logisk felt, flere widgets, én verdi

Under dfsAutoNumber produserer den samme inndataen Shared og Shared_2 som separate felt med uavhengige verdier. Velg mellom de to ved å stille ett enkelt spørsmål: skal det å fylle ut den ene kontrollen fylle ut den andre? For et signatørnavn gjentatt på hver del av en pakke, ja, og dfsMerge er riktig. For en sum som betyr noe forskjellig på hvert skjema, nei, og autonummerering er riktig

// Etter en sammenslåing, ramse opp hva du faktisk fikk
for I := 1 to Lib.FormFieldCount do
  Log(Format('%d: %s = %s',
    [I, Lib.GetFormFieldTitle(I), Lib.GetFormFieldValue(I)]));

Praktiske merknader for å sette sammen skjemapakker

Den vellykkede sammenslåingen forbruker kildedokumentet: det fjernes fra bibliotekets dokumentliste, som er grunnen til at DocumentCount faller fra to til én. Ikke fortsett å bruke kildeidentifikatoren etterpå. Dokumentversjonen heves til den høyeste av de to, så å slå et PDF 2.0-skjema sammen med et 1.7-dokument gir en 2.0-fil

Rekkefølge betyr noe for navn. Å slå sammen A inn i B og å slå sammen B inn i A produserer forskjellige autonummererte resultater, siden dokumentet som utfører sammenslåingen, beholder sine navn uendret. Når en pakke har et kanonisk primærskjema, gjør det til målet

Signaturfelt fortjener sin egen vurdering. En signatur som ble påført før en sammenslåing, dekker bare revisjonen den signerte, så sammenslåing gjør den ugyldig i den praktiske forstand at filen har endret seg siden signering. Sett sammen først og signer det sammensatte dokumentet, fremfor å slå sammen signerte deler. Når sammenslåingen handler om sideinnhold snarere enn skjemaer, er den raskere banen beskrevet i rask PDF-sammenslåing med byte-referanseforskyvning det bedre verktøyet

Til slutt, planlegg datasiden av pakken sammen med sammenslåingen. Hvis feltverdier kommer fra et eksternt system, avgjør om det systemet adresserer felt etter navn før du velger autonummerering, fordi Shared_2 ikke vil matche en kartlegging som forventer Shared. Import- og eksportformater er dekket i FDF, XFDF og XFA skjemadatautveksling, og skripting på feltnivå-oppførsel som også kan påvirkes av omdøping, er dekket i interaktive skjemahandlinger og JavaScript

Skjemasammenslåing, datautveksling og signering kjører i det samme biblioteket for Delphi, C++Builder og Free Pascal; den komplette funksjonslisten finnes på PDF Library for Delphi-siden