PDF Library for Delphi (PDFlibPas) scrive i metadati dei comandi Web Capture nel catalogo del documento attraverso AddWebCaptureCommand, popolando il dizionario /SpiderInfo definito in ISO 32000-1 §14.10. Ogni comando registra l'URL che è stato recuperato, a quanti livelli di profondità è arrivato il fetch e un piccolo insieme di flag. Registra l'intento — non esegue alcun fetch
Questa distinzione è esattamente il motivo per cui questa API è facile da fraintendere. Un tecnico del supporto ti consegna un PDF assemblato dalla documentazione online di un fornitore e ti chiede da dove arriva la pagina 40. Se il produttore ha compilato /SpiderInfo, la risposta sta nel catalogo: un URL, una profondità di crawl, magari il body POST che ha generato la pagina. Se non lo ha fatto, non c'è lettura del content stream che te lo dica. Scrivere questa provenienza costa poco, è strutturalmente laborioso e quasi sempre viene saltato
Cosa registra davvero il dizionario /SpiderInfo del catalogo?
/SpiderInfo è un dizionario informativo Web Capture appeso al catalogo del documento, e contiene esattamente le due cose a cui PDF Library for Delphi tiene: /V, una versione numerica del database Web Capture che la libreria scrive sempre come 1.0, e /C, un array di dizionari comando. Il numero di versione non è la versione del PDF — identifica il modello dati Web Capture stesso, e 1.0 è l'unico valore che ISO 32000-1 §14.10 definisce. Tutto l'interessante sta in /C. Ogni dizionario comando trasporta /URL (l'indirizzo che è stato richiesto), /L (quanti livelli di link sono stati seguiti da lì) e /F (i flag di attraversamento). Tre chiavi opzionali descrivono la richiesta stessa quando non è un semplice GET: /P per i dati POST, /CT per il content type di quei dati e /H per header di richiesta aggiuntivi. PDF Library for Delphi mappa tutti e sei su un unico record, TPDFlibWebCaptureCommand, i cui campi sono URL, Levels, Flags, PostData, ContentType e Headers, tutti AnsiString tranne i due contatori Integer. La presenza di /SpiderInfo alza anche la versione minima del documento: AddWebCaptureCommand chiama EnsureMinVersion('1.3', 'Catalog /SpiderInfo Web Capture command'), e le regole di rilevamento versione di PDF Library for Delphi registrano una regola PDF 1.3 corrispondente, così un documento riletto in seguito riporta lo stesso limite minimo
Perché un comando Web Capture deve essere un oggetto indiretto
L'unica regola strutturale che frega tutti: un dizionario comando va registrato come oggetto indiretto prima di finire in /C, perché ISO 32000-1 §14.10 definisce /C come array di riferimenti indiretti a dizionari comando, non come array di dizionari. Se infili un dizionario diretto in quell'array ottieni qualcosa che si analizza come PDF valido ma non significa nulla come record Web Capture: un viewer o uno strumento di archiviazione che scorre la lista dei comandi trova un oggetto inline dove dovrebbe esserci un riferimento, e la voce semplicemente non è un comando. PDF Library for Delphi gestisce tutto dentro AddWebCaptureCommand: costruisce il dizionario, lo passa a OnNewIndObj per ottenere numero di oggetto e numero di generazione, avvolge la coppia in un riferimento indiretto e aggiunge il riferimento all'array. Se OnNewIndObj non produce un numero di oggetto positivo, la funzione restituisce 0 e l'array resta intatto. Il motivo per cui la spec insiste sull'indirezione è che un singolo comando può essere condiviso — lo stesso fetch può essere referenziato da più punti in una struttura Web Capture — e gli oggetti condivisi in PDF sono, per definizione, quelli indiretti
Come si aggiunge un comando Web Capture da Delphi?
Compila per intero il record e poi chiama AddWebCaptureCommand, che restituisce l'indice del nuovo comando a base uno, oppure 0 se la validazione lo rifiuta. Qui la completezza conta più del solito. Delphi inizializza a stringa vuota i campi AnsiString gestiti di un record locale, ma i campi Integer — Levels e Flags — non ricevono lo stesso trattamento, quindi un record che dimentichi di compilare arriva portandosi dietro qualunque cosa ci fosse nello stack. AddWebCaptureCommand richiede Levels almeno 1 e Flags non negativo, e un valore spazzatura dallo stack verrà o rifiutato secco o, peggio, accettato in silenzio come una profondità dall'aria plausibile
var
Lib: TPDFlib;
Command: TPDFlibWebCaptureCommand;
Index: Integer;
begin
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile('captured-site.pdf', '') = 1 then
begin
// Compila ogni campo: Levels e Flags sono Integer semplici e
// si portano dietro spazzatura dallo stack se li salti.
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 := '';
// Restituisce l'indice del comando a base uno, oppure 0 se rifiutato.
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;
Anche la prima chiamata andata a buon fine è quella che crea la struttura. AddWebCaptureCommand chiede l'array dei comandi all'helper interno WebCaptureCommands(True), e quell'helper crea /SpiderInfo con la sua voce /V e poi un array /C vuoto solo se mancano. È volutamente conservativo su ciò che trova: se il catalogo ha già una chiave /SpiderInfo il cui valore non è un dizionario, o una chiave /C il cui valore non è un array, l'helper restituisce nil invece di sovrascrivere l'oggetto esistente. Una struttura ereditata malformata fa fallire l'aggiunta; non fa distruggere a PDF Library for Delphi qualunque cosa abbia scritto il produttore precedente
La whitelist dei flag: tre bit definiti e nient'altro
AddWebCaptureCommand valida Flags contro una whitelist e rifiuta qualsiasi valore con un bit impostato fuori dalle tre costanti che PDF Library for Delphi definisce: PDF_WEB_CAPTURE_SAME_SITE = 1 (segui solo i link che restano sullo stesso sito), PDF_WEB_CAPTURE_SAME_PATH = 2 (segui solo i link sotto lo stesso path) e PDF_WEB_CAPTURE_SUBMIT = 4 (la richiesta era un invio di form, non un semplice recupero). Passa 8, 16 o un -1 per sbaglio, e la chiamata restituisce 0 senza toccare il documento
È più severo di quanto lo siano di solito i parser PDF, e di proposito. /F è un campo di bit senza struttura auto-descrittiva, quindi un bit non definito è indistinguibile da uno corrotto, e un consumatore che in seguito rilegge il valore non ha modo di distinguere un'estensione futura da un refuso. Rifiutare in scrittura tiene l'ambiguità del tutto fuori dal file. Nota anche che /P, /CT e /H vengono scritti solo quando i campi record corrispondenti non sono vuoti, quindi un normale GET produce un dizionario comando a tre chiavi — /URL, /L, /F — senza segnaposto a stringa vuota a ingombrarlo, che è ciò che un lettore che analizza il file si aspetta per una richiesta senza body
// Un invio di form: body POST più il suo content type.
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');
// Stesso sito più stesso path è una combinazione legale: 1 oppure 2 = 3.
Command.Flags := PDF_WEB_CAPTURE_SAME_SITE or PDF_WEB_CAPTURE_SAME_PATH;
// Il bit 3 non è definito dalla spec, quindi questo non arriva mai al file.
Command.Flags := 8;
if Lib.AddWebCaptureCommand(Command) <> 0 then
raise Exception.Create('undefined flag bit should have been refused');
Rileggere i comandi: una chiave, due rappresentazioni legali
GetWebCaptureCommand restituisce 1 in caso di successo e 0 altrimenti, e deve essere più tollerante dello scrittore, perché legge file scritti da altri produttori. Contano due asimmetrie. Prima: il lettore accetta una voce di comando sia come riferimento indiretto sia come dizionario diretto, dereferenziando quando serve — PDF Library for Delphi scrive sempre la forma indiretta richiesta dalla spec, ma rifiutarsi di leggere un file sciatto non giova a nessuno. Seconda: /P può essere una stringa oppure uno stream, e questa generalizza ben oltre Web Capture. ISO 32000-1 permette ripetutamente a una singola chiave di contenere una stringa o uno stream quando il valore può essere arbitrariamente grande — i body POST qui, e lo stesso schema compare altrove nel formato per metadati e payload incorporati. L'helper interno ReadString in GetWebCaptureCommand prende quindi un flag AllowStream, e solo la lettura di /P passa True: un valore a stream viene decodificato attraverso la sua catena di filtri e restituito nel campo PostData, mentre /URL, /CT e /H restano solo stringa perché la spec non offre loro l'alternativa a stream. Se scrivi del tuo codice di lettura PDF, è l'abitudine che vale la pena rubare: controlla nella spec quali chiavi sono a doppio tipo invece di assumere che sia una stringa
var
Lib: TPDFlib;
Command: TPDFlibWebCaptureCommand;
I, Total, Removed: Integer;
begin
Total := Lib.GetWebCaptureCommandCount;
// L'indicizzazione pubblica è a base uno, come nel resto di 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)]));
// Rimuove l'intero dizionario /SpiderInfo e riporta quanti
// comandi sono stati rimossi; restituisce 0 quando non c'era nulla da pulire.
Removed := Lib.ClearWebCaptureCommands;
Writeln(Format('cleared %d command(s)', [Removed]));
end;
ClearWebCaptureCommands conta i comandi e poi elimina l'intera chiave /SpiderInfo dal catalogo invece di limitarsi a svuotare /C. Lasciare un /SpiderInfo con un array di comandi vuoto affermerebbe che il documento ha una provenienza Web Capture e che non è stato recuperato nulla, che è un'asserzione diversa e falsa rispetto a non avere affatto provenienza registrata. Lo stesso istinto vale in generale per i metadati del catalogo: un dizionario guscio vuoto è una dichiarazione, non un residuo neutro. Se stai verificando ciò che un documento già dichiara prima di riscriverlo, il pattern di introspezione in rileggere azioni di outline e annotazioni da un PDF esistente si abbina naturalmente a un passaggio di lettura-e-pulizia qui
Scrivere metadati non è implementare Web Capture
Sii spietato con te stesso sul perimetro: AddWebCaptureCommand salva il record di un fetch previsto o completato, e fa solo questo. PDF Library for Delphi non esegue richieste HTTP, non segue i link che descrive il tuo valore Levels e non onora i flag SAME_SITE o SAME_PATH — quei flag documentano come si è comportato qualche crawler, non sono istruzioni che la libreria esegue. Il tuo codice di fetching, qualunque sia, resta interamente un problema tuo
Anche altri due pezzi di ISO 32000-1 §14.10 restano fuori da questa API. Web Capture definisce i content set — le strutture /Contents che legano i singoli pezzi di pagina al materiale sorgente da cui provengono — e un name tree che mappa identificatori digitali su quei content set. PDF Library for Delphi non mantiene né l'uno né l'altro. Quello che ottieni è la lista dei comandi: la risposta di primo livello a «da dove è stato scaricato questo documento, e quanto in profondità è andato il crawl», che per provenienza, audit e acquisizione archivistica è di solito la parte a cui tutti chiedono davvero. Se ti servono URL cliccabili per i lettori invece che solo registrati per gli auditor, è un altro lavoro — per il lato azioni vedi costruire azioni GoToR, GoToE e Launch, e i trigger delle azioni di ciclo di vita di documento e pagina per il comportamento all'apertura o alla chiusura. I metadati dei comandi /SpiderInfo del catalogo come descritti qui fanno parte di PDF Library for Delphi, la libreria PDF nativa per Delphi e C++Builder