ARTICLE · 1132305
OFD 主入口与文档根节点:标准第 7 章细读
很多 OFD 问题最后都会回到两个文件:
OFD.xml 包的唯一主入口Document.xml 某个 DocBody 的文档根节点主入口决定“包里有哪些文书、签名和版本挂在哪”;文档根节点决定“这本文书如何组织页面、权限和资源”。GB/T 33190 第 7 章以及附录 A 的 Schema,对两者的元素顺序、必选性、类型和枚举都有硬约束。
本文按标准资料 OFD.xsd、Document.xsd、Definitions.xsd 细读这两层结构,说明每个字段的交换含义、常见生成错误,以及与 XSD / 语义校验的分工。
标准中的“应 / 可”与默认值,不等于具体阅读器一定会执行;本文不构成符合性认证。
一、两层入口各管什么
ZIP 包 └── OFD.xml 主入口:DocBody 列表 ├── DocInfo 文书元数据 ├── DocRoot ──────────> Document.xml ├── Versions? 版本入口 └── Signatures? 签名清单路径Document.xml ├── CommonData 页区、资源、模板、MaxUnitID ├── Pages 页树(顺序 = 页序) ├── Outlines? / Bookmarks? 导航 ├── Permissions? 权限声明 ├── Actions? / VPreferences? ├── Annotations? / CustomTags? └── Attachments? / Extensions?要点:
- 一个包只能有一个
OFD.xml,文件名不应修改。 DocBody可重复一包多文书(多文档体)。 每个 DocBody有自己的DocInfo、DocRoot,以及可选的版本与签名入口。Document.xml里可选节点很多,但 CommonData与Pages在结构上是骨架必选(Schema 中Pages无minOccurs=0)。
二、OFD.xml:标准与 Schema 怎么写
1. 根元素固定项
Schema(对应附录 A)要点:
<xs:elementname="OFD"><xs:sequence><xs:elementname="DocBody"maxOccurs="unbounded"> ... </xs:element></xs:sequence><xs:attributename="Version"type="xs:string"use="required"fixed="1.0" /><xs:attributename="DocType"use="required"fixed="OFD" /></xs:element>Version | ||
DocType | ||
DocBody | 至少 1 个 |
命名空间(标准资料与 Schema):
http://www.ofdspec.org/2016只写本地名、命名空间写错,XSD 与语义校验都会按“非 OFD 文档模型”处理。
最小合法骨架:
<?xml version="1.0" encoding="UTF-8"?><OFDxmlns="http://www.ofdspec.org/2016"Version="1.0"DocType="OFD"><DocBody><DocInfo><DocID>demo-001</DocID></DocInfo><DocRoot>Doc_0/Document.xml</DocRoot></DocBody></OFD>2. DocInfo:文书级元数据
Schema 中 CT_DocInfo必选子元素只有 DocID,其余均可选:
DocID 必选,文书标识Title? 标题Author? Subject? Abstract?CreationDate? ModDate? xs:dateDocUsage? 用途Cover? 封面 ST_LocKeywords? Keyword 1..nCreator? CreatorVersion?CustomDatas? Name + 文本值,用户自定义元数据交换时的实用含义:
DocID在包内应可区分不同文书多 DocBody时不要全部复制同一 ID(合并工具会对重复DocID做处理,生成端最好一开始就不重复)。Title常被阅读器用于标签页/大纲标题;空标题会退回文件名一类显示。 CreationDate/ ModDate是声明,不自动等于可信时间源;需要强时间证据仍要靠签名时间戳等机制。CustomDatas是标准内建的轻量扩展元数据,适合放与文书绑定的业务键;不要与第 16/17 章的标引/扩展混为一谈。
3. Versions:可选的版本入口
<Versions><VersionID="V1"Index="1"Current="true"BaseLoc="/Doc_0/Versions/V1.xml"/></Versions>ID | xs:ID |
Index | |
Current | true 标识当前版本 |
BaseLoc |
版本描述文件(Version.xsd / DocVersion)包含:
FileList/File+ 该版本涉及的文件(ST_Loc + ID)DocRoot 该版本的文档根属性:ID、Version、Name、CreationDate标准语义:因注释或其它改动产生的多版本,通过入口 + 版本文件描述“这一版有哪些文件、根在哪”。生成器若不用多版本,整段省略即可,不要留空的 Versions 却无 Version 子节点(会违反 maxOccurs/结构)。
4. Signatures:签名清单指针
<Signatures>Signatures/Signatures.xml</Signatures>类型 ST_Loc,指向签名清单,不是某一个Signature.xml。清单再指向各个签名文件——两级间接,便于一文书多签。 无签名时不要写空元素占位( minOccurs=0,应省略)。
5. 多 DocBody 的交换含义
DocBody[0] -> Doc_0/... 全局页 1..nDocBody[1] -> Doc_1/... 其后页码继续标准允许一包多文书;转换与阅读器通常按出现顺序展开为全局页序。生成端注意:
各 DocRoot路径基于包内定位写清。各文书资源默认按文档作用域理解,不要假设全包共享同一 ID 空间(字体等资源 ID 在不同文档体可重号,引用按文档解析)。 签名入口是 per DocBody 的,不是整包唯一一处。
三、Document.xml:文档根节点骨架
1. 元素顺序受 Schema sequence 约束
Document.xsd 中顺序示意:
CommonDataPagesOutlines?Permissions?Actions?VPreferences?Bookmarks?Annotations?CustomTags?Attachments?Extensions?生成器若随意打乱顺序,strict XSD 会失败。手工改 XML 时尤其容易把 Attachments 提到 Pages 前面。
2. CommonData:全局默认
<CommonData><MaxUnitID>100</MaxUnitID><PageArea><PhysicalBox>0 0 210 297</PhysicalBox></PageArea><PublicRes>PublicRes.xml</PublicRes><DocumentRes>DocumentRes.xml</DocumentRes><TemplatePageID="10"Name="Header"ZOrder="Background"BaseLoc="Templates/T0.xml"/><DefaultCS>1</DefaultCS></CommonData>MaxUnitID | 0 无效 ID | |
PageArea | PhysicalBox;缺页级 Area 时回退 | |
PublicRes | ||
DocumentRes | ||
TemplatePage | ZOrder | |
DefaultCS |
页面区域四盒(标准概念):
PhysicalBox 物理区域,必选,页面空间参考ApplicationBox 显示/打印区域ContentBox 版心BleedBox 出血,生产裁切可选盒缺失、越界时标准有处理规则;生成端不要把“四盒都写满”当成唯一合法,也不要写宽高 ≤ 0 的 Box(ST_Box 要求宽高 > 0)。
3. Pages:页序的唯一权威
<Pages><PageID="1"BaseLoc="Pages/Page_0/Content.xml"/><PageID="2"BaseLoc="Pages/Page_1/Content.xml"/></Pages>标准级事实:
- 顺序 = 阅读页序
,与文件夹名数字无关。 ID为 ST_ID(无符号整数),供动作、注释、StampAnnot@PageRef等引用。BaseLoc为 ST_Loc,相对当前文档根所在上下文解析。
常见错误:
生成时按字符串排序文件名,导致 Page_10插到Page_2前(若错误地用文件名排序)。Page@ID与对象 ID 撞号且作用域未分清。 BaseLoc写成相对 ZIP 根或相对错误的上级目录。
4. 导航:大纲与书签
大纲 Outlines(树):
OutlineElem @Title 必选 @Count? @Expanded 默认 true Actions? -> Action... OutlineElem* 递归子节点书签 Bookmarks(平表):
Bookmark @Name 必选 Dest -> CT_DestCT_Dest(Definitions.xsd):
@Type 必选:XYZ | Fit | FitH | FitV | FitR@PageID 必选:ST_RefID@Left @Top @Right @Bottom @Zoom 可选差别:
大纲适合章节树 + 激活动作。 书签适合命名位置快跳。 两者都通过 Dest/PageID挂到页 ID;页 ID 错则导航全错。
生成端应保证:大纲/书签引用的 PageID 必须存在于 Pages;未保留页(如抽页合并)要重写或丢弃失效目标。
5. Permissions:声明,不是 DRM
<Permissions><Edit>false</Edit><Annot>true</Annot><Export>false</Export><Signature>true</Signature><Watermark>true</Watermark><PrintScreen>true</PrintScreen><PrintPrintable="false"Copies="0"/><ValidPeriodStartDate="2026-01-01T00:00:00"EndDate="2027-01-01T00:00:00"/></Permissions>Schema 中布尔项 default 多为 true,且均可 minOccurs=0:省略 ≈ 默认允许。
标准与实现的边界:
节点表达文档作者的权限声明。 阅读器是否强制拒绝打印/导出,取决于产品策略与威胁模型;项目支持清单也写明不会自动强制全部权限。 ValidPeriod是时间窗声明,不自动等于密码学有效期。
因此:写 Export=false 不能替代加密与访问控制;读到 Printable=true 也不能解释为“已授权打印具有法律意义”。
6. VPreferences:打开时的阅读偏好
PageMode None/FullScreen/UseOutlines/UseThumbs/UseCustomTags/ UseLayers/UseAttatchs/UseBookmarksPageLayout OnePage/OneColumn/TwoPageL/TwoColumnL/TwoPageR/TwoColumnRTabDisplay DocTitle/FileNameHideToolbar/HideMenubar/HideWindowUI 默认 falseZoomMode Default/FitHeight/FitWidth/FitRect或 Zoom 数值注意 Schema 中存在历史拼写 UseAttatchs——生成时必须用枚举原值,不能“纠正”为 UseAttachments 否则 XSD 失败。
这些是查看偏好;WASM/桌面阅读器可能只实现子集(例如执行 PageMode/PageLayout/Zoom,忽略 HideToolbar)。偏好未实现时应视为兼容性缺口,而不是文件损坏。
7. 可选挂载点一览
Annotations | ST_Loc | |
CustomTags | ||
Attachments | ||
Extensions | ||
Actions |
全部 minOccurs=0:没有就省略。写了指针就必须保证目标文件存在且根元素正确——这是模型层检查,不是“ZIP 里碰巧有同名文件”就能过。
四、基础类型在两层里怎么用
ST_Loc | |||
ST_ID | |||
ST_RefID | |||
ST_Box | X Y W H | ||
xs:ID |
两类“ID”并存是标准实现里最容易混的点:
ST_ID/ST_RefID -> OFD 对象号,语义按文档/资源类型解释xs:ID -> XML 层 ID(如 Version、Signature 清单项)校验器必须分开建索引;生成器不要假设“全包数字不重复就万事大吉”。
五、XSD 通过之后还要查什么
以主入口/文档根为例:
Version1.0;DocType=OFD | ||
DocRootDefaultCS 有指向 | ||
Signatures |
项目校验对应关系(概念上):
strict -> ZIP + XML + XSD + 引用 + 语义 + 摘要compat -> XSD 错误降级为警告structural -> 跳过 XSD,仍做引用与语义只跑 XSD、不跑引用语义,会漏掉“结构完美、DocRoot 路径写错”的坏包。
六、生成器检查清单
□ 仅一个 OFD.xml,UTF-8,命名空间正确□ Version=1.0,DocType=OFD□ 每个 DocBody:DocID 非空且不与其它文书冲突;DocRoot 存在□ 无签名则省略 Signatures;无版本则省略 Versions□ Document 元素顺序符合 Schema sequence□ CommonData:MaxUnitID、PageArea/PhysicalBox 合法□ Pages 至少一页;ID 唯一;BaseLoc 存在;顺序即页序□ 可选节点:要么省略,要么目标文件根元素正确□ Dest/PageID、Template@ID、DefaultCS 均可解析□ Permissions/VPreferences 用枚举原值(含 UseAttatchs)□ strict 校验错误 0手工最小包与 ofd-creator 生成都建议最后跑:
go run ./cmd/ofd-validator --mode strict --format text document.ofd七、阅读器解析顺序建议
1. 打开 ZIP,读唯一 OFD.xml2. 校验根元素、Version、DocType、命名空间3. 按序遍历 DocBody4. 解析 DocInfo(至少 DocID)5. 解析 ST_Loc -> Document.xml6. 读 CommonData(页区、资源、模板、MaxUnitID)7. 按序读 Pages,建立 ID -> 页面文件索引8. 再按需加载 Outlines/Permissions/Attachments/...9. 最后处理各 DocBody 的 Signatures 清单先骨架后可选、先页序后内容,失败时才能明确报“入口错 / 根错 / 页树错 / 内容错”,而不是一律“文件损坏”。
结语:两个根文件定义“可交换”
可以把标准第 7 章收成两条链:
包级交换链 OFD.xml -> DocBody -> DocRoot / Signatures / Versions文书级组织链 Document -> CommonData + Pages -> Page Content -> 可选导航/权限/注释/附件/扩展主入口答对“有谁、根在哪、签挂哪”;文档根答对“怎么排页、默认什么、还能挂什么”。这两层闭合了,后面的成像与签名才有意义;这两层错了,再精细的 CTM 与字体也救不回一份无法交换的文件。
实现生成器或阅读器时,先把本章的 Schema 顺序、必选项和 ST_Loc 上下文钉死,再扩展第 8 章以后的绘制能力——这是标准阅读顺序,也是工程上最省返工的顺序。
参考资料:GB/T 33190-2016 第 7 章及附录 A;internal/schema/xsd/OFD.xsd、Document.xsd、Definitions.xsd、Version.xsd。