PDF Library for Delphi führt zwei AcroForm-Dokumente mit einer expliziten Richtlinie für Felder zusammen, die sich einen Namen teilen. MergeDocumentEx nimmt die Quelldokumentkennung und eine von drei Strategien entgegen: dfsReject verweigert die Zusammenführung, dfsMerge behält den gemeinsamen Namen bei und synchronisiert Werte, und dfsAutoNumber benennt die eingehenden Felder deterministisch um. Der Namensabgleich erfolgt, bevor sich irgendwelche Objektnummern verschieben, sodass eine abgelehnte Zusammenführung beide Dokumente vollständig nutzbar zurücklässt
Wer schon einmal ein PDF-Antragspaket zusammengestellt hat, ist darauf gestoßen. Drei Formulare, jedes mit einem Feld namens Signature, Date oder Total, werden in eine Datei zusammengeführt. In einem AcroForm ist der vollqualifizierte Feldname die Identität des Feldes, daher sind zwei Felder mit demselben Namen überhaupt keine zwei Felder: Das Ausfüllen des einen füllt das andere, und eine auf eines angewendete Signatur deckt einen Geltungsbereich ab, den niemand beabsichtigt hat
Warum wird die Namenskollision vor der Zusammenführung entschieden?
Das ältere MergeDocument verkettet die beiden AcroForm-Wurzelfeld-Arrays und bietet keine Wahl. Schlimmer noch: Ist das Ergebnis unbrauchbar, geschieht die Entdeckung erst, nachdem Objektnummern neu vergeben und Seitenbäume zusammengefügt wurden, was den Aufrufer mit einem Dokument in einem Zustand zurücklässt, in dem keines der Originale war
MergeDocumentEx kehrt die Reihenfolge um. Es sammelt die Feldnamen der obersten Ebene aus beiden Dokumenten, vergleicht sie und wendet die Strategie an, bevor sich irgendetwas bewegt. Eine Ablehnung ist daher ein sauberer No-op: Das Zieldokument bleibt unangetastet, das Quelldokument bleibt unangetastet, und beide bleiben geöffnet und nutzbar, was der Zusammenführungstest verifiziert, indem er nach einer verweigerten Zusammenführung einen Feldwert aus der Quelle zurückliest
Der Vergleich verwendet eine geordnete, groß-/kleinschreibungssensitive Namensmenge, daher sind die Kosten proportional zur kombinierten Feldanzahl mal einem logarithmischen Faktor statt zum Produkt der beiden Anzahlen. Groß-/Kleinschreibungssensitivität ist hier die richtige Wahl, weil PDF-Feldnamen groß-/kleinschreibungssensitiv sind; sie zu falten würde Felder zusammenführen, die die Spezifikation als unterschiedlich behandelt
Die drei Strategien, und wann welche richtig ist
dfsReject ist die Strategie für automatisierte Pipelines, die keine mehrdeutigen Dokumente erzeugen dürfen. Die Zusammenführung gibt null zurück, und LastErrorCode meldet 705, einen eigenen Code, sodass doppelte Namen von jedem anderen Zusammenführungsfehler unterschieden und einer bestimmten Abhilfe zugeführt werden können, meist dem vorgelagerten Umbenennen von Feldern
dfsMerge behält den gemeinsamen Namen bewusst bei und synchronisiert Zielwert und Standardwert in das Quellfeld, sodass ein konformer Viewer die mehreren Widgets als ein logisch benanntes Feld behandelt, was Standardverhalten von AcroForm für ein Feld mit mehreren Widget-Annotationen ist. Was es nicht tut, ist unterschiedliche Feld-Dictionaries in ein einziges Objekt zu falten. Jedes Feld behält seine eigene Seitenzuordnung, Erscheinung und Aktionen, weil ein Zusammenfalten still Formatierung und Verhalten verwerfen würde, die zum eingehenden Dokument gehören
dfsAutoNumber benennt eingehende Duplikate um, indem es ein numerisches Suffix anhängt, beginnend bei _2 und das erste freie nehmend. Das Ergebnis ist reproduzierbar: Es hängt nur von den vorhandenen Namen ab, niemals von Feldobjektnummern, sodass das Zusammenführen desselben Dokumentenpaars zweimal beide Male dieselben Namen liefert. Diese Eigenschaft ist wichtig, wenn nachgelagerter Code, ein FDF-Import oder eine Datenbankzuordnung Felder namentlich referenziert
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 Dokumente sind noch intakt - erneuter Versuch mit einer Richtlinie
Log('duplicate field names; retrying with auto-numbering');
Lib.MergeDocumentEx(SourceDoc, dfsAutoNumber);
end;
end;
Lib.SaveToFile('application-complete.pdf');
finally
Lib.Free;
end;
end;
Man beachte das zweistufige Muster in diesem Code, das nur möglich ist, weil Ablehnung nicht-destruktiv ist. Zuerst die strikte Richtlinie versuchen, den Fehler prüfen, dann entscheiden. Bei einer Zusammenführung, die auf halbem Weg fehlschlägt, müsste der Fallback von vorn beginnen, indem beide Dateien neu geladen werden
Wie das zusammengeführte Formular danach aussieht
Unter dfsMerge erzeugen ein Zielfeld namens Shared mit "Target value" und ein Quellfeld mit demselben Namen zwei Felder, beide namens Shared, beide mit dem Zielwert, weil Zielwert und Standardwert in das eingehende Feld synchronisiert werden. Das ist die beabsichtigte Semantik für einen gemeinsamen Namen: ein logisches Feld, mehrere Widgets, ein Wert
Unter dfsAutoNumber erzeugt dieselbe Eingabe Shared und Shared_2 als separate Felder mit unabhängigen Werten. Die Wahl zwischen beiden ergibt sich aus einer einzigen Frage: Soll das Ausfüllen des einen Steuerelements das andere füllen? Für einen auf jedem Teil eines Pakets wiederholten Unterzeichnernamen ja, und dfsMerge ist richtig. Für eine Summe, die auf jedem Formular etwas anderes bedeutet, nein, und Auto-Nummerierung ist richtig
// Nach einer Zusammenführung auflisten, was man tatsächlich erhalten hat
for I := 1 to Lib.FormFieldCount do
Log(Format('%d: %s = %s',
[I, Lib.GetFormFieldTitle(I), Lib.GetFormFieldValue(I)]));
Praktische Hinweise für das Zusammenstellen von Formularpaketen
Die erfolgreiche Zusammenführung verbraucht das Quelldokument: Es wird aus der Dokumentliste der Bibliothek entfernt, weshalb DocumentCount von zwei auf eins sinkt. Die Quellkennung danach nicht weiterverwenden. Die Dokumentversion wird auf die höhere der beiden angehoben, sodass das Zusammenführen eines PDF-2.0-Formulars in ein 1.7-Dokument eine 2.0-Datei ergibt
Die Reihenfolge ist für Namen wichtig. A in B zusammenzuführen und B in A zusammenzuführen liefern unterschiedliche auto-nummerierte Ergebnisse, da das zusammenführende Dokument seine Namen unverändert behält. Hat ein Paket ein kanonisches Hauptformular, sollte dieses das Ziel sein
Signaturfelder verdienen eigene Überlegung. Eine vor einer Zusammenführung angewendete Signatur deckt nur die von ihr signierte Revision ab, daher macht die Zusammenführung sie im praktischen Sinne ungültig, weil sich die Datei seit der Signierung verändert hat. Erst zusammenstellen und dann das zusammengestellte Dokument signieren, statt signierte Teile zusammenzuführen. Geht es bei der Zusammenführung um Seiteninhalt statt um Formulare, ist der schnellere, in schneller PDF-Zusammenführung mit Byte-Referenzverschiebung beschriebene Pfad das bessere Werkzeug
Schließlich sollte die Datenseite des Pakets zusammen mit der Zusammenführung geplant werden. Kommen Feldwerte aus einem externen System, sollte vor der Wahl der Auto-Nummerierung entschieden werden, ob dieses System Felder namentlich adressiert, denn Shared_2 passt nicht zu einer Zuordnung, die Shared erwartet. Import- und Exportformate werden in FDF-, XFDF- und XFA-Formulardatenaustausch behandelt, und Feldebenen-Skriptverhalten, das durch Umbenennen ebenfalls betroffen sein kann, wird in interaktiven Formularaktionen und JavaScript behandelt
Formularzusammenführung, Datenaustausch und Signierung laufen in derselben Bibliothek für Delphi, C++Builder und Free Pascal; die vollständige Funktionsliste findet sich auf der PDF-Library-für-Delphi-Seite