Ir al contenido

Ast: árbol semántico de documento y serialización

El módulo Ast es el árbol semántico de documento del motor. Modela un documento como una jerarquía de nodos tipados — Document, Section, Heading, Paragraph, List, Table, Figure, Code, FormField — con cajas delimitadoras, anclas de citación y una serialización JSON versionada. La capa de etiquetado de accesibilidad lo consume para producir un árbol de estructura.

Estabilidad: experimental. Esta es una superficie de modelo interno. Sus clases no ofrecen garantías de una API pública congelada por versión, y el conjunto de nodos y los atributos de los nodos evolucionan. El esquema de serialización se versiona de forma independiente (AstDocument::CURRENT_SCHEMA_VERSION = '1.0.0'). El serializador puede detectar y rechazar un esquema incompatible, de modo que el JSON del AST persistido tiene un contrato estable incluso cuando la API en memoria no lo tiene.

Ventana de terminal
composer require nextpdf/core:^3

En este contexto, un AST es una abstracción semántica sobre la estructura lógica de un documento, no un árbol de análisis sintáctico para un formato de entrada concreto. AstDocument es el contenedor. Incluye el AstNode raíz (que debe ser NodeType::Document), una versión de esquema, un hash del PDF de origen y un recuento de páginas. Rechaza construcciones no válidas (versión de esquema vacía, recuento de páginas inferior a uno, tipo de raíz incorrecto).

AstNode es el nodo recursivo. NodeType enumera los tipos semánticos. Un nodo contiene hijos, un BoundingBox opcional, contenido de texto opcional y atributos validados por esquema mediante NodeAttributeSchema. La API del nodo está concebida para la derivación inmutable. withBboxAndText() devuelve un nuevo nodo. deepClone() copia un subárbol. NodeId representa la identidad como objeto de valor. CitationAnchor vincula un nodo a una ubicación de origen para su trazabilidad. AstNodeCollection es un conjunto Countable/IteratorAggregate con filtrado ofType().

AstSerializer es la frontera de persistencia. serialize() escribe un AstDocument en JSON. deserialize() lo vuelve a cargar. canDeserialize() y extractSchemaVersion() permiten que un consumidor compruebe la compatibilidad antes de analizar, de modo que una discrepancia de esquema sea una condición detectada y no una carga corrupta. AstDocument::estimateTokenCount() existe porque el árbol también se usa para dimensionar el contenido para el procesamiento posterior limitado por tokens.

ClaseMiembros claveFunción
AstDocumenttoJson(), nodeCount(), estimateTokenCount(), CURRENT_SCHEMA_VERSIONContenedor raíz; valida el tipo de raíz y el esquema
AstNodeaddChild(), children(), childCount(), totalNodeCount(), withBboxAndText(), deepClone()Nodo semántico recursivo
NodeType (enum)Document, Heading, Table, Figure, FormField, …Tipo de nodo semántico
AstNodeCollectionadd(), count(), isEmpty(), ofType(), toArray()Conjunto de nodos iterable y filtrable por tipo
AstSerializerserialize(), deserialize(), canDeserialize(), extractSchemaVersion()Persistencia JSON versionada
BoundingBoxtoArray(), equals()Objeto de valor geométrico (comparación con épsilon)
NodeId / CitationAnchortoString(), equals(), toArray()Identidad de nodo y ancla de trazabilidad de origen
NodeAttributeSchemavalidación de atributosEsquema de los atributos de nodo

Ejecutar composer docs:generate-api-php -- --module=Ast para obtener la tabla completa de PHPDoc.

Construir un árbol pequeño y serializarlo.

<?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 */);

Realizar un ciclo de ida y vuelta del AST persistido de forma defensiva, comprobando la compatibilidad de esquema antes de deserializar JSON no confiable.

<?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 requiere que el nodo raíz sea NodeType::Document. Un árbol con cualquier otra raíz lanza una excepción durante la construcción.
  • AstNode::withBboxAndText() y deepClone() devuelven nuevas instancias. Los mutadores de nodo existentes (addChild()) sí mutan; los ayudantes de derivación, no. Conviene saber cuál se está llamando.
  • Proteger siempre deserialize() con canDeserialize() para JSON de origen externo. Una discrepancia de versión de esquema es una condición detectable y esperada.
  • estimateTokenCount() es una estimación para dimensionar el procesamiento posterior, no un recuento exacto del tokenizador. No debe tratarse como autoritativo.
  • BoundingBox::equals() es una comparación con épsilon (predeterminado 0.001). El contrato no es la igualdad exacta de coma flotante.

La construcción y el recorrido del árbol son O(n) respecto del recuento de nodos. La serialización es lineal respecto del tamaño del árbol. El perfil de reproducibilidad es bitwise. El mismo árbol se serializa en los mismos bytes JSON, lo que convierte al esquema en un contrato de persistencia estable. La carga de trabajo de referencia predeterminada queda holgadamente dentro del presupuesto de 1500 ms de tiempo de pared / 64 MB de pico.

AstSerializer::deserialize() analiza JSON que puede haberse persistido o transmitido. Validar primero la compatibilidad con canDeserialize(). Tratar el contenido de texto y los atributos del árbol deserializado como cadenas no confiables cuando vuelvan a entrar en la aplicación o se representen. El módulo en sí no realiza E/S ni incorpora datos externos. Consultar el modelo de amenazas del motor en /modules/core/security/.

Este módulo no declara cumplir ningún requisito normativo de la especificación PDF. El AST semántico es una abstracción interna del motor. No implementa un modelo de documento estandarizado cuyas cláusulas deban citarse. Cuando el AST alimenta el etiquetado de accesibilidad, la conformidad PDF/UA y PDF etiquetado de la salida se documenta y valida en /modules/core/accessibility/ y /modules/core/conformance/, no aquí.