Teknisk artikel

SpiderInfo Web Capture-kommandon i Delphi-PDF:er

PDF Library for Delphi (PDFlibPas) skriver Web Capture-kommandots metadata in i dokumentets katalog via AddWebCaptureCommand och fyller i dictionaryn /SpiderInfo som definieras i ISO 32000-1 §14.10. Varje kommando registrerar vilken URL som hämtades, hur många nivåer djupt hämtningen gick och en liten uppsättning flaggor. Det registrerar avsikten — det hämtar ingenting

Den distinktionen är hela anledningen till att det här API:et är lätt att missläsa. En supportingenjör räcker dig en PDF som byggts ihop från en leverantörs onlinedokumentation och frågar var sida 40 kommer ifrån. Om producenten fyllde i /SpiderInfo finns svaret i katalogen: en URL, ett crawl-djup, kanske POST-brödtexten som genererade sidan. Om producenten inte gjorde det hjälper ingen mängd läsande av content stream dig. Att skriva den provenance är billigt, strukturellt petigt och nästan alltid hoppat över

Vad registrerar Catalog-/SpiderInfo-dictionaryn egentligen?

/SpiderInfo är en Web Capture-informationsdictionary som hänger i dokumentets katalog och innehåller exakt två saker PDF Library for Delphi bryr sig om: /V, en numerisk Web Capture-databasversion som biblioteket alltid skriver som 1.0, och /C, en array av kommandodictionarys. Versionsnumret är inte PDF-versionen — det identifierar själva Web Capture-datamodellen, och 1.0 är det enda värde ISO 32000-1 §14.10 definierar. Allt intressant bor i /C. Varje kommandodictionary bär /URL (adressen som begärdes), /L (hur många nivåer av länkar som följdes från den) och /F (genomsökningsflaggor). Tre valfria nycklar beskriver själva begäran när den inte var en vanlig GET: /P för POST-data, /CT för datans innehållstyp och /H för extra request headers. PDF Library for Delphi mappar alla sex till en enda post, TPDFlibWebCaptureCommand, vars fält är URL, Levels, Flags, PostData, ContentType och Headers, alla AnsiString utom de två Integer-räknarna. Närvaron av /SpiderInfo höjer också dokumentets minimiversion: AddWebCaptureCommand anropar EnsureMinVersion('1.3', 'Catalog /SpiderInfo Web Capture command'), och versionsdetekteringsreglerna i PDF Library for Delphi registrerar en matchande PDF 1.3-regel så att ett dokument som läses tillbaka senare rapporterar samma golv

Strukturen hos Catalog-SpiderInfo-dictionaryn som PDFlibPas skriver: /SpiderInfo bär /V satt till Web Capture-databasversionen 1.0 och /C som en array av indirekta referenser, där varje kommandodictionary håller /URL, /L, /F och de valfria nycklarna /P, /CT, /H som mappar på fälten i TPDFlibWebCaptureCommand
Kommandolistan hänger i dokumentets katalog som en versionspost plus en array av indirekta referenser, så en delad hämtning kan refereras från mer än ett ställe medan versionsgolvet stiger till PDF 1.3

Varför ett Web Capture-kommando måste vara ett indirekt objekt

Den enda strukturella regel som snubblar upp folk: en kommandodictionary måste registreras som ett indirekt objekt innan den hamnar i /C, eftersom ISO 32000-1 §14.10 specificerar /C som en array av indirekta referenser till kommandodictionarys, inte en array av dictionarys. Stoppa in en direkt dictionary i den arrayen och du får något som parsar som giltig PDF men inte betyder något som Web Capture-post — en viewer eller ett arkiveringsverktyg som vandrar igenom kommandolistan hittar ett inline-objekt där en referens borde vara, och posten är helt enkelt inte ett kommando. PDF Library for Delphi sköter detta inne i AddWebCaptureCommand: det bygger dictionaryn, lämnar den till OnNewIndObj för att få ett objektnummer och ett generationnummer, slår in paret i en indirekt referens och lägger till referensen i arrayen. Om OnNewIndObj inte producerar ett positivt objektnummer returnerar funktionen 0 och arrayen lämnas ifred. Anledningen till att specen insisterar på indirektion är att ett enda kommando kan delas — samma hämtning kan refereras från mer än ett ställe i en Web Capture-struktur — och delade objekt i PDF är per definition indirekta

Hur lägger du till ett Web Capture-kommando från Delphi?

Fyll posten helt och hållet, anropa sedan AddWebCaptureCommand, som returnerar det nya kommandots 1-baserade index eller 0 om valideringen avvisade det. Fullständighet spelar större roll här än vanligt. Delphi initierar de hanterade AnsiString-fälten i en lokal post till tomma, men Integer-fälten — Levels och Flags — får ingen sådan behandling, så en post du glömmer att fylla i anländer bärande på vad som råkar ligga på stacken. AddWebCaptureCommand kräver att Levels är minst 1 och att Flags är icke-negativt, och ett stackskräpsvärde avvisas antingen direkt eller, värre, tyst accepteras som ett trovärdigt djup

Valideringsflödet för AddWebCaptureCommand i PDFlibPas: fyll varje postfält eftersom Levels och Flags är vanliga Integers som bär stackskräp, kräv Levels på minst 1, avvisa Flags utanför whitelisten 1, 2, 4, skapa /SpiderInfo lat, registrera kommandot som ett indirekt objekt via OnNewIndObj och returnera det 1-baserade indexet eller 0
Varje avvisning sker innan dokumentet rörs: en post som missar räknar- eller flaggkontrollerna returnerar 0, lämnar katalogen exakt som den var och skriver aldrig in en tom platshållare i filen
var
  Lib: TPDFlib;
  Command: TPDFlibWebCaptureCommand;
  Index: Integer;
begin
  Lib := TPDFlib.Create;
  try
    if Lib.LoadFromFile('captured-site.pdf', '') = 1 then
    begin
      // Tilldela varje fält: Levels och Flags är vanliga Integers och
      // bär stackskräp om du hoppar över dem.
      Command.URL := 'https://example.com/docs/index.html';
      Command.Levels := 2;
      Command.Flags := PDF_WEB_CAPTURE_SAME_SITE or PDF_WEB_CAPTURE_SAME_PATH;
      Command.PostData := '';
      Command.ContentType := '';
      Command.Headers := '';

      // Returnerar det nya 1-baserade kommandotindexet, eller 0 vid avvisning.
      Index := Lib.AddWebCaptureCommand(Command);
      if Index = 0 then
        raise Exception.Create('Web Capture command rejected');

      Lib.SaveToFile('captured-site.pdf');
    end;
  finally
    Lib.Free;
  end;
end;

Det första lyckade anropet är också det som skapar strukturen. AddWebCaptureCommand ber den interna hjälpen WebCaptureCommands(True) om kommandoarrayen, och den hjälpen skapar /SpiderInfo med sin /V-post och sedan en tom /C-array bara om de saknas. Den är medvetet konservativ med vad den hittar: om katalogen redan har en /SpiderInfo-nyckel vars värde inte är en dictionary, eller en /C-nyckel vars värde inte är en array, returnerar hjälpen nil i stället för att skriva över det befintliga objektet. En felformad ärvd struktur får tillägget att misslyckas; det får inte PDF Library for Delphi att förstöra vad den tidigare producenten skrev

Flaggwhitelisten: tre definierade bitar och inget annat

AddWebCaptureCommand validerar Flags mot en whitelist och avvisar alla värden med en bit satt utanför de tre konstanterna PDF Library for Delphi definierar: PDF_WEB_CAPTURE_SAME_SITE = 1 (följ bara länkar som stannar på samma webbplats), PDF_WEB_CAPTURE_SAME_PATH = 2 (följ bara länkar under samma sökväg) och PDF_WEB_CAPTURE_SUBMIT = 4 (denna begäran var en formulärinlämning snarare än en vanlig hämtning). Passa 8, eller 16, eller en oavsiktlig -1, och anropet returnerar 0 utan att röra dokumentet

Flaggwhitelisten som AddWebCaptureCommand i PDFlibPas upprätthåller: bitfältet /F accepterar exakt tre definierade bitar, PDF_WEB_CAPTURE_SAME_SITE satt till 1, PDF_WEB_CAPTURE_SAME_PATH satt till 2 och PDF_WEB_CAPTURE_SUBMIT satt till 4, medan varje odefinierad bit som 8 eller en oavsiktlig minus 1 vägras vid skrivtillfället
En odefinierad bit går inte att skilja från en korrupt, så att vägra värdet vid skrivtillfället håller tvetydigheten borta från filen, och en laglig kombination som 1 eller 2 satt till 3 passerar fortfarande

Detta är strängare än PDF-parsers brukar vara, och det är medvetet. /F är ett bitfält utan självbeskrivande struktur, så en odefinierad bit går inte att skilja från en korrupt, och en konsument som senare läser tillbaka värdet har inget sätt att skilja en framtida utökning från en felskrivning. Att vägra vid skrivtillfället håller tvetydigheten helt borta från filen. Notera också att /P, /CT och /H bara skrivs när motsvarande postfält är icke-tomma, så en vanlig GET producerar en kommandodictionary med tre nycklar — /URL, /L, /F — utan tomsträngsplatshållare som skräpar ner, vilket är vad en läsare som parsar filen förväntar sig för en begäran utan brödtext

// En formulärinlämning: POST-brödtext plus dess innehållstyp.
Command.URL := 'https://example.com/search';
Command.Levels := 1;
Command.Flags := PDF_WEB_CAPTURE_SUBMIT;          // 4
Command.PostData := 'q=iso32000&scope=all';
Command.ContentType := 'application/x-www-form-urlencoded';
Command.Headers := 'Accept-Language: en-US';
if Lib.AddWebCaptureCommand(Command) = 0 then
  raise Exception.Create('submission command rejected');

// Same-site plus same-path är en laglig kombination: 1 eller 2 = 3.
Command.Flags := PDF_WEB_CAPTURE_SAME_SITE or PDF_WEB_CAPTURE_SAME_PATH;

// Bit 3 är inte definierad av specen, så den här når aldrig filen.
Command.Flags := 8;
if Lib.AddWebCaptureCommand(Command) <> 0 then
  raise Exception.Create('undefined flag bit should have been refused');

Läsa tillbaka kommandon: en nyckel, två lagliga representationer

GetWebCaptureCommand returnerar 1 vid framgång och 0 annars, och den måste vara mer tolerant än skrivaren, eftersom den läser filer som andra producenter skrev. Två asymmetrier spelar roll. För det första accepterar läsaren en kommandopost vare sig den är en indirekt referens eller en direkt dictionary och derefererar vid behov — PDF Library for Delphi skriver alltid den indirekta form specen kräver, men att vägra läsa en slarvig fil hjälper ingen. För det andra får /P vara antingen en string eller en stream, och den generaliserar långt bortom Web Capture. ISO 32000-1 låter upprepade gånger en enda nyckel hålla antingen en string eller en stream när värdet kan vara godtyckligt stort — POST-brödtexter här, och samma mönster dyker upp på andra håll i formatet för metadata och inbäddade nyttolaster. Den interna hjälpen ReadString i GetWebCaptureCommand tar därför en AllowStream-flagga, och bara /P-läsningen passerar True: ett stream-värde avkodas genom sin filterkedja och returneras som fältet PostData, medan /URL, /CT och /H förblir string-only eftersom specen inte erbjuder dem stream-alternativet. Skriver du egen PDF-läskod är detta vanan värd att stjäla — kolla specen för vilka nycklar som är dubbeltypade i stället för att anta en string

var
  Lib: TPDFlib;
  Command: TPDFlibWebCaptureCommand;
  I, Total, Removed: Integer;
begin
  Total := Lib.GetWebCaptureCommandCount;
  // Publik indexering är 1-baserad, i linje med resten av PDF Library for Delphi.
  for I := 1 to Total do
    if Lib.GetWebCaptureCommand(I, Command) = 1 then
      Writeln(Format('%s  levels=%d  flags=%d  post=%d bytes',
        [Command.URL, Command.Levels, Command.Flags, Length(Command.PostData)]));

  // Tar bort hela /SpiderInfo-dictionaryn och rapporterar hur många
  // kommandon som släpptes; returnerar 0 när det inte fanns något att rensa.
  Removed := Lib.ClearWebCaptureCommands;
  Writeln(Format('cleared %d command(s)', [Removed]));
end;

ClearWebCaptureCommands räknar kommandona och rensar sedan hela /SpiderInfo-nyckeln ur katalogen i stället för att bara tömma /C. Att lämna kvar ett /SpiderInfo med en tom kommandoarray skulle hävda att dokumentet har Web Capture-provenance och att ingenting hämtades, vilket är ett annat och falskt påstående än att ingen provenance registrerats. Samma instinkt gäller katalogmetadata i allmänhet — en tom skal-dictionary är ett uttalande, inte en neutral rest. Om du granskar vad ett dokument redan påstår innan du skriver om det, passar introspektionsmönstret i att läsa tillbaka outline- och annoteringsåtgärder ur en befintlig PDF naturligt ihop med en läs-sedan-rensa-pass här

Att skriva metadata är inte att implementera Web Capture

Var rättfram med dig själv om omfattningen: AddWebCaptureCommand sparar en post över en avsedd eller genomförd hämtning, och det är allt den gör. PDF Library for Delphi utför inga HTTP-begäranden, följer inte länkarna ditt Levels-värde beskriver och hedrar inte flaggorna SAME_SITE eller SAME_PATH — de flaggorna är dokumentation av hur någon crawler betedde sig, inte instruktioner biblioteket kör. Din egen hämtkod, vad den än är, förblir helt och hållet ditt problem

Två ytterligare delar av ISO 32000-1 §14.10 ligger också utanför detta API. Web Capture definierar innehållsuppsättningar — /Contents-strukturerna som knyter enskilda sidbitar tillbaka till källmaterialet de kom från — och ett namnträd som mappar digitala identifierare till dessa innehållsuppsättningar. PDF Library for Delphi underhåller varken. Vad du får är kommandolistan: svaret på toppnivå på "var hämtades detta dokument ifrån, och hur djupt gick genomsökningen", vilket för provenance-, gransknings- och arkivintagningsändamål brukar vara den del någon faktiskt frågar efter. Behöver du att URL:erna är klickbara för läsare snarare än bara registrerade för granskare, är det ett separat jobb — se att bygga GoToR-, GoToE- och Launch-åtgärder för åtgärdssidan, och dokument- och sidlivscykelåtgärdsutlösare för att utlösa beteende vid öppning eller stängning. Katalog-/SpiderInfo-kommandometadata som beskrivs här är en del av PDF Library for Delphi, det native PDF-biblioteket för Delphi och C++Builder