Skip to main content

easyofd_core/
xml_impls.rs

1//! XmlElement trait 实现集合。
2//!
3//! 为 easyofd-core 中的 OFD 元素类型实现 [`XmlElement`] trait,
4//! 使其可 [`to_xml`](XmlElement::to_xml) 序列化、
5//! [`from_xml`](XmlElement::from_xml) 反序列化。
6//!
7//! 对应 Java: ofdrw 中各 OFDElement 子类的 toXML /代理解析。
8
9use crate::OfdMetadata;
10use crate::action::{CTDest, DestType, Goto};
11use crate::annotation::{Annot, AnnotType, Appearance};
12use crate::attachment::{Attachments, CTAttachment};
13use crate::basic_type::ST_Loc;
14use crate::doc::doc_body::DocBody;
15use crate::doc::document::{Document, PageRef};
16use crate::doc::keywords::Keywords;
17use crate::doc::outlines::{CT_OutlineElem, Outlines};
18use crate::doc::pages::{PageEntry, Pages};
19use crate::doc::res::Res;
20use crate::image::CT_Image;
21use crate::page_obj::{
22    CT_CommonData, CT_Layer, CT_PageArea, CT_TemplatePage, LayerType, TemplateZOrder,
23};
24use crate::page_obj::{
25    CT_PageBlock, PageBlockImageObject, PageBlockPathObject, PageBlockTextObject,
26};
27use crate::text::{CT_CGTransform, CT_Text, TextCode};
28use crate::xml_element::{XmlElement, XmlElementError, XmlNode};
29use crate::xml_parse::parse_xml_to_nodes;
30
31// ═══════════════════════════════════════════════════════════════
32// 辅助函数
33// ═══════════════════════════════════════════════════════════════
34
35/// 将 [`XmlElement`] 实例转为 [`XmlNode`](用于 `child_nodes` 构建嵌套树)。
36fn to_xml_node<E: XmlElement>(el: &E) -> XmlNode {
37    let mut node = XmlNode::element(el.element_name());
38    for (k, v) in el.attributes() {
39        node.attrs.push((k, v));
40    }
41    for child in el.child_nodes() {
42        node.push_child(child);
43    }
44    if let Some(text) = el.text_content() {
45        // 用 text_node 子节点表示文本内容,与 write_self_xml 的序列化路径一致。
46        node.push_child(XmlNode::text_node(text));
47    }
48    node
49}
50
51/// 创建带文本内容的子元素节点。
52///
53/// 输出形如 `<name>text</name>`。
54fn text_child(name: &str, text: &str) -> XmlNode {
55    let mut node = XmlNode::element(name);
56    node.push_child(XmlNode::text_node(text));
57    node
58}
59
60/// 从节点读取属性并解析为 `u32`。
61fn attr_u32(node: &XmlNode, key: &str) -> Option<u32> {
62    node.get_attr(key).and_then(|s| s.parse().ok())
63}
64
65/// 从节点读取属性并解析为 `f64`。
66fn attr_f64(node: &XmlNode, key: &str) -> Option<f64> {
67    node.get_attr(key).and_then(|s| s.parse().ok())
68}
69
70/// 从节点读取属性并解析为 `bool`(缺省返回 `default`)。
71fn attr_bool(node: &XmlNode, key: &str, default: bool) -> bool {
72    match node.get_attr(key) {
73        Some("true" | "1") => true,
74        Some("false" | "0") => false,
75        _ => default,
76    }
77}
78
79/// 从首个匹配名字的子元素提取文本内容。
80fn child_text(node: &XmlNode, name: &str) -> Option<String> {
81    node.child(name).and_then(|c| c.text.clone())
82}
83
84/// 从首个匹配名字的子元素提取文本并解析为 `u32`。
85fn child_u32(node: &XmlNode, name: &str) -> Option<u32> {
86    child_text(node, name).and_then(|s| s.parse().ok())
87}
88
89/// 从节点属性解析 [`LayerType`]。
90fn parse_layer_type(s: &str) -> LayerType {
91    match s {
92        "Foreground" => LayerType::Foreground,
93        "Background" => LayerType::Background,
94        _ => LayerType::Body,
95    }
96}
97
98/// 从节点属性解析 [`TemplateZOrder`]。
99fn parse_z_order(s: &str) -> Option<TemplateZOrder> {
100    match s {
101        "Back" => Some(TemplateZOrder::Back),
102        "Front" => Some(TemplateZOrder::Front),
103        _ => None,
104    }
105}
106
107/// 从节点属性解析 [`AnnotType`]。
108fn parse_annot_type(s: &str) -> AnnotType {
109    AnnotType::from_str_opt(s).unwrap_or(AnnotType::Text)
110}
111
112/// 从节点属性解析 [`DestType`]。
113fn parse_dest_type(s: &str) -> DestType {
114    match s {
115        "XYZ" => DestType::XYZ,
116        "FitH" => DestType::FitH,
117        "FitV" => DestType::FitV,
118        "FitBH" => DestType::FitBH,
119        "FitBV" => DestType::FitBV,
120        _ => DestType::Fit,
121    }
122}
123
124// ═══════════════════════════════════════════════════════════════
125// doc 模块类型
126// ═══════════════════════════════════════════════════════════════
127
128impl XmlElement for PageEntry {
129    /// 对应 Java: org.ofdrw.core.basicStructure.pageTree.Page
130    fn element_name(&self) -> &'static str {
131        "Page"
132    }
133
134    fn attributes(&self) -> Vec<(String, String)> {
135        vec![
136            ("ID".to_string(), self.id.to_string()),
137            ("BaseLoc".to_string(), self.base_loc.clone()),
138        ]
139    }
140
141    fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
142        let id = attr_u32(node, "ID").unwrap_or(0);
143        let base_loc = node.get_attr("BaseLoc").unwrap_or_default().to_string();
144        Ok(Self { id, base_loc })
145    }
146}
147
148impl XmlElement for Pages {
149    /// 对应 Java: org.ofdrw.core.basicStructure.pageTree.Pages
150    fn element_name(&self) -> &'static str {
151        "Pages"
152    }
153
154    fn attributes(&self) -> Vec<(String, String)> {
155        Vec::new()
156    }
157
158    fn child_nodes(&self) -> Vec<XmlNode> {
159        self.pages.iter().map(to_xml_node).collect()
160    }
161
162    fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
163        let pages = node
164            .children_named("Page")
165            .map(PageEntry::from_xml)
166            .collect::<Result<Vec<_>, _>>()?;
167        Ok(Self { pages })
168    }
169}
170
171impl XmlElement for Document {
172    /// 对应 Java: org.ofdrw.core.basicStructure.doc.Document
173    fn element_name(&self) -> &'static str {
174        "Document"
175    }
176
177    fn attributes(&self) -> Vec<(String, String)> {
178        Vec::new()
179    }
180
181    fn child_nodes(&self) -> Vec<XmlNode> {
182        let mut nodes = Vec::new();
183        if let Some(ref cd) = self.common_data {
184            nodes.push(text_child("CommonData", cd));
185        }
186        if !self.pages.is_empty() {
187            let pages_node = {
188                let p = Pages {
189                    pages: self
190                        .pages
191                        .iter()
192                        .map(|pr| PageEntry {
193                            id: pr.id,
194                            base_loc: pr.base_loc.loc().to_string(),
195                        })
196                        .collect(),
197                };
198                to_xml_node(&p)
199            };
200            nodes.push(pages_node);
201        }
202        if let Some(ref ol) = self.outlines {
203            nodes.push(text_child("Outlines", ol));
204        }
205        if let Some(ref pm) = self.permissions {
206            nodes.push(text_child("Permissions", pm));
207        }
208        if let Some(ref ac) = self.actions {
209            nodes.push(text_child("Actions", ac));
210        }
211        if let Some(ref vp) = self.v_preferences {
212            nodes.push(text_child("VPreferences", vp));
213        }
214        if let Some(ref bm) = self.bookmarks {
215            nodes.push(text_child("Bookmarks", bm));
216        }
217        if let Some(ref an) = self.annotations {
218            nodes.push(text_child("Annotations", an.loc()));
219        }
220        if let Some(ref ct) = self.custom_tags {
221            nodes.push(text_child("CustomTags", ct.loc()));
222        }
223        if let Some(ref at) = self.attachments {
224            nodes.push(text_child("Attachments", at.loc()));
225        }
226        if let Some(ref ex) = self.extensions {
227            nodes.push(text_child("Extensions", ex.loc()));
228        }
229        nodes
230    }
231
232    fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
233        let common_data = child_text(node, "CommonData");
234        let pages = node
235            .child("Pages")
236            .map(|pn| {
237                pn.children_named("Page")
238                    .map(|pe| {
239                        Ok::<_, XmlElementError>(PageRef::new(
240                            attr_u32(pe, "ID").unwrap_or(0),
241                            ST_Loc::new(pe.get_attr("BaseLoc").unwrap_or_default()),
242                        ))
243                    })
244                    .collect::<Result<Vec<_>, _>>()
245            })
246            .transpose()?
247            .unwrap_or_default();
248        let outlines = child_text(node, "Outlines");
249        let permissions = child_text(node, "Permissions");
250        let actions = child_text(node, "Actions");
251        let v_preferences = child_text(node, "VPreferences");
252        let bookmarks = child_text(node, "Bookmarks");
253        let annotations = child_text(node, "Annotations").map(|s| ST_Loc::new(&s));
254        let custom_tags = child_text(node, "CustomTags").map(|s| ST_Loc::new(&s));
255        let attachments = child_text(node, "Attachments").map(|s| ST_Loc::new(&s));
256        let extensions = child_text(node, "Extensions").map(|s| ST_Loc::new(&s));
257        Ok(Self {
258            common_data,
259            pages,
260            outlines,
261            permissions,
262            actions,
263            v_preferences,
264            bookmarks,
265            annotations,
266            custom_tags,
267            attachments,
268            extensions,
269        })
270    }
271}
272
273impl XmlElement for DocBody {
274    /// 对应 Java: org.ofdrw.core.basicStructure.ofd.DocBody
275    fn element_name(&self) -> &'static str {
276        "DocBody"
277    }
278
279    fn attributes(&self) -> Vec<(String, String)> {
280        Vec::new()
281    }
282
283    fn child_nodes(&self) -> Vec<XmlNode> {
284        let mut nodes = vec![text_child("DocRoot", self.doc_root.loc())];
285        if let Some(ref info) = self.doc_info {
286            nodes.push(text_child("DocInfo", info));
287        }
288        if let Some(ref sig) = self.signatures {
289            nodes.push(text_child("Signatures", sig.loc()));
290        }
291        nodes
292    }
293
294    fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
295        let doc_root = child_text(node, "DocRoot")
296            .ok_or_else(|| XmlElementError("DocBody 缺少 DocRoot".to_string()))?;
297        let doc_info = child_text(node, "DocInfo");
298        let signatures = child_text(node, "Signatures").map(|s| ST_Loc::new(&s));
299        Ok(Self {
300            doc_root: ST_Loc::new(&doc_root),
301            doc_info,
302            signatures,
303        })
304    }
305}
306
307impl XmlElement for Res {
308    /// 对应 Java: org.ofdrw.core.basicStructure.res.Res
309    fn element_name(&self) -> &'static str {
310        "Res"
311    }
312
313    fn attributes(&self) -> Vec<(String, String)> {
314        let mut attrs = Vec::new();
315        if let Some(ref loc) = self.base_loc {
316            attrs.push(("BaseLoc".to_string(), loc.loc().to_string()));
317        }
318        attrs
319    }
320
321    fn child_nodes(&self) -> Vec<XmlNode> {
322        self.resources
323            .iter()
324            .filter_map(|r| parse_xml_to_nodes(r).ok())
325            .collect()
326    }
327
328    fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
329        let base_loc = node.get_attr("BaseLoc").map(ST_Loc::new);
330        let resources = node.children.iter().map(|c| c.to_xml_string()).collect();
331        Ok(Self {
332            base_loc,
333            resources,
334        })
335    }
336}
337
338impl XmlElement for CT_OutlineElem {
339    /// 对应 Java: org.ofdrw.core.basicStructure.outlines.CT_OutlineElem
340    fn element_name(&self) -> &'static str {
341        "OutlineElem"
342    }
343
344    fn attributes(&self) -> Vec<(String, String)> {
345        let mut attrs = vec![("Title".to_string(), self.title.clone())];
346        if let Some(p) = self.page {
347            attrs.push(("Page".to_string(), p.to_string()));
348        }
349        attrs
350    }
351
352    fn child_nodes(&self) -> Vec<XmlNode> {
353        self.children.iter().map(to_xml_node).collect()
354    }
355
356    fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
357        let title = node.get_attr("Title").unwrap_or_default().to_string();
358        let page = attr_u32(node, "Page");
359        let children = node
360            .children_named("OutlineElem")
361            .map(CT_OutlineElem::from_xml)
362            .collect::<Result<Vec<_>, _>>()?;
363        Ok(Self {
364            title,
365            page,
366            children,
367        })
368    }
369}
370
371impl XmlElement for Outlines {
372    /// 对应 Java: org.ofdrw.core.basicStructure.outlines.Outlines
373    fn element_name(&self) -> &'static str {
374        "Outlines"
375    }
376
377    fn attributes(&self) -> Vec<(String, String)> {
378        Vec::new()
379    }
380
381    fn child_nodes(&self) -> Vec<XmlNode> {
382        self.elements.iter().map(to_xml_node).collect()
383    }
384
385    fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
386        let elements = node
387            .children_named("OutlineElem")
388            .map(CT_OutlineElem::from_xml)
389            .collect::<Result<Vec<_>, _>>()?;
390        Ok(Self { elements })
391    }
392}
393
394impl XmlElement for Keywords {
395    /// 对应 Java: org.ofdrw.core.basicStructure.ofd.docInfo.Keywords
396    fn element_name(&self) -> &'static str {
397        "Keywords"
398    }
399
400    fn attributes(&self) -> Vec<(String, String)> {
401        Vec::new()
402    }
403
404    fn child_nodes(&self) -> Vec<XmlNode> {
405        self.keywords
406            .iter()
407            .map(|kw| text_child("Keyword", kw))
408            .collect()
409    }
410
411    fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
412        let keywords = node
413            .children_named("Keyword")
414            .filter_map(|c| c.text.clone())
415            .collect();
416        Ok(Self { keywords })
417    }
418}
419
420// ═══════════════════════════════════════════════════════════════
421// page_obj 模块类型
422// ═══════════════════════════════════════════════════════════════
423
424impl XmlElement for CT_TemplatePage {
425    /// 对应 Java: org.ofdrw.core.basicStructure.pageObj.CT_TemplatePage
426    fn element_name(&self) -> &'static str {
427        "TemplatePage"
428    }
429
430    fn attributes(&self) -> Vec<(String, String)> {
431        let mut attrs = vec![("ID".to_string(), self.id.to_string())];
432        if let Some(ref name) = self.name {
433            attrs.push(("TemplatePageName".to_string(), name.clone()));
434        }
435        if let Some(zo) = self.z_order {
436            attrs.push(("ZOrder".to_string(), zo.as_str().to_string()));
437        }
438        if let Some(ref loc) = self.base_loc {
439            attrs.push(("BaseLoc".to_string(), loc.clone()));
440        }
441        attrs
442    }
443
444    fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
445        Ok(Self {
446            id: attr_u32(node, "ID").unwrap_or(0),
447            name: node.get_attr("TemplatePageName").map(String::from),
448            z_order: node.get_attr("ZOrder").and_then(parse_z_order),
449            base_loc: node.get_attr("BaseLoc").map(String::from),
450        })
451    }
452}
453
454impl XmlElement for CT_PageArea {
455    /// 对应 Java: org.ofdrw.core.basicStructure.doc.CT_PageArea
456    fn element_name(&self) -> &'static str {
457        "PageArea"
458    }
459
460    fn attributes(&self) -> Vec<(String, String)> {
461        Vec::new()
462    }
463
464    // GB/T 33190-2016:PhysicalBox/ApplicationBox 等是 PageArea 的**子元素**
465    // (<ofd:PageArea><ofd:PhysicalBox>0 0 210 297</ofd:PhysicalBox>...),
466    // 与 ofdrw 输出一致(不是属性)。
467    fn child_nodes(&self) -> Vec<XmlNode> {
468        let mut nodes = Vec::new();
469        if let Some(ref pb) = self.physical_box {
470            nodes.push(text_child("PhysicalBox", pb));
471        }
472        if let Some(ref ab) = self.application_box {
473            nodes.push(text_child("ApplicationBox", ab));
474        }
475        if let Some(ref cb) = self.content_box {
476            nodes.push(text_child("ContentBox", cb));
477        }
478        if let Some(ref bb) = self.bleed_box {
479            nodes.push(text_child("BleedBox", bb));
480        }
481        nodes
482    }
483
484    fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
485        Ok(Self {
486            physical_box: node.child("PhysicalBox").and_then(|c| c.text.clone()),
487            application_box: node.child("ApplicationBox").and_then(|c| c.text.clone()),
488            content_box: node.child("ContentBox").and_then(|c| c.text.clone()),
489            bleed_box: node.child("BleedBox").and_then(|c| c.text.clone()),
490        })
491    }
492}
493
494impl XmlElement for CT_CommonData {
495    /// 对应 Java: org.ofdrw.core.basicStructure.doc.CT_CommonData
496    fn element_name(&self) -> &'static str {
497        "CommonData"
498    }
499
500    fn attributes(&self) -> Vec<(String, String)> {
501        Vec::new()
502    }
503
504    fn child_nodes(&self) -> Vec<XmlNode> {
505        let mut nodes = Vec::new();
506        if let Some(id) = self.max_unit_id {
507            nodes.push(text_child("MaxUnitID", &id.to_string()));
508        }
509        if let Some(ref pa) = self.page_area {
510            nodes.push(to_xml_node(pa));
511        }
512        for res in &self.public_res {
513            nodes.push(text_child("PublicRes", res));
514        }
515        for res in &self.document_res {
516            nodes.push(text_child("DocumentRes", res));
517        }
518        for tpl in &self.template_pages {
519            nodes.push(to_xml_node(tpl));
520        }
521        if let Some(cs) = self.default_cs {
522            nodes.push(text_child("DefaultCS", &cs.to_string()));
523        }
524        nodes
525    }
526
527    fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
528        let max_unit_id = child_u32(node, "MaxUnitID");
529        let page_area = node
530            .child("PageArea")
531            .map(CT_PageArea::from_xml)
532            .transpose()?;
533        let public_res = node
534            .children_named("PublicRes")
535            .filter_map(|c| c.text.clone())
536            .collect();
537        let document_res = node
538            .children_named("DocumentRes")
539            .filter_map(|c| c.text.clone())
540            .collect();
541        let template_pages = node
542            .children_named("TemplatePage")
543            .map(CT_TemplatePage::from_xml)
544            .collect::<Result<Vec<_>, _>>()?;
545        let default_cs = child_u32(node, "DefaultCS");
546        Ok(Self {
547            max_unit_id,
548            page_area,
549            public_res,
550            document_res,
551            template_pages,
552            default_cs,
553        })
554    }
555}
556
557impl XmlElement for PageBlockTextObject {
558    /// 对应 Java: org.ofdrw.core.basicStructure.pageObj.layer.block.TextObject(简化版)
559    fn element_name(&self) -> &'static str {
560        "TextObject"
561    }
562
563    fn attributes(&self) -> Vec<(String, String)> {
564        vec![
565            ("ID".to_string(), self.id.to_string()),
566            ("Boundary".to_string(), self.boundary.clone()),
567        ]
568    }
569
570    fn text_content(&self) -> Option<&str> {
571        Some(&self.content)
572    }
573
574    fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
575        Ok(Self {
576            id: attr_u32(node, "ID").unwrap_or(0),
577            boundary: node.get_attr("Boundary").unwrap_or_default().to_string(),
578            content: node.text.clone().unwrap_or_default(),
579            font_size: 12.0,
580        })
581    }
582}
583
584impl XmlElement for PageBlockPathObject {
585    /// 对应 Java: org.ofdrw.core.basicStructure.pageObj.layer.block.PathObject(简化版)
586    fn element_name(&self) -> &'static str {
587        "PathObject"
588    }
589
590    fn attributes(&self) -> Vec<(String, String)> {
591        vec![
592            ("ID".to_string(), self.id.to_string()),
593            ("Boundary".to_string(), self.boundary.clone()),
594        ]
595    }
596
597    fn child_nodes(&self) -> Vec<XmlNode> {
598        vec![text_child("AbbreviatedData", &self.abbreviated_data)]
599    }
600
601    fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
602        let abbreviated_data = child_text(node, "AbbreviatedData").unwrap_or_default();
603        Ok(Self {
604            id: attr_u32(node, "ID").unwrap_or(0),
605            boundary: node.get_attr("Boundary").unwrap_or_default().to_string(),
606            abbreviated_data,
607        })
608    }
609}
610
611impl XmlElement for PageBlockImageObject {
612    /// 对应 Java: org.ofdrw.core.basicStructure.pageObj.layer.block.ImageObject(简化版)
613    fn element_name(&self) -> &'static str {
614        "ImageObject"
615    }
616
617    fn attributes(&self) -> Vec<(String, String)> {
618        vec![
619            ("ID".to_string(), self.id.to_string()),
620            ("Boundary".to_string(), self.boundary.clone()),
621            ("ResourceID".to_string(), self.resource_id.to_string()),
622        ]
623    }
624
625    fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
626        Ok(Self {
627            id: attr_u32(node, "ID").unwrap_or(0),
628            boundary: node.get_attr("Boundary").unwrap_or_default().to_string(),
629            resource_id: attr_u32(node, "ResourceID").unwrap_or(0),
630        })
631    }
632}
633
634impl XmlElement for CT_PageBlock {
635    /// 对应 Java: org.ofdrw.core.basicStructure.pageObj.layer.block.CT_PageBlock
636    fn element_name(&self) -> &'static str {
637        "PageBlock"
638    }
639
640    fn attributes(&self) -> Vec<(String, String)> {
641        Vec::new()
642    }
643
644    fn child_nodes(&self) -> Vec<XmlNode> {
645        let mut nodes = Vec::new();
646        for obj in &self.text_objects {
647            nodes.push(to_xml_node(obj));
648        }
649        for obj in &self.path_objects {
650            nodes.push(to_xml_node(obj));
651        }
652        for obj in &self.image_objects {
653            nodes.push(to_xml_node(obj));
654        }
655        for block in &self.page_blocks {
656            nodes.push(to_xml_node(block));
657        }
658        nodes
659    }
660
661    fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
662        let mut block = CT_PageBlock::new();
663        for child in &node.children {
664            match child.name.as_str() {
665                "TextObject" => {
666                    block
667                        .text_objects
668                        .push(PageBlockTextObject::from_xml(child)?);
669                }
670                "PathObject" => {
671                    block
672                        .path_objects
673                        .push(PageBlockPathObject::from_xml(child)?);
674                }
675                "ImageObject" => {
676                    block
677                        .image_objects
678                        .push(PageBlockImageObject::from_xml(child)?);
679                }
680                "PageBlock" => {
681                    block.page_blocks.push(CT_PageBlock::from_xml(child)?);
682                }
683                _ => {}
684            }
685        }
686        Ok(block)
687    }
688}
689
690impl XmlElement for CT_Layer {
691    /// 对应 Java: org.ofdrw.core.basicStructure.pageObj.layer.CT_Layer
692    fn element_name(&self) -> &'static str {
693        "Layer"
694    }
695
696    fn attributes(&self) -> Vec<(String, String)> {
697        let mut attrs = vec![("Type".to_string(), self.layer_type.as_str().to_string())];
698        if let Some(dp) = self.draw_param {
699            attrs.push(("DrawParam".to_string(), dp.to_string()));
700        }
701        attrs
702    }
703
704    /// Layer 内联 PageBlock 的子内容(不包裹 PageBlock 标签),
705    /// 与 ofdrw 的 toXML 行为一致。
706    fn child_nodes(&self) -> Vec<XmlNode> {
707        self.block.child_nodes()
708    }
709
710    fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
711        let layer_type = node
712            .get_attr("Type")
713            .map_or(LayerType::Body, parse_layer_type);
714        let draw_param = attr_u32(node, "DrawParam");
715        let block = CT_PageBlock::from_xml(node)?;
716        Ok(Self {
717            layer_type,
718            draw_param,
719            block,
720        })
721    }
722}
723
724// ═══════════════════════════════════════════════════════════════
725// text 模块类型
726// ═══════════════════════════════════════════════════════════════
727
728impl XmlElement for TextCode {
729    /// 对应 Java: org.ofdrw.core.text.TextCode
730    fn element_name(&self) -> &'static str {
731        "TextCode"
732    }
733
734    fn attributes(&self) -> Vec<(String, String)> {
735        let mut attrs = Vec::new();
736        if let Some(x) = self.x {
737            attrs.push(("X".to_string(), x.to_string()));
738        }
739        if let Some(y) = self.y {
740            attrs.push(("Y".to_string(), y.to_string()));
741        }
742        if !self.delta_x.is_empty() {
743            let s = self
744                .delta_x
745                .iter()
746                .map(|d| d.to_string())
747                .collect::<Vec<_>>()
748                .join(" ");
749            attrs.push(("DeltaX".to_string(), s));
750        }
751        if !self.delta_y.is_empty() {
752            let s = self
753                .delta_y
754                .iter()
755                .map(|d| d.to_string())
756                .collect::<Vec<_>>()
757                .join(" ");
758            attrs.push(("DeltaY".to_string(), s));
759        }
760        attrs
761    }
762
763    fn text_content(&self) -> Option<&str> {
764        Some(&self.content)
765    }
766
767    fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
768        let content = node.text.clone().unwrap_or_default();
769        let x = attr_f64(node, "X");
770        let y = attr_f64(node, "Y");
771        let delta_x = node
772            .get_attr("DeltaX")
773            .map(|s| {
774                s.split_whitespace()
775                    .filter_map(|v| v.parse().ok())
776                    .collect()
777            })
778            .unwrap_or_default();
779        let delta_y = node
780            .get_attr("DeltaY")
781            .map(|s| {
782                s.split_whitespace()
783                    .filter_map(|v| v.parse().ok())
784                    .collect()
785            })
786            .unwrap_or_default();
787        Ok(Self {
788            content,
789            x,
790            y,
791            delta_x,
792            delta_y,
793        })
794    }
795}
796
797impl XmlElement for CT_CGTransform {
798    /// 对应 Java: org.ofdrw.core.text.CT_CGTransform
799    fn element_name(&self) -> &'static str {
800        "CGTransform"
801    }
802
803    fn attributes(&self) -> Vec<(String, String)> {
804        let mut attrs = Vec::new();
805        if let Some(cp) = self.code_position {
806            attrs.push(("CodePosition".to_string(), cp.to_string()));
807        }
808        if let Some(cc) = self.code_count {
809            attrs.push(("CodeCount".to_string(), cc.to_string()));
810        }
811        if let Some(gc) = self.glyph_count {
812            attrs.push(("GlyphCount".to_string(), gc.to_string()));
813        }
814        if !self.glyphs.is_empty() {
815            let s = self
816                .glyphs
817                .iter()
818                .map(|g| g.to_string())
819                .collect::<Vec<_>>()
820                .join(" ");
821            attrs.push(("Glyphs".to_string(), s));
822        }
823        attrs
824    }
825
826    fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
827        let code_position = attr_u32(node, "CodePosition");
828        let code_count = attr_u32(node, "CodeCount");
829        let glyph_count = attr_u32(node, "GlyphCount");
830        let glyphs = node
831            .get_attr("Glyphs")
832            .map(|s| {
833                s.split_whitespace()
834                    .filter_map(|v| v.parse().ok())
835                    .collect()
836            })
837            .unwrap_or_default();
838        Ok(Self {
839            code_position,
840            code_count,
841            glyph_count,
842            glyphs,
843        })
844    }
845}
846
847impl XmlElement for CT_Text {
848    /// 对应 Java: org.ofdrw.core.text.text.CT_Text
849    fn element_name(&self) -> &'static str {
850        "TextObject"
851    }
852
853    fn attributes(&self) -> Vec<(String, String)> {
854        let mut attrs = vec![
855            ("ID".to_string(), self.id.to_string()),
856            ("Boundary".to_string(), self.boundary.clone()),
857        ];
858        if let Some(fr) = self.font_ref {
859            attrs.push(("Font".to_string(), fr.to_string()));
860        }
861        if let Some(sz) = self.size {
862            attrs.push(("Size".to_string(), sz.to_string()));
863        }
864        if self.stroke {
865            attrs.push(("Stroke".to_string(), "true".to_string()));
866        }
867        if !self.fill {
868            attrs.push(("Fill".to_string(), "false".to_string()));
869        }
870        if let Some(hs) = self.h_scale {
871            attrs.push(("HScale".to_string(), hs.to_string()));
872        }
873        if let Some(rd) = self.read_direction {
874            attrs.push(("ReadDirection".to_string(), rd.to_string()));
875        }
876        if let Some(cd) = self.char_direction {
877            attrs.push(("CharDirection".to_string(), cd.to_string()));
878        }
879        if let Some(w) = self.weight {
880            attrs.push(("Weight".to_string(), w.to_string()));
881        }
882        if self.italic {
883            attrs.push(("Italic".to_string(), "true".to_string()));
884        }
885        if let Some(fc) = self.fill_color {
886            attrs.push(("FillColor".to_string(), fc.to_string()));
887        }
888        if let Some(sc) = self.stroke_color {
889            attrs.push(("StrokeColor".to_string(), sc.to_string()));
890        }
891        attrs
892    }
893
894    fn child_nodes(&self) -> Vec<XmlNode> {
895        let mut nodes = Vec::new();
896        for cg in &self.cg_transforms {
897            nodes.push(to_xml_node(cg));
898        }
899        for tc in &self.text_codes {
900            nodes.push(to_xml_node(tc));
901        }
902        nodes
903    }
904
905    fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
906        let id = attr_u32(node, "ID").unwrap_or(0);
907        let boundary = node.get_attr("Boundary").unwrap_or_default().to_string();
908        let font_ref = attr_u32(node, "Font");
909        let size = attr_f64(node, "Size");
910        let stroke = attr_bool(node, "Stroke", false);
911        let fill = attr_bool(node, "Fill", true);
912        let h_scale = attr_f64(node, "HScale");
913        let read_direction = attr_u32(node, "ReadDirection");
914        let char_direction = attr_u32(node, "CharDirection");
915        let weight = attr_u32(node, "Weight");
916        let italic = attr_bool(node, "Italic", false);
917        let fill_color = attr_u32(node, "FillColor");
918        let stroke_color = attr_u32(node, "StrokeColor");
919        let cg_transforms = node
920            .children_named("CGTransform")
921            .map(CT_CGTransform::from_xml)
922            .collect::<Result<Vec<_>, _>>()?;
923        let text_codes = node
924            .children_named("TextCode")
925            .map(TextCode::from_xml)
926            .collect::<Result<Vec<_>, _>>()?;
927        Ok(Self {
928            id,
929            boundary,
930            font_ref,
931            size,
932            stroke,
933            fill,
934            h_scale,
935            read_direction,
936            char_direction,
937            weight,
938            italic,
939            fill_color,
940            stroke_color,
941            cg_transforms,
942            text_codes,
943        })
944    }
945}
946
947// ═══════════════════════════════════════════════════════════════
948// image 模块类型
949// ═══════════════════════════════════════════════════════════════
950
951impl XmlElement for CT_Image {
952    /// 对应 Java: org.ofdrw.core.image.CT_Image
953    fn element_name(&self) -> &'static str {
954        "Image"
955    }
956
957    fn attributes(&self) -> Vec<(String, String)> {
958        let mut attrs = vec![
959            ("ID".to_string(), self.id.to_string()),
960            ("Boundary".to_string(), self.boundary.clone()),
961            ("ResourceID".to_string(), self.resource_id.to_string()),
962        ];
963        if self.interpolate {
964            attrs.push(("Interpolate".to_string(), "true".to_string()));
965        }
966        attrs
967    }
968
969    fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
970        Ok(Self {
971            id: attr_u32(node, "ID").unwrap_or(0),
972            boundary: node.get_attr("Boundary").unwrap_or_default().to_string(),
973            resource_id: attr_u32(node, "ResourceID").unwrap_or(0),
974            interpolate: attr_bool(node, "Interpolate", false),
975        })
976    }
977}
978
979// ═══════════════════════════════════════════════════════════════
980// action 模块类型
981// ═══════════════════════════════════════════════════════════════
982
983impl XmlElement for CTDest {
984    /// 对应 Java: org.ofdrw.core.action.CT_Dest
985    fn element_name(&self) -> &'static str {
986        "Dest"
987    }
988
989    fn attributes(&self) -> Vec<(String, String)> {
990        let mut attrs = vec![
991            ("PageID".to_string(), self.page.to_string()),
992            ("Type".to_string(), self.dest_type.to_string()),
993        ];
994        if let Some(left) = self.left {
995            attrs.push(("Left".to_string(), left.to_string()));
996        }
997        if let Some(top) = self.top {
998            attrs.push(("Top".to_string(), top.to_string()));
999        }
1000        if let Some(zoom) = self.zoom {
1001            attrs.push(("Zoom".to_string(), zoom.to_string()));
1002        }
1003        attrs
1004    }
1005
1006    fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
1007        let page = attr_u32(node, "PageID").unwrap_or(0);
1008        let dest_type = node.get_attr("Type").map_or(DestType::Fit, parse_dest_type);
1009        let left = attr_f64(node, "Left");
1010        let top = attr_f64(node, "Top");
1011        let zoom = attr_f64(node, "Zoom");
1012        Ok(Self {
1013            page,
1014            dest_type,
1015            left,
1016            top,
1017            zoom,
1018        })
1019    }
1020}
1021
1022impl XmlElement for Goto {
1023    /// 对应 Java: org.ofdrw.core.action.actionType.Goto
1024    fn element_name(&self) -> &'static str {
1025        "Goto"
1026    }
1027
1028    fn attributes(&self) -> Vec<(String, String)> {
1029        Vec::new()
1030    }
1031
1032    fn child_nodes(&self) -> Vec<XmlNode> {
1033        vec![to_xml_node(&self.dest)]
1034    }
1035
1036    fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
1037        let dest = node
1038            .child("Dest")
1039            .map(CTDest::from_xml)
1040            .ok_or_else(|| XmlElementError("Goto 缺少 Dest 子元素".to_string()))??;
1041        Ok(Self { dest })
1042    }
1043}
1044
1045// ═══════════════════════════════════════════════════════════════
1046// annotation 模块类型
1047// ═══════════════════════════════════════════════════════════════
1048
1049impl XmlElement for Appearance {
1050    /// 对应 Java: org.ofdrw.core.annotation.Appearance
1051    fn element_name(&self) -> &'static str {
1052        "Appearance"
1053    }
1054
1055    fn attributes(&self) -> Vec<(String, String)> {
1056        let mut attrs = vec![
1057            ("ID".to_string(), self.id.clone()),
1058            ("Type".to_string(), self.appearance_type.clone()),
1059        ];
1060        if let Some(ref path) = self.resource_path {
1061            attrs.push(("ResourcePath".to_string(), path.clone()));
1062        }
1063        attrs
1064    }
1065
1066    fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
1067        Ok(Self {
1068            id: node.get_attr("ID").unwrap_or_default().to_string(),
1069            appearance_type: node.get_attr("Type").unwrap_or_default().to_string(),
1070            resource_path: node.get_attr("ResourcePath").map(String::from),
1071        })
1072    }
1073}
1074
1075impl XmlElement for Annot {
1076    /// 对应 Java: org.ofdrw.core.annotation.Annot
1077    fn element_name(&self) -> &'static str {
1078        "Annot"
1079    }
1080
1081    fn attributes(&self) -> Vec<(String, String)> {
1082        let mut attrs = vec![
1083            ("ID".to_string(), self.id.clone()),
1084            ("Type".to_string(), self.annot_type.as_str().to_string()),
1085            ("Flags".to_string(), self.flags.to_string()),
1086        ];
1087        if let Some(ref creator) = self.creator {
1088            attrs.push(("Creator".to_string(), creator.clone()));
1089        }
1090        if let Some(ref date) = self.last_mod_date {
1091            attrs.push(("LastModDate".to_string(), date.clone()));
1092        }
1093        let [x, y, w, h] = self.location;
1094        attrs.push(("Location".to_string(), format!("{x} {y} {w} {h}")));
1095        attrs
1096    }
1097
1098    fn child_nodes(&self) -> Vec<XmlNode> {
1099        self.appearances.iter().map(to_xml_node).collect()
1100    }
1101
1102    fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
1103        let id = node.get_attr("ID").unwrap_or_default().to_string();
1104        let annot_type = node
1105            .get_attr("Type")
1106            .map_or(AnnotType::Text, parse_annot_type);
1107        let flags = attr_u32(node, "Flags").unwrap_or(0);
1108        let creator = node.get_attr("Creator").map(String::from);
1109        let last_mod_date = node.get_attr("LastModDate").map(String::from);
1110        let location = node
1111            .get_attr("Location")
1112            .and_then(|s| {
1113                let parts: Vec<f64> = s
1114                    .split_whitespace()
1115                    .filter_map(|v| v.parse().ok())
1116                    .collect();
1117                if parts.len() == 4 {
1118                    Some([parts[0], parts[1], parts[2], parts[3]])
1119                } else {
1120                    None
1121                }
1122            })
1123            .unwrap_or([0.0, 0.0, 0.0, 0.0]);
1124        let appearances = node
1125            .children_named("Appearance")
1126            .map(Appearance::from_xml)
1127            .collect::<Result<Vec<_>, _>>()?;
1128        Ok(Self {
1129            id,
1130            creator,
1131            annot_type,
1132            flags,
1133            last_mod_date,
1134            location,
1135            appearances,
1136        })
1137    }
1138}
1139
1140// ═══════════════════════════════════════════════════════════════
1141// attachment 模块类型
1142// ═══════════════════════════════════════════════════════════════
1143
1144impl XmlElement for CTAttachment {
1145    /// 对应 Java: org.ofdrw.core.attachment.CT_Attachment
1146    fn element_name(&self) -> &'static str {
1147        "Attachment"
1148    }
1149
1150    fn attributes(&self) -> Vec<(String, String)> {
1151        let mut attrs = vec![
1152            ("ID".to_string(), self.id.clone()),
1153            ("Name".to_string(), self.name.clone()),
1154        ];
1155        if let Some(ref fmt) = self.format {
1156            attrs.push(("Format".to_string(), fmt.clone()));
1157        }
1158        if let Some(ref date) = self.creation_date {
1159            attrs.push(("CreationDate".to_string(), date.clone()));
1160        }
1161        if let Some(sz) = self.size {
1162            attrs.push(("Size".to_string(), sz.to_string()));
1163        }
1164        if !self.visible {
1165            attrs.push(("Visible".to_string(), "false".to_string()));
1166        }
1167        if let Some(ref file) = self.file {
1168            attrs.push(("File".to_string(), file.clone()));
1169        }
1170        attrs
1171    }
1172
1173    fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
1174        Ok(Self {
1175            id: node.get_attr("ID").unwrap_or_default().to_string(),
1176            name: node.get_attr("Name").unwrap_or_default().to_string(),
1177            format: node.get_attr("Format").map(String::from),
1178            creation_date: node.get_attr("CreationDate").map(String::from),
1179            size: node.get_attr("Size").and_then(|s| s.parse().ok()),
1180            visible: attr_bool(node, "Visible", true),
1181            file: node.get_attr("File").map(String::from),
1182        })
1183    }
1184}
1185
1186impl XmlElement for Attachments {
1187    /// 对应 Java: org.ofdrw.core.attachment.Attachments
1188    fn element_name(&self) -> &'static str {
1189        "Attachments"
1190    }
1191
1192    fn attributes(&self) -> Vec<(String, String)> {
1193        Vec::new()
1194    }
1195
1196    fn child_nodes(&self) -> Vec<XmlNode> {
1197        self.items.iter().map(to_xml_node).collect()
1198    }
1199
1200    fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
1201        let items = node
1202            .children_named("Attachment")
1203            .map(CTAttachment::from_xml)
1204            .collect::<Result<Vec<_>, _>>()?;
1205        Ok(Self { items })
1206    }
1207}
1208
1209// ═══════════════════════════════════════════════════════════════
1210// model 模块类型
1211// ═══════════════════════════════════════════════════════════════
1212
1213impl XmlElement for OfdMetadata {
1214    /// 对应 Java: org.ofdrw.core.basicStructure.ofd.docInfo.CT_DocInfo
1215    ///
1216    /// 仅序列化/反序列化 DocInfo 相关字段;非 XML 元素字段
1217    /// (如 `doc_dir`、`document_file` 等)保持默认值。
1218    fn element_name(&self) -> &'static str {
1219        "DocInfo"
1220    }
1221
1222    fn attributes(&self) -> Vec<(String, String)> {
1223        Vec::new()
1224    }
1225
1226    fn child_nodes(&self) -> Vec<XmlNode> {
1227        let mut nodes = Vec::new();
1228        if let Some(id) = &self.doc_id {
1229            nodes.push(text_child("DocID", id));
1230        }
1231        if let Some(title) = &self.title {
1232            nodes.push(text_child("Title", title));
1233        }
1234        if let Some(author) = &self.author {
1235            nodes.push(text_child("Author", author));
1236        }
1237        if let Some(creator) = &self.creator {
1238            nodes.push(text_child("Creator", creator));
1239        }
1240        if let Some(cv) = &self.creator_version {
1241            nodes.push(text_child("CreatorVersion", cv));
1242        }
1243        // CT_DocInfo 日期字段:优先使用原始文本(roundtrip 保真)
1244        if self.creation_date_raw.is_some() || self.creation_date.is_some() {
1245            let date_text = self
1246                .creation_date_raw
1247                .as_deref()
1248                .map(String::from)
1249                .or_else(|| {
1250                    self.creation_date
1251                        .map(|dt| dt.format("%Y-%m-%dT%H:%M:%S").to_string())
1252                });
1253            if let Some(text) = date_text {
1254                nodes.push(text_child("CreationDate", &text));
1255            }
1256        }
1257        if self.mod_date_raw.is_some() || self.mod_date.is_some() {
1258            let date_text = self.mod_date_raw.as_deref().map(String::from).or_else(|| {
1259                self.mod_date
1260                    .map(|dt| dt.format("%Y-%m-%dT%H:%M:%S").to_string())
1261            });
1262            if let Some(text) = date_text {
1263                nodes.push(text_child("ModDate", &text));
1264            }
1265        }
1266        if self.max_unit_id > 0 {
1267            nodes.push(text_child("MaxUnitID", &self.max_unit_id.to_string()));
1268        }
1269        if let Some(usage) = &self.doc_usage {
1270            nodes.push(text_child("DocUsage", usage));
1271        }
1272        if let Some(kw) = &self.keywords {
1273            nodes.push(text_child("Keywords", kw));
1274        }
1275        // 对应 ofdrw CT_DocInfo.Subject
1276        if let Some(subj) = &self.subject {
1277            nodes.push(text_child("Subject", subj));
1278        }
1279        nodes
1280    }
1281
1282    fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
1283        let creation_date_raw = child_text(node, "CreationDate");
1284        let mod_date_raw = child_text(node, "ModDate");
1285        Ok(Self {
1286            doc_id: child_text(node, "DocID"),
1287            title: child_text(node, "Title"),
1288            author: child_text(node, "Author"),
1289            creator: child_text(node, "Creator"),
1290            creator_version: child_text(node, "CreatorVersion"),
1291            creation_date: creation_date_raw
1292                .as_deref()
1293                .and_then(|s| chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S").ok()),
1294            creation_date_raw,
1295            mod_date: mod_date_raw
1296                .as_deref()
1297                .and_then(|s| chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S").ok()),
1298            mod_date_raw,
1299            max_unit_id: child_u32(node, "MaxUnitID").unwrap_or(0),
1300            doc_usage: child_text(node, "DocUsage"),
1301            keywords: child_text(node, "Keywords"),
1302            subject: child_text(node, "Subject"),
1303            ..Default::default()
1304        })
1305    }
1306}
1307
1308// ═══════════════════════════════════════════════════════════════
1309// 测试
1310// ═══════════════════════════════════════════════════════════════
1311
1312#[cfg(test)]
1313mod tests {
1314    use super::*;
1315
1316    /// 辅助:to_xml → parse → from_xml,返回恢复后的类型。
1317    fn roundtrip_parse<E: XmlElement>(original: &E) -> E {
1318        let xml = original.to_xml();
1319        let node =
1320            parse_xml_to_nodes(&xml).unwrap_or_else(|e| panic!("parse 失败: {e}\nXML: {xml}"));
1321        E::from_xml(&node).unwrap_or_else(|e| panic!("from_xml 失败: {e}\nXML: {xml}"))
1322    }
1323
1324    // ── PageEntry ──
1325
1326    #[test]
1327    fn page_entry_roundtrip() {
1328        let pe = PageEntry::new(5, "Pages/Page_4.xml");
1329        let r = roundtrip_parse(&pe);
1330        assert_eq!(r.id, 5);
1331        assert_eq!(r.base_loc, "Pages/Page_4.xml");
1332    }
1333
1334    #[test]
1335    fn page_entry_to_xml_attrs() {
1336        let pe = PageEntry::new(1, "Pages/Page_0.xml");
1337        let xml = pe.to_xml();
1338        assert!(xml.contains("ID=\"1\""));
1339        assert!(xml.contains("BaseLoc=\"Pages/Page_0.xml\""));
1340    }
1341
1342    // ── Pages ──
1343
1344    #[test]
1345    fn pages_roundtrip() {
1346        let mut p = Pages::new();
1347        p.add_page(PageEntry::new(1, "Pages/Page_0.xml"));
1348        p.add_page(PageEntry::new(2, "Pages/Page_1.xml"));
1349        let r = roundtrip_parse(&p);
1350        assert_eq!(r.pages.len(), 2);
1351        assert_eq!(r.pages[0].id, 1);
1352        assert_eq!(r.pages[1].id, 2);
1353    }
1354
1355    #[test]
1356    fn pages_to_xml_children() {
1357        let mut p = Pages::new();
1358        p.add_page(PageEntry::new(1, "P0.xml"));
1359        let xml = p.to_xml();
1360        assert!(xml.contains("<Pages>"));
1361        assert!(xml.contains("<Page "));
1362        assert!(xml.contains("</Pages>"));
1363    }
1364
1365    // ── Document ──
1366
1367    #[test]
1368    fn document_roundtrip_basic() {
1369        let mut doc = Document::new();
1370        doc.common_data = Some("CommonData.xml".to_string());
1371        doc.add_page(PageRef::new(1, ST_Loc::new("Pages/Page_0.xml")));
1372        let r = roundtrip_parse(&doc);
1373        assert_eq!(r.common_data.as_deref(), Some("CommonData.xml"));
1374        assert_eq!(r.pages.len(), 1);
1375        assert_eq!(r.pages[0].id, 1);
1376    }
1377
1378    #[test]
1379    fn document_to_xml_with_refs() {
1380        let doc = Document::new()
1381            .annotations(ST_Loc::new("Annots/Annotations.xml"))
1382            .attachments(ST_Loc::new("Attachs/Attachments.xml"));
1383        let xml = doc.to_xml();
1384        assert!(xml.contains("<Document>"));
1385        assert!(xml.contains("<Annotations>Annots/Annotations.xml</Annotations>"));
1386        assert!(xml.contains("<Attachments>Attachs/Attachments.xml</Attachments>"));
1387        assert!(xml.contains("</Document>"));
1388    }
1389
1390    // ── DocBody ──
1391
1392    #[test]
1393    fn doc_body_roundtrip() {
1394        let db = DocBody::new(ST_Loc::new("Doc_0/Document.xml"))
1395            .doc_info("test info")
1396            .signatures(ST_Loc::new("Doc_0/Signs/Signatures.xml"));
1397        let xml = db.to_xml();
1398        let node = parse_xml_to_nodes(&xml).unwrap();
1399        let restored = DocBody::from_xml(&node).unwrap();
1400        assert_eq!(restored.doc_root.loc(), "Doc_0/Document.xml");
1401        assert_eq!(restored.doc_info.as_deref(), Some("test info"));
1402        assert_eq!(
1403            restored.signatures.as_ref().map(|s| s.loc()),
1404            Some("Doc_0/Signs/Signatures.xml")
1405        );
1406    }
1407
1408    #[test]
1409    fn doc_body_to_xml_children() {
1410        let db = DocBody::new(ST_Loc::new("Doc_0/Document.xml"));
1411        let xml = db.to_xml();
1412        assert!(xml.contains("<DocRoot>Doc_0/Document.xml</DocRoot>"));
1413    }
1414
1415    // ── Res ──
1416
1417    #[test]
1418    fn res_roundtrip() {
1419        let mut r = Res::new().base_loc(ST_Loc::new("./Res"));
1420        r.add_resource("<Font ID=\"1\" FamilyName=\"SimSun\"/>");
1421        let xml = r.to_xml();
1422        let node = parse_xml_to_nodes(&xml).unwrap();
1423        let restored = Res::from_xml(&node).unwrap();
1424        assert_eq!(restored.base_loc.as_ref().map(|l| l.loc()), Some("./Res"));
1425        assert_eq!(restored.resource_count(), 1);
1426    }
1427
1428    // ── Outlines / CT_OutlineElem ──
1429
1430    #[test]
1431    fn outline_elem_roundtrip() {
1432        let elem = CT_OutlineElem::new("Chapter 1").page(3);
1433        let r = roundtrip_parse(&elem);
1434        assert_eq!(r.title, "Chapter 1");
1435        assert_eq!(r.page, Some(3));
1436    }
1437
1438    #[test]
1439    fn outlines_roundtrip() {
1440        let mut ol = Outlines::new();
1441        ol.add(CT_OutlineElem::new("Ch1").page(1));
1442        ol.add(CT_OutlineElem::new("Ch2").page(5));
1443        let r = roundtrip_parse(&ol);
1444        assert_eq!(r.elements.len(), 2);
1445        assert_eq!(r.elements[0].title, "Ch1");
1446        assert_eq!(r.elements[1].page, Some(5));
1447    }
1448
1449    #[test]
1450    fn outline_elem_nested() {
1451        let mut root = CT_OutlineElem::new("Root").page(1);
1452        root.add_child(CT_OutlineElem::new("Child").page(2));
1453        let xml = root.to_xml();
1454        assert!(xml.contains("Title=\"Root\""));
1455        assert!(xml.contains("Title=\"Child\""));
1456        let node = parse_xml_to_nodes(&xml).unwrap();
1457        let restored = CT_OutlineElem::from_xml(&node).unwrap();
1458        assert_eq!(restored.title, "Root");
1459        assert_eq!(restored.children.len(), 1);
1460        assert_eq!(restored.children[0].title, "Child");
1461    }
1462
1463    // ── Keywords ──
1464
1465    #[test]
1466    fn keywords_roundtrip() {
1467        let mut kw = Keywords::new();
1468        kw.add("OFD");
1469        kw.add("PDF");
1470        let xml = kw.to_xml();
1471        let node = parse_xml_to_nodes(&xml).unwrap();
1472        let restored = Keywords::from_xml(&node).unwrap();
1473        assert_eq!(restored.keywords, vec!["OFD", "PDF"]);
1474    }
1475
1476    // ── CT_TemplatePage ──
1477
1478    #[test]
1479    fn template_page_roundtrip() {
1480        let tpl = CT_TemplatePage::new(10)
1481            .name("bg")
1482            .z_order(TemplateZOrder::Front)
1483            .base_loc("Tpl_10.xml");
1484        let r = roundtrip_parse(&tpl);
1485        assert_eq!(r.id, 10);
1486        assert_eq!(r.name.as_deref(), Some("bg"));
1487        assert_eq!(r.z_order, Some(TemplateZOrder::Front));
1488        assert_eq!(r.base_loc.as_deref(), Some("Tpl_10.xml"));
1489    }
1490
1491    // ── CT_PageArea ──
1492
1493    #[test]
1494    fn page_area_roundtrip() {
1495        let area = CT_PageArea::new()
1496            .physical_box(0.0, 0.0, 210.0, 297.0)
1497            .bleed_box(-5.0, -5.0, 220.0, 307.0);
1498        let r = roundtrip_parse(&area);
1499        assert_eq!(r.physical_box.as_deref(), Some("0 0 210 297"));
1500        assert_eq!(r.bleed_box.as_deref(), Some("-5 -5 220 307"));
1501    }
1502
1503    // ── CT_CommonData ──
1504
1505    #[test]
1506    fn common_data_roundtrip() {
1507        let mut cd = CT_CommonData::new()
1508            .max_unit_id(50)
1509            .page_area(CT_PageArea::with_physical(0.0, 0.0, 210.0, 297.0))
1510            .default_cs(2);
1511        cd.add_public_res("PublicRes.xml");
1512        cd.add_template_page(CT_TemplatePage::new(1).name("bg"));
1513        let xml = cd.to_xml();
1514        let node = parse_xml_to_nodes(&xml).unwrap();
1515        let restored = CT_CommonData::from_xml(&node).unwrap();
1516        assert_eq!(restored.max_unit_id, Some(50));
1517        assert!(restored.page_area.is_some());
1518        assert_eq!(restored.public_res, vec!["PublicRes.xml"]);
1519        assert_eq!(restored.default_cs, Some(2));
1520        assert_eq!(restored.template_pages.len(), 1);
1521    }
1522
1523    // ── CT_PageBlock ──
1524
1525    #[test]
1526    fn page_block_roundtrip() {
1527        let mut block = CT_PageBlock::new();
1528        block.add_text_object(PageBlockTextObject::new(1, "0 0 100 20", "hello"));
1529        block.add_path_object(PageBlockPathObject::new(2, "0 0 50 50", "M0 0L10 10"));
1530        block.add_image_object(PageBlockImageObject::new(3, "0 0 100 100", 10));
1531        let xml = block.to_xml();
1532        let node = parse_xml_to_nodes(&xml).unwrap();
1533        let restored = CT_PageBlock::from_xml(&node).unwrap();
1534        assert_eq!(restored.text_objects.len(), 1);
1535        assert_eq!(restored.text_objects[0].content, "hello");
1536        assert_eq!(restored.path_objects.len(), 1);
1537        assert_eq!(restored.image_objects.len(), 1);
1538        assert_eq!(restored.image_objects[0].resource_id, 10);
1539    }
1540
1541    #[test]
1542    fn page_block_nested_roundtrip() {
1543        let mut inner = CT_PageBlock::new();
1544        inner.add_text_object(PageBlockTextObject::new(1, "0 0 10 10", "x"));
1545        let mut outer = CT_PageBlock::new();
1546        outer.add_text_object(PageBlockTextObject::new(2, "0 0 10 10", "y"));
1547        outer.add_page_block(inner);
1548        let xml = outer.to_xml();
1549        let node = parse_xml_to_nodes(&xml).unwrap();
1550        let restored = CT_PageBlock::from_xml(&node).unwrap();
1551        assert_eq!(restored.text_objects.len(), 1);
1552        assert_eq!(restored.page_blocks.len(), 1);
1553        assert_eq!(restored.page_blocks[0].text_objects.len(), 1);
1554    }
1555
1556    // ── CT_Layer ──
1557
1558    #[test]
1559    fn layer_roundtrip() {
1560        let mut layer = CT_Layer::foreground().draw_param(7);
1561        layer
1562            .block
1563            .add_text_object(PageBlockTextObject::new(1, "0 0 50 20", "hi"));
1564        let xml = layer.to_xml();
1565        let node = parse_xml_to_nodes(&xml).unwrap();
1566        let restored = CT_Layer::from_xml(&node).unwrap();
1567        assert_eq!(restored.layer_type, LayerType::Foreground);
1568        assert_eq!(restored.draw_param, Some(7));
1569        assert_eq!(restored.block.text_objects.len(), 1);
1570        assert_eq!(restored.block.text_objects[0].content, "hi");
1571    }
1572
1573    #[test]
1574    fn layer_to_xml_attrs() {
1575        let layer = CT_Layer::body();
1576        let xml = layer.to_xml();
1577        assert!(xml.contains("Type=\"Body\""));
1578    }
1579
1580    // ── TextCode ──
1581
1582    #[test]
1583    fn text_code_roundtrip() {
1584        let tc = TextCode::with_content("Hello")
1585            .coordinate(10.0, 20.0)
1586            .delta_x(vec![6.0, 6.0, 6.0]);
1587        let xml = tc.to_xml();
1588        let node = parse_xml_to_nodes(&xml).unwrap();
1589        let restored = TextCode::from_xml(&node).unwrap();
1590        assert_eq!(restored.content, "Hello");
1591        assert!((restored.x.unwrap() - 10.0).abs() < f64::EPSILON);
1592        assert!((restored.y.unwrap() - 20.0).abs() < f64::EPSILON);
1593        assert_eq!(restored.delta_x.len(), 3);
1594    }
1595
1596    // ── CT_CGTransform ──
1597
1598    #[test]
1599    fn cg_transform_roundtrip() {
1600        let cg = CT_CGTransform::new()
1601            .code_position(0)
1602            .code_count(2)
1603            .glyph_count(2)
1604            .glyphs(vec![100, 200]);
1605        let r = roundtrip_parse(&cg);
1606        assert_eq!(r.code_position, Some(0));
1607        assert_eq!(r.code_count, Some(2));
1608        assert_eq!(r.glyph_count, Some(2));
1609        assert_eq!(r.glyphs, vec![100, 200]);
1610    }
1611
1612    // ── CT_Text ──
1613
1614    #[test]
1615    fn ct_text_roundtrip() {
1616        let mut t = CT_Text::new(5, "10 20 200 30")
1617            .font(3)
1618            .size(12.0)
1619            .weight(700)
1620            .italic(true)
1621            .stroke(true)
1622            .fill(false);
1623        t.add_text_code(TextCode::with_content("test").coordinate(10.0, 30.0));
1624        let xml = t.to_xml();
1625        let node = parse_xml_to_nodes(&xml).unwrap();
1626        let restored = CT_Text::from_xml(&node).unwrap();
1627        assert_eq!(restored.id, 5);
1628        assert_eq!(restored.boundary, "10 20 200 30");
1629        assert_eq!(restored.font_ref, Some(3));
1630        assert!((restored.size.unwrap() - 12.0).abs() < f64::EPSILON);
1631        assert_eq!(restored.weight, Some(700));
1632        assert!(restored.italic);
1633        assert!(restored.stroke);
1634        assert!(!restored.fill);
1635        assert_eq!(restored.text_codes.len(), 1);
1636        assert_eq!(restored.text_codes[0].content, "test");
1637    }
1638
1639    #[test]
1640    fn ct_text_to_xml_attrs() {
1641        let mut t = CT_Text::new(1, "0 0 100 20").font(2).size(14.0);
1642        t.add_text_code(TextCode::with_content("x"));
1643        let xml = t.to_xml();
1644        assert!(xml.contains("ID=\"1\""));
1645        assert!(xml.contains("Boundary=\"0 0 100 20\""));
1646        assert!(xml.contains("Font=\"2\""));
1647        assert!(xml.contains("Size=\"14\""));
1648        assert!(xml.contains("<TextObject"));
1649        assert!(xml.contains("</TextObject>"));
1650    }
1651
1652    // ── CT_Image ──
1653
1654    #[test]
1655    fn ct_image_roundtrip() {
1656        let img = CT_Image::new(1, "0 0 100 100", 5).interpolate(true);
1657        let r = roundtrip_parse(&img);
1658        assert_eq!(r.id, 1);
1659        assert_eq!(r.boundary, "0 0 100 100");
1660        assert_eq!(r.resource_id, 5);
1661        assert!(r.interpolate);
1662    }
1663
1664    #[test]
1665    fn ct_image_to_xml_attrs() {
1666        let img = CT_Image::new(2, "10 20 50 50", 3);
1667        let xml = img.to_xml();
1668        assert!(xml.contains("ID=\"2\""));
1669        assert!(xml.contains("ResourceID=\"3\""));
1670        assert!(!xml.contains("Interpolate"));
1671    }
1672
1673    // ── CTDest ──
1674
1675    #[test]
1676    fn ct_dest_roundtrip() {
1677        let dest = CTDest::new(2)
1678            .dest_type(DestType::XYZ)
1679            .left(10.5)
1680            .top(20.5)
1681            .zoom(2.0);
1682        let r = roundtrip_parse(&dest);
1683        assert_eq!(r.page, 2);
1684        assert_eq!(r.dest_type, DestType::XYZ);
1685        assert!((r.left.unwrap() - 10.5).abs() < f64::EPSILON);
1686        assert!((r.top.unwrap() - 20.5).abs() < f64::EPSILON);
1687        assert!((r.zoom.unwrap() - 2.0).abs() < f64::EPSILON);
1688    }
1689
1690    // ── Goto ──
1691
1692    #[test]
1693    fn goto_roundtrip() {
1694        let dest = CTDest::new(3).dest_type(DestType::FitH).left(10.0);
1695        let goto = Goto::new(dest);
1696        let xml = goto.to_xml();
1697        let node = parse_xml_to_nodes(&xml).unwrap();
1698        let restored = Goto::from_xml(&node).unwrap();
1699        assert_eq!(restored.dest.page, 3);
1700        assert_eq!(restored.dest.dest_type, DestType::FitH);
1701        assert!((restored.dest.left.unwrap() - 10.0).abs() < f64::EPSILON);
1702    }
1703
1704    // ── Appearance ──
1705
1706    #[test]
1707    fn appearance_roundtrip() {
1708        let app = Appearance::new("app1", "Normal").resource_path("/res/a.xml");
1709        let r = roundtrip_parse(&app);
1710        assert_eq!(r.id, "app1");
1711        assert_eq!(r.appearance_type, "Normal");
1712        assert_eq!(r.resource_path.as_deref(), Some("/res/a.xml"));
1713    }
1714
1715    // ── Annot ──
1716
1717    #[test]
1718    fn annot_roundtrip() {
1719        let a = Annot::new("ann1", AnnotType::Highlight)
1720            .creator("user1")
1721            .flags(4)
1722            .last_mod_date("2025-01-01T00:00:00")
1723            .location(10.0, 20.0, 30.0, 40.0)
1724            .add_appearance(Appearance::new("app1", "Normal"));
1725        let xml = a.to_xml();
1726        let node = parse_xml_to_nodes(&xml).unwrap();
1727        let restored = Annot::from_xml(&node).unwrap();
1728        assert_eq!(restored.id, "ann1");
1729        assert_eq!(restored.annot_type, AnnotType::Highlight);
1730        assert_eq!(restored.creator.as_deref(), Some("user1"));
1731        assert_eq!(restored.flags, 4);
1732        assert_eq!(restored.appearances.len(), 1);
1733    }
1734
1735    // ── CTAttachment ──
1736
1737    #[test]
1738    fn ct_attachment_roundtrip() {
1739        let a = CTAttachment::new("a1", "test.pdf")
1740            .format("application/pdf")
1741            .size(1024)
1742            .visible(false)
1743            .file("Attachments/test.pdf");
1744        let r = roundtrip_parse(&a);
1745        assert_eq!(r.id, "a1");
1746        assert_eq!(r.name, "test.pdf");
1747        assert_eq!(r.format.as_deref(), Some("application/pdf"));
1748        assert_eq!(r.size, Some(1024));
1749        assert!(!r.visible);
1750        assert_eq!(r.file.as_deref(), Some("Attachments/test.pdf"));
1751    }
1752
1753    // ── Attachments ──
1754
1755    #[test]
1756    fn attachments_roundtrip() {
1757        let a = Attachments::new()
1758            .add_attachment(CTAttachment::new("a1", "readme.txt"))
1759            .add_attachment(CTAttachment::new("a2", "data.xlsx"));
1760        let xml = a.to_xml();
1761        let node = parse_xml_to_nodes(&xml).unwrap();
1762        let restored = Attachments::from_xml(&node).unwrap();
1763        assert_eq!(restored.len(), 2);
1764        assert_eq!(restored.items[0].id, "a1");
1765        assert_eq!(restored.items[1].id, "a2");
1766    }
1767
1768    // ── OfdMetadata ──
1769
1770    #[test]
1771    fn ofd_metadata_roundtrip() {
1772        let meta = OfdMetadata {
1773            doc_id: Some("doc-001".to_string()),
1774            title: Some("Test Document".to_string()),
1775            author: Some("Author Name".to_string()),
1776            creator: Some("easyofd".to_string()),
1777            creator_version: Some("1.0".to_string()),
1778            max_unit_id: 42,
1779            doc_usage: Some("Normal".to_string()),
1780            keywords: Some("OFD,test".to_string()),
1781            ..Default::default()
1782        };
1783        let xml = meta.to_xml();
1784        let node = parse_xml_to_nodes(&xml).unwrap();
1785        let restored = OfdMetadata::from_xml(&node).unwrap();
1786        assert_eq!(restored.doc_id.as_deref(), Some("doc-001"));
1787        assert_eq!(restored.title.as_deref(), Some("Test Document"));
1788        assert_eq!(restored.author.as_deref(), Some("Author Name"));
1789        assert_eq!(restored.creator.as_deref(), Some("easyofd"));
1790        assert_eq!(restored.max_unit_id, 42);
1791        assert_eq!(restored.doc_usage.as_deref(), Some("Normal"));
1792    }
1793
1794    #[test]
1795    fn ofd_metadata_to_xml_children() {
1796        let meta = OfdMetadata {
1797            doc_id: Some("id1".to_string()),
1798            author: Some("auth".to_string()),
1799            ..Default::default()
1800        };
1801        let xml = meta.to_xml();
1802        assert!(xml.contains("<DocID>id1</DocID>"));
1803        assert!(xml.contains("<Author>auth</Author>"));
1804        assert!(xml.contains("<DocInfo"));
1805        assert!(xml.contains("</DocInfo>"));
1806    }
1807}