Tekninen artikkeli

PDF-lomakkeiden yhdistäminen Delphissä: kaksoiskappalekenttien säännöt

PDF Library for Delphi yhdistää kaksi AcroForm-dokumenttia selkeällä käytännöllä kentille, jotka jakavat nimen. MergeDocumentEx ottaa lähdedokumentin tunnisteen ja yhden kolmesta strategiasta: dfsReject kieltäytyy yhdistämisestä, dfsMerge säilyttää jaetun nimen ja synkronoi arvot, ja dfsAutoNumber nimeää saapuvat kentät uudelleen deterministisesti. Nimien skannaus tapahtuu ennen kuin mitkään objektinumerot siirtyvät, joten hylätty yhdistäminen jättää molemmat dokumentit täysin käyttökelpoisiksi

Jokainen, joka on koonnut PDF-hakemuspaketin, on törmännyt tähän. Kolme lomaketta, joista jokaisessa on kenttä nimeltä Signature, Date tai Total, yhdistetään yhdeksi tiedostoksi. AcroFormissa täysin määritelty kentän nimi on kentän identiteetti, joten kaksi kenttää samalla nimellä eivät ole lainkaan kaksi kenttää: yhden täyttäminen täyttää toisen, ja yhteen sovellettu allekirjoitus kattaa laajuuden, jota kukaan ei tarkoittanut

Miksi nimiristiriita ratkaistaan ennen yhdistämistä?

Vanhempi MergeDocument ketjuttaa kaksi AcroForm-juurikenttätaulukkoa eikä tarjoa valintaa. Vielä pahempaa, kun tulos on käyttökelvoton, havainto tapahtuu sen jälkeen, kun objektinumerot on numeroitu uudelleen ja sivupuut ommeltu yhteen, mikä jättää kutsujan käteen dokumentin tilassa, jossa kumpikaan alkuperäinen ei ollut

MergeDocumentEx kääntää järjestyksen. Se kerää ylätason kenttänimet molemmista dokumenteista, vertailee niitä ja soveltaa strategiaa ennen kuin mikään siirtyy. Hylkäys on siis puhdas ei-toiminto: kohdedokumentti on koskematon, lähdedokumentti on koskematon, ja molemmat pysyvät avoinna ja käyttökelpoisina, minkä yhdistämistesti vahvistaa lukemalla kentän arvon takaisin lähteestä hylätyn yhdistämisen jälkeen

Vertailu käyttää järjestettyä, kirjainkokoherkkää nimijoukkoa, joten kustannus on verrannollinen yhdistettyyn kenttämäärään kertaa logaritminen tekijä, ei kahden määrän tuloon. Kirjainkokoherkkyys on tässä oikea valinta, koska PDF:n kenttänimet ovat kirjainkokoherkkiä; niiden taittaminen yhdistäisi kenttiä, joita spesifikaatio kohtelee erillisinä

Kolme strategiaa ja milloin kukin on oikea

dfsReject on strategia automatisoiduille putkille, jotka eivät saa tuottaa moniselitteisiä dokumentteja. Yhdistäminen palauttaa nollan, ja LastErrorCode raportoi arvon 705, omistetun koodin, jotta kaksoiskappalenimet voidaan erottaa jokaisesta muusta yhdistämisvirheestä ja reitittää tiettyyn korjaustoimenpiteeseen, yleensä kenttien uudelleennimeämiseen ylävirrassa

dfsMerge säilyttää jaetun nimen tarkoituksella ja synkronoi kohdearvon ja oletusarvon lähdekenttään, joten vaatimustenmukainen katseluohjelma kohtelee useita widgetejä yhtenä loogisesti nimettynä kenttänä, mikä on tavanomainen AcroForm-käyttäytyminen kentälle, jolla on useita widget-merkintöjä. Mitä se ei tee, on eri kenttäsanakirjojen taittaminen yhdeksi objektiksi. Jokainen kenttä säilyttää oman sivuassosiaationsa, ulkoasunsa ja toimintonsa, koska niiden yhdistäminen hylkäisi hiljaa saapuvaan dokumenttiin kuuluvan muotoilun ja käyttäytymisen

dfsAutoNumber nimeää saapuvat kaksoiskappaleet uudelleen liittämällä numeerisen loppuliitteen alkaen arvosta _2 ja ottamalla ensimmäisen vapaan. Tulos on toistettavissa: se riippuu vain läsnä olevista nimistä, ei koskaan kenttien objektinumeroista, joten saman dokumenttiparin yhdistäminen kahdesti tuottaa samat nimet molemmilla kerroilla. Tällä ominaisuudella on merkitystä, kun jatkokoodi, FDF-tuonti tai tietokantakuvaus viittaa kenttiin nimellä

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
        // Molemmat dokumentit ovat yhä koskemattomia - yritä uudelleen käytännöllä
        Log('duplicate field names; retrying with auto-numbering');
        Lib.MergeDocumentEx(SourceDoc, dfsAutoNumber);
      end;
    end;

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

Huomaa tuon koodin kaksivaiheinen malli, joka on mahdollinen vain siksi, että hylkäys ei ole tuhoisa. Kokeile ensin tiukkaa käytäntöä, tarkasta virhe, sitten päätä. Yhdistämisellä, joka epäonnistuu kesken, varasuunnitelman täytyisi aloittaa alusta lataamalla molemmat tiedostot uudelleen

Miltä yhdistetty lomake näyttää jälkikäteen

Asetuksella dfsMerge kohdekenttä nimeltä Shared, joka kantaa arvoa "Target value", ja samannimineen lähdekenttä tuottavat kaksi kenttää, molemmat nimeltä Shared, molemmat raportoiden kohdearvon, koska kohdearvo ja oletusarvo synkronoidaan saapuvaan kenttään. Se on tarkoitettu semantiikka jaetulle nimelle: yksi looginen kenttä, useita widgetejä, yksi arvo

Asetuksella dfsAutoNumber sama syöte tuottaa Shared- ja Shared_2-kentät erillisinä kenttinä itsenäisillä arvoilla. Valitse näiden kahden väliltä kysymällä yksi kysymys: pitäisikö yhden ohjaimen täyttämisen täyttää toinen? Allekirjoittajan nimelle, joka toistuu jokaisessa paketin osassa, kyllä, ja dfsMerge on oikea. Summalle, joka tarkoittaa jotain eri lomakkeessa, ei, ja automaattinen numerointi on oikea

// Yhdistämisen jälkeen luetteloi, mitä todella sait
for I := 1 to Lib.FormFieldCount do
  Log(Format('%d: %s = %s',
    [I, Lib.GetFormFieldTitle(I), Lib.GetFormFieldValue(I)]));

Käytännön huomioita lomakepakettien kokoamiseen

Onnistunut yhdistäminen kuluttaa lähdedokumentin: se poistetaan kirjaston dokumenttilistalta, minkä vuoksi DocumentCount laskee kahdesta yhteen. Älä jatka lähdetunnisteen käyttöä jälkikäteen. Dokumentin versio nostetaan kahdesta korkeampaan, joten PDF 2.0 -lomakkeen yhdistäminen 1.7-dokumenttiin tuottaa 2.0-tiedoston

Järjestyksellä on merkitystä nimille. A:n yhdistäminen B:hen ja B:n yhdistäminen A:han tuottavat eri automaattisesti numeroituja tuloksia, koska yhdistämistä tekevä dokumentti säilyttää nimensä muuttumattomina. Kun paketilla on kanoninen ensisijainen lomake, tee siitä kohde

Allekirjoituskentät ansaitsevat oman harkintansa. Allekirjoitus, joka sovellettiin ennen yhdistämistä, kattaa vain sen version, jonka se allekirjoitti, joten yhdistäminen mitätöi sen käytännön mielessä, että tiedosto on muuttunut allekirjoituksen jälkeen. Kokoa ensin ja allekirjoita koottu dokumentti sen sijaan, että yhdistäisit allekirjoitettuja osia. Kun yhdistäminen koskee sivusisältöä lomakkeiden sijaan, nopeampi polku, joka kuvataan artikkelissa nopea PDF-yhdistäminen tavuviittausten siirrolla, on parempi työkalu

Lopuksi, suunnittele paketin datapuoli yhdessä yhdistämisen kanssa. Jos kentän arvot saapuvat ulkoisesta järjestelmästä, päätä, osoittaako tuo järjestelmä kenttiä nimellä ennen automaattisen numeroinnin valitsemista, koska Shared_2 ei täsmää kuvaukseen, joka odottaa nimeä Shared. Tuonti- ja vientimuodot käsitellään artikkelissa FDF-, XFDF- ja XFA-lomaketiedon vaihto, ja kenttätason skriptauskäyttäytyminen, johon uudelleennimeäminen voi myös vaikuttaa, käsitellään artikkelissa interaktiiviset lomaketoiminnot ja JavaScript

Lomakkeiden yhdistäminen, tiedonvaihto ja allekirjoitus toimivat samassa kirjastossa Delphille, C++Builderille ja Free Pascalille; täydellinen ominaisuusluettelo on sivulla PDF Library for Delphi -sivulla