Tekninen artikkeli

Eräajettavat (Batch) PDF-ennakkotarkastusraportit (Preflight Reports) Delphissä PDFium Component -komponentin (PDFium Component) CLI:llä

Eräajettava (batch) ennakkotarkastustyökalu (preflight tool) on ikkunaton konsoliohjelma (console program with no window), joka osoitetaan PDF-tiedostojen kansioon (pointed at a folder of PDFs), se validoi (validates) jokaisen tiedoston nimeämiäsi vaatimustenmukaisuusstandardeja (conformance standards) vastaan ja jättää jälkeensä koneellisesti luettavan (machine-readable) todisteen (proof) siitä, mitä se löysi. Kukaan ei istu katsomassa (watches) sitä. Se pyörii (runs) aamuyhdeltä (at two in the morning) cronin tai Windowsin tehtävienajoituksen (Task Scheduler) alaisena, tai (or as a) porttina CI-liukuhihnassa (pipeline), ja seuraava (the next) henkilö (person), joka välittää (to care about) sen tulosteesta (its output), on joko ajoittaja (scheduler), joka lukee poistumiskoodia (exit code), tai auditoija, joka avaa (opening a) raportin (report) viikkoja myöhemmin (weeks later). Se (That) muuttaa sitä (changes what), mitä "oikein" (correct) tarkoittaa. PDFium Componentin (lähdekoodilla toimitettava PDF-kirjasto Delphille, C++Builderille ja Lazarukselle) ennakkotarkastusmoottori (preflight engine) tekee itse validointikutsuista (validation calls) lähes triviaaleja. Se työ, joka (decides) päättää, onko työkalu (tool) hintansa väärti (earns its keep), on (sits around) näiden kutsujen ympärillä: minkä (which) profiilin (profile you) tarkistit, mitä (what the) poistumiskoodi (exit code) kertoi ajoittajalle (scheduler), ja onko (whether the) raportti, joka olisi (would have) paljastanut (caught) virheen (mistake), edelleen olemassa (still exists), kun joku alkaa etsiä (goes looking for) sitä

Sopimus (contract): mitä ajoittaja (scheduler) voi todella (actually) nähdä

CI-ajaja (CI runner) tai Windowsin tehtävienajoitus (Task Scheduler) näkee työkalustasi (tool) tasan kaksi (two) asiaa: poistumiskoodin (exit code) ja (and) ne tiedostot, jotka se jätti jälkeensä (left behind). Lokirivit (Log lines), konsolin värit (console colors), edistymisen tuloste (progress output): kaikki tämä (all of that) on tarkoitettu (for a) ihmiselle (human), joka katsoo livenä (watching live), ja aamuyhdeltä (at two in the morning) kukaan (nobody) ei katso. Joten kiinnitä (fix the) poistumiskoodien sanasto (exit-code vocabulary) ennen kuin kosket API:in, ja pidä se tylsänä (boring):

  • 0: jokainen tiedosto (every file) oli (conformed to) jokaisen pyydetyn (requested) profiilin mukainen
  • 1: ainakin (at least) yksi tiedosto tuotti validointilöydöksiä (validation findings)
  • 2: työkalu (tool) itse (itself) epäonnistui ainakin yhdessä tiedostossa (korruptoitunut syöte (corrupt input), lukko (lock), kaatuminen (crash))

Ero (distinction) koodien 1 ja 2 välillä (between) on se (is the one), jonka tiimit ohittavat (skip) ja jota he myöhemmin (later) katuvat (regret). Korruptoitunut PDF, joka ei aukea, ei ole validointivirhe (validation failure). Taita se (Fold it) osaksi koodia 1, ja kuorma-autollinen (truckload) vaurioituneita skannauksia (scans) näkyy kojelaudoillasi (dashboards) äkillisenä (sudden) vaatimustenmukaisuuden romahduksena (conformance collapse), ja (sending) lähettää jonkun jahtaamaan standardiregressiota (standards regression), jota ei koskaan (never) tapahtunut, kun (when the) todellinen (real) tarina (story) on ylävirrassa (upstream) oleva rikkinäinen (busted) skanneri (scanner)

Sopimukseen (contract) kuuluu kaksi muutakin asiaa (Two more items belong in the). Ensimmäinen on tiedostokohtainen aikakatkaisu (per-file timeout). Patologinen PDF (pathological PDF), tuhansia sivuja, joilla on (with) syvästi (deeply) sisäkkäisiä (nested) objektirakenteita (object structures), voi pidätellä (can hold a) yhtä validointiajoa minuutteja, ja (and a) öisellä (nightly) ikkunalla ei ole (has no) kärsivällisyyttä sille. Tapa (Kill) tuon tiedoston työ (job) takarajalla (at the deadline), laske se (count it as a) työkalun epäonnistumiseksi, ja pidä erä (batch) liikkeellä. Toinen on karanteenihakemisto (quarantine directory): siirrä (move) jokainen aikakatkaistu (timed-out) tai avaamaton (unopenable) syöte (input) sivuun (aside) sen sijaan, että jättäisit sen paikoilleen (in place). Muutaman kuukauden (months) aikana tuo hakemisto kerää hiljaa pahimmat asiakirjat (worst documents), joita todelliset asiakkaasi (real customers) lähettävät (send), ja (and that) tuo (that) korpus on (corpus is) julkaisutestaukselle (release testing) arvokkaampi (worth more) kuin mikään synteettinen näyte (synthetic sample), jonka voisit (could) kirjoittaa käsin (by hand)

Standardien (standards) valitseminen (Picking), ja miksi (why the) vaatimustenmukaisuuden tasolla (conformance level) on väliä (matters)

Luettelo TPdfPreflightStandard kattaa (covers the) perheet (families), jotka (that) tulevat vastaan käytännössä (in practice): ppsPdfA ISO 19005 -arkistointivaatimustenmukaisuutta (archival conformance) varten, ppsPdfUa ISO 14289 -saavutettavuutta (accessibility) varten, ppsPdfX tulostusvaihtoa (print exchange) varten, plus ppsPdfE, ppsPdfR ja ppsPdfVT suunnittelu- (engineering), rasteri- (raster) ja muuttuvadata- (variable-data) töitä (work) varten. Perheen sisällä moottori (engine) lukee asiakirjan väittämän vaatimustenmukaisuuden tason ja raportoi sen standardikohtaisesti (per standard) tuloksen kohdassa ConformanceName. Perheen nimeäminen (Naming the family) on harvoin (rarely) riittävä (enough), koska taso (level) on se, missä todellinen ero (real difference) asuu. PDF/A-2b lupaa visuaalisen toistettavuuden (visual reproducibility) eikä mitään muuta. PDF/A-3a lisää (adds a) vaatimuksen (demand for) loogisesta rakennetägituksestä (logical structure tagging) ja (and) sallii (allows) upotetut (embedded) lähdetiedostot, mikä on (which is a) paljon korkeampi kynnys ylitettäväksi skannatulle (scanned) materiaalille, jolla ei ole lainkaan tägipuuta (tag tree at all). Tee tämä (Get this) väärin kumpaan tahansa suuntaan (in either direction), ja (and the) erä (batch) valehtelee (lies to) sinulle. Jos (If your) säilytyskäytäntösi (retention policy) todella (actually) haluaa (wants) PDF/A-2b:tä, mutta hylkäät (fail) tiedostot puuttuvien rakennetägien vuoksi (missing structure tags), raportti (report) täyttyy (fills with) löydöksillä, joita kukaan ei koskaan korjaa (fix). Hyväksy (Accept) mikä tahansa PDF/A-tunniste (label) tarkistamatta (without checking the) tasoa, ja kuittaat (sign off on) asiakirjat (documents), jotka (that) täyttävät heikomman kynnyksen (weaker bar) kuin mitä lupasit. Valtion (government) ostajien (buyers) asettamat saavutettavuusvaatimukset pinoavat (stack) yhä enemmän (increasingly) PDF/UA:n kaiken tämän (all this) päälle, mikä (which) ei lisää (adds no) ajon hintaa (cost to the run), koska (because) BuildPdfPreflightReport (yksiköstä (from the) FPdfPreflightReport) ottaa (takes a) joukon standardeja:

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

Yksi kutsu arvioi (evaluates) molemmat standardit ja antaa takaisin (hands back a) yhden (single) yhdistetyn (consolidated) raporttitietueen (report record)

Miksi tyhjä löydösluettelo ei ole hyväksyntä (pass)

Raportti luettelee löydökset standardikohtaisesti, ja tyhjä ongelmaluettelo (issue list) tarkoittaa vain, että "niissä standardeissa, jotka todella ajettiin, ei löytynyt ongelmia" ("no problems found in the standards that actually ran"). Se (That) on kapeampi väite (narrower claim) kuin "tiedosto (file conforms to the) on välittämäsi standardin mukainen" ("the file conforms to the standard you care about"), ja kuilu (gap) näiden kahden välillä on se (where), missä (batch preflight quietly rots) eräennakkotarkastus mätänee hiljaa. Asetusten (configuration) kirjoitusvirhe (typo), joka (that) pudottaa (drops) ppsPdfA-standardin joukosta (from the set), tuottaa täsmälleen (exactly the) saman tyhjän ongelmaluettelon (issue list) kuin (as a) aidosti (genuinely) puhdas tiedosto. Käsittele siis hiljaisuutta (silence) epäilyttävänä. Käy läpi (Walk) Report.Results ja varmista (assert) kaksi (two) asiaa jokaisen (every) standardin osalta, jonka aioit (meant to) tarkistaa: että sille (for it) on ylipäätään olemassa (exists at all) tulosmerkintä (result entry), ja (and) että (that its) sen IsCompliant-lippu (flag), jonka taustalla on Status = pfsPass, on (is) tosi (true). Yöllinen (nightly) työ (job), joka (that) rinnastaa "ei löydöksiä" ("no findings" with) tilaan "arkistovalmis" ("archive ready") vahvistamatta (without ever confirming) koskaan, mitä (which) standardeja arvioitiin (were evaluated), on klassinen tapa (way), jolla kansiollinen vaatimustenvastaisia tiedostoja (nonconformant files) purjehtii (sails) läpi (through) kuukausien ajan (for months), kunnes (until an) ulkopuolinen auditoija (external auditor) avaa (opens) yhden veraPDF-ohjelmalla ja (and the) koko arkisto joutuu kyseenalaiseksi (comes into question)

Toinen (second) ansa piilee siinä, mikä löydös ylipäätään (even) on. Jokainen (Each) TPdfPreflightIssue kantaa kenttiä Code, Category, Description ja Recommendation, ja se nimeää (names the) säännön (rule), jota rikottiin (was violated), ei (not a) sivua (page) tai (or an) objektia (object). Se (That) on suunnitteluvalinta (design choice), jolla (with) on seurauksia (consequences for the) palautesilmukalle (feedback loop). Raportti kertoo tuottavalle (producing) tiimille, minkä luokan (class of) virhe (defect) on olemassa, upottamaton (unembedded) fontti tai (or a) puuttuva (missing) XMP-tunniste (identifier), ja nimenomaisen rikkovan (offending) objektin (object) löytäminen (finding the) on korjaustyökalun (remediation tool's) työ (job) myöhemmässä vaiheessa (downstream), ei validoijan (not the validator's). Rakenna (Build your) raportinlukijasi (report consumers) vakaita (stable) Code-arvoja vasten (against the), ei koskaan ihmissilmin luettavaa (human-readable) kuvaustekstiä (description text) vasten, jota (which) voidaan (can be) muotoilla uudelleen (reworded) julkaisujen (releases) välillä (between) ilman (without) varoitusta (warning)

Raporttitiedostoja koneille ja päivystäjälle (person on call)

Raporttitietue (report record) kirjoittaa (writes the) samat (same) löydökset (findings in) viidessä muodossa (formats): SaveJsonToFile, SaveCsvToFile, SaveHtmlToFile, SaveTextToFile ja (and) SaveMarkdownToFile, joista kullakin (each) on vastaava (matching) ToJson-tyylinen funktio, kun (when you) haluat (want the) merkkijonon (string in) muistiin (memory) levyn (disk) sijaan (rather than). Vastusta (Resist the) kiusausta (urge to) valita yksi. Kirjoita JSONia (JSON for the) putkelle (pipeline), jotta (so) CI voi (can) liittää (attach it to the) sen työtietueeseen (job record) ja jäsentää (parse) ongelmakoodit (issue codes) ja (and) standardikohtaiset tilat (per-standard statuses) ilman, että sen tarvitsee raapia (scraping) tekstiä (text). Kirjoita HTML-raportti ihmiselle, joka (who) saa (gets) hälytyksen (paged), koska se (because it) avautuu missä tahansa selaimessa ilman minkäänlaisia työkaluja. Nämä (The) kaksi (two) yhdessä maksavat yhden (one) ylimääräisen rivin (extra line per) tiedostoa kohden ja (and) säästävät (spare your) päivystävän insinöörisi eräkäsittelyn yksittäiseltä (single) pahimmalta (worst) tehtävältä (task), joka (which) on (is) raa'an (raw) JSON-möykyn takaisinmallintaminen (reverse-engineering) aamuyhdeltä sen selvittämiseksi (to learn which), mikä tiedosto (file) meni rikki (broke). Yhdellä kurinalaisuudella (discipline) on enemmän väliä (matters more) kuin muodolla (format choice): johda (derive each) jokainen raportin nimi (report name from the) syötetiedoston nimestä (input file name), älä koskaan (never from a) aikaleimasta (timestamp), tai (or) kaksi (two) rinnakkaista (parallel) ajoa lomittaa (runs will interleave) raportteja (reports you), joita (can) et enää (no longer) voi yhdistää (match back to) niiden syötteisiin (inputs)

Vakavuuskynnykset (Severity thresholds) kuuluvat konfiguraatioon (configuration), ei (rather than in) koodiin (code). Huomautus (annotation), jolla ei ole (with no) vaihtoehtoista (alternate) kuvausta (description), on ankara (hard) epäonnistuminen (failure) PDF/UA-palautusportaaliin (submission portal) ja huomiotta jätettävä (ignorable) huomautus (note) sisäiselle (internal) arkistolle (archive), vaikka (yet) se (it) on (is the) identtinen (identical) löydös (finding) molemmissa (in both). Paljasta (Expose a) "epäonnistu tasolla" -asetus (fail-on level) profiilia kohden (per profile), jotta (so the) käytäntö (policy) voi (can) muuttua (shift) ilman uudelleenkääntämistä (recompile), ja leimaa (stamp the) voimassa (in force into the) ollut taso itse työnsammariin (job summary itself). Ensi (Next) vuosineljänneksellä (quarter) kukaan ei muista (nobody will remember), millä kynnyksellä (threshold) viime (last) lokakuun (October's) erä (batch) ajettiin (ran under), ja yhteenveto (summary) on (is the) ainoa paikka (only place), jossa tuo muisto selviää (survives)

Tiedostojen (files) eristäminen (Isolating), jotta (so) yksi huono (bad) PDF ei voi (cannot) upottaa (sink the) erää (batch)

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;

Kolme tarkoituksellista (deliberate) valintaa asuu (live in) tuossa (that) silmukassa (loop). Uusi (fresh) TPdf-instanssi tiedostoa kohden takaa (guarantees), että yksi moottorin (engine) tilan (state) korruptoiva asiakirja (document) ei voi myrkyttää (poison) sitä (it) seuraavia (follow) tiedostoja. Eksplisiittinen (explicit) Active-tarkistus (check) ansaitsee (earns) paikkansa (its place), koska Active := True nielee latausvirheet nostamisen (raising them) sijaan; pudota suojus (drop the guard), ja typistetty (truncated) tiedosto ajautuu (drifts on into the) validointikutsuun (validation call) ennen (before) epäonnistumista (failing) jossain myöhemmässä vaiheessa (downstream with a) harhaanjohtavalla viestillä (misleading message). Sisempi try..except asuu (lives) tiedostokohtaisen (per-file) laajuuden (scope) sisällä (inside the) tarkoituksella (on purpose), jotta (so a) yksittäinen (single) poikkeus nostaa virhelaskuria (failure counter) ja silmukka jatkuu (carries on). Haluat puhtaat raportit (reports for the) 4 999 hyvälle tiedostolle jopa silloin (even when), kun (when) tiedosto (file) 5 000 on (is) silputtu (shredded). Ja (And) molemmat (both) raporttimuodot (report formats) kirjoitetaan levylle ennen tuomion laskemista (verdict is tallied), mikä tarkoittaa (which means the), että todisteet säilyvät jopa silloin, jos (even if a) yhteenvetologiikan myöhempi bugi laskee väärin (miscounts)

Poistumiskoodin kartoitus (exit-code mapping) kutistuu sitten muutamaan riviin projektitiedostossa:

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.

Mitä (What) ennakkotarkastus (preflight) ei tee puolestasi (will not do for you)

Moottori havaitsee; se ei korjaa (repair). Löydös upottamattomasta fontista tai laiteriippuvaisesta (device-dependent) väriavaruudesta on (is a) työmääräys (work order) sille, joka tuottaa (produces) tiedostot, eikä (and the) validoijalla (validator has no) ole (way to) tapaa paikata sitä paikalleen. Suunnittele (So plan) palautesilmukka (feedback loop) siis harkitusti (deliberately). Raporttien on laskeuduttava (Reports have to land) sinne, missä tuottava tiimi (producing team) todella lukee (actually reads) ne (them), tai samat löydökset ilmestyvät (reappear) joka (every) yö (night), kunnes (until) joku (someone) vihdoin (finally) kysyy (asks), miksi (why the) vaatimustenmukaisuusaste (conformance rate) ei koskaan parane. On myös kannattavaa ristiintarkistaa (cross-check a) otos (sample of) tuomioita (verdicts) riippumatonta validoijaa (independent validator), kuten veraPDF:ää (veraPDF for) PDF/A:lle (PDF/A or) tai Acrobatin ennakkotarkastusta (Acrobat's preflight for) PDF/X:lle, vasten, ennen kuin ulkopuolinen auditoija ristiintarkistaa ne (them) puolestasi (for you). Kun (When) kaksi (two) moottoria (engines) on (disagree) eri (on a) mieltä todellisesta asiakastiedostosta (real customer file), se (that) asiakirja (document) ei ole haitta (nuisance); se on täsmälleen (exactly the) se regressiotapaus (regression case), jota julkaisutestaukseltasi (release testing was) puuttui (missing). Pidä (Keep) se (it), nimeä (name) se (it) ja (and) aja (run) se jokaisessa (on every) koontiversiossa (build)

Vielä yksi (One more) pari (pairing is) on (worth) tietämisen (knowing) arvoinen (worth knowing). Sama validointimoottori ohjaa vuorovaikutteisia tarkistuksia (interactive checks in a) tarkastelun käyttöliittymässä (review UI), joten (so) tämä päätön (headless) CLI ja analyytikolle suunnattu PDF-sisäänoton tarkastelutyöpöytä (PDF intake review workbench) voivat jakaa yhden (single) validointisanaston (validation vocabulary) sen sijaan, että ne ajautuisivat erilleen (drifting apart) ajan myötä. Ja koska (And because) [ppsPdfA, ppsPdfUa] arvioi (evaluates) saavutettavuuden (accessibility) samalla (in the) kerralla (same pass), erän (batch) PDF/UA-puoli (side of the batch lines) osuu (lines up) siististi (cleanly) yksiin (lines up cleanly with) katseluohjelman (viewer-side) työn (work like), kuten saavutettavan PDF-lukijan (accessible PDF reader) rakentamisen Delphissä, kanssa. Profiilit (Profiles), raporttimuodot (report formats) ja (and the) täydellinen (full) ennakkotarkastus-API (preflight API) on dokumentoitu (are documented on the) PDFium Component -komponentin tuotesivulla (product page)