Skip to main content

ofd_core/model/
document.rs

1//! 文档根节点 `Document.xml`(见 GB/T 33190—2016 7.5)。
2//!
3//! 文档根节点结构见图 5、属性见表 5;文档公共数据 `CT_CommonData` 见图 6、
4//! 表 6;页面区域 `CT_PageArea` 见图 7、表 7;权限声明、视图首选项、书签、
5//! 大纲等也在本章定义。
6
7use serde::{Deserialize, Serialize};
8
9use crate::model::common::Actions;
10use crate::model::common::CtDest;
11use crate::model::page::{CtTemplatePage, Pages};
12use crate::types::{StBox, StId, StLoc, StRefId};
13
14/// 文档根节点(见表 5)。
15#[derive(Debug, Clone, Deserialize, Serialize)]
16pub struct Document {
17    /// 文档公共数据,定义页面区域、公共资源等(必选)。
18    #[serde(rename = "CommonData")]
19    pub common_data: CommonData,
20    /// 页树,见 7.6(必选)。
21    #[serde(rename = "Pages")]
22    pub pages: Pages,
23    /// 大纲,见 7.8(可选)。
24    #[serde(rename = "Outlines")]
25    pub outlines: Option<Outlines>,
26    /// 文档权限声明(可选)。
27    #[serde(rename = "Permissions")]
28    pub permissions: Option<CtPermission>,
29    /// 文档关联的动作序列(可选)。
30    #[serde(rename = "Actions")]
31    pub actions: Option<Actions>,
32    /// 文档视图首选项(可选)。
33    #[serde(rename = "VPreferences")]
34    pub v_preferences: Option<VPreferences>,
35    /// 文档书签集(可选)。
36    #[serde(rename = "Bookmarks")]
37    pub bookmarks: Option<Bookmarks>,
38    /// 指向附件列表文件,见第 20 章(可选)。
39    #[serde(rename = "Attachments")]
40    pub attachments: Option<StLoc>,
41    /// 指向注释列表文件,见第 15 章(可选)。
42    #[serde(rename = "Annotations")]
43    pub annotations: Option<StLoc>,
44    /// 指向自定义标引列表文件,见第 16 章(可选)。
45    #[serde(rename = "CustomTags")]
46    pub custom_tags: Option<StLoc>,
47    /// 指向扩展列表文件,见第 17 章(可选)。
48    #[serde(rename = "Extensions")]
49    pub extensions: Option<StLoc>,
50}
51
52/// `CT_CommonData`:文档公共数据(见表 6)。
53#[derive(Debug, Clone, Deserialize, Serialize)]
54pub struct CommonData {
55    /// 当前文档中所有对象使用标识的最大值,初始值为 0(必选)。
56    #[serde(rename = "MaxUnitID")]
57    pub max_unit_id: StId,
58    /// 指定该文档页面区域的默认大小和位置。
59    ///
60    /// GB/T 33190—2016 表 6 将其列为必选,但部分开票系统生成的发票 OFD 省略了
61    /// 文档级 `PageArea`,转而仅在每页的 `Page/Area`([`crate::PageObject::area`])
62    /// 声明页面区域。为兼容此类文件,这里放宽为可选;缺省时应回退到本页 `Area`,
63    /// 仍缺省则用 A4(见 [`crate::types::StBox::A4_MM`])。
64    #[serde(rename = "PageArea")]
65    pub page_area: Option<CtPageArea>,
66    /// 公共资源序列,每个节点指向包内的一个资源描述文档(可选)。
67    #[serde(rename = "PublicRes", default)]
68    pub public_res: Vec<StLoc>,
69    /// 文档资源序列,每个节点指向包内的一个资源描述文档(可选)。
70    #[serde(rename = "DocumentRes", default)]
71    pub document_res: Vec<StLoc>,
72    /// 模板页序列(可选)。
73    #[serde(rename = "TemplatePage", default)]
74    pub template_pages: Vec<CtTemplatePage>,
75    /// 引用资源文件中定义的默认颜色空间标识,缺省采用 RGB(可选)。
76    #[serde(rename = "DefaultCS")]
77    pub default_cs: Option<StRefId>,
78}
79
80/// `CT_PageArea`:页面区域(见表 7、图 8)。
81///
82/// 四个边界框逐层嵌套:出血框 ⊃ 物理框 ⊃ 显示框 ⊃ 版心框。
83#[derive(Debug, Clone, Deserialize, Serialize)]
84pub struct CtPageArea {
85    /// 页面物理区域,左上角为页面空间坐标系原点(必选)。
86    #[serde(rename = "PhysicalBox")]
87    pub physical_box: StBox,
88    /// 显示区域,页面内容实际显示或打印输出的区域(可选)。
89    #[serde(rename = "ApplicationBox")]
90    pub application_box: Option<StBox>,
91    /// 版心区域,即文件的正文区域(可选)。
92    #[serde(rename = "ContentBox")]
93    pub content_box: Option<StBox>,
94    /// 出血区域,超出设备性能限制的额外出血区域;缺省为物理区域(可选)。
95    #[serde(rename = "BleedBox")]
96    pub bleed_box: Option<StBox>,
97}
98
99/// `CT_Permission`:文档权限声明(见表 8)。
100///
101/// 各布尔项缺省值均为 `true`;此处以 [`Option`] 表达“是否显式声明”,
102/// 取值请配合 [`CtPermission::get`] 风格的默认逻辑使用。
103#[derive(Debug, Clone, Default, Deserialize, Serialize)]
104pub struct CtPermission {
105    /// 是否允许编辑,默认 `true`。
106    #[serde(rename = "Edit")]
107    pub edit: Option<bool>,
108    /// 是否允许添加或修改标注,默认 `true`。
109    #[serde(rename = "Annot")]
110    pub annot: Option<bool>,
111    /// 是否允许导出,默认 `true`。
112    #[serde(rename = "Export")]
113    pub export: Option<bool>,
114    /// 是否允许进行数字签名,默认 `true`。
115    #[serde(rename = "Signature")]
116    pub signature: Option<bool>,
117    /// 是否允许添加水印,默认 `true`。
118    #[serde(rename = "Watermark")]
119    pub watermark: Option<bool>,
120    /// 是否允许截屏,默认 `true`。
121    #[serde(rename = "PrintScreen")]
122    pub print_screen: Option<bool>,
123    /// 打印权限设置(可选)。
124    #[serde(rename = "Print")]
125    pub print: Option<Print>,
126    /// 有效期(可选)。
127    #[serde(rename = "ValidPeriod")]
128    pub valid_period: Option<ValidPeriod>,
129}
130
131/// 打印权限(见表 8)。
132#[derive(Debug, Clone, Default, Deserialize, Serialize)]
133pub struct Print {
134    /// 文档是否允许被打印,默认 `true`。
135    #[serde(rename = "@Printable")]
136    pub printable: Option<bool>,
137    /// 打印份数;`<0` 不受限、`0` 不允许、`>0` 为实际份数。
138    #[serde(rename = "@Copies")]
139    pub copies: Option<i64>,
140}
141
142/// 文档有效期(见表 8)。
143#[derive(Debug, Clone, Default, Deserialize, Serialize)]
144pub struct ValidPeriod {
145    /// 有效期开始日期。
146    #[serde(rename = "@StartDate")]
147    pub start_date: Option<String>,
148    /// 有效期结束日期。
149    #[serde(rename = "@EndDate")]
150    pub end_date: Option<String>,
151}
152
153/// `CT_VPreferences`:文档视图首选项(见表 9)。
154#[derive(Debug, Clone, Default, Deserialize, Serialize)]
155pub struct VPreferences {
156    /// 窗口模式,默认 `None`。
157    #[serde(rename = "PageMode")]
158    pub page_mode: Option<String>,
159    /// 页面布局模式,默认 `OneColumn`。
160    #[serde(rename = "PageLayout")]
161    pub page_layout: Option<String>,
162    /// 标题栏显示模式,默认 `FileName`。
163    #[serde(rename = "TabDisplay")]
164    pub tab_display: Option<String>,
165    /// 是否隐藏工具栏,默认 `false`。
166    #[serde(rename = "HideToolbar")]
167    pub hide_toolbar: Option<bool>,
168    /// 是否隐藏菜单栏,默认 `false`。
169    #[serde(rename = "HideMenubar")]
170    pub hide_menubar: Option<bool>,
171    /// 是否隐藏主窗口之外的其他窗体组件,默认 `false`。
172    #[serde(rename = "HideWindowUI")]
173    pub hide_window_ui: Option<bool>,
174    /// 自动缩放模式,默认 `Default`。
175    #[serde(rename = "ZoomMode")]
176    pub zoom_mode: Option<String>,
177    /// 文档的缩放率。
178    #[serde(rename = "Zoom")]
179    pub zoom: Option<f64>,
180}
181
182/// 书签集,包含一组书签(见表 5、表 10)。
183#[derive(Debug, Clone, Default, Deserialize, Serialize)]
184pub struct Bookmarks {
185    /// 书签列表。
186    #[serde(rename = "Bookmark", default)]
187    pub bookmarks: Vec<CtBookmark>,
188}
189
190/// `CT_Bookmark`:文档书签(见表 10)。
191#[derive(Debug, Clone, Default, Deserialize, Serialize)]
192pub struct CtBookmark {
193    /// 书签名称(必选)。
194    #[serde(rename = "@Name")]
195    pub name: String,
196    /// 书签对应的文档位置(必选)。
197    #[serde(rename = "Dest")]
198    pub dest: Option<CtDest>,
199}
200
201/// 大纲根节点,按树状结构组织(见 7.8、图 18)。
202#[derive(Debug, Clone, Default, Deserialize, Serialize)]
203pub struct Outlines {
204    /// 顶层大纲节点。
205    #[serde(rename = "OutlineElem", default)]
206    pub outline_elems: Vec<CtOutlineElem>,
207}
208
209/// `CT_OutlineElem`:大纲节点(见表 17)。
210#[derive(Debug, Clone, Default, Deserialize, Serialize)]
211pub struct CtOutlineElem {
212    /// 大纲节点标题(必选)。
213    #[serde(rename = "@Title")]
214    pub title: String,
215    /// 该节点下所有叶节点的数目参考值,默认 0(可选)。
216    #[serde(rename = "@Count")]
217    pub count: Option<i64>,
218    /// 初始状态下是否展开子节点,默认 `true`(可选)。
219    #[serde(rename = "@Expanded")]
220    pub expanded: Option<bool>,
221    /// 节点被激活时执行的动作序列(可选)。
222    #[serde(rename = "Actions")]
223    pub actions: Option<Actions>,
224    /// 子大纲节点,层层嵌套形成树状结构(可选)。
225    #[serde(rename = "OutlineElem", default)]
226    pub children: Vec<CtOutlineElem>,
227}
228
229#[cfg(test)]
230mod tests {
231    use super::CommonData;
232
233    /// `PublicRes` / `DocumentRes` are repeatable sequences (表 6). Some 开票系统
234    /// emit them interleaved (`PublicRes … DocumentRes … PublicRes`). Without
235    /// quick-xml's `overlapped-lists` feature this fails with
236    /// `duplicate field PublicRes`; with it the non-consecutive repeats fold
237    /// into the `Vec` as intended.
238    #[test]
239    fn interleaved_public_res_collects_into_vec() {
240        let xml = r#"<ofd:CommonData xmlns:ofd="http://www.ofdspec.org/2016">
241            <ofd:MaxUnitID>10</ofd:MaxUnitID>
242            <ofd:PublicRes>PublicRes.xml</ofd:PublicRes>
243            <ofd:DocumentRes>DocumentRes.xml</ofd:DocumentRes>
244            <ofd:PublicRes>PublicRes_2.xml</ofd:PublicRes>
245        </ofd:CommonData>"#;
246
247        let cd: CommonData = quick_xml::de::from_str(xml).expect("parse CommonData");
248        assert_eq!(
249            cd.public_res
250                .iter()
251                .map(|l| l.0.as_str())
252                .collect::<Vec<_>>(),
253            ["PublicRes.xml", "PublicRes_2.xml"],
254        );
255        assert_eq!(cd.document_res.len(), 1);
256    }
257}