Salta ai contenuti

Ast: albero semantico del documento e serializzazione

Il modulo Ast è l’albero semantico del documento usato dal motore. Modella un documento come una gerarchia di nodi tipizzati — Document, Section, Heading, Paragraph, List, Table, Figure, Code, FormField — con riquadri di delimitazione, ancore di citazione e serializzazione JSON versionata. Il livello di marcatura per l’accessibilità lo usa per produrre un albero di struttura.

Stabilità: sperimentale. Questa è una superficie di modello interna. Le sue classi non offrono garanzie di API pubblica con versione congelata; l’insieme dei nodi e i relativi attributi sono destinati a evolvere. Lo schema di serializzazione è versionato in modo indipendente (AstDocument::CURRENT_SCHEMA_VERSION = '1.0.0'). Il serializzatore può rilevare e rifiutare uno schema incompatibile, così il JSON AST persistito mantiene un contratto stabile anche se l’API in memoria non offre la stessa garanzia.

Terminal window
composer require nextpdf/core:^3

In questo contesto, un AST è un’astrazione semantica della struttura logica di un documento, non un albero sintattico di parsing legato a un singolo formato di input. AstDocument è il container. Contiene il AstNode radice (che deve essere NodeType::Document), una versione di schema, un hash del PDF di origine e un conteggio di pagine. Rifiuta costruzioni non valide (versione di schema vuota, conteggio di pagine inferiore a uno, tipo di radice errato).

AstNode è il nodo ricorsivo. NodeType enumera i tipi semantici. Un nodo contiene figli, un BoundingBox opzionale, contenuto testuale opzionale e attributi validati dallo schema tramite NodeAttributeSchema. L’API del nodo è progettata per la derivazione immutabile. withBboxAndText() restituisce un nuovo nodo. deepClone() copia un sottoalbero. NodeId rappresenta l’identità come oggetto-valore. CitationAnchor collega un nodo a una posizione di origine per la tracciabilità. AstNodeCollection è un insieme Countable/IteratorAggregate filtrabile tramite ofType().

AstSerializer è il confine di persistenza. serialize() scrive un AstDocument in JSON. deserialize() lo rilegge. canDeserialize() ed extractSchemaVersion() consentono a un consumatore di verificare la compatibilità prima del parsing, così una mancata corrispondenza di schema diventa una condizione rilevata anziché un caricamento corrotto. AstDocument::estimateTokenCount() esiste perché l’albero viene usato anche per dimensionare il contenuto destinato all’elaborazione a valle vincolata dai token.

ClasseMembri chiaveRuolo
AstDocumenttoJson(), nodeCount(), estimateTokenCount(), CURRENT_SCHEMA_VERSIONContainer radice; valida tipo di radice e schema
AstNodeaddChild(), children(), childCount(), totalNodeCount(), withBboxAndText(), deepClone()Nodo semantico ricorsivo
NodeType (enum)Document, Heading, Table, Figure, FormField, …Tipo di nodo semantico
AstNodeCollectionadd(), count(), isEmpty(), ofType(), toArray()Insieme di nodi iterabile e filtrabile per tipo
AstSerializerserialize(), deserialize(), canDeserialize(), extractSchemaVersion()Persistenza JSON versionata
BoundingBoxtoArray(), equals()Oggetto-valore geometrico (confronto con epsilon)
NodeId / CitationAnchortoString(), equals(), toArray()Identità del nodo e ancora di tracciabilità verso l’origine
NodeAttributeSchemavalidazione degli attributiSchema per gli attributi dei nodi

Eseguire composer docs:generate-api-php -- --module=Ast per la tabella PHPDoc completa.

Costruire un piccolo albero e serializzarlo.

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Ast\AstNode;
use NextPDF\Ast\AstSerializer;
use NextPDF\Ast\NodeType;
$root = new AstNode(NodeType::Document);
$heading = new AstNode(NodeType::Heading);
$root->addChild($heading);
$root->addChild(new AstNode(NodeType::Paragraph));
echo "Nodes: {$root->totalNodeCount()}\n";
$json = (new AstSerializer())->serialize(/* an AstDocument wrapping $root */);

Round-trip difensivo di un AST persistito, con verifica della compatibilità dello schema prima di deserializzare JSON non attendibile.

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Ast\AstDocument;
use NextPDF\Ast\AstSerializer;
use Psr\Log\LoggerInterface;
final readonly class AstStore
{
public function __construct(
private AstSerializer $serializer,
private LoggerInterface $logger,
) {}
public function load(string $json): ?AstDocument
{
if (!$this->serializer->canDeserialize($json)) {
$this->logger->warning('AST JSON schema incompatible; rejected.', [
'found_schema' => $this->serializer->extractSchemaVersion($json),
'expected' => AstDocument::CURRENT_SCHEMA_VERSION,
]);
return null;
}
return $this->serializer->deserialize($json);
}
}
  • AstDocument richiede che il nodo radice sia NodeType::Document. Un albero con qualsiasi altra radice solleva un’eccezione durante la costruzione.
  • AstNode::withBboxAndText() e deepClone() restituiscono nuove istanze. I mutatori del nodo esistenti (addChild()) modificano lo stato. Gli helper di derivazione no. È importante sapere quale metodo si sta invocando.
  • Proteggere sempre deserialize() con canDeserialize() per il JSON proveniente da fonti esterne. Una mancata corrispondenza della versione di schema è una condizione rilevabile e prevista.
  • estimateTokenCount() è una stima per il dimensionamento dell’elaborazione a valle, non un conteggio esatto del tokenizzatore. Non trattarla come autorevole.
  • BoundingBox::equals() è un confronto con epsilon (predefinito 0.001). L’uguaglianza esatta tra float non è il contratto.

La costruzione e l’attraversamento dell’albero sono O(n) rispetto al conteggio dei nodi. La serializzazione è lineare rispetto alla dimensione dell’albero. Il profilo di riproducibilità è bitwise. Lo stesso albero viene serializzato negli stessi byte JSON: questo rende lo schema un contratto di persistenza stabile. Il carico di lavoro di riferimento predefinito rientra ampiamente nel budget di 1500 ms wall / 64 MB di picco.

AstSerializer::deserialize() effettua il parsing di JSON che può essere persistito o trasmesso. Validare prima la compatibilità con canDeserialize(). Trattare il contenuto testuale e gli attributi dell’albero deserializzato come stringhe non attendibili quando rientrano nell’applicazione o vengono renderizzati. Il modulo stesso non esegue alcun I/O e non incorpora dati esterni. Vedere il modello di minaccia del motore in /modules/core/security/.

Questo modulo non formula alcuna affermazione normativa su specifiche PDF. L’AST semantico è un’astrazione interna al motore. Non implementa un modello di documento standardizzato le cui clausole debbano essere citate. Quando l’AST alimenta la marcatura per l’accessibilità, la conformità PDF/UA e tagged-PDF dell’output è documentata e validata in /modules/core/accessibility/ e /modules/core/conformance/, non qui.