1use 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
31fn 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 node.push_child(XmlNode::text_node(text));
47 }
48 node
49}
50
51fn 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
60fn attr_u32(node: &XmlNode, key: &str) -> Option<u32> {
62 node.get_attr(key).and_then(|s| s.parse().ok())
63}
64
65fn attr_f64(node: &XmlNode, key: &str) -> Option<f64> {
67 node.get_attr(key).and_then(|s| s.parse().ok())
68}
69
70fn 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
79fn child_text(node: &XmlNode, name: &str) -> Option<String> {
81 node.child(name).and_then(|c| c.text.clone())
82}
83
84fn child_u32(node: &XmlNode, name: &str) -> Option<u32> {
86 child_text(node, name).and_then(|s| s.parse().ok())
87}
88
89fn parse_layer_type(s: &str) -> LayerType {
91 match s {
92 "Foreground" => LayerType::Foreground,
93 "Background" => LayerType::Background,
94 _ => LayerType::Body,
95 }
96}
97
98fn 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
107fn parse_annot_type(s: &str) -> AnnotType {
109 AnnotType::from_str_opt(s).unwrap_or(AnnotType::Text)
110}
111
112fn 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
124impl XmlElement for PageEntry {
129 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 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 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 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 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 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 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 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
420impl XmlElement for CT_TemplatePage {
425 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 fn element_name(&self) -> &'static str {
457 "PageArea"
458 }
459
460 fn attributes(&self) -> Vec<(String, String)> {
461 Vec::new()
462 }
463
464 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 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 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 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 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 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 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 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
724impl XmlElement for TextCode {
729 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 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 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
947impl XmlElement for CT_Image {
952 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
979impl XmlElement for CTDest {
984 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 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
1045impl XmlElement for Appearance {
1050 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 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
1140impl XmlElement for CTAttachment {
1145 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 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
1209impl XmlElement for OfdMetadata {
1214 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 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 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#[cfg(test)]
1313mod tests {
1314 use super::*;
1315
1316 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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}