Aller au contenu

Ast : arbre sémantique du document et sérialisation

Le module Ast est l’arbre sémantique du document du moteur. Il modélise un document comme une hiérarchie de nœuds typés — Document, Section, Heading, Paragraph, List, Table, Figure, Code, FormField — avec des zones englobantes, des ancres de citation et une sérialisation JSON versionnée. La couche de balisage d’accessibilité s’en sert pour produire un arbre de structure.

Stabilité : expérimentale. Il s’agit d’une surface de modèle interne. Ses classes ne bénéficient pas de garanties d’API publique figées par version ; l’ensemble des nœuds ainsi que les attributs de nœud évoluent. Le schéma de sérialisation est versionné indépendamment (AstDocument::CURRENT_SCHEMA_VERSION = '1.0.0'). Le sérialiseur peut détecter et rejeter un schéma incompatible, de sorte que le JSON d’AST persisté a un contrat stable même lorsque l’API en mémoire ne l’a pas.

Fenêtre de terminal
composer require nextpdf/core:^3

Ici, un AST est une abstraction sémantique de la structure logique d’un document, et non un arbre syntaxique produit par un analyseur pour un seul format d’entrée. AstDocument est le conteneur. Il contient le AstNode racine (qui doit être NodeType::Document), une version de schéma, une empreinte du PDF source et un nombre de pages. Il rejette toute construction invalide (version de schéma vide, nombre de pages inférieur à un, type de racine erroné).

AstNode est le nœud récursif. NodeType énumère les genres sémantiques. Un nœud contient des enfants, un BoundingBox optionnel, un contenu textuel optionnel et des attributs validés par schéma via NodeAttributeSchema. L’API de nœud est conçue pour la dérivation immuable. withBboxAndText() renvoie un nouveau nœud. deepClone() copie un sous-arbre. NodeId porte l’identité sous forme d’objet-valeur. CitationAnchor relie un nœud à un emplacement source pour la traçabilité. AstNodeCollection est un ensemble Countable/IteratorAggregate filtrable par ofType().

AstSerializer est la frontière de persistance. serialize() écrit un AstDocument en JSON. deserialize() le relit. canDeserialize() et extractSchemaVersion() permettent à un consommateur de vérifier la compatibilité avant l’analyse, de sorte qu’une incompatibilité de schéma reste une condition détectée plutôt qu’un chargement corrompu. AstDocument::estimateTokenCount() existe parce que l’arbre sert aussi à dimensionner le contenu pour un traitement aval borné par les jetons.

ClasseMembres clésRôle
AstDocumenttoJson(), nodeCount(), estimateTokenCount(), CURRENT_SCHEMA_VERSIONConteneur racine ; valide le type de racine et le schéma
AstNodeaddChild(), children(), childCount(), totalNodeCount(), withBboxAndText(), deepClone()Nœud sémantique récursif
NodeType (énumération)Document, Heading, Table, Figure, FormField, …Genre de nœud sémantique
AstNodeCollectionadd(), count(), isEmpty(), ofType(), toArray()Ensemble de nœuds itérable et filtrable par type
AstSerializerserialize(), deserialize(), canDeserialize(), extractSchemaVersion()Persistance JSON versionnée
BoundingBoxtoArray(), equals()Objet-valeur géométrique (comparaison epsilon)
NodeId / CitationAnchortoString(), equals(), toArray()Ancre d’identité de nœud et de traçabilité de source
NodeAttributeSchemavalidation des attributsSchéma pour les attributs de nœud

Exécute composer docs:generate-api-php -- --module=Ast pour obtenir la table PHPDoc complète.

Construis un petit arbre et sérialise-le.

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

Effectue un aller-retour défensif sur un AST persisté, en vérifiant la compatibilité du schéma avant de désérialiser du JSON non fiable.

<?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 exige que le nœud racine soit NodeType::Document. Un arbre avec toute autre racine lève une exception à la construction.
  • AstNode::withBboxAndText() et deepClone() renvoient de nouvelles instances. Les mutateurs de nœud existants (addChild()) mutent. Les assistants de dérivation, eux, ne mutent pas. Vérifie lequel tu appelles.
  • Protège toujours deserialize() avec canDeserialize() pour du JSON provenant de l’extérieur. Une incompatibilité de version de schéma est une condition détectable et attendue.
  • estimateTokenCount() est une estimation destinée à dimensionner le traitement aval, pas un décompte exact de tokeniseur. Ne le considère pas comme faisant autorité.
  • BoundingBox::equals() est une comparaison epsilon (0.001 par défaut). L’égalité exacte des flottants n’est pas le contrat.

La construction et le parcours de l’arbre sont en O(n) par rapport au nombre de nœuds. La sérialisation est linéaire par rapport à la taille de l’arbre. Le profil de reproductibilité est bitwise. Un même arbre se sérialise vers les mêmes octets JSON, ce qui fait du schéma un contrat de persistance stable. La charge de référence par défaut reste largement dans le budget de 1500 ms en temps écoulé / 64 Mo en pic.

AstSerializer::deserialize() analyse du JSON susceptible d’avoir été persisté ou transmis. Valide d’abord la compatibilité avec canDeserialize(). Considère le contenu textuel et les attributs de l’arbre désérialisé comme des chaînes non fiables lorsqu’ils réintègrent l’application ou sont rendus. Le module lui-même n’effectue aucune E/S et n’intègre aucune donnée externe. Consulte le modèle de menaces du moteur dans /modules/core/security/.

Ce module n’affirme aucune revendication normative de spécification PDF. L’AST sémantique est une abstraction interne au moteur. Il n’implémente pas un modèle de document normalisé dont les clauses devraient être citées. Lorsque l’AST alimente le balisage d’accessibilité, la conformité PDF/UA et PDF balisé de la sortie est documentée et validée sur /modules/core/accessibility/ et /modules/core/conformance/, pas ici.