Перейти к содержимому

Ast: семантическое дерево документа и сериализация

Модуль Ast предоставляет семантическое абстрактное синтаксическое дерево документа (AST) для движка. Он моделирует документ как типизированную иерархию узлов: Document, Section, Heading, Paragraph, List, Table, Figure, Code и FormField. Модель фиксирует ограничивающие прямоугольники и якоря цитирования и сериализуется в версионированный формат JavaScript Object Notation (JSON). Слой тегирования доступности использует это дерево для построения дерева структуры.

Стабильность: экспериментальная. Это внутренняя часть модели. Её классы не гарантируют публичный программный интерфейс (API), зафиксированный для версии. Набор узлов и атрибуты узлов могут измениться. Схема сериализации версионируется независимо (AstDocument::CURRENT_SCHEMA_VERSION = '1.0.0'). Сериализатор обнаруживает и отклоняет несовместимую схему, поэтому сохранённый AST JSON остаётся стабильным контрактом даже при изменении API в памяти.

Окно терминала
composer require nextpdf/core:^3

Здесь AST представляет логическую структуру документа. Это не синтаксическое дерево парсера для конкретного входного формата. AstDocument — это контейнер. Он содержит корневой AstNode (который должен быть NodeType::Document), версию схемы, хэш исходного файла Portable Document Format (PDF) и число страниц. Он отклоняет некорректное построение, в том числе пустую версию схемы, число страниц меньше одной или неверный тип корня.

AstNode — это рекурсивный узел. NodeType перечисляет семантические виды. Узел содержит дочерние узлы, необязательный BoundingBox, необязательное текстовое содержимое и атрибуты, проверяемые с помощью NodeAttributeSchema. API узла поддерживает неизменяемое порождение. withBboxAndText() возвращает новый узел. deepClone() копирует поддерево. NodeId — это объект-значение, задающий идентичность. CitationAnchor связывает узел с местоположением в источнике для отслеживаемости. AstNodeCollection — это набор, реализующий Countable/IteratorAggregate, с фильтрацией через ofType().

AstSerializer — это граница сохранения. serialize() записывает AstDocument в JSON. deserialize() считывает его обратно. canDeserialize() и extractSchemaVersion() позволяют проверить совместимость перед разбором, поэтому несоответствие схемы становится обнаруживаемым состоянием, а не повреждённой загрузкой. AstDocument::estimateTokenCount() помогает оценить объём содержимого для последующей обработки, ограниченной по числу токенов.

КлассКлючевые членыНазначение
AstDocumenttoJson(), nodeCount(), estimateTokenCount(), CURRENT_SCHEMA_VERSIONКорневой контейнер; проверяет тип корня и схему
AstNodeaddChild(), children(), childCount(), totalNodeCount(), withBboxAndText(), deepClone()Рекурсивный семантический узел
NodeType (enum)Document, Heading, Table, Figure, FormField, …Семантический вид узла
AstNodeCollectionadd(), count(), isEmpty(), ofType(), toArray()Итерируемый набор узлов с фильтрацией по типу
AstSerializerserialize(), deserialize(), canDeserialize(), extractSchemaVersion()Версионированное сохранение в JSON
BoundingBoxtoArray(), equals()Объект-значение геометрии (сравнение с эпсилоном)
NodeId / CitationAnchortoString(), equals(), toArray()Идентичность узла и якорь отслеживаемости в источнике
NodeAttributeSchemaпроверка атрибутовСхема для атрибутов узла

Запустите composer docs:generate-api-php -- --module=Ast, чтобы сгенерировать полную таблицу PHPDoc.

Соберите небольшое дерево, а затем сериализуйте его.

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

Выполните полный цикл чтения-записи сохранённого AST с защитными проверками. Проверяйте совместимость схемы перед десериализацией недоверенного JSON.

<?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 требует, чтобы корневым узлом был NodeType::Document. При построении дерева с любым другим корнем выбрасывается исключение.
  • AstNode::withBboxAndText() и deepClone() возвращают новые экземпляры. Доступные мутаторы узла (addChild()) изменяют узел. Вспомогательные методы порождения — нет. Учитывайте, какой метод вызываете.
  • Всегда перед deserialize() вызывайте canDeserialize() для JSON из внешних источников. Несоответствие версии схемы — это обнаруживаемое, ожидаемое состояние.
  • estimateTokenCount() — это оценка для определения объёма последующей обработки, а не точный подсчёт токенизатора. Не считайте её авторитетной.
  • BoundingBox::equals() — это сравнение с эпсилоном (по умолчанию 0.001). Точное равенство чисел с плавающей точкой не является контрактом.

Построение и обход дерева выполняются за O(n) по числу узлов. Сериализация линейна относительно размера дерева. Профиль воспроизводимости — bitwise. Одно и то же дерево сериализуется в одни и те же байты JSON, благодаря чему схема остаётся стабильным контрактом сохранения. Эталонная нагрузка по умолчанию остаётся значительно ниже бюджета 1500 мс по времени / 64 МБ по пиковому потреблению.

AstSerializer::deserialize() разбирает JSON, который может быть сохранён или передан. Сначала проверьте совместимость с помощью canDeserialize(). Относитесь к текстовому содержимому и атрибутам десериализованного дерева как к недоверенным строкам при возврате в приложение или отображении. Сам модуль не выполняет input/output (I/O) и не встраивает внешние данные. См. модель угроз движка в /modules/core/security/.

Этот модуль не содержит нормативных заявлений о спецификации PDF. Семантический AST — это внутренняя абстракция движка. Он не реализует стандартизированную модель документа, разделы которой нужно цитировать. Там, где AST используется для тегирования доступности, соответствие вывода стандартам PDF/UA и tagged-PDF документируется и проверяется в /modules/core/accessibility/ и /modules/core/conformance/, а не здесь.