1use crate::oxml::writer::XmlWriter;
32
33#[derive(Clone, Debug, Default)]
61pub struct DataModel {
62 pub points: Vec<DataModelPoint>,
64 pub connections: Vec<DataModelConnection>,
66}
67
68#[derive(Clone, Debug, Default)]
72pub struct DataModelPoint {
73 pub model_id: u32,
75 pub pt_type: Option<String>,
80 pub text: Option<String>,
84 pub raw_xml: String,
86}
87
88#[derive(Clone, Debug, Default)]
92pub struct DataModelConnection {
93 pub src_id: u32,
95 pub dest_id: u32,
97 pub cxn_type: Option<String>,
101 pub raw_xml: String,
103}
104
105impl DataModelPoint {
106 pub fn set_text(&mut self, new_text: impl Into<String>) {
124 let new_text = new_text.into();
125 let escaped = escape_xml_text(&new_text);
126 if let Some(start) = self.raw_xml.find("<a:t>") {
128 if let Some(end_rel) = self.raw_xml[start..].find("</a:t>") {
130 let content_start = start + "<a:t>".len();
131 let content_end = start + end_rel;
132 let prefix = &self.raw_xml[..content_start];
133 let suffix = &self.raw_xml[content_end..];
134 self.raw_xml = format!("{}{}{}", prefix, escaped, suffix);
135 }
136 }
137 self.text = Some(new_text);
138 }
139
140 pub fn clear_text(&mut self) {
145 if let Some(start) = self.raw_xml.find("<a:t>") {
147 if let Some(end_rel) = self.raw_xml[start..].find("</a:t>") {
148 let content_start = start + "<a:t>".len();
149 let content_end = start + end_rel;
150 let prefix = &self.raw_xml[..content_start];
151 let suffix = &self.raw_xml[content_end..];
152 self.raw_xml = format!("{}{}", prefix, suffix);
153 }
154 }
155 self.text = None;
156 }
157
158 pub fn is_type(&self, type_str: &str) -> bool {
162 self.pt_type.as_deref() == Some(type_str)
163 }
164}
165
166fn escape_xml_text(s: &str) -> String {
170 let mut out = String::with_capacity(s.len());
171 for c in s.chars() {
172 match c {
173 '&' => out.push_str("&"),
174 '<' => out.push_str("<"),
175 '>' => out.push_str(">"),
176 '\'' => out.push_str("'"),
177 '"' => out.push_str("""),
178 _ => out.push(c),
179 }
180 }
181 out
182}
183
184impl DataModel {
185 pub fn parse_from_xml(xml: &str) -> crate::Result<DataModel> {
205 let _ = xml; let mut points: Vec<DataModelPoint> = Vec::new();
207 let mut connections: Vec<DataModelConnection> = Vec::new();
208
209 let mut rd = quick_xml::reader::Reader::from_str(xml);
210 rd.config_mut().trim_text(true);
211 let mut buf = Vec::new();
212
213 let mut cur_pt: Option<DataModelPoint> = None;
215 let mut cur_cxn: Option<DataModelConnection> = None;
216 let mut pt_depth: i32 = 0;
218 let mut cxn_depth: i32 = 0;
219 let mut in_pt_text = false; let mut cur_pt_raw = String::new();
222 let mut cur_cxn_raw = String::new();
223 let mut cur_text_buf = String::new();
225
226 loop {
227 match rd.read_event_into(&mut buf) {
228 Ok(quick_xml::events::Event::Start(e)) => {
229 let name = e.name();
231 let local = local_name(name.as_ref());
232 if local == b"pt" && pt_depth == 0 {
233 pt_depth = 1;
235 let mut pt = DataModelPoint::default();
236 for a in e.attributes().flatten() {
237 let key = a.key.as_ref();
238 let val = a
239 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
240 .unwrap_or_default()
241 .to_string();
242 if key == b"modelId" {
243 if let Ok(n) = val.parse::<u32>() {
244 pt.model_id = n;
245 }
246 } else if key == b"type" {
247 pt.pt_type = Some(val);
248 }
249 }
250 cur_pt = Some(pt);
251 cur_text_buf.clear();
252 cur_pt_raw.clear();
254 cur_pt_raw.push('<');
255 cur_pt_raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
256 cur_pt_raw.push('>');
257 } else if local == b"cxn" && cxn_depth == 0 {
258 cxn_depth = 1;
260 let mut cxn = DataModelConnection::default();
261 for a in e.attributes().flatten() {
262 let key = a.key.as_ref();
263 let val = a
264 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
265 .unwrap_or_default()
266 .to_string();
267 if key == b"srcId" {
268 if let Ok(n) = val.parse::<u32>() {
269 cxn.src_id = n;
270 }
271 } else if key == b"destId" {
272 if let Ok(n) = val.parse::<u32>() {
273 cxn.dest_id = n;
274 }
275 } else if key == b"type" {
276 cxn.cxn_type = Some(val);
277 }
278 }
279 cur_cxn = Some(cxn);
280 cur_cxn_raw.clear();
281 cur_cxn_raw.push('<');
282 cur_cxn_raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
283 cur_cxn_raw.push('>');
284 } else {
285 if pt_depth > 0 {
287 cur_pt_raw.push('<');
288 cur_pt_raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
289 cur_pt_raw.push('>');
290 if local == b"t" {
291 in_pt_text = true;
292 }
293 if local == b"pt" {
295 pt_depth += 1;
296 }
297 } else if cxn_depth > 0 {
298 cur_cxn_raw.push('<');
299 cur_cxn_raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
300 cur_cxn_raw.push('>');
301 if local == b"cxn" {
302 cxn_depth += 1;
303 }
304 }
305 }
306 }
307 Ok(quick_xml::events::Event::Empty(e)) => {
308 let name = e.name();
309 let local = local_name(name.as_ref());
310 if local == b"pt" && pt_depth == 0 {
311 let mut pt = DataModelPoint::default();
313 for a in e.attributes().flatten() {
314 let key = a.key.as_ref();
315 let val = a
316 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
317 .unwrap_or_default()
318 .to_string();
319 if key == b"modelId" {
320 if let Ok(n) = val.parse::<u32>() {
321 pt.model_id = n;
322 }
323 } else if key == b"type" {
324 pt.pt_type = Some(val);
325 }
326 }
327 let mut raw = String::from("<");
329 raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
330 raw.push_str("/>");
331 pt.raw_xml = raw;
332 points.push(pt);
333 } else if local == b"cxn" && cxn_depth == 0 {
334 let mut cxn = DataModelConnection::default();
336 for a in e.attributes().flatten() {
337 let key = a.key.as_ref();
338 let val = a
339 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
340 .unwrap_or_default()
341 .to_string();
342 if key == b"srcId" {
343 if let Ok(n) = val.parse::<u32>() {
344 cxn.src_id = n;
345 }
346 } else if key == b"destId" {
347 if let Ok(n) = val.parse::<u32>() {
348 cxn.dest_id = n;
349 }
350 } else if key == b"type" {
351 cxn.cxn_type = Some(val);
352 }
353 }
354 let mut raw = String::from("<");
355 raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
356 raw.push_str("/>");
357 cxn.raw_xml = raw;
358 connections.push(cxn);
359 } else if pt_depth > 0 {
360 cur_pt_raw.push('<');
362 cur_pt_raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
363 cur_pt_raw.push_str("/>");
364 } else if cxn_depth > 0 {
365 cur_cxn_raw.push('<');
367 cur_cxn_raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
368 cur_cxn_raw.push_str("/>");
369 }
370 }
371 Ok(quick_xml::events::Event::Text(t)) => {
372 if in_pt_text && pt_depth > 0 {
373 let text_str = std::str::from_utf8(t.as_ref()).unwrap_or("");
377 let text = quick_xml::escape::unescape(text_str)
378 .unwrap_or_default()
379 .to_string();
380 if !text.is_empty() {
381 cur_text_buf.push_str(&text);
382 }
383 }
384 if pt_depth > 0 {
386 cur_pt_raw.push_str(std::str::from_utf8(t.as_ref()).unwrap_or(""));
387 } else if cxn_depth > 0 {
388 cur_cxn_raw.push_str(std::str::from_utf8(t.as_ref()).unwrap_or(""));
389 }
390 }
391 Ok(quick_xml::events::Event::End(e)) => {
392 let name = e.name();
393 let local = local_name(name.as_ref());
394 if pt_depth > 0 {
395 cur_pt_raw.push_str("</");
397 cur_pt_raw.push_str(std::str::from_utf8(name.as_ref()).unwrap_or(""));
398 cur_pt_raw.push('>');
399 if local == b"t" {
400 in_pt_text = false;
401 } else if local == b"pt" {
402 pt_depth -= 1;
403 if pt_depth == 0 {
404 if let Some(mut pt) = cur_pt.take() {
406 pt.raw_xml = std::mem::take(&mut cur_pt_raw);
407 if !cur_text_buf.is_empty() {
408 pt.text = Some(cur_text_buf.clone());
409 }
410 points.push(pt);
411 }
412 cur_text_buf.clear();
413 }
414 }
415 } else if cxn_depth > 0 {
416 cur_cxn_raw.push_str("</");
417 cur_cxn_raw.push_str(std::str::from_utf8(name.as_ref()).unwrap_or(""));
418 cur_cxn_raw.push('>');
419 if local == b"cxn" {
420 cxn_depth -= 1;
421 if cxn_depth == 0 {
422 if let Some(mut cxn) = cur_cxn.take() {
423 cxn.raw_xml = std::mem::take(&mut cur_cxn_raw);
424 connections.push(cxn);
425 }
426 }
427 }
428 }
429 }
430 Ok(quick_xml::events::Event::Eof) => break,
431 Err(e) => return Err(crate::Error::Xml(format!("DataModel parse_from_xml: {e}"))),
432 _ => {}
433 }
434 buf.clear();
435 }
436
437 Ok(DataModel {
438 points,
439 connections,
440 })
441 }
442
443 pub fn to_xml(&self) -> String {
480 let mut w = XmlWriter::new();
481 w.raw("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n");
482 w.open_with(
483 "dgm:dataModel",
484 &[
485 (
486 "xmlns:dgm",
487 "http://schemas.openxmlformats.org/drawingml/2006/diagram",
488 ),
489 (
490 "xmlns:a",
491 "http://schemas.openxmlformats.org/drawingml/2006/main",
492 ),
493 ],
494 );
495 w.open("dgm:ptLst");
497 for pt in &self.points {
498 if pt.raw_xml.is_empty() {
499 let id_s = pt.model_id.to_string();
501 let mut attrs: Vec<(&str, &str)> = vec![("modelId", id_s.as_str())];
502 if let Some(t) = &pt.pt_type {
503 attrs.push(("type", t.as_str()));
504 }
505 if let Some(text) = &pt.text {
506 w.open_with("dgm:pt", &attrs);
508 w.open("dgm:t");
509 w.empty("a:bodyPr");
510 w.empty("a:lstStyle");
511 w.open("a:p");
512 w.open("a:r");
513 let escaped = escape_xml_text(text);
514 w.leaf("a:t", escaped.as_str());
515 w.close("a:r");
516 w.close("a:p");
517 w.close("dgm:t");
518 w.close("dgm:pt");
519 } else {
520 w.empty_with("dgm:pt", &attrs);
522 }
523 } else {
524 w.raw(&pt.raw_xml);
526 }
527 }
528 w.close("dgm:ptLst");
529 if !self.connections.is_empty() {
531 w.open("dgm:cxnLst");
532 for cxn in &self.connections {
533 if cxn.raw_xml.is_empty() {
534 let src_s = cxn.src_id.to_string();
535 let dst_s = cxn.dest_id.to_string();
536 let mut attrs: Vec<(&str, &str)> =
537 vec![("srcId", src_s.as_str()), ("destId", dst_s.as_str())];
538 if let Some(t) = &cxn.cxn_type {
539 attrs.push(("type", t.as_str()));
540 }
541 w.empty_with("dgm:cxn", &attrs);
542 } else {
543 w.raw(&cxn.raw_xml);
544 }
545 }
546 w.close("dgm:cxnLst");
547 }
548 w.close("dgm:dataModel");
549 w.into_string()
550 }
551
552 pub fn point_mut(&mut self, model_id: u32) -> Option<&mut DataModelPoint> {
556 self.points.iter_mut().find(|p| p.model_id == model_id)
557 }
558
559 pub fn point(&self, model_id: u32) -> Option<&DataModelPoint> {
561 self.points.iter().find(|p| p.model_id == model_id)
562 }
563
564 pub fn set_point_text(&mut self, model_id: u32, new_text: impl Into<String>) -> bool {
569 if let Some(pt) = self.point_mut(model_id) {
570 pt.set_text(new_text);
571 true
572 } else {
573 false
574 }
575 }
576}
577
578#[derive(Clone, Debug, Default)]
593pub struct LayoutDef {
594 pub unique_id: Option<String>,
596 pub title: Option<String>,
598 pub desc: Option<String>,
600 pub categories: Vec<LayoutCategory>,
602 pub layout_node_xml: String,
606}
607
608#[derive(Clone, Debug, Default)]
610pub struct LayoutCategory {
611 pub cat_type: Option<String>,
613 pub priority: Option<i32>,
615}
616
617impl LayoutDef {
618 pub fn parse_from_xml(xml: &str) -> crate::Result<LayoutDef> {
630 let _ = xml; let mut layout = LayoutDef::default();
632
633 let mut rd = quick_xml::reader::Reader::from_str(xml);
634 rd.config_mut().trim_text(true);
635 let mut buf = Vec::new();
636
637 let mut in_cat_lst = false;
638 let mut layout_node_depth: i32 = 0;
639 let mut cur_layout_raw = String::new();
641
642 loop {
643 match rd.read_event_into(&mut buf) {
644 Ok(quick_xml::events::Event::Start(e)) => {
645 let name = e.name();
647 let local = local_name(name.as_ref());
648 if local == b"layoutDef" {
649 for a in e.attributes().flatten() {
651 if a.key.as_ref() == b"uniqueId" {
652 layout.unique_id = Some(
653 a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
654 .unwrap_or_default()
655 .to_string(),
656 );
657 }
658 }
659 } else if local == b"title" {
660 for a in e.attributes().flatten() {
661 if a.key.as_ref() == b"val" {
662 layout.title = Some(
663 a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
664 .unwrap_or_default()
665 .to_string(),
666 );
667 }
668 }
669 } else if local == b"desc" {
670 for a in e.attributes().flatten() {
671 if a.key.as_ref() == b"val" {
672 layout.desc = Some(
673 a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
674 .unwrap_or_default()
675 .to_string(),
676 );
677 }
678 }
679 } else if local == b"catLst" {
680 in_cat_lst = true;
681 } else if local == b"cat" && in_cat_lst {
682 let mut cat = LayoutCategory::default();
683 for a in e.attributes().flatten() {
684 let key = a.key.as_ref();
685 let val = a
686 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
687 .unwrap_or_default()
688 .to_string();
689 if key == b"type" {
690 cat.cat_type = Some(val);
691 } else if key == b"pri" {
692 if let Ok(n) = val.parse::<i32>() {
693 cat.priority = Some(n);
694 }
695 }
696 }
697 layout.categories.push(cat);
698 } else if local == b"layoutNode" {
699 layout_node_depth += 1;
700 if layout_node_depth == 1 {
701 cur_layout_raw.clear();
703 }
704 cur_layout_raw.push('<');
706 cur_layout_raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
707 cur_layout_raw.push('>');
708 } else if layout_node_depth > 0 {
709 cur_layout_raw.push('<');
711 cur_layout_raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
712 cur_layout_raw.push('>');
713 }
714 }
715 Ok(quick_xml::events::Event::Empty(e)) => {
716 let name = e.name();
717 let local = local_name(name.as_ref());
718 if local == b"title" {
719 for a in e.attributes().flatten() {
720 if a.key.as_ref() == b"val" {
721 layout.title = Some(
722 a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
723 .unwrap_or_default()
724 .to_string(),
725 );
726 }
727 }
728 } else if local == b"desc" {
729 for a in e.attributes().flatten() {
730 if a.key.as_ref() == b"val" {
731 layout.desc = Some(
732 a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
733 .unwrap_or_default()
734 .to_string(),
735 );
736 }
737 }
738 } else if local == b"cat" && in_cat_lst {
739 let mut cat = LayoutCategory::default();
740 for a in e.attributes().flatten() {
741 let key = a.key.as_ref();
742 let val = a
743 .normalized_value(quick_xml::XmlVersion::Implicit1_0)
744 .unwrap_or_default()
745 .to_string();
746 if key == b"type" {
747 cat.cat_type = Some(val);
748 } else if key == b"pri" {
749 if let Ok(n) = val.parse::<i32>() {
750 cat.priority = Some(n);
751 }
752 }
753 }
754 layout.categories.push(cat);
755 } else if layout_node_depth > 0 {
756 cur_layout_raw.push('<');
758 cur_layout_raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
759 cur_layout_raw.push_str("/>");
760 }
761 }
762 Ok(quick_xml::events::Event::End(e)) => {
763 let name = e.name();
765 let local = local_name(name.as_ref());
766 if local == b"catLst" {
767 in_cat_lst = false;
768 } else if local == b"layoutNode" && layout_node_depth > 0 {
769 cur_layout_raw.push_str("</");
771 cur_layout_raw.push_str(std::str::from_utf8(name.as_ref()).unwrap_or(""));
772 cur_layout_raw.push('>');
773 layout_node_depth -= 1;
774 if layout_node_depth == 0 {
775 layout.layout_node_xml = std::mem::take(&mut cur_layout_raw);
777 }
778 } else if layout_node_depth > 0 {
779 cur_layout_raw.push_str("</");
781 cur_layout_raw.push_str(std::str::from_utf8(name.as_ref()).unwrap_or(""));
782 cur_layout_raw.push('>');
783 }
784 }
785 Ok(quick_xml::events::Event::Text(t)) => {
786 if layout_node_depth > 0 {
787 cur_layout_raw.push_str(std::str::from_utf8(t.as_ref()).unwrap_or(""));
788 }
789 }
790 Ok(quick_xml::events::Event::Eof) => break,
791 Err(e) => return Err(crate::Error::Xml(format!("LayoutDef parse_from_xml: {e}"))),
792 _ => {}
793 }
794 buf.clear();
795 }
796
797 Ok(layout)
798 }
799
800 pub fn to_xml(&self) -> String {
804 let mut w = XmlWriter::new();
805 w.raw("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n");
806 let mut attrs: Vec<(&str, &str)> = vec![(
807 "xmlns:dgm",
808 "http://schemas.openxmlformats.org/drawingml/2006/diagram",
809 )];
810 if let Some(id) = &self.unique_id {
811 attrs.push(("uniqueId", id.as_str()));
812 }
813 w.open_with("dgm:layoutDef", &attrs);
814 if let Some(t) = &self.title {
815 w.empty_with("dgm:title", &[("val", t.as_str())]);
816 }
817 if let Some(d) = &self.desc {
818 w.empty_with("dgm:desc", &[("val", d.as_str())]);
819 }
820 if !self.categories.is_empty() {
821 w.open("dgm:catLst");
822 for cat in &self.categories {
823 let p_s = cat.priority.as_ref().map(|p| p.to_string());
825 let mut cattrs: Vec<(&str, &str)> = Vec::new();
826 if let Some(t) = &cat.cat_type {
827 cattrs.push(("type", t.as_str()));
828 }
829 if let Some(s) = p_s.as_deref() {
830 cattrs.push(("pri", s));
831 }
832 w.empty_with("dgm:cat", &cattrs);
833 }
834 w.close("dgm:catLst");
835 }
836 if !self.layout_node_xml.is_empty() {
838 w.raw(&self.layout_node_xml);
839 }
840 w.close("dgm:layoutDef");
841 w.into_string()
842 }
843}
844
845#[derive(Clone, Debug, Default)]
859pub struct QuickStyleDef {
860 pub style_labels: Vec<StyleLabel>,
862}
863
864#[derive(Clone, Debug, Default)]
868pub struct StyleLabel {
869 pub name: Option<String>,
871 pub raw_xml: String,
873}
874
875impl QuickStyleDef {
876 pub fn parse_from_xml(xml: &str) -> crate::Result<QuickStyleDef> {
886 let _ = xml; let mut style_labels: Vec<StyleLabel> = Vec::new();
888
889 let mut rd = quick_xml::reader::Reader::from_str(xml);
890 rd.config_mut().trim_text(true);
891 let mut buf = Vec::new();
892
893 let mut cur_lbl: Option<StyleLabel> = None;
894 let mut lbl_depth: i32 = 0;
895 let mut cur_raw = String::new();
897
898 loop {
899 match rd.read_event_into(&mut buf) {
900 Ok(quick_xml::events::Event::Start(e)) => {
901 let name = e.name();
903 let local = local_name(name.as_ref());
904 if local == b"styleLbl" {
905 if lbl_depth == 0 {
906 cur_lbl = Some(StyleLabel::default());
907 lbl_depth = 1;
908 cur_raw.clear();
909 cur_raw.push('<');
912 cur_raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
913 cur_raw.push('>');
914 for a in e.attributes().flatten() {
916 if a.key.as_ref() == b"name" {
917 if let Some(lbl) = cur_lbl.as_mut() {
918 lbl.name = Some(
919 a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
920 .unwrap_or_default()
921 .to_string(),
922 );
923 }
924 }
925 }
926 } else {
927 lbl_depth += 1;
928 cur_raw.push('<');
930 cur_raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
931 cur_raw.push('>');
932 }
933 } else if lbl_depth > 0 {
934 cur_raw.push('<');
936 cur_raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
937 cur_raw.push('>');
938 }
939 }
940 Ok(quick_xml::events::Event::End(e)) => {
941 let name = e.name();
942 let local = local_name(name.as_ref());
943 if lbl_depth > 0 {
944 cur_raw.push_str("</");
946 cur_raw.push_str(std::str::from_utf8(name.as_ref()).unwrap_or(""));
947 cur_raw.push('>');
948 }
949 if local == b"styleLbl" && lbl_depth > 0 {
950 lbl_depth -= 1;
951 if lbl_depth == 0 {
952 if let Some(mut lbl) = cur_lbl.take() {
953 lbl.raw_xml = std::mem::take(&mut cur_raw);
954 style_labels.push(lbl);
955 }
956 }
957 }
958 }
959 Ok(quick_xml::events::Event::Empty(e)) => {
960 if lbl_depth > 0 {
961 cur_raw.push('<');
964 cur_raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
965 cur_raw.push_str("/>");
966 }
967 }
968 Ok(quick_xml::events::Event::Text(t)) => {
969 if lbl_depth > 0 {
970 cur_raw.push_str(std::str::from_utf8(&t).unwrap_or(""));
971 }
972 }
973 Ok(quick_xml::events::Event::Eof) => break,
974 Err(e) => {
975 return Err(crate::Error::Xml(format!(
976 "QuickStyleDef parse_from_xml: {e}"
977 )))
978 }
979 _ => {}
980 }
981 buf.clear();
982 }
983
984 Ok(QuickStyleDef { style_labels })
985 }
986
987 pub fn to_xml(&self) -> String {
991 let mut w = XmlWriter::new();
992 w.raw("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n");
993 w.open_with(
994 "dgm:styleData",
995 &[(
996 "xmlns:dgm",
997 "http://schemas.openxmlformats.org/drawingml/2006/diagram",
998 )],
999 );
1000 for lbl in &self.style_labels {
1001 if lbl.raw_xml.is_empty() {
1002 let mut attrs: Vec<(&str, &str)> = Vec::new();
1003 if let Some(n) = &lbl.name {
1004 attrs.push(("name", n.as_str()));
1005 }
1006 w.empty_with("dgm:styleLbl", &attrs);
1007 } else {
1008 w.raw(&lbl.raw_xml);
1009 }
1010 }
1011 w.close("dgm:styleData");
1012 w.into_string()
1013 }
1014}
1015
1016#[derive(Clone, Debug, Default)]
1025pub struct ColorsDef {
1026 pub unique_id: Option<String>,
1028 pub title: Option<String>,
1030 pub desc: Option<String>,
1032 pub style_color_labels: Vec<StyleLabel>,
1034}
1035
1036impl ColorsDef {
1037 pub fn parse_from_xml(xml: &str) -> crate::Result<ColorsDef> {
1048 let _ = xml; let mut colors = ColorsDef::default();
1050
1051 let mut rd = quick_xml::reader::Reader::from_str(xml);
1052 rd.config_mut().trim_text(true);
1053 let mut buf = Vec::new();
1054
1055 let mut cur_lbl: Option<StyleLabel> = None;
1056 let mut lbl_depth: i32 = 0;
1057 let mut cur_raw = String::new();
1059
1060 loop {
1061 match rd.read_event_into(&mut buf) {
1062 Ok(quick_xml::events::Event::Start(e)) => {
1063 let name = e.name();
1065 let local = local_name(name.as_ref());
1066 if local == b"colorsDef" {
1067 for a in e.attributes().flatten() {
1068 if a.key.as_ref() == b"uniqueId" {
1069 colors.unique_id = Some(
1070 a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
1071 .unwrap_or_default()
1072 .to_string(),
1073 );
1074 }
1075 }
1076 } else if local == b"title" {
1077 for a in e.attributes().flatten() {
1079 if a.key.as_ref() == b"val" {
1080 colors.title = Some(
1081 a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
1082 .unwrap_or_default()
1083 .to_string(),
1084 );
1085 }
1086 }
1087 } else if local == b"desc" {
1088 for a in e.attributes().flatten() {
1089 if a.key.as_ref() == b"val" {
1090 colors.desc = Some(
1091 a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
1092 .unwrap_or_default()
1093 .to_string(),
1094 );
1095 }
1096 }
1097 } else if local == b"styleLbl" {
1098 if lbl_depth == 0 {
1099 cur_lbl = Some(StyleLabel::default());
1100 lbl_depth = 1;
1101 cur_raw.clear();
1102 cur_raw.push('<');
1104 cur_raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
1105 cur_raw.push('>');
1106 for a in e.attributes().flatten() {
1107 if a.key.as_ref() == b"name" {
1108 if let Some(lbl) = cur_lbl.as_mut() {
1109 lbl.name = Some(
1110 a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
1111 .unwrap_or_default()
1112 .to_string(),
1113 );
1114 }
1115 }
1116 }
1117 } else {
1118 lbl_depth += 1;
1119 cur_raw.push('<');
1121 cur_raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
1122 cur_raw.push('>');
1123 }
1124 } else if lbl_depth > 0 {
1125 cur_raw.push('<');
1127 cur_raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
1128 cur_raw.push('>');
1129 }
1130 }
1131 Ok(quick_xml::events::Event::Empty(e)) => {
1132 let name = e.name();
1133 let local = local_name(name.as_ref());
1134 if local == b"title" {
1135 for a in e.attributes().flatten() {
1137 if a.key.as_ref() == b"val" {
1138 colors.title = Some(
1139 a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
1140 .unwrap_or_default()
1141 .to_string(),
1142 );
1143 }
1144 }
1145 } else if local == b"desc" {
1146 for a in e.attributes().flatten() {
1147 if a.key.as_ref() == b"val" {
1148 colors.desc = Some(
1149 a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
1150 .unwrap_or_default()
1151 .to_string(),
1152 );
1153 }
1154 }
1155 } else if lbl_depth > 0 {
1156 cur_raw.push('<');
1158 cur_raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
1159 cur_raw.push_str("/>");
1160 }
1161 }
1162 Ok(quick_xml::events::Event::End(e)) => {
1163 let name = e.name();
1165 let local = local_name(name.as_ref());
1166 if lbl_depth > 0 {
1167 cur_raw.push_str("</");
1169 cur_raw.push_str(std::str::from_utf8(name.as_ref()).unwrap_or(""));
1170 cur_raw.push('>');
1171 }
1172 if local == b"styleLbl" && lbl_depth > 0 {
1173 lbl_depth -= 1;
1174 if lbl_depth == 0 {
1175 if let Some(mut lbl) = cur_lbl.take() {
1176 lbl.raw_xml = std::mem::take(&mut cur_raw);
1177 colors.style_color_labels.push(lbl);
1178 }
1179 }
1180 }
1181 }
1182 Ok(quick_xml::events::Event::Text(t)) => {
1183 if lbl_depth > 0 {
1184 cur_raw.push_str(std::str::from_utf8(t.as_ref()).unwrap_or(""));
1185 }
1186 }
1187 Ok(quick_xml::events::Event::Eof) => break,
1188 Err(e) => return Err(crate::Error::Xml(format!("ColorsDef parse_from_xml: {e}"))),
1189 _ => {}
1190 }
1191 buf.clear();
1192 }
1193
1194 Ok(colors)
1195 }
1196
1197 pub fn to_xml(&self) -> String {
1199 let mut w = XmlWriter::new();
1200 w.raw("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n");
1201 let mut attrs: Vec<(&str, &str)> = vec![(
1202 "xmlns:dgm",
1203 "http://schemas.openxmlformats.org/drawingml/2006/diagram",
1204 )];
1205 if let Some(id) = &self.unique_id {
1206 attrs.push(("uniqueId", id.as_str()));
1207 }
1208 w.open_with("dgm:colorsDef", &attrs);
1209 if let Some(t) = &self.title {
1210 w.empty_with("dgm:title", &[("val", t.as_str())]);
1211 }
1212 if let Some(d) = &self.desc {
1213 w.empty_with("dgm:desc", &[("val", d.as_str())]);
1214 }
1215 if !self.style_color_labels.is_empty() {
1216 w.open("dgm:styleClrData");
1217 for lbl in &self.style_color_labels {
1218 if lbl.raw_xml.is_empty() {
1219 let mut lattrs: Vec<(&str, &str)> = Vec::new();
1220 if let Some(n) = &lbl.name {
1221 lattrs.push(("name", n.as_str()));
1222 }
1223 w.empty_with("dgm:styleLbl", &lattrs);
1224 } else {
1225 w.raw(&lbl.raw_xml);
1226 }
1227 }
1228 w.close("dgm:styleClrData");
1229 }
1230 w.close("dgm:colorsDef");
1231 w.into_string()
1232 }
1233}
1234
1235fn local_name(name: &[u8]) -> &[u8] {
1243 match name.iter().position(|&b| b == b':') {
1244 Some(i) => &name[i + 1..],
1245 None => name,
1246 }
1247}
1248
1249#[cfg(test)]
1254mod tests {
1255 use super::*;
1256
1257 fn sample_data_model_xml() -> &'static str {
1259 "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n\
1260 <dgm:dataModel xmlns:dgm=\"http://schemas.openxmlformats.org/drawingml/2006/diagram\" xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\">\
1261 <dgm:ptLst>\
1262 <dgm:pt modelId=\"0\" type=\"doc\"/>\
1263 <dgm:pt modelId=\"1\" type=\"par\">\
1264 <dgm:prSet ang=\"0\"/>\
1265 <dgm:spPr/>\
1266 <dgm:t><a:bodyPr/><a:lstStyle/><a:p><a:r><a:t>根节点</a:t></a:r></a:p></dgm:t>\
1267 </dgm:pt>\
1268 </dgm:ptLst>\
1269 <dgm:cxnLst>\
1270 <dgm:cxn type=\"parChld\" srcId=\"1\" destId=\"2\"/>\
1271 </dgm:cxnLst>\
1272 </dgm:dataModel>"
1273 }
1274
1275 #[test]
1277 fn data_model_parse_basic() {
1278 let xml = sample_data_model_xml();
1279 let dm = DataModel::parse_from_xml(xml).expect("parse dataModel");
1280
1281 assert_eq!(dm.points.len(), 2, "should have 2 points");
1282 assert_eq!(dm.points[0].model_id, 0);
1284 assert_eq!(dm.points[0].pt_type.as_deref(), Some("doc"));
1285 assert!(dm.points[0].text.is_none(), "doc node has no text");
1286
1287 assert_eq!(dm.points[1].model_id, 1);
1289 assert_eq!(dm.points[1].pt_type.as_deref(), Some("par"));
1290 assert_eq!(dm.points[1].text.as_deref(), Some("根节点"));
1291
1292 assert_eq!(dm.connections.len(), 1);
1294 assert_eq!(dm.connections[0].src_id, 1);
1295 assert_eq!(dm.connections[0].dest_id, 2);
1296 assert_eq!(dm.connections[0].cxn_type.as_deref(), Some("parChld"));
1297 }
1298
1299 #[test]
1301 fn data_model_parse_preserves_raw_xml() {
1302 let xml = sample_data_model_xml();
1303 let dm = DataModel::parse_from_xml(xml).expect("parse dataModel");
1304
1305 let pt_raw = &dm.points[1].raw_xml;
1307 assert!(
1308 pt_raw.contains("<dgm:pt"),
1309 "raw_xml should contain pt tag: {}",
1310 pt_raw
1311 );
1312 assert!(
1313 pt_raw.contains("<dgm:prSet"),
1314 "raw_xml should contain prSet: {}",
1315 pt_raw
1316 );
1317 assert!(
1318 pt_raw.contains("根节点"),
1319 "raw_xml should contain text: {}",
1320 pt_raw
1321 );
1322
1323 let cxn_raw = &dm.connections[0].raw_xml;
1325 assert!(cxn_raw.contains("<dgm:cxn"), "cxn raw_xml: {}", cxn_raw);
1326 }
1327
1328 #[test]
1330 fn data_model_round_trip() {
1331 let xml = sample_data_model_xml();
1332 let dm = DataModel::parse_from_xml(xml).expect("parse");
1333 let out = dm.to_xml();
1334 assert!(out.contains("<dgm:dataModel"), "out: {}", out);
1335 assert!(out.contains("<dgm:ptLst>"), "out: {}", out);
1336 assert!(out.contains("<dgm:cxnLst>"), "out: {}", out);
1337 assert!(out.contains("根节点"), "out should contain text: {}", out);
1339 assert!(out.contains("modelId=\"0\""), "out: {}", out);
1341 assert!(out.contains("modelId=\"1\""), "out: {}", out);
1342 }
1343
1344 #[test]
1346 fn data_model_parse_empty_no_panic() {
1347 let xml = "<?xml version=\"1.0\"?>\
1348 <dgm:dataModel xmlns:dgm=\"http://schemas.openxmlformats.org/drawingml/2006/diagram\"/>";
1349 let dm = DataModel::parse_from_xml(xml).expect("parse empty");
1350 assert!(dm.points.is_empty());
1351 assert!(dm.connections.is_empty());
1352 }
1353
1354 #[test]
1356 fn data_model_parse_malformed_returns_error() {
1357 let xml = "<dgm:dataModel xmlns:dgm=\"http://schemas.openxmlformats.org/drawingml/2006/diagram\"><!-- unclosed comment <dgm:pt modelId=\"1\"/></dgm:dataModel>";
1361 let result = DataModel::parse_from_xml(xml);
1362 assert!(result.is_err(), "畸形 XML 应返回错误,实际: {result:?}");
1363 }
1364
1365 fn sample_layout_def_xml() -> &'static str {
1367 "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n\
1368 <dgm:layoutDef xmlns:dgm=\"http://schemas.openxmlformats.org/drawingml/2006/diagram\" uniqueId=\"process1\">\
1369 <dgm:title val=\"Process\"/>\
1370 <dgm:desc val=\"A simple process layout\"/>\
1371 <dgm:catLst>\
1372 <dgm:cat type=\"process\" pri=\"1\"/>\
1373 <dgm:cat type=\"cycle\" pri=\"2\"/>\
1374 </dgm:catLst>\
1375 <dgm:layoutNode name=\"root\">\
1376 <dgm:alg type=\"lin\"/>\
1377 <dgm:forEach name=\"node\"/>\
1378 </dgm:layoutNode>\
1379 </dgm:layoutDef>"
1380 }
1381
1382 #[test]
1384 fn layout_def_parse_basic() {
1385 let xml = sample_layout_def_xml();
1386 let layout = LayoutDef::parse_from_xml(xml).expect("parse layoutDef");
1387
1388 assert_eq!(layout.unique_id.as_deref(), Some("process1"));
1389 assert_eq!(layout.title.as_deref(), Some("Process"));
1390 assert_eq!(layout.desc.as_deref(), Some("A simple process layout"));
1391 assert_eq!(layout.categories.len(), 2);
1392 assert_eq!(layout.categories[0].cat_type.as_deref(), Some("process"));
1393 assert_eq!(layout.categories[0].priority, Some(1));
1394 assert_eq!(layout.categories[1].cat_type.as_deref(), Some("cycle"));
1395 assert_eq!(layout.categories[1].priority, Some(2));
1396
1397 let ln = &layout.layout_node_xml;
1399 assert!(ln.contains("<dgm:layoutNode"), "layout_node_xml: {}", ln);
1400 assert!(ln.contains("name=\"root\""), "layout_node_xml: {}", ln);
1401 assert!(ln.contains("<dgm:alg"), "layout_node_xml: {}", ln);
1402 }
1403
1404 #[test]
1406 fn layout_def_round_trip() {
1407 let xml = sample_layout_def_xml();
1408 let layout = LayoutDef::parse_from_xml(xml).expect("parse");
1409 let out = layout.to_xml();
1410 assert!(out.contains("<dgm:layoutDef"), "out: {}", out);
1411 assert!(out.contains("uniqueId=\"process1\""), "out: {}", out);
1412 assert!(out.contains("val=\"Process\""), "out: {}", out);
1413 assert!(out.contains("<dgm:catLst>"), "out: {}", out);
1414 assert!(out.contains("type=\"process\""), "out: {}", out);
1415 assert!(out.contains("<dgm:layoutNode"), "out: {}", out);
1417 assert!(out.contains("name=\"root\""), "out: {}", out);
1418 }
1419
1420 fn sample_quick_style_xml() -> &'static str {
1422 "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n\
1423 <dgm:styleData xmlns:dgm=\"http://schemas.openxmlformats.org/drawingml/2006/diagram\">\
1424 <dgm:styleLbl name=\"node0\">\
1425 <dgm:spPr/>\
1426 </dgm:styleLbl>\
1427 <dgm:styleLbl name=\"node1\">\
1428 <dgm:spPr/>\
1429 </dgm:styleLbl>\
1430 <dgm:extLst/>\
1431 </dgm:styleData>"
1432 }
1433
1434 #[test]
1436 fn quick_style_parse_basic() {
1437 let xml = sample_quick_style_xml();
1438 let qs = QuickStyleDef::parse_from_xml(xml).expect("parse quickStyle");
1439
1440 assert_eq!(qs.style_labels.len(), 2, "should have 2 styleLbl");
1441 assert_eq!(qs.style_labels[0].name.as_deref(), Some("node0"));
1442 assert_eq!(qs.style_labels[1].name.as_deref(), Some("node1"));
1443 assert!(qs.style_labels[0].raw_xml.contains("<dgm:styleLbl"));
1445 }
1446
1447 #[test]
1449 fn quick_style_round_trip() {
1450 let xml = sample_quick_style_xml();
1451 let qs = QuickStyleDef::parse_from_xml(xml).expect("parse");
1452 let out = qs.to_xml();
1453 assert!(out.contains("<dgm:styleData"), "out: {}", out);
1454 assert!(out.contains("name=\"node0\""), "out: {}", out);
1455 assert!(out.contains("name=\"node1\""), "out: {}", out);
1456 }
1457
1458 fn sample_colors_def_xml() -> &'static str {
1460 "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n\
1461 <dgm:colorsDef xmlns:dgm=\"http://schemas.openxmlformats.org/drawingml/2006/diagram\" uniqueId=\"color1\">\
1462 <dgm:title val=\"Primary Colors\"/>\
1463 <dgm:desc val=\"Theme color variants\"/>\
1464 <dgm:catLst><dgm:cat type=\"primary\" pri=\"1\"/></dgm:catLst>\
1465 <dgm:styleClrData>\
1466 <dgm:styleLbl name=\"node0\">\
1467 <a:effectClrLst xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\"><a:schemeClr val=\"accent1\"/></a:effectClrLst>\
1468 <a:fillClrLst xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\"><a:schemeClr val=\"accent1\"/></a:fillClrLst>\
1469 </dgm:styleLbl>\
1470 </dgm:styleClrData>\
1471 </dgm:colorsDef>"
1472 }
1473
1474 #[test]
1476 fn colors_def_parse_basic() {
1477 let xml = sample_colors_def_xml();
1478 let colors = ColorsDef::parse_from_xml(xml).expect("parse colorsDef");
1479
1480 assert_eq!(colors.unique_id.as_deref(), Some("color1"));
1481 assert_eq!(colors.title.as_deref(), Some("Primary Colors"));
1482 assert_eq!(colors.desc.as_deref(), Some("Theme color variants"));
1483 assert_eq!(colors.style_color_labels.len(), 1);
1484 assert_eq!(colors.style_color_labels[0].name.as_deref(), Some("node0"));
1485 let raw = &colors.style_color_labels[0].raw_xml;
1487 assert!(raw.contains("<dgm:styleLbl"), "raw: {}", raw);
1488 assert!(
1489 raw.contains("accent1"),
1490 "raw should contain color ref: {}",
1491 raw
1492 );
1493 }
1494
1495 #[test]
1497 fn colors_def_round_trip() {
1498 let xml = sample_colors_def_xml();
1499 let colors = ColorsDef::parse_from_xml(xml).expect("parse");
1500 let out = colors.to_xml();
1501 assert!(out.contains("<dgm:colorsDef"), "out: {}", out);
1502 assert!(out.contains("uniqueId=\"color1\""), "out: {}", out);
1503 assert!(out.contains("val=\"Primary Colors\""), "out: {}", out);
1504 assert!(out.contains("<dgm:styleClrData>"), "out: {}", out);
1505 assert!(out.contains("name=\"node0\""), "out: {}", out);
1506 }
1507
1508 #[test]
1510 fn local_name_strips_prefix() {
1511 assert_eq!(local_name(b"dgm:pt"), b"pt");
1512 assert_eq!(local_name(b"pt"), b"pt");
1513 assert_eq!(local_name(b"a:t"), b"t");
1514 }
1515
1516 #[test]
1520 fn point_set_text_updates_both_fields() {
1521 let xml = sample_data_model_xml();
1522 let mut dm = DataModel::parse_from_xml(xml).expect("parse");
1523 let pt = dm.point_mut(1).expect("point 1 should exist");
1525 pt.set_text("新文本");
1526 assert_eq!(pt.text.as_deref(), Some("新文本"));
1528 assert!(
1530 pt.raw_xml.contains("<a:t>新文本</a:t>"),
1531 "raw_xml: {}",
1532 pt.raw_xml
1533 );
1534 assert!(
1536 !pt.raw_xml.contains("根节点"),
1537 "old text should be replaced: {}",
1538 pt.raw_xml
1539 );
1540 }
1541
1542 #[test]
1544 fn point_set_text_on_doc_node_no_a_t() {
1545 let xml = sample_data_model_xml();
1546 let mut dm = DataModel::parse_from_xml(xml).expect("parse");
1547 let pt = dm.point_mut(0).expect("point 0 should exist");
1548 pt.set_text("doc文本");
1550 assert_eq!(pt.text.as_deref(), Some("doc文本"));
1551 assert!(
1553 !pt.raw_xml.contains("doc文本"),
1554 "raw_xml should not change: {}",
1555 pt.raw_xml
1556 );
1557 }
1558
1559 #[test]
1561 fn point_clear_text_empties_both_fields() {
1562 let xml = sample_data_model_xml();
1563 let mut dm = DataModel::parse_from_xml(xml).expect("parse");
1564 let pt = dm.point_mut(1).expect("point 1 should exist");
1565 assert!(pt.text.is_some());
1566 pt.clear_text();
1567 assert!(pt.text.is_none());
1569 assert!(
1571 pt.raw_xml.contains("<a:t></a:t>"),
1572 "raw_xml: {}",
1573 pt.raw_xml
1574 );
1575 assert!(!pt.raw_xml.contains("根节点"), "raw_xml: {}", pt.raw_xml);
1576 }
1577
1578 #[test]
1580 fn point_set_text_escapes_xml_special_chars() {
1581 let xml = sample_data_model_xml();
1582 let mut dm = DataModel::parse_from_xml(xml).expect("parse");
1583 let pt = dm.point_mut(1).expect("point 1 should exist");
1584 pt.set_text("a<b>&c\"d'e");
1585 assert!(
1587 pt.raw_xml.contains("a<b>&c"d'e"),
1588 "raw_xml: {}",
1589 pt.raw_xml
1590 );
1591 assert_eq!(pt.text.as_deref(), Some("a<b>&c\"d'e"));
1593 }
1594
1595 #[test]
1597 fn data_model_set_point_text_by_id() {
1598 let xml = sample_data_model_xml();
1599 let mut dm = DataModel::parse_from_xml(xml).expect("parse");
1600 assert!(dm.set_point_text(1, "节点1新文本"));
1602 assert_eq!(dm.point(1).unwrap().text.as_deref(), Some("节点1新文本"));
1603 assert!(!dm.set_point_text(999, "不存在"));
1605 }
1606
1607 #[test]
1609 fn data_model_to_xml_structured_rebuild() {
1610 let mut dm = DataModel::default();
1611 dm.points.push(DataModelPoint {
1613 model_id: 1,
1614 pt_type: Some("par".to_string()),
1615 text: Some("新节点".to_string()),
1616 raw_xml: String::new(), });
1618 let xml = dm.to_xml();
1619 assert!(
1621 xml.contains(r#"<dgm:pt modelId="1" type="par">"#),
1622 "xml: {}",
1623 xml
1624 );
1625 assert!(xml.contains("<dgm:t>"), "xml: {}", xml);
1627 assert!(xml.contains("<a:bodyPr/>"), "xml: {}", xml);
1629 assert!(xml.contains("<a:lstStyle/>"), "xml: {}", xml);
1630 assert!(xml.contains("<a:t>新节点</a:t>"), "xml: {}", xml);
1632 }
1633
1634 #[test]
1636 fn data_model_to_xml_structured_rebuild_no_text() {
1637 let mut dm = DataModel::default();
1638 dm.points.push(DataModelPoint {
1639 model_id: 0,
1640 pt_type: Some("doc".to_string()),
1641 text: None,
1642 raw_xml: String::new(),
1643 });
1644 let xml = dm.to_xml();
1645 assert!(
1647 xml.contains(r#"<dgm:pt modelId="0" type="doc"/>"#),
1648 "xml: {}",
1649 xml
1650 );
1651 assert!(
1653 !xml.contains("<dgm:t>"),
1654 "xml should not have dgm:t: {}",
1655 xml
1656 );
1657 }
1658
1659 #[test]
1661 fn escape_xml_text_all_special_chars() {
1662 assert_eq!(escape_xml_text("&"), "&");
1663 assert_eq!(escape_xml_text("<"), "<");
1664 assert_eq!(escape_xml_text(">"), ">");
1665 assert_eq!(escape_xml_text("'"), "'");
1666 assert_eq!(escape_xml_text("\""), """);
1667 assert_eq!(
1669 escape_xml_text("a&b<c>d'e\"f"),
1670 "a&b<c>d'e"f"
1671 );
1672 assert_eq!(escape_xml_text("普通文本"), "普通文本");
1674 }
1675
1676 #[test]
1678 fn point_is_type_query() {
1679 let xml = sample_data_model_xml();
1680 let dm = DataModel::parse_from_xml(xml).expect("parse");
1681 assert!(dm.point(0).unwrap().is_type("doc"));
1682 assert!(dm.point(1).unwrap().is_type("par"));
1683 assert!(!dm.point(0).unwrap().is_type("par"));
1684 }
1685}