Skip to main content

easyofd_core/signatures/
signatures.rs

1//! 签名列表根节点(Signatures)。
2//!
3//! 对应 Java: org.ofdrw.core.signatures.Signatures
4//!
5//! 签名列表文件的入口点,可包含多个签名(例如联合发文等情况)。
6//! 当允许下次继续添加签名时,该文件不会被包含到本次签名的
7//! 保护文件列表(References)中。
8//! GB/T 33190 第 18.1 节 图 85 表 66。
9
10use super::signature::Signature;
11
12/// 签名列表根节点。
13///
14/// 包含文档中所有数字签名和安全签章的注册信息。
15#[derive(Debug, Clone)]
16pub struct Signatures {
17    /// 安全标识的最大值(可选)。
18    /// 作用与文档入口文件 Document.xml 中的 MaxID 相同,
19    /// 推荐使用 `sNNN` 的编码方式,NNN 从 1 开始。
20    pub max_sign_id: Option<String>,
21    /// 数字签名或安全签章在列表中的注册信息序列。
22    pub signatures: Vec<Signature>,
23}
24
25impl Signatures {
26    /// 创建空的签名列表。
27    #[must_use]
28    pub fn new() -> Self {
29        Self {
30            max_sign_id: None,
31            signatures: Vec::new(),
32        }
33    }
34
35    /// 设置安全标识的最大值。
36    #[must_use]
37    pub fn max_sign_id(mut self, max_sign_id: impl Into<String>) -> Self {
38        self.max_sign_id = Some(max_sign_id.into());
39        self
40    }
41
42    /// 添加签名注册信息。
43    #[must_use]
44    pub fn add_signature(mut self, signature: Signature) -> Self {
45        self.signatures.push(signature);
46        self
47    }
48
49    /// 序列化为 XML 字符串。
50    #[must_use]
51    pub fn to_xml_string(&self) -> String {
52        use std::fmt::Write;
53
54        let mut xml = String::from(r#"<?xml version="1.0" encoding="UTF-8"?>"#);
55        xml.push('\n');
56        xml.push_str(r#"<ofd:Signatures xmlns:ofd="http://www.ofdspec.org/2016">"#);
57
58        if let Some(ref max_id) = self.max_sign_id {
59            xml.push('\n');
60            let _ = write!(xml, "<ofd:MaxSignId>{max_id}</ofd:MaxSignId>");
61        }
62
63        for sig in &self.signatures {
64            xml.push('\n');
65            xml.push_str(&sig.to_xml_string());
66        }
67
68        xml.push_str("\n</ofd:Signatures>");
69        xml
70    }
71}
72
73impl Default for Signatures {
74    fn default() -> Self {
75        Self::new()
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn test_signatures_new() {
85        let sigs = Signatures::new();
86        assert!(sigs.max_sign_id.is_none());
87        assert!(sigs.signatures.is_empty());
88    }
89
90    #[test]
91    fn test_signatures_builder() {
92        let sigs = Signatures::new()
93            .max_sign_id("s2")
94            .add_signature(Signature::new("s1", "/Doc_0/Signs/Sign_0/Signature.xml"))
95            .add_signature(Signature::new("s2", "/Doc_0/Signs/Sign_1/Signature.xml"));
96        assert_eq!(sigs.max_sign_id.as_deref(), Some("s2"));
97        assert_eq!(sigs.signatures.len(), 2);
98    }
99
100    #[test]
101    fn test_signatures_xml() {
102        let sigs = Signatures::new()
103            .max_sign_id("s1")
104            .add_signature(Signature::new("s1", "/Doc_0/Signs/Sign_0/Signature.xml"));
105        let xml = sigs.to_xml_string();
106        assert!(xml.contains("<?xml version=\"1.0\""));
107        assert!(xml.contains("ofd:Signatures"));
108        assert!(xml.contains("<ofd:MaxSignId>s1</ofd:MaxSignId>"));
109        assert!(xml.contains(r#"ID="s1""#));
110        assert!(xml.contains("</ofd:Signatures>"));
111    }
112
113    #[test]
114    fn test_signatures_default() {
115        let sigs = Signatures::default();
116        assert!(sigs.signatures.is_empty());
117    }
118}