Technisch artikel

PDF-formulieren samenvoegen in Delphi: regels voor dubbele velden

PDF Library for Delphi voegt twee AcroForm-documenten samen met een expliciet beleid voor velden die een naam delen. MergeDocumentEx neemt de bron-document-ID en een van drie strategieën: dfsReject weigert de samenvoeging, dfsMerge behoudt de gedeelde naam en synchroniseert waarden, en dfsAutoNumber hernoemt de binnenkomende velden deterministisch. De naamscan gebeurt voordat objectnummers verschuiven, dus een geweigerde samenvoeging laat beide documenten volledig bruikbaar

Iedereen die ooit een PDF-aanvraagpakket heeft samengesteld, is dit tegengekomen. Drie formulieren, elk met een veld genaamd Signature, Date of Total, worden samengevoegd tot één bestand. In een AcroForm is de volledig gekwalificeerde veldnaam de identiteit van het veld, dus twee velden met dezelfde naam zijn helemaal geen twee velden: de ene invullen vult de andere in, en een handtekening toegepast op de ene dekt een reikwijdte af die niemand bedoelde

Waarom wordt de naambotsing vóór de samenvoeging beslist?

De oudere MergeDocument voegt de twee AcroForm-root-veldarrays samen en biedt geen keuze. Erger nog: wanneer het resultaat onbruikbaar is, gebeurt de ontdekking nadat objectnummers zijn hernummerd en paginabomen aan elkaar zijn genaaid, waardoor de aanroeper achterblijft met een document in een staat waarin geen van beide originelen zich bevond

MergeDocumentEx keert de volgorde om. Het verzamelt de veldnamen op het hoogste niveau uit beide documenten, vergelijkt ze, en past de strategie toe voordat er iets verplaatst. Een weigering is daarom een schone no-op: het doeldocument is onaangeroerd, het brondocument is onaangeroerd, en beide blijven open en bruikbaar, wat de samenvoegtest verifieert door na een geweigerde samenvoeging een veldwaarde terug uit de bron te lezen

De vergelijking gebruikt een geordende, hoofdlettergevoelige naamset, dus de kosten zijn evenredig met het gecombineerde veldaantal maal een logaritmische factor, niet met het product van de twee aantallen. Hoofdlettergevoeligheid is hier de juiste keuze, omdat PDF-veldnamen hoofdlettergevoelig zijn; ze folden zou velden samenvoegen die de specificatie als verschillend behandelt

De drie strategieën, en wanneer elk juist is

dfsReject is de strategie voor geautomatiseerde pijplijnen die geen dubbelzinnige documenten mogen produceren. De samenvoeging retourneert nul en LastErrorCode rapporteert 705, een toegewijde code zodat dubbele namen kunnen worden onderscheiden van elke andere samenvoegfout en naar een specifieke remedie kunnen worden geleid, meestal het hernoemen van velden stroomopwaarts

dfsMerge behoudt de gedeelde naam bewust en synchroniseert de doelwaarde en standaardwaarde in het bronveld, zodat een conforme viewer de verschillende widgets behandelt als één logisch benoemd veld, wat standaard AcroForm-gedrag is voor een veld met meerdere widget-annotaties. Wat het niet doet, is verschillende velddictionaries samenvouwen tot één object. Elk veld behoudt zijn eigen pagina-associatie, weergave en acties, omdat ze samenvouwen stilzwijgend opmaak en gedrag zou weggooien die bij het binnenkomende document horen

dfsAutoNumber hernoemt binnenkomende duplicaten door een numeriek achtervoegsel toe te voegen, beginnend bij _2, en het eerste vrije te nemen. Het resultaat is reproduceerbaar: het hangt alleen af van de aanwezige namen, nooit van veldobjectnummers, dus het samenvoegen van hetzelfde paar documenten geeft beide keren dezelfde namen. Die eigenschap doet ertoe wanneer downstream-code, een FDF-import of een databasemapping velden op naam aanspreekt

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
        // Beide documenten zijn nog intact - opnieuw proberen met een beleid
        Log('duplicate field names; retrying with auto-numbering');
        Lib.MergeDocumentEx(SourceDoc, dfsAutoNumber);
      end;
    end;

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

Let op het tweestappenpatroon in die code, dat alleen mogelijk is omdat weigering niet-destructief is. Probeer eerst het strikte beleid, inspecteer de fout, en beslis dan. Bij een samenvoeging die halverwege faalt, zou de fallback opnieuw moeten beginnen door beide bestanden opnieuw te laden

Hoe het samengevoegde formulier er daarna uitziet

Onder dfsMerge produceren een doelveld genaamd Shared met "Target value" en een bronveld met dezelfde naam twee velden, beide genaamd Shared, beide met de doelwaarde, omdat de doelwaarde en standaardwaarde in het binnenkomende veld worden gesynchroniseerd. Dat is de bedoelde semantiek voor een gedeelde naam: één logisch veld, meerdere widgets, één waarde

Onder dfsAutoNumber produceert dezelfde invoer Shared en Shared_2 als afzonderlijke velden met onafhankelijke waarden. Kies tussen de twee door één vraag te stellen: moet het invullen van het ene besturingselement het andere invullen? Voor een naam van een ondertekenaar die op elk onderdeel van een pakket wordt herhaald, ja, en dfsMerge is juist. Voor een totaal dat op elk formulier iets anders betekent, nee, en auto-nummering is juist

// Enumereer na een samenvoeging wat u daadwerkelijk kreeg
for I := 1 to Lib.FormFieldCount do
  Log(Format('%d: %s = %s',
    [I, Lib.GetFormFieldTitle(I), Lib.GetFormFieldValue(I)]));

Praktische opmerkingen voor het samenstellen van formulierpakketten

Een geslaagde samenvoeging verbruikt het brondocument: het wordt verwijderd uit de documentlijst van de bibliotheek, en daarom daalt DocumentCount van twee naar één. Blijf de bron-ID daarna niet gebruiken. De documentversie wordt verhoogd naar de hoogste van de twee, dus het samenvoegen van een PDF 2.0-formulier in een 1.7-document levert een 2.0-bestand op

Volgorde doet ertoe voor namen. A samenvoegen in B en B samenvoegen in A leveren verschillende auto-genummerde resultaten op, omdat het document dat de samenvoeging uitvoert zijn namen ongewijzigd houdt. Wanneer een pakket een canoniek primair formulier heeft, maak dat dan het doel

Handtekeningvelden verdienen hun eigen overweging. Een handtekening die vóór een samenvoeging is toegepast, dekt alleen de revisie die ze ondertekende, dus samenvoegen maakt deze ongeldig in de praktische zin dat het bestand is veranderd sinds ondertekening. Stel eerst samen en onderteken het samengestelde document, in plaats van ondertekende onderdelen samen te voegen. Wanneer de samenvoeging over pagina-inhoud gaat in plaats van formulieren, is het snellere pad beschreven in snelle PDF-samenvoeging met byte-referentieverschuiving het betere gereedschap

Plan ten slotte de datakant van het pakket samen met de samenvoeging. Als veldwaarden afkomstig zijn van een extern systeem, bepaal dan of dat systeem velden op naam aanspreekt vóórdat u voor auto-nummering kiest, omdat Shared_2 niet zal overeenkomen met een mapping die Shared verwacht. Import- en exportformaten worden behandeld in FDF-, XFDF- en XFA-formuliergegevensuitwisseling, en gedrag van scripting op veldniveau dat ook door hernoeming kan worden beïnvloed, wordt behandeld in interactieve formulieracties en JavaScript

Formuliersamenvoeging, gegevensuitwisseling en ondertekening draaien in dezelfde bibliotheek voor Delphi, C++Builder en Free Pascal; de volledige functielijst staat op de PDF Library for Delphi-pagina