跳到內容

Ast:語意文件樹與序列化

Ast 模組提供引擎使用的語意文件樹。它會將文件建模為具型別的節點階層,包括 DocumentSectionHeadingParagraphListTableFigureCodeFormField,並包含定界框、引文錨點,以及具版本控管的 JSON 序列化。無障礙標記層會用它產生結構樹。

穩定度:實驗性。 這是內部模型介面。各類別並不承載已凍結版本的公開 API 保證,節點集合與節點屬性也會持續演進。不過,序列化結構描述會獨立進行版本控管 (AstDocument::CURRENT_SCHEMA_VERSION = '1.0.0')。序列化器能偵測並拒絕不相容的結構描述,因此即使記憶體內 API 並不穩定, 持久化的 AST JSON 仍有穩定契約。

Terminal window
composer require nextpdf/core:^3

此處的 AST 是文件邏輯結構的語意抽象,而不是針對單一輸入格式的剖析語法樹。AstDocument 是容器,持有根 AstNode(必須是 NodeType::Document)、結構描述版本、來源 PDF 雜湊值與頁數。它會拒絕無效建構(結構描述版本為空、頁數小於一、根型別錯誤)。

AstNode 是遞迴節點。NodeType 列舉各種語意種類。節點會帶有子節點、選用的 BoundingBox、選用的文字內容,以及透過 NodeAttributeSchema 進行結構描述驗證的屬性。節點 API 是為不可變衍生設計。withBboxAndText() 會回傳新節點。deepClone() 會複製子樹。NodeId 是代表身分的值物件。CitationAnchor 會將節點繫結到來源位置,以便追溯。AstNodeCollectionCountable/IteratorAggregate 集合,並支援使用 ofType() 過濾。

AstSerializer 是持久化邊界。serialize() 會將 AstDocument 寫成 JSON。deserialize() 會把它讀回來。canDeserialize()extractSchemaVersion() 讓使用端能在剖析前先檢查相容性,因此結構描述不符會被偵測出來,而不是變成一次損毀的載入。AstDocument::estimateTokenCount() 之所以存在,是因為這棵樹也用於為下游受 token 數量限制的處理估算內容大小。

類別主要成員角色
AstDocumenttoJson(), nodeCount(), estimateTokenCount(), CURRENT_SCHEMA_VERSION根容器;驗證根型別與結構描述版本
AstNodeaddChild(), children(), childCount(), totalNodeCount(), withBboxAndText(), deepClone()遞迴語意節點
NodeType(列舉)Document, Heading, Table, Figure, FormField, …語意節點種類
AstNodeCollectionadd(), count(), isEmpty(), ofType(), toArray()可疊代、可依型別過濾的節點集合
AstSerializerserialize(), deserialize(), canDeserialize(), extractSchemaVersion()具版本控管的 JSON 持久化
BoundingBoxtoArray(), equals()幾何值物件(epsilon 比較)
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 */);

在反序列化不受信任的 JSON 前,先檢查結構描述相容性,並以防禦性方式處理持久化 AST 的往返流程。

<?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())會就地修改既有實例;衍生輔助方法則不會。請確認自己呼叫的是哪一種。
  • 對於外部來源的 JSON,請務必以 canDeserialize() 來把關 deserialize()。結構描述版本不符是可偵測且預期中的狀況。
  • estimateTokenCount() 是用來為下游處理估算大小的估計值,並非精確的 tokenizer 計數。請勿將它視為權威依據。
  • BoundingBox::equals() 是一種 epsilon 比較(預設 0.001)。精確的浮點相等並非其契約。

樹的建構與走訪複雜度相對於節點數量為 O(n)。序列化也與樹大小呈線性關係。它的可重現性設定檔為 bitwise。同一棵樹會序列化成相同的 JSON 位元組,這也是結構描述能成為穩定持久化契約的原因。預設參考工作負載遠低於 1500 ms 牆鐘時間 / 64 MB 峰值預算。

AstSerializer::deserialize() 所剖析的 JSON 可能曾被持久化或傳輸。請先以 canDeserialize() 驗證相容性。當反序列化後樹中的文字內容與屬性再次進入應用程式或被算繪時,請將它們視為不受信任的字串。此模組本身不進行任何 I/O,也不內嵌任何外部資料。請參閱 /modules/core/security/ 中的引擎威脅模型。

此模組不對任何 PDF 規格提出規範性聲明。此語意 AST 是引擎內部抽象。它並未實作任何必須引用其條款的標準化文件模型。當 AST 餵入無障礙標記時,其輸出的 PDF/UA 與標記式 PDF 符合性會記錄並驗證於 /modules/core/accessibility//modules/core/conformance/,而非此處。