Pro 版本
AST — 深度参考
本页是 Pro AST 模块的深度参考。它涵盖公开的构建、缓存、变更、写入与写出接口面,它们的行为契约以及失败模式。该模块将已加载的 PDF 解析为不可变的 AstDocument 树,应用带日志的内存内变更,并写入基于叠加层的增量更新。AstDocument 与 AstNode 是 NextPDF\Ast 命名空间中的 Core 值类型;本模块生产并消费它们。
可用性与授权
标题为“可用性与授权”的章节该能力随 NextPDF Pro(nextpdf/pro)发布,并通过 Pro 层级授权信封激活。缺少该权益的部署不会加载该能力的类。对比版本并获取授权。
不存在逐功能的授权标记。这是一项 Pro 版本能力。构建行为完全由 AstBuildOptions 管辖。
公共 API 接口面
标题为“公共 API 接口面”的章节| 符号 | 参数 | 默认行为 | 返回 | 抛出或失败于 | 备注 |
|---|---|---|---|---|---|
AstBuilder::__construct | PdfReader $reader, AstBuildOptions $options, ?AstCache $cache = null | 将已加载的 reader 绑定到构建选项;缓存为可选 | AstBuilder | — | null 缓存意味着每次 build() 调用都会重建。 |
AstBuilder::build | string $sourceHash(PDF 字节的完整 SHA-256 十六进制) | 缓存查找、加密拒绝、结构树路径、未标记回退、包围盒附加、缓存存储 | AstDocument | AstUnsupportedEncryptionException, AstBuildLimitException, AstBuildTimeoutException | 缓存命中时无需重新解析即返回。 |
AstBuildOptions::__construct | ?int $pageRangeStart = null, ?int $pageRangeEnd = null, int $maxNodes = 100_000, int $maxDepth = 200, ?int $estimatedTokenBudget = null, int $maxMemoryBytes = 268435456, float $timeoutSeconds = 30.0, bool $useHeuristic = false | 不可变的配置值对象 | AstBuildOptions | — | estimatedTokenBudget 是信息性提示;它不会被强制执行。 |
AstBuildOptions::pageRangeContains | int $pageIndex | 当 0 基索引落在已配置范围内时为 True | bool | — | null 边界是开放的;两者均为 null 意味着所有页面。 |
AstBuildOptions::hash | — | 对所有选项值稳定的 SHA-256 | string | — | 相等的值在各实例间产生相等的哈希;用作缓存键分段。 |
AstCache::__construct | CacheInterface $backend | 封装任意 PSR-16 后端 | AstCache | — | — |
AstCache::buildKey | string $sourceHash, AstBuildOptions $options | 键 = nextpdf_ast_v1_ + 源哈希前 32 位十六进制 + _ + 选项哈希前 16 位十六进制 | string | — | 选项更改会自动使已缓存结果失效。 |
AstCache::get | string $cacheKey | 通过严格的逐字段校验解码 JSON 载荷 | ?AstDocument | 从不抛出;失败返回 null | 畸形或被篡改的载荷会以缓存未命中的方式安全失败。 |
AstCache::set | string $cacheKey, AstDocument $document | 以 24 小时 TTL 存储 JSON,随后通过立即回读进行验证 | void | AstWriteVerificationException(Exception 命名空间) | 后端写入失败或往返失败时抛出。 |
AstCache::delete | string $cacheKey | 尽力而为的移除 | void | 从不抛出 | 后端删除失败会被吞掉。 |
AstCache::has | string $cacheKey | 尽力而为的存在性检查 | bool | 从不抛出;失败返回 false | — |
AstMutator::updateNode | AstDocument $document, string $nodeId, array $updates | 替换 text_content,记录一条 Updated 条目 | AstDocument(新实例) | InvalidArgumentException | 仅应用 text_content 键;未知键被忽略。 |
AstMutator::deleteNode | AstDocument $document, string $nodeId | 从内存内树中移除该节点,记录一条 Deleted 条目 | AstDocument(新实例) | InvalidArgumentException | 仅内存内移除;参见下文的编辑遮蔽(redaction)注意事项。 |
AstMutator::getMutationLog | — | 返回共享的日志实例 | MutationLog | — | 将同一日志传给 AstWriter。 |
AstMutator::resetLog | — | 丢弃所有已记录的变更 | void | — | 开启一个全新的日志。 |
MutationLog | record, all, isEmpty, count, forNode, mutatedNodeIds | 仅追加的内存内日志,保留插入顺序 | 逐方法 | — | forNode 返回某节点最近的一条条目;最后一条条目胜出。 |
MutationEntry::__construct | string $nodeId, MutationType $type, ?AstNode $originalNode, ?AstNode $mutatedNode, DateTimeImmutable $timestamp | 单次变更的不可变记录 | MutationEntry | — | 对于 Inserted,originalNode 为 null;对于 Deleted,mutatedNode 为 null。 |
MutationType | 枚举分支 Updated, Inserted, Deleted | 字符串支撑的分类 | — | — | OVERLAY 下的 Deleted 遮蔽内容;它不会擦除字节。 |
AstWriter::write | string $originalPdfBytes, MutationLog $log | 追加一次增量更新,其叠加层流覆盖被变更的包围盒 | string(修改后的 PDF 字节) | AstWriteException | 空日志会原样返回输入。Inserted 条目以及没有包围盒的条目会被跳过。 |
AstWriter::writeAndVerify | string $originalPdfBytes, MutationLog $log | 运行 write(),随后进行一次结构性输出检查 | string(已验证的 PDF 字节) | AstWriteException, AstWriteVerificationException(Writer 命名空间) | 验证是结构性的,而非语义性的。 |
AstPdfEmitter::emit | AstNode $root, BinaryBuffer $buffer, ObjectRegistry $registry, array $pageObjects | 为所提供的树写入 StructTreeRoot、StructElem 链与 ParentTree | EmitResult | AstEmitException | 根必须是带子节点的 Document 节点。用于结构树验证的往返写出器。 |
EmitResult::__construct | int $structTreeRootObject, int $rootElementObject, int $parentTreeObject, int $elementObjectCount, int $parentTreeNextKey | 已写出对象标识符的不可变记录 | EmitResult | — | — |
public function build(string $sourceHash): AstDocumentpublic function updateNode(AstDocument $document, string $nodeId, array $updates): AstDocumentpublic function deleteNode(AstDocument $document, string $nodeId): AstDocumentpublic function write(string $originalPdfBytes, MutationLog $log): stringpublic function writeAndVerify(string $originalPdfBytes, MutationLog $log): string异常层级
标题为“异常层级”的章节NextPDF\Pro\Ast\Exception\AstExceptionextendsRuntimeException— 构建层级的基类。AstBuildLimitExceptionextendsAstException— 超出了节点、深度或内存上限。AstBuildTimeoutExceptionextendsAstBuildLimitException— 墙钟构建超时已到。AstNoStructTreeExceptionextendsAstException— 不存在结构树。AstBuilder::build()在内部捕获它并回退;build()的调用方观察不到它。AstUnsupportedEncryptionExceptionextendsAstException— 输入 PDF 已加密。NextPDF\Pro\Ast\Exception\AstWriteVerificationExceptionextendsAstException— 缓存写入验证失败。NextPDF\Pro\Ast\Writer\AstWriteExceptionextendsRuntimeException— 写入器输入或结构失败。NextPDF\Pro\Ast\Writer\AstWriteVerificationExceptionextendsAstWriteException— 写入后的结构验证失败。
存在两个位于不同命名空间的独立 AstWriteVerificationException 类。AstCache::set() 抛出 Exception 命名空间的类;AstWriter::writeAndVerify() 抛出 Writer 命名空间的类。请在 catch 子句中匹配命名空间。
行为契约
标题为“行为契约”的章节AstBuilder::build($sourceHash) 需要源字节的完整 SHA-256 十六进制。流水线为:可选的缓存查找、加密拒绝、结构树路径、未标记回退、包围盒附加、可选的缓存存储。
缓存键将源哈希与 AstBuildOptions 哈希组合在一起。选项哈希在具有相同值的各实例间保持稳定,因此相同的输入与选项会返回相同的树。当未提供缓存时,每次调用都会重建。已缓存的载荷是 JSON,绝非原生 PHP 序列化:读取路径校验每个字段并只实例化 AST 值类型,因此被投毒的缓存条目无法触发对象注入,只会退化为缓存未命中。
结构树路径在结构树存在时运行。资源上限——节点数、深度、内存增量与墙钟时间——在结构树读取期间强制执行,并抛出 AstBuildLimitException 或 AstBuildTimeoutException。如果 reader 报告没有结构树,构建器会切换到未标记路径:当 useHeuristic 为 true 时使用启发式构建器,否则使用裸回退构建器。包围盒通过分析每个范围内页面的内容流来附加;一个内容流无法被解析的页面会被跳过,并让树的其余部分保持完整。
AstNode 是不可变的。树更新自底向上重建受影响的节点;未改变的子树按标识返回。AstMutator 遵循同一契约:每次变更返回一个新的 AstDocument,只重建从根到目标的路径,并在共享的 MutationLog 中记录一条 MutationEntry。
AstWriter 在 OVERLAY 模式下以仅追加的增量更新应用 MutationLog:新的叠加层内容流、更新后的页面对象、仅覆盖新对象的交叉引用段,以及其 /Prev 指向先前 startxref 的尾部。原始字节保持完整,符合 ISO 32000-2:2020, 7.5.6 的增量更新模型。为 Updated 条目绘制的替换文本会在字面字符串中转义 \、( 与 ),符合 ISO 32000-2:2020, 7.3.4.2。
AstPdfEmitter::emit() 是结构树读取的对称逆过程:由 reader 生成的树往返为结构上等价的树,除节点 id 重新编号与已记录的规范化类别外。节点上存在的 MCID 会被逐字重新写出,绝不重新分配。
边界情形与失败模式
标题为“边界情形与失败模式”的章节- 加密输入在任何树工作之前即被拒绝;对于加密 PDF 没有部分树结果。请先解密。
- 资源上限:最大节点数(默认 100,000)、最大深度(默认 200)、最大内存(默认 256 MiB)、墙钟超时(默认 30 s)。超出某个上限会抛出
AstBuildLimitException;超时会抛出其子类AstBuildTimeoutException。 - 页码范围是从 0 起算且包含端点的;null 边界意味着所有页面。
- 一个内容流无法被解析的页面会在包围盒附加期间被跳过;树的其余部分不受影响。
AstCache::get()从不抛出:畸形、被篡改或非字符串的载荷会返回 null 并强制重建。AstCache::set()在后端写入或立即回读失败时会显式抛出。- 当节点 id 未找到时,
AstMutator抛出InvalidArgumentException。未知的更新键被静默忽略;仅应用text_content。 - 当输入缺少
%PDF-头或可定位的startxref时,AstWriter::write()抛出AstWriteException。没有包围盒的条目会被静默跳过。无法通过对象扫描定位的页面——例如在压缩交叉引用流之下——会被跳过;如果无法应用任何叠加层,则原样返回输入字节。 - OVERLAY 输出不是编辑遮蔽(redaction)。白色矩形与重绘的文本是被追加的;原始内容字节仍保留在文件中,并可通过原始提取恢复。请勿将其用于 GDPR 第 17 条的擦除或法律编辑遮蔽。源码树中存在一个重建模式(reconstruct-mode)写入器,但它被标记为内部使用、尚未达到生产就绪,且不在受支持的 API 接口面之内。
- 叠加层几何假定 A4 纵向(595 x 842 pt),因为写入器不读取页面 MediaBox。在非 A4 页面上叠加层可能略有错位;输出仍在结构上有效。
writeAndVerify()仅检查结构:头部、尾随的%%EOF以及输出增长。它不会在语义上重新解析被变更的文档。- 当根不是 Document 节点或没有子节点时,
AstPdfEmitter::emit()抛出AstEmitException。本次发布中不写出 OBJR(注释)伴随条目。 - 本模块不执行任何密码学操作,也不定义任何 FIPS 专属行为。SHA-256 仅作为缓存键的内容寻址出现。
符合性
标题为“符合性”的章节结构树路径读取 ISO 32000-2 定义的 tagged-PDF 逻辑结构设施;撰写时可用的 RAG 语料库不包含逻辑结构条款,因此该陈述基于来源标注、以产品为依据。写入器的增量更新布局遵循 ISO 32000-2:2020, 7.5.6(下文引用),其字面字符串转义遵循 ISO 32000-2:2020, 7.3.4.2(下文引用)。
这些陈述描述的是相对于所引用条款的能力。NextPDF 不持有任何符合性认证,对某条款的支持并非认证主张。
开发说明
标题为“开发说明”的章节- 为每个已加载的
PdfReader组合一个AstBuilder。跨构建复用一个AstCache以摊薄解析成本;键的设计使选项更改自失效。 - 在
AstMutator与AstWriter之间共享一个MutationLog,以便写入器恰好应用所记录的会话。在各独立编辑会话之间调用resetLog()。 - 对于未标记文档,当基于布局推导的分组优于裸回退树时,将
useHeuristic设为 true。 - 对相同的字节与选项,构建是确定性的;可在快照式测试中依赖这一点。
- 通过
NextPDF\Pro\Ast\Exception层级捕获构建失败,通过NextPDF\Pro\Ast\Writer层级捕获写入失败;两者在RuntimeException之下不共享基类。
发布边界
标题为“发布边界”的章节本页仅记录外部可观察的行为与受支持的公共 API 接口面。内部命名空间路径、辅助类、机制表、运行手册文件名以及工单前缀均不在范围之内。