Technisch artikel

Batch PDF-preflightrapporten in Delphi met de PDFium Component CLI

Een batch preflight-tool is een consoleprogramma zonder venster, gericht op een map met PDF's, dat elke PDF valideert tegen de conformiteitsnormen die u opgeeft en machinaal leesbaar bewijs achterlaat van wat het heeft gevonden. Niemand zit ernaar te kijken. Het draait om twee uur 's nachts onder cron of Windows Taakplanner, of als een poort in een CI-pijplijn, en de volgende persoon die om de uitvoer geeft, is ofwel een planner (scheduler) die een exitcode leest, of een auditor die weken later een rapport opent. Dat verandert wat "correct" betekent. De preflight-engine van PDFium Component, een broncode PDF-bibliotheek voor Delphi, C++Builder en Lazarus, maakt de validatie-aanroepen zelf bijna triviaal. Het werk dat bepaalt of de tool zijn geld waard is, zit rondom die aanroepen: welk profiel u heeft gecontroleerd, wat de exit-code de planner vertelde, en of het rapport dat een fout zou hebben opgemerkt nog steeds bestaat wanneer iemand ernaar gaat zoeken

Het contract: wat een planner daadwerkelijk kan zien

Een CI-runner of Windows Taakplanner ziet precies twee dingen van uw tool: de exit-code en alle bestanden die hij heeft achtergelaten. Logboekregels, consolekleuren, voortgangsuitvoer: dat alles is voor een mens die live meekijkt, en om twee uur 's nachts is dat niemand. Stel dus de exit-code-vocabulaire vast voordat u de API aanraakt, en houd het saai:

  • 0: elk bestand voldeed aan elk opgevraagd profiel
  • 1: minstens één bestand produceerde validatiebevindingen
  • 2: de tool zelf faalde bij minstens één bestand (beschadigde invoer, vergrendeling (lock), crash)

Het onderscheid tussen codes 1 en 2 is het onderscheid dat teams overslaan en waar ze later spijt van krijgen. Een corrupte PDF die niet opent, is geen validatiefout. Voeg het samen in code 1 en een vrachtwagenlading beschadigde scans verschijnt in uw dashboards als een plotselinge ineenstorting van de conformiteit, waardoor iemand gaat jagen op een norm-regressie die nooit is gebeurd, terwijl het echte verhaal een kapotte scanner verderop in de keten is

Nog twee zaken horen thuis in het contract. De eerste is een time-out per bestand. Een pathologische PDF, duizenden pagina's met diep geneste objectstructuren, kan een enkele validatiepas minutenlang vasthouden, en een nachtelijk venster heeft daar geen geduld voor. Beëindig de taak van dat bestand op de deadline, tel het mee als een toolfout en houd de batch in beweging. De tweede is een quarantainemap: verplaats elke invoer die een time-out heeft of niet kan worden geopend opzij in plaats van deze op zijn plaats te laten. In de loop van enkele maanden verzamelt die map in alle stilte de slechtste documenten die uw echte klanten verzenden, en dat corpus is meer waard voor release-testen dan welk synthetisch voorbeeld u ook met de hand zou kunnen schrijven

Standaarden kiezen, en waarom het conformiteitsniveau ertoe doet

De TPdfPreflightStandard opsomming dekt de families die in de praktijk voorkomen: ppsPdfA voor ISO 19005 archiefconformiteit, ppsPdfUa voor ISO 14289 toegankelijkheid, ppsPdfX voor uitwisseling van afdrukken, plus ppsPdfE, ppsPdfR, en ppsPdfVT voor techniek, raster en variabele-data werk. Binnen een familie leest de engine het conformiteitsniveau dat het document claimt en rapporteert dit per norm in de ConformanceName van het resultaat. Het benoemen van de familie is zelden voldoende, want het niveau is waar het echte verschil zit. PDF/A-2b belooft visuele reproduceerbaarheid en niets meer. PDF/A-3a voegt de eis toe voor logische structuur-tags (tagging) en staat ingesloten bronbestanden toe, wat een veel moeilijkere lat is om te halen voor gescand materiaal dat helemaal geen tagboom heeft. Als u dit in beide richtingen verkeerd doet, liegt de batch tegen u. Als uw bewaarbeleid eigenlijk PDF/A-2b wil, maar u keurt bestanden af wegens ontbrekende structuur-tags, vult het rapport zich met bevindingen die niemand ooit zal repareren. Accepteer elk PDF/A-label zonder het niveau te controleren en u tekent voor documenten die aan een zwakkere eis voldoen dan u beloofd had. Toegankelijkheidsmandaten van kopers bij de overheid stapelen in toenemende mate PDF/UA bovenop dit alles, wat geen kosten toevoegt aan de uitvoering omdat BuildPdfPreflightReport (van de FPdfPreflightReport unit) een set standaarden accepteert:

Report := BuildPdfPreflightReport(Pdf, [ppsPdfA, ppsPdfUa]);

Eén aanroep evalueert beide normen en geeft één enkel geconsolideerd rapportrecord terug

Waarom een lege bevindingenlijst geen voldoende is

Het rapport somt bevindingen per standaard op, en een lege problemenlijst betekent alleen "geen problemen gevonden in de normen die daadwerkelijk zijn uitgevoerd." Dat is een smallere bewering dan "het bestand voldoet aan de norm die u belangrijk vindt", en de kloof tussen die twee is waar batch-preflight stilletjes wegrot. Een configuratietypfout die ppsPdfA uit de set laat vallen, produceert exact dezelfde lege problemenlijst als een echt schoon bestand. Behandel stilte dus als verdacht. Loop door Report.Results en bevestig twee dingen voor elke norm die u wilde controleren: dat er überhaupt een resultaatinvoer voor bestaat, en dat zijn IsCompliant vlag (flag), ondersteund door Status = pfsPass, waar (true) is. Een nachtelijke taak die "geen bevindingen" gelijkstelt aan "klaar voor archief" zonder ooit te bevestigen welke normen werden geëvalueerd, is de klassieke manier waarop een map met niet-conforme bestanden maandenlang doorvaart, totdat een externe auditor er een opent met veraPDF en het hele archief in twijfel wordt getrokken

Een tweede valstrik verbergt zich in wat een bevinding eigenlijk is. Elke TPdfPreflightIssue draagt een Code, een Category, een Description en een Recommendation met zich mee, en benoemt de regel die werd overtreden, niet een pagina of een object. Dat is een ontwerpkeuze met gevolgen voor de feedbacklus. Het rapport vertelt het producerende team welke klasse defect er bestaat, een niet-ingesloten lettertype of een ontbrekende XMP-identificatie, en het vinden van het specifieke overtredende object is de taak van het saneringstool (remediation tool) verderop, niet die van de validator. Bouw uw rapportconsumenten tegen de stabiele Code-waarden, nooit tegen de voor mensen leesbare beschrijvingstekst, die tussen releases zonder waarschuwing opnieuw geformuleerd kan worden

Rapportbestanden voor machines en voor de oproepbare persoon

De rapportrecord schrijft dezelfde bevindingen in vijf formaten: SaveJsonToFile, SaveCsvToFile, SaveHtmlToFile, SaveTextToFile, en SaveMarkdownToFile, elk met een bijpassende ToJson-achtige functie voor wanneer u de tekenreeks in het geheugen wilt in plaats van op schijf. Weersta de drang om er een te kiezen. Schrijf JSON voor de pijplijn, zodat CI dit aan de taakrecord kan toevoegen en probleemcodes en statussen per standaard kan parseren zonder tekst te schrapen. Schrijf HTML voor de mens die wordt opgeroepen (paged), omdat het in elke browser opent zonder enige tooling. De twee samen kosten één extra regel per bestand en besparen uw oproepbare (on-call) ingenieur de allerslechtste taak in batchverwerking, namelijk het reverse-engineeren van een ruwe JSON-blob om twee uur 's nachts om te achterhalen welk bestand brak. Eén discipline is belangrijker dan de keuze van het formaat: leid elke rapportnaam af van de invoerbestandsnaam, nooit van een tijdstempel, anders zullen twee parallelle runs rapporten verweven die u niet langer aan hun invoer kunt koppelen

Drempelwaarden (thresholds) voor de ernst horen thuis in de configuratie en niet in code. Een annotatie zonder alternatieve beschrijving is een harde mislukking voor een PDF/UA-indieningsportaal en een negeerbare notitie voor een intern archief, maar het is wel dezelfde bevinding in beide. Zorg voor een fail-on niveau per profiel, zodat het beleid kan verschuiven zonder een hercompilatie, en stempel het niveau dat van kracht was in de taaksamenvatting zelf. Volgend kwartaal herinnert niemand zich onder welke drempel de batch van oktober vorig jaar draaide, en de samenvatting is de enige plek waar die herinnering overleeft

Bestanden isoleren zodat één slechte PDF niet de batch kan laten zinken

procedure RunPreflightBatch(const InputDir, ReportDir: string;
  out FilesWithFindings, ToolFailures: Integer);
var
  SR: TSearchRec;
  Pdf: TPdf;
  Report: TPdfPreflightReport;
begin
  FilesWithFindings := 0;
  ToolFailures := 0;
  if FindFirst(InputDir + '*.pdf', faAnyFile, SR) = 0 then
  try
    repeat
      Pdf := TPdf.Create(nil);   // fresh instance per file: no state bleed
      try
        try
          Pdf.FileName := InputDir + SR.Name;
          Pdf.Active := True;
          if not Pdf.Active then  // load failures are silent, not raised
            raise EPdfError.Create('Cannot open ' + SR.Name);
          Report := BuildPdfPreflightReport(Pdf, [ppsPdfA, ppsPdfUa]);
          Report.SaveJsonToFile(ReportDir + ChangeFileExt(SR.Name, '.json'));
          Report.SaveHtmlToFile(ReportDir + ChangeFileExt(SR.Name, '.html'));
          if Report.TotalIssueCount > 0 then
            Inc(FilesWithFindings);
        except
          on E: Exception do
          begin
            Inc(ToolFailures);   // exit-code-2 territory, not a validation verdict
            WriteLn(ErrOutput, SR.Name + ': ' + E.Message);
          end;
        end;
      finally
        Pdf.Free;
      end;
    until FindNext(SR) <> 0;
  finally
    FindClose(SR);
  end;
end;

Drie weloverwogen keuzes leven in die lus. Een verse TPdf per bestand garandeert dat één document dat de enginestatus corrumpeert, de bestanden die erop volgen niet kan vergiftigen. De expliciete Active-controle verdient zijn plaats omdat Active := True laadfouten inslikt in plaats van ze op te werpen (raising); laat de beveiliging weg en een afgekapt bestand drijft door naar de validatie-aanroep voordat het ergens verderop mislukt met een misleidende foutmelding. Het binnenste try..except-blok bevindt zich expres binnen de reikwijdte (scope) per bestand, zodat één enkele uitzondering de storingssteller verhoogt en de lus doorgaat. U wilt schone rapporten voor de 4.999 goede bestanden, zelfs wanneer bestand 5.000 versnipperd is. En beide rapportformaten worden naar de schijf geschreven voordat het oordeel is geteld, wat betekent dat het bewijs overleeft, zelfs als een bug later in de samenvattingslogica een telfout maakt

De mapping van de exit-code stort dan ineen tot een paar regels in het projectbestand:

begin
  RunPreflightBatch(ParamStr(1), ParamStr(2), Findings, Failures);
  if Failures > 0 then
    Halt(2)
  else if Findings > 0 then
    Halt(1);
  // falling through exits with 0: every file conformed
end.

Wat preflight niet voor u zal doen

De engine detecteert; het repareert niet. Een bevinding over een niet-ingesloten lettertype of een apparaatafhankelijke kleurruimte is een werkorder voor degene die de bestanden produceert, en de validator heeft geen manier om het op zijn plaats te patchen. Plan de feedbacklus dus weloverwogen. Rapporten moeten daar belanden waar het producerende team ze daadwerkelijk leest, anders verschijnen dezelfde bevindingen elke nacht totdat iemand uiteindelijk vraagt waarom het conformiteitspercentage nooit verbetert. Het loont ook om een steekproef van oordelen (verdicts) te vergelijken met een onafhankelijke validator, veraPDF voor PDF/A of de preflight van Acrobat voor PDF/X, voordat een externe auditor ze voor u controleert. Wanneer twee engines het oneens zijn over een echt klantenbestand, is dat document geen overlast; het is exact de regressiecase die bij uw release-testen ontbrak. Bewaar het, geef het een naam en draai het bij elke build

Er is nog een combinatie (pairing) die de moeite waard is om te kennen. Dezelfde validatie-engine stuurt de interactieve controles in een beoordelings-UI aan, dus deze headless CLI en een analistgerichte PDF intake-beoordelingswerkbank kunnen een enkele validatievocabulaire delen in plaats van in de loop van de tijd uit elkaar te drijven. En omdat [ppsPdfA, ppsPdfUa] de toegankelijkheid in dezelfde pas evalueert, sluit de PDF/UA kant van de batch netjes aan bij werk aan de viewerzijde, zoals het bouwen van een toegankelijke PDF-lezer in Delphi. Profielen, rapportformaten en de volledige preflight-API worden gedocumenteerd op de productpagina voor de PDFium Component