PDFium Component til Delphi udfylder en AcroForm atomart med TPdf.SetFormFieldValues: du giver 1 til 256 typede requests adresseret efter side og widget, og komponenten anvender enten hver værdi i én dokumenterstatning eller ingen. Når én request er ugyldig, navngiver TPdfFormValuesWriteReport.FailedRequest den request, der sank batchen
Den garanti betyder mest i de jobs, ingen ser til. En natlig import skubber CRM-rækker ind i en skadesanmeldelsesformular, eller en printservice lægger policidata over en fortrykt skabelon. Kører du de felter én ad gangen, efterlader den første dårlige værdi — en kode, der ikke er en gyldig radioeksport, en streng længere end feltets MaxLen — dig med en PDF, der er halvt gammel data og halvt ny. Intet crashede, så intet alarmerede nogen. Batch-API'en findes, så en fejl er et rent afslag i stedet for et halvskrevet dokument
Hvorfor er felt-for-felt formularudfyldning farlig for dataimport?
Felt-for-felt-udfyldning er farlig, for hver skrivning committer for sig selv, så en fejl midt i løkken efterlader et dokument, der matcher hverken kilderecorden eller den oprindelige skabelon. Det klassiske mønster er en løkke over FormField[Index]-tildelinger, hvor hver går gennem PDFiums native form-fill-miljø én widget ad gangen. Værdi 1 til 17 lander, værdi 18 udløser en fejl, og den kaldende må nu vide præcis, hvilke værdier kom ind for at omgøre dem
De fleste kaldende skriver aldrig den undo-kode, og dem, der gør, tager den normalt subtilt forkert: et fælles felt har allerede skiftet på hver side, det optræder på, og checkbox-tilstande er allerede flippet. Kører gemningen i en finally-blok, ryger den halvt fyldte fil på disken og ser komplet ud. I et overprint-job er det værre end ingen fil, for den printes, og nogen underskriver den
Det andet lydløse problem er fokus. Den klassiske sætter driver PDFiums interaktive editor: den dræber eksisterende fokus, fokuserer target-widgeten, markerer al tekst, erstatter den og dræber fokus igen. Det er fint til et headless-job, men i en viewer afslutter det også enhver redigeringssession, brugeren havde åben i et andet felt. Den typede skrivevej, der kom i PDFium Component 3.231.0, går slet ikke gennem native feltfokus, og 3.233.0 udvidede den fra enkeltfelter til cross-page batches
Hvordan SetFormFieldValues anvender en batch i én erstatning
TPdf.SetFormFieldValues opstiller hver request mod ét indfanget snapshot af dokumentet, skriver alle felt-dictionaries i ét træk og bytter det færdige resultat ind med en enkelt erstatning. Det oprindelige dokument forbliver live helt til sidste trin, så enhver fejl før det trin efterlader det urørt
Sekvensen forklarer både garantien og omkostningen:
- Preflight-tjek kører først: dokumentet skal være aktivt, request-antallet skal være 1 til 256, ingen page-object- eller edit-journal-transaktion må være åben, intet native felt må holde fokus, og dokumentet skal give fill-forms- eller modify-tilladelse
- Hver request valideres for sideinterval og inputbudget, og
FailedRequestsporer det indeks, der tjekkes - Komponenten fanger et edit-snapshot, normaliserer AcroForm i den kopi og anvender hver request i orden og skriver
/V,/Ifor choice-valg og/ASfor hver widget, der deler feltet - Ændringerne tilføjes snapshotet som én sparsom inkrementel opdatering i betydningen ISO 32000-1 §7.5.6, så urelaterede objekter følges med byte for byte
- En separat verifikator-instans loader kandidatbyte først; først efter den åbner rent, afliver komponenten det live dokument og loader det nye og falder tilbage til det oprindelige snapshot, hvis loadet fejler
Det interne erstatningstrin er samme maskineri, som komponenten bruger til sine edit-journal-snapshots, og derfor kan undo- og crash-recovery-journalen og en batchskrivning ikke overlappe: afslut eller annuller journaltransaktionen først. Automatiske livscyklushandlinger undertrykkes, mens dokumentet byttes, så et document-open-script kan ikke omskrive felter eller snappe fokus midt i erstatningen
At bygge en typet request-batch i Delphi
En batch er et dynamisk array af TPdfFormValueRequest-records, som hver holder en 1-baseret Page, et 0-baseret side-lokalt FieldIndex og en typet TPdfFormValue. Værdirecorden bærer en Kind af fvkText, fvkChoice, fvkCheckBox eller fvkRadioButton plus Text-, ChoiceIndices- og Checked-medlemmer brugt efter den kind
Importdata kommer normalt nøglet efter feltnavn, så det første job er at oversætte navne til side- og widget-adresser. FieldIndex tæller widget-annotationer på en side i samme orden som FormFieldCount og FormFieldInfo, så et simpelt scan virker. Hjælperen tager den første widget, den finder for et navn, hvilket betyder noget for fælles felter, som næste afsnit forklarer
type
TFieldAddress = record
Page, FieldIndex: Integer;
end;
function FindFieldAddress(Pdf: TPdf; const FieldName: WString;
out Address: TFieldAddress): Boolean;
var
P, I: Integer;
begin
Result := False;
for P := 1 to Pdf.PageCount do
begin
Pdf.PageNumber := P;
for I := 0 to Pdf.FormFieldCount - 1 do
if Pdf.FormFieldInfo[I].Name = FieldName then
begin
Address.Page := P;
Address.FieldIndex := I;
Exit(True); // kun første widget: én request per felt
end;
end;
end;
procedure AddRequest(var Requests: TPdfFormValueRequests;
const FieldName: WString; Pdf: TPdf; const Value: TPdfFormValue);
var
Address: TFieldAddress;
N: Integer;
begin
if not FindFieldAddress(Pdf, FieldName, Address) then
raise Exception.CreateFmt('Template has no field "%s"', [FieldName]);
N := Length(Requests);
SetLength(Requests, N + 1);
Requests[N].Page := Address.Page;
Requests[N].FieldIndex := Address.FieldIndex;
Requests[N].Value := Value;
end;
Bemærk at AddRequest udløser en fejl ved et ukendt navn i stedet for at springe det over. Batchen validerer adresser, ikke din mapping, så et manglende skabelonfelt må fejle her, ellers fejler det aldrig. Med adresserne opløst er det kort at bygge værdierne og committe dem
var
Pdf: TPdf;
Requests: TPdfFormValueRequests;
Report: TPdfFormValuesWriteReport;
V: TPdfFormValue;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'claim-template.pdf';
Pdf.Active := True;
V := Default(TPdfFormValue);
V.Kind := fvkText;
V.Text := 'Maria Lopez';
AddRequest(Requests, 'Claimant.Name', Pdf, V);
V := Default(TPdfFormValue);
V.Kind := fvkCheckBox;
V.Checked := True;
AddRequest(Requests, 'Consent', Pdf, V);
V := Default(TPdfFormValue);
V.Kind := fvkRadioButton;
V.Text := 'Express'; // eksportværdi for knappen, der skal vælges
AddRequest(Requests, 'Shipping', Pdf, V);
V := Default(TPdfFormValue);
V.Kind := fvkChoice;
SetLength(V.ChoiceIndices, 1);
V.ChoiceIndices[0] := 2; // 0-baseret indeks i feltets Opt-array
AddRequest(Requests, 'Region', Pdf, V);
if Pdf.SetFormFieldValues(Requests, Report) then
Pdf.SaveAs('claim-filled.pdf')
else
LogBatchFailure(Requests, Report);
finally
Pdf.Free;
end;
end;
De typede kinds tjekkes mod felttypen, før noget skrives. En tekstværdi sigtet mod en checkbox, en radioværdi sigtet mod en almindelig checkbox eller en skrivning til en push-knap afviser alle. Choice-indeks skal være gyldige, unikke og stigende; mere end ét indeks kræver MultiSelect-flaget, og fri tekst i et choice-felt accepteres kun af en redigérbar combo box. En tekstværdi længere end feltets MaxLen afvises i stedet for at blive trunkeret, og read-only-felter afviser kategorisk. For radiogrupper med NoToggleToOff-flaget afvises en tom eksportværdi — betydningen "ryd gruppen" — også
Hvorfor fejler skrivning af et fælles felt to gange hele batchen?
TPdf.SetFormFieldValues afviser to requests, der opløses til samme terminalfelt, for de ville begge skrive en enkelt værdi, og komponenten nægter at lade én request lydløst overskrive en anden. Tjekket kører på felt-ejeren, ikke widgeten, så to forskellige widgets af ét felt på to forskellige sider tæller stadig som en duplikat
Årsagen er feltmodellen i ISO 32000-1 §12.7.3.1. Et terminalfelt ejer værdien; dets widget-annotationer er kun steder, hvor den værdi vises. Et felt ved navn PolicyNumber, der optræder i headeren på hver side, er ét felt med flere widgets, og hver af de widgets viser samme /V. En radiogruppe virker på samme måde: hver knap er en widget, gruppen er feltet. At skrive PolicyNumber gennem side 1-widgeten og igen gennem side 9-widgeten er ét felt skrevet to gange, og enhver regel, der udpeger en vinder, skjuler en bug i din mapping
Så behandler batchen det som en fejl og navngiver den anden request i FailedRequest. I praksis er løsningen upstream: opløs hvert feltnavn til præcis én adresse, som FindFieldAddress gør, og lad komponenten propagere værdien til de andre widgets. Det gør den som del af skrivningen ved at sætte /AS på hver widget i en delt checkbox eller radiogruppe, så appearance-tilstanden stemmer med feltværdien — samme opdeling dækket i artiklen om checkbox-feltværdier versus widget-tilstande
At læse fejlrapporten
En fejlet batch returnerer False, udløser ingen fejl og efterlader det oprindelige dokument og hver feltværdi præcis, som de var. TPdfFormValuesWriteReport rapporterer så Succeeded = False, nul for både UpdatedFields og UpdatedWidgets, det 0-baserede indeks for den overtrædende request i FailedRequest og en menneskelæsbar grund i ErrorMessage
procedure LogBatchFailure(const Requests: TPdfFormValueRequests;
const Report: TPdfFormValuesWriteReport);
var
R: Integer;
begin
R := Report.FailedRequest;
if R >= 0 then
Log(Format('Request %d (page %d, widget %d) rejected: %s',
[R, Requests[R].Page, Requests[R].FieldIndex, Report.ErrorMessage]))
else
Log('Batch rejected as a whole: ' + Report.ErrorMessage);
end;
En FailedRequest på -1 betyder, at afvisningen tilhører transaktionen frem for en request: native fokus var aktivt, dokumentet er signeret, formularen er XFA, eller et tilladelsestjek fejlede. En ikke-negativ værdi peger på én post. Ved et samlet budgetoverskud peger den på den request, der skubbede batchen over grænsen, hvilket ikke nødvendigvis er den request, der er forkert. Hold et parallelt array af kildekolonnenavne ved siden af Requests, så kan du vende indeks 14 direkte til "række 2087, kolonne ShipDate" i importloggen
Ved succes er UpdatedFields lig request-antallet, og UpdatedWidgets tæller hver berørt widget, de delte inkluderet — et billigt sanity-tjek mod skabelonen
Forudsætninger og grænser, du vil støde på
Den mest almindelige afvisning i et viewer-program er native fokus: taster en bruger i et felt, nægter SetFormFieldValues, før den så meget som fanger snapshotet, og den ventende editorbuffer overlever intakt. Komponenten dropper ikke fokus bag din ryg, for det ville enten committe eller miste tekst, brugeren ikke er færdig med. Kald ClearFormFieldFocus bevidst, hvilket afslutter editorsessionen som et klik uden for feltet, og genprøv så
Budgetterne er faste og dokumenterede, så du kan dimensionere imports mod dem:
- 1 til 256 requests pr. batch; opdel større records i flere batches og acceptér, at atomariteten så gælder pr. batch
- Højst 262.144 UTF-16-enheder tekst og 4.096 choice-indeks pr. request
- Højst 1.048.576 UTF-16-enheder tekst og 16.384 choice-indeks på tværs af hele batchen
- De sædvanlige dokumentbudgetter for snapshot-parseren gælder stadig, inklusiv en kilde på 64 MiB og 65.536 objekter
En vellykket erstatning ugyldiggør også native handles, søgninger og markeringer opnået før kaldet, for dokumentet nedenunder er nyt. Side- og view-aktivering og logiske felt-cursorer beholdes; alt andet, du har cachet, som annotation- eller page-object-handles, skal hentes igen
Hvordan adskiller det sig fra XFDF-import?
TPdf.ImportXFDF og TPdf.SetFormFieldValues putter begge data i felter, men XFDF-import er en tolerant merge, mens batchen er en streng transaktion. XFDF-import matcher værdier til felter efter navn, springer navne over, den ikke kan finde, ignorerer duplikater og returnerer, hvor mange poster den anvendte. Det er præcis rigtigt til at merge en reviewers kommentarer, hvor et delvist resultat stadig er nyttigt
Batchen gør det modsatte på hvert punkt. Den adresserer widgets eksplicit, afviser ukendte eller mistypede targets, behandler duplikater som fejl og har intet delvist udfald. Brug XFDF, når data er en interchange-fil fra et andet værktøj; brug SetFormFieldValues, når din egen kode ejer mappingen, og en halvanvendt record ville være en defekt
Hvad batchskrivningen ikke dækker
Batchen er en afgrænset AcroForm-profil, og den siger nej i stedet for at gætte uden for den profil. XFA-formularer afvises og behøver deres dedikerede editor. Signerede dokumenter afvises, for at omskrive felt-dictionaries under en eksisterende signatur er et signatur-workflow, ikke en udfyldningsoperation; krypterede snapshots er også uden for profilen. Misdannede eller tvetydige button-eksportarrays afvises, og radioeksportværdier, der matcher mere end én knap, nægtes, medmindre gruppen sætter RadiosInUnison. Transaktionen normaliserer også direkte felt-dictionaries før skrivningen — tilfældet bag feltværdier, der tilsyneladende vender tilbage
Appearances fortjener én sætning af omhu. For ændrede tekst- og choice-widgets dropper batchen det forældede /AP i stedet for at skibe et appearance, der viser den gamle værdi, og den slår NeedAppearances fra, så PDFium ikke genopbygger hver urelateret widget i formularen. PDFium regenererer de manglende appearances, når den renderer. Ryger den udfyldte PDF til en streng printpipeline, der aldrig syntetiserer appearances, så render eller flatten den gennem PDFium først og test den vej med dine egne skabeloner
SetFormFieldValues og de typede værdirecords følger med i PDFium Component til Delphi og Lazarus, hvor productsiden lister den fulde formularudfyldnings- og AcroForm-API ved siden af rendering, redigering og viewer-kontroller