use crate::oxml::writer::XmlWriter;
#[derive(Clone, Debug, Default)]
pub struct DataModel {
pub points: Vec<DataModelPoint>,
pub connections: Vec<DataModelConnection>,
}
#[derive(Clone, Debug, Default)]
pub struct DataModelPoint {
pub model_id: u32,
pub pt_type: Option<String>,
pub text: Option<String>,
pub raw_xml: String,
}
#[derive(Clone, Debug, Default)]
pub struct DataModelConnection {
pub src_id: u32,
pub dest_id: u32,
pub cxn_type: Option<String>,
pub raw_xml: String,
}
impl DataModelPoint {
pub fn set_text(&mut self, new_text: impl Into<String>) {
let new_text = new_text.into();
let escaped = escape_xml_text(&new_text);
if let Some(start) = self.raw_xml.find("<a:t>") {
if let Some(end_rel) = self.raw_xml[start..].find("</a:t>") {
let content_start = start + "<a:t>".len();
let content_end = start + end_rel;
let prefix = &self.raw_xml[..content_start];
let suffix = &self.raw_xml[content_end..];
self.raw_xml = format!("{}{}{}", prefix, escaped, suffix);
}
}
self.text = Some(new_text);
}
pub fn clear_text(&mut self) {
if let Some(start) = self.raw_xml.find("<a:t>") {
if let Some(end_rel) = self.raw_xml[start..].find("</a:t>") {
let content_start = start + "<a:t>".len();
let content_end = start + end_rel;
let prefix = &self.raw_xml[..content_start];
let suffix = &self.raw_xml[content_end..];
self.raw_xml = format!("{}{}", prefix, suffix);
}
}
self.text = None;
}
pub fn is_type(&self, type_str: &str) -> bool {
self.pt_type.as_deref() == Some(type_str)
}
}
fn escape_xml_text(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for c in s.chars() {
match c {
'&' => out.push_str("&"),
'<' => out.push_str("<"),
'>' => out.push_str(">"),
'\'' => out.push_str("'"),
'"' => out.push_str("""),
_ => out.push(c),
}
}
out
}
impl DataModel {
pub fn parse_from_xml(xml: &str) -> crate::Result<DataModel> {
let _ = xml; let mut points: Vec<DataModelPoint> = Vec::new();
let mut connections: Vec<DataModelConnection> = Vec::new();
let mut rd = quick_xml::reader::Reader::from_str(xml);
rd.config_mut().trim_text(true);
let mut buf = Vec::new();
let mut cur_pt: Option<DataModelPoint> = None;
let mut cur_cxn: Option<DataModelConnection> = None;
let mut pt_depth: i32 = 0;
let mut cxn_depth: i32 = 0;
let mut in_pt_text = false; let mut cur_pt_raw = String::new();
let mut cur_cxn_raw = String::new();
let mut cur_text_buf = String::new();
loop {
match rd.read_event_into(&mut buf) {
Ok(quick_xml::events::Event::Start(e)) => {
let name = e.name();
let local = local_name(name.as_ref());
if local == b"pt" && pt_depth == 0 {
pt_depth = 1;
let mut pt = DataModelPoint::default();
for a in e.attributes().flatten() {
let key = a.key.as_ref();
let val = a
.normalized_value(quick_xml::XmlVersion::Implicit1_0)
.unwrap_or_default()
.to_string();
if key == b"modelId" {
if let Ok(n) = val.parse::<u32>() {
pt.model_id = n;
}
} else if key == b"type" {
pt.pt_type = Some(val);
}
}
cur_pt = Some(pt);
cur_text_buf.clear();
cur_pt_raw.clear();
cur_pt_raw.push('<');
cur_pt_raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
cur_pt_raw.push('>');
} else if local == b"cxn" && cxn_depth == 0 {
cxn_depth = 1;
let mut cxn = DataModelConnection::default();
for a in e.attributes().flatten() {
let key = a.key.as_ref();
let val = a
.normalized_value(quick_xml::XmlVersion::Implicit1_0)
.unwrap_or_default()
.to_string();
if key == b"srcId" {
if let Ok(n) = val.parse::<u32>() {
cxn.src_id = n;
}
} else if key == b"destId" {
if let Ok(n) = val.parse::<u32>() {
cxn.dest_id = n;
}
} else if key == b"type" {
cxn.cxn_type = Some(val);
}
}
cur_cxn = Some(cxn);
cur_cxn_raw.clear();
cur_cxn_raw.push('<');
cur_cxn_raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
cur_cxn_raw.push('>');
} else {
if pt_depth > 0 {
cur_pt_raw.push('<');
cur_pt_raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
cur_pt_raw.push('>');
if local == b"t" {
in_pt_text = true;
}
if local == b"pt" {
pt_depth += 1;
}
} else if cxn_depth > 0 {
cur_cxn_raw.push('<');
cur_cxn_raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
cur_cxn_raw.push('>');
if local == b"cxn" {
cxn_depth += 1;
}
}
}
}
Ok(quick_xml::events::Event::Empty(e)) => {
let name = e.name();
let local = local_name(name.as_ref());
if local == b"pt" && pt_depth == 0 {
let mut pt = DataModelPoint::default();
for a in e.attributes().flatten() {
let key = a.key.as_ref();
let val = a
.normalized_value(quick_xml::XmlVersion::Implicit1_0)
.unwrap_or_default()
.to_string();
if key == b"modelId" {
if let Ok(n) = val.parse::<u32>() {
pt.model_id = n;
}
} else if key == b"type" {
pt.pt_type = Some(val);
}
}
let mut raw = String::from("<");
raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
raw.push_str("/>");
pt.raw_xml = raw;
points.push(pt);
} else if local == b"cxn" && cxn_depth == 0 {
let mut cxn = DataModelConnection::default();
for a in e.attributes().flatten() {
let key = a.key.as_ref();
let val = a
.normalized_value(quick_xml::XmlVersion::Implicit1_0)
.unwrap_or_default()
.to_string();
if key == b"srcId" {
if let Ok(n) = val.parse::<u32>() {
cxn.src_id = n;
}
} else if key == b"destId" {
if let Ok(n) = val.parse::<u32>() {
cxn.dest_id = n;
}
} else if key == b"type" {
cxn.cxn_type = Some(val);
}
}
let mut raw = String::from("<");
raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
raw.push_str("/>");
cxn.raw_xml = raw;
connections.push(cxn);
} else if pt_depth > 0 {
cur_pt_raw.push('<');
cur_pt_raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
cur_pt_raw.push_str("/>");
} else if cxn_depth > 0 {
cur_cxn_raw.push('<');
cur_cxn_raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
cur_cxn_raw.push_str("/>");
}
}
Ok(quick_xml::events::Event::Text(t)) => {
if in_pt_text && pt_depth > 0 {
let text_str = std::str::from_utf8(t.as_ref()).unwrap_or("");
let text = quick_xml::escape::unescape(text_str)
.unwrap_or_default()
.to_string();
if !text.is_empty() {
cur_text_buf.push_str(&text);
}
}
if pt_depth > 0 {
cur_pt_raw.push_str(std::str::from_utf8(t.as_ref()).unwrap_or(""));
} else if cxn_depth > 0 {
cur_cxn_raw.push_str(std::str::from_utf8(t.as_ref()).unwrap_or(""));
}
}
Ok(quick_xml::events::Event::End(e)) => {
let name = e.name();
let local = local_name(name.as_ref());
if pt_depth > 0 {
cur_pt_raw.push_str("</");
cur_pt_raw.push_str(std::str::from_utf8(name.as_ref()).unwrap_or(""));
cur_pt_raw.push('>');
if local == b"t" {
in_pt_text = false;
} else if local == b"pt" {
pt_depth -= 1;
if pt_depth == 0 {
if let Some(mut pt) = cur_pt.take() {
pt.raw_xml = std::mem::take(&mut cur_pt_raw);
if !cur_text_buf.is_empty() {
pt.text = Some(cur_text_buf.clone());
}
points.push(pt);
}
cur_text_buf.clear();
}
}
} else if cxn_depth > 0 {
cur_cxn_raw.push_str("</");
cur_cxn_raw.push_str(std::str::from_utf8(name.as_ref()).unwrap_or(""));
cur_cxn_raw.push('>');
if local == b"cxn" {
cxn_depth -= 1;
if cxn_depth == 0 {
if let Some(mut cxn) = cur_cxn.take() {
cxn.raw_xml = std::mem::take(&mut cur_cxn_raw);
connections.push(cxn);
}
}
}
}
}
Ok(quick_xml::events::Event::Eof) => break,
Err(e) => return Err(crate::Error::Xml(format!("DataModel parse_from_xml: {e}"))),
_ => {}
}
buf.clear();
}
Ok(DataModel {
points,
connections,
})
}
pub fn to_xml(&self) -> String {
let mut w = XmlWriter::new();
w.raw("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n");
w.open_with(
"dgm:dataModel",
&[
(
"xmlns:dgm",
"http://schemas.openxmlformats.org/drawingml/2006/diagram",
),
(
"xmlns:a",
"http://schemas.openxmlformats.org/drawingml/2006/main",
),
],
);
w.open("dgm:ptLst");
for pt in &self.points {
if pt.raw_xml.is_empty() {
let id_s = pt.model_id.to_string();
let mut attrs: Vec<(&str, &str)> = vec![("modelId", id_s.as_str())];
if let Some(t) = &pt.pt_type {
attrs.push(("type", t.as_str()));
}
if let Some(text) = &pt.text {
w.open_with("dgm:pt", &attrs);
w.open("dgm:t");
w.empty("a:bodyPr");
w.empty("a:lstStyle");
w.open("a:p");
w.open("a:r");
let escaped = escape_xml_text(text);
w.leaf("a:t", escaped.as_str());
w.close("a:r");
w.close("a:p");
w.close("dgm:t");
w.close("dgm:pt");
} else {
w.empty_with("dgm:pt", &attrs);
}
} else {
w.raw(&pt.raw_xml);
}
}
w.close("dgm:ptLst");
if !self.connections.is_empty() {
w.open("dgm:cxnLst");
for cxn in &self.connections {
if cxn.raw_xml.is_empty() {
let src_s = cxn.src_id.to_string();
let dst_s = cxn.dest_id.to_string();
let mut attrs: Vec<(&str, &str)> =
vec![("srcId", src_s.as_str()), ("destId", dst_s.as_str())];
if let Some(t) = &cxn.cxn_type {
attrs.push(("type", t.as_str()));
}
w.empty_with("dgm:cxn", &attrs);
} else {
w.raw(&cxn.raw_xml);
}
}
w.close("dgm:cxnLst");
}
w.close("dgm:dataModel");
w.into_string()
}
pub fn point_mut(&mut self, model_id: u32) -> Option<&mut DataModelPoint> {
self.points.iter_mut().find(|p| p.model_id == model_id)
}
pub fn point(&self, model_id: u32) -> Option<&DataModelPoint> {
self.points.iter().find(|p| p.model_id == model_id)
}
pub fn set_point_text(&mut self, model_id: u32, new_text: impl Into<String>) -> bool {
if let Some(pt) = self.point_mut(model_id) {
pt.set_text(new_text);
true
} else {
false
}
}
}
#[derive(Clone, Debug, Default)]
pub struct LayoutDef {
pub unique_id: Option<String>,
pub title: Option<String>,
pub desc: Option<String>,
pub categories: Vec<LayoutCategory>,
pub layout_node_xml: String,
}
#[derive(Clone, Debug, Default)]
pub struct LayoutCategory {
pub cat_type: Option<String>,
pub priority: Option<i32>,
}
impl LayoutDef {
pub fn parse_from_xml(xml: &str) -> crate::Result<LayoutDef> {
let _ = xml; let mut layout = LayoutDef::default();
let mut rd = quick_xml::reader::Reader::from_str(xml);
rd.config_mut().trim_text(true);
let mut buf = Vec::new();
let mut in_cat_lst = false;
let mut layout_node_depth: i32 = 0;
let mut cur_layout_raw = String::new();
loop {
match rd.read_event_into(&mut buf) {
Ok(quick_xml::events::Event::Start(e)) => {
let name = e.name();
let local = local_name(name.as_ref());
if local == b"layoutDef" {
for a in e.attributes().flatten() {
if a.key.as_ref() == b"uniqueId" {
layout.unique_id = Some(
a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
.unwrap_or_default()
.to_string(),
);
}
}
} else if local == b"title" {
for a in e.attributes().flatten() {
if a.key.as_ref() == b"val" {
layout.title = Some(
a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
.unwrap_or_default()
.to_string(),
);
}
}
} else if local == b"desc" {
for a in e.attributes().flatten() {
if a.key.as_ref() == b"val" {
layout.desc = Some(
a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
.unwrap_or_default()
.to_string(),
);
}
}
} else if local == b"catLst" {
in_cat_lst = true;
} else if local == b"cat" && in_cat_lst {
let mut cat = LayoutCategory::default();
for a in e.attributes().flatten() {
let key = a.key.as_ref();
let val = a
.normalized_value(quick_xml::XmlVersion::Implicit1_0)
.unwrap_or_default()
.to_string();
if key == b"type" {
cat.cat_type = Some(val);
} else if key == b"pri" {
if let Ok(n) = val.parse::<i32>() {
cat.priority = Some(n);
}
}
}
layout.categories.push(cat);
} else if local == b"layoutNode" {
layout_node_depth += 1;
if layout_node_depth == 1 {
cur_layout_raw.clear();
}
cur_layout_raw.push('<');
cur_layout_raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
cur_layout_raw.push('>');
} else if layout_node_depth > 0 {
cur_layout_raw.push('<');
cur_layout_raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
cur_layout_raw.push('>');
}
}
Ok(quick_xml::events::Event::Empty(e)) => {
let name = e.name();
let local = local_name(name.as_ref());
if local == b"title" {
for a in e.attributes().flatten() {
if a.key.as_ref() == b"val" {
layout.title = Some(
a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
.unwrap_or_default()
.to_string(),
);
}
}
} else if local == b"desc" {
for a in e.attributes().flatten() {
if a.key.as_ref() == b"val" {
layout.desc = Some(
a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
.unwrap_or_default()
.to_string(),
);
}
}
} else if local == b"cat" && in_cat_lst {
let mut cat = LayoutCategory::default();
for a in e.attributes().flatten() {
let key = a.key.as_ref();
let val = a
.normalized_value(quick_xml::XmlVersion::Implicit1_0)
.unwrap_or_default()
.to_string();
if key == b"type" {
cat.cat_type = Some(val);
} else if key == b"pri" {
if let Ok(n) = val.parse::<i32>() {
cat.priority = Some(n);
}
}
}
layout.categories.push(cat);
} else if layout_node_depth > 0 {
cur_layout_raw.push('<');
cur_layout_raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
cur_layout_raw.push_str("/>");
}
}
Ok(quick_xml::events::Event::End(e)) => {
let name = e.name();
let local = local_name(name.as_ref());
if local == b"catLst" {
in_cat_lst = false;
} else if local == b"layoutNode" && layout_node_depth > 0 {
cur_layout_raw.push_str("</");
cur_layout_raw.push_str(std::str::from_utf8(name.as_ref()).unwrap_or(""));
cur_layout_raw.push('>');
layout_node_depth -= 1;
if layout_node_depth == 0 {
layout.layout_node_xml = std::mem::take(&mut cur_layout_raw);
}
} else if layout_node_depth > 0 {
cur_layout_raw.push_str("</");
cur_layout_raw.push_str(std::str::from_utf8(name.as_ref()).unwrap_or(""));
cur_layout_raw.push('>');
}
}
Ok(quick_xml::events::Event::Text(t)) => {
if layout_node_depth > 0 {
cur_layout_raw.push_str(std::str::from_utf8(t.as_ref()).unwrap_or(""));
}
}
Ok(quick_xml::events::Event::Eof) => break,
Err(e) => return Err(crate::Error::Xml(format!("LayoutDef parse_from_xml: {e}"))),
_ => {}
}
buf.clear();
}
Ok(layout)
}
pub fn to_xml(&self) -> String {
let mut w = XmlWriter::new();
w.raw("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n");
let mut attrs: Vec<(&str, &str)> = vec![(
"xmlns:dgm",
"http://schemas.openxmlformats.org/drawingml/2006/diagram",
)];
if let Some(id) = &self.unique_id {
attrs.push(("uniqueId", id.as_str()));
}
w.open_with("dgm:layoutDef", &attrs);
if let Some(t) = &self.title {
w.empty_with("dgm:title", &[("val", t.as_str())]);
}
if let Some(d) = &self.desc {
w.empty_with("dgm:desc", &[("val", d.as_str())]);
}
if !self.categories.is_empty() {
w.open("dgm:catLst");
for cat in &self.categories {
let p_s = cat.priority.as_ref().map(|p| p.to_string());
let mut cattrs: Vec<(&str, &str)> = Vec::new();
if let Some(t) = &cat.cat_type {
cattrs.push(("type", t.as_str()));
}
if let Some(s) = p_s.as_deref() {
cattrs.push(("pri", s));
}
w.empty_with("dgm:cat", &cattrs);
}
w.close("dgm:catLst");
}
if !self.layout_node_xml.is_empty() {
w.raw(&self.layout_node_xml);
}
w.close("dgm:layoutDef");
w.into_string()
}
}
#[derive(Clone, Debug, Default)]
pub struct QuickStyleDef {
pub style_labels: Vec<StyleLabel>,
}
#[derive(Clone, Debug, Default)]
pub struct StyleLabel {
pub name: Option<String>,
pub raw_xml: String,
}
impl QuickStyleDef {
pub fn parse_from_xml(xml: &str) -> crate::Result<QuickStyleDef> {
let _ = xml; let mut style_labels: Vec<StyleLabel> = Vec::new();
let mut rd = quick_xml::reader::Reader::from_str(xml);
rd.config_mut().trim_text(true);
let mut buf = Vec::new();
let mut cur_lbl: Option<StyleLabel> = None;
let mut lbl_depth: i32 = 0;
let mut cur_raw = String::new();
loop {
match rd.read_event_into(&mut buf) {
Ok(quick_xml::events::Event::Start(e)) => {
let name = e.name();
let local = local_name(name.as_ref());
if local == b"styleLbl" {
if lbl_depth == 0 {
cur_lbl = Some(StyleLabel::default());
lbl_depth = 1;
cur_raw.clear();
cur_raw.push('<');
cur_raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
cur_raw.push('>');
for a in e.attributes().flatten() {
if a.key.as_ref() == b"name" {
if let Some(lbl) = cur_lbl.as_mut() {
lbl.name = Some(
a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
.unwrap_or_default()
.to_string(),
);
}
}
}
} else {
lbl_depth += 1;
cur_raw.push('<');
cur_raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
cur_raw.push('>');
}
} else if lbl_depth > 0 {
cur_raw.push('<');
cur_raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
cur_raw.push('>');
}
}
Ok(quick_xml::events::Event::End(e)) => {
let name = e.name();
let local = local_name(name.as_ref());
if lbl_depth > 0 {
cur_raw.push_str("</");
cur_raw.push_str(std::str::from_utf8(name.as_ref()).unwrap_or(""));
cur_raw.push('>');
}
if local == b"styleLbl" && lbl_depth > 0 {
lbl_depth -= 1;
if lbl_depth == 0 {
if let Some(mut lbl) = cur_lbl.take() {
lbl.raw_xml = std::mem::take(&mut cur_raw);
style_labels.push(lbl);
}
}
}
}
Ok(quick_xml::events::Event::Empty(e)) => {
if lbl_depth > 0 {
cur_raw.push('<');
cur_raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
cur_raw.push_str("/>");
}
}
Ok(quick_xml::events::Event::Text(t)) => {
if lbl_depth > 0 {
cur_raw.push_str(std::str::from_utf8(&t).unwrap_or(""));
}
}
Ok(quick_xml::events::Event::Eof) => break,
Err(e) => {
return Err(crate::Error::Xml(format!(
"QuickStyleDef parse_from_xml: {e}"
)))
}
_ => {}
}
buf.clear();
}
Ok(QuickStyleDef { style_labels })
}
pub fn to_xml(&self) -> String {
let mut w = XmlWriter::new();
w.raw("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n");
w.open_with(
"dgm:styleData",
&[(
"xmlns:dgm",
"http://schemas.openxmlformats.org/drawingml/2006/diagram",
)],
);
for lbl in &self.style_labels {
if lbl.raw_xml.is_empty() {
let mut attrs: Vec<(&str, &str)> = Vec::new();
if let Some(n) = &lbl.name {
attrs.push(("name", n.as_str()));
}
w.empty_with("dgm:styleLbl", &attrs);
} else {
w.raw(&lbl.raw_xml);
}
}
w.close("dgm:styleData");
w.into_string()
}
}
#[derive(Clone, Debug, Default)]
pub struct ColorsDef {
pub unique_id: Option<String>,
pub title: Option<String>,
pub desc: Option<String>,
pub style_color_labels: Vec<StyleLabel>,
}
impl ColorsDef {
pub fn parse_from_xml(xml: &str) -> crate::Result<ColorsDef> {
let _ = xml; let mut colors = ColorsDef::default();
let mut rd = quick_xml::reader::Reader::from_str(xml);
rd.config_mut().trim_text(true);
let mut buf = Vec::new();
let mut cur_lbl: Option<StyleLabel> = None;
let mut lbl_depth: i32 = 0;
let mut cur_raw = String::new();
loop {
match rd.read_event_into(&mut buf) {
Ok(quick_xml::events::Event::Start(e)) => {
let name = e.name();
let local = local_name(name.as_ref());
if local == b"colorsDef" {
for a in e.attributes().flatten() {
if a.key.as_ref() == b"uniqueId" {
colors.unique_id = Some(
a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
.unwrap_or_default()
.to_string(),
);
}
}
} else if local == b"title" {
for a in e.attributes().flatten() {
if a.key.as_ref() == b"val" {
colors.title = Some(
a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
.unwrap_or_default()
.to_string(),
);
}
}
} else if local == b"desc" {
for a in e.attributes().flatten() {
if a.key.as_ref() == b"val" {
colors.desc = Some(
a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
.unwrap_or_default()
.to_string(),
);
}
}
} else if local == b"styleLbl" {
if lbl_depth == 0 {
cur_lbl = Some(StyleLabel::default());
lbl_depth = 1;
cur_raw.clear();
cur_raw.push('<');
cur_raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
cur_raw.push('>');
for a in e.attributes().flatten() {
if a.key.as_ref() == b"name" {
if let Some(lbl) = cur_lbl.as_mut() {
lbl.name = Some(
a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
.unwrap_or_default()
.to_string(),
);
}
}
}
} else {
lbl_depth += 1;
cur_raw.push('<');
cur_raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
cur_raw.push('>');
}
} else if lbl_depth > 0 {
cur_raw.push('<');
cur_raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
cur_raw.push('>');
}
}
Ok(quick_xml::events::Event::Empty(e)) => {
let name = e.name();
let local = local_name(name.as_ref());
if local == b"title" {
for a in e.attributes().flatten() {
if a.key.as_ref() == b"val" {
colors.title = Some(
a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
.unwrap_or_default()
.to_string(),
);
}
}
} else if local == b"desc" {
for a in e.attributes().flatten() {
if a.key.as_ref() == b"val" {
colors.desc = Some(
a.normalized_value(quick_xml::XmlVersion::Implicit1_0)
.unwrap_or_default()
.to_string(),
);
}
}
} else if lbl_depth > 0 {
cur_raw.push('<');
cur_raw.push_str(std::str::from_utf8(e.as_ref()).unwrap_or(""));
cur_raw.push_str("/>");
}
}
Ok(quick_xml::events::Event::End(e)) => {
let name = e.name();
let local = local_name(name.as_ref());
if lbl_depth > 0 {
cur_raw.push_str("</");
cur_raw.push_str(std::str::from_utf8(name.as_ref()).unwrap_or(""));
cur_raw.push('>');
}
if local == b"styleLbl" && lbl_depth > 0 {
lbl_depth -= 1;
if lbl_depth == 0 {
if let Some(mut lbl) = cur_lbl.take() {
lbl.raw_xml = std::mem::take(&mut cur_raw);
colors.style_color_labels.push(lbl);
}
}
}
}
Ok(quick_xml::events::Event::Text(t)) => {
if lbl_depth > 0 {
cur_raw.push_str(std::str::from_utf8(t.as_ref()).unwrap_or(""));
}
}
Ok(quick_xml::events::Event::Eof) => break,
Err(e) => return Err(crate::Error::Xml(format!("ColorsDef parse_from_xml: {e}"))),
_ => {}
}
buf.clear();
}
Ok(colors)
}
pub fn to_xml(&self) -> String {
let mut w = XmlWriter::new();
w.raw("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n");
let mut attrs: Vec<(&str, &str)> = vec![(
"xmlns:dgm",
"http://schemas.openxmlformats.org/drawingml/2006/diagram",
)];
if let Some(id) = &self.unique_id {
attrs.push(("uniqueId", id.as_str()));
}
w.open_with("dgm:colorsDef", &attrs);
if let Some(t) = &self.title {
w.empty_with("dgm:title", &[("val", t.as_str())]);
}
if let Some(d) = &self.desc {
w.empty_with("dgm:desc", &[("val", d.as_str())]);
}
if !self.style_color_labels.is_empty() {
w.open("dgm:styleClrData");
for lbl in &self.style_color_labels {
if lbl.raw_xml.is_empty() {
let mut lattrs: Vec<(&str, &str)> = Vec::new();
if let Some(n) = &lbl.name {
lattrs.push(("name", n.as_str()));
}
w.empty_with("dgm:styleLbl", &lattrs);
} else {
w.raw(&lbl.raw_xml);
}
}
w.close("dgm:styleClrData");
}
w.close("dgm:colorsDef");
w.into_string()
}
}
fn local_name(name: &[u8]) -> &[u8] {
match name.iter().position(|&b| b == b':') {
Some(i) => &name[i + 1..],
None => name,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_data_model_xml() -> &'static str {
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n\
<dgm:dataModel xmlns:dgm=\"http://schemas.openxmlformats.org/drawingml/2006/diagram\" xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\">\
<dgm:ptLst>\
<dgm:pt modelId=\"0\" type=\"doc\"/>\
<dgm:pt modelId=\"1\" type=\"par\">\
<dgm:prSet ang=\"0\"/>\
<dgm:spPr/>\
<dgm:t><a:bodyPr/><a:lstStyle/><a:p><a:r><a:t>根节点</a:t></a:r></a:p></dgm:t>\
</dgm:pt>\
</dgm:ptLst>\
<dgm:cxnLst>\
<dgm:cxn type=\"parChld\" srcId=\"1\" destId=\"2\"/>\
</dgm:cxnLst>\
</dgm:dataModel>"
}
#[test]
fn data_model_parse_basic() {
let xml = sample_data_model_xml();
let dm = DataModel::parse_from_xml(xml).expect("parse dataModel");
assert_eq!(dm.points.len(), 2, "should have 2 points");
assert_eq!(dm.points[0].model_id, 0);
assert_eq!(dm.points[0].pt_type.as_deref(), Some("doc"));
assert!(dm.points[0].text.is_none(), "doc node has no text");
assert_eq!(dm.points[1].model_id, 1);
assert_eq!(dm.points[1].pt_type.as_deref(), Some("par"));
assert_eq!(dm.points[1].text.as_deref(), Some("根节点"));
assert_eq!(dm.connections.len(), 1);
assert_eq!(dm.connections[0].src_id, 1);
assert_eq!(dm.connections[0].dest_id, 2);
assert_eq!(dm.connections[0].cxn_type.as_deref(), Some("parChld"));
}
#[test]
fn data_model_parse_preserves_raw_xml() {
let xml = sample_data_model_xml();
let dm = DataModel::parse_from_xml(xml).expect("parse dataModel");
let pt_raw = &dm.points[1].raw_xml;
assert!(
pt_raw.contains("<dgm:pt"),
"raw_xml should contain pt tag: {}",
pt_raw
);
assert!(
pt_raw.contains("<dgm:prSet"),
"raw_xml should contain prSet: {}",
pt_raw
);
assert!(
pt_raw.contains("根节点"),
"raw_xml should contain text: {}",
pt_raw
);
let cxn_raw = &dm.connections[0].raw_xml;
assert!(cxn_raw.contains("<dgm:cxn"), "cxn raw_xml: {}", cxn_raw);
}
#[test]
fn data_model_round_trip() {
let xml = sample_data_model_xml();
let dm = DataModel::parse_from_xml(xml).expect("parse");
let out = dm.to_xml();
assert!(out.contains("<dgm:dataModel"), "out: {}", out);
assert!(out.contains("<dgm:ptLst>"), "out: {}", out);
assert!(out.contains("<dgm:cxnLst>"), "out: {}", out);
assert!(out.contains("根节点"), "out should contain text: {}", out);
assert!(out.contains("modelId=\"0\""), "out: {}", out);
assert!(out.contains("modelId=\"1\""), "out: {}", out);
}
#[test]
fn data_model_parse_empty_no_panic() {
let xml = "<?xml version=\"1.0\"?>\
<dgm:dataModel xmlns:dgm=\"http://schemas.openxmlformats.org/drawingml/2006/diagram\"/>";
let dm = DataModel::parse_from_xml(xml).expect("parse empty");
assert!(dm.points.is_empty());
assert!(dm.connections.is_empty());
}
#[test]
fn data_model_parse_malformed_returns_error() {
let xml = "<dgm:dataModel xmlns:dgm=\"http://schemas.openxmlformats.org/drawingml/2006/diagram\"><!-- unclosed comment <dgm:pt modelId=\"1\"/></dgm:dataModel>";
let result = DataModel::parse_from_xml(xml);
assert!(result.is_err(), "畸形 XML 应返回错误,实际: {result:?}");
}
fn sample_layout_def_xml() -> &'static str {
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n\
<dgm:layoutDef xmlns:dgm=\"http://schemas.openxmlformats.org/drawingml/2006/diagram\" uniqueId=\"process1\">\
<dgm:title val=\"Process\"/>\
<dgm:desc val=\"A simple process layout\"/>\
<dgm:catLst>\
<dgm:cat type=\"process\" pri=\"1\"/>\
<dgm:cat type=\"cycle\" pri=\"2\"/>\
</dgm:catLst>\
<dgm:layoutNode name=\"root\">\
<dgm:alg type=\"lin\"/>\
<dgm:forEach name=\"node\"/>\
</dgm:layoutNode>\
</dgm:layoutDef>"
}
#[test]
fn layout_def_parse_basic() {
let xml = sample_layout_def_xml();
let layout = LayoutDef::parse_from_xml(xml).expect("parse layoutDef");
assert_eq!(layout.unique_id.as_deref(), Some("process1"));
assert_eq!(layout.title.as_deref(), Some("Process"));
assert_eq!(layout.desc.as_deref(), Some("A simple process layout"));
assert_eq!(layout.categories.len(), 2);
assert_eq!(layout.categories[0].cat_type.as_deref(), Some("process"));
assert_eq!(layout.categories[0].priority, Some(1));
assert_eq!(layout.categories[1].cat_type.as_deref(), Some("cycle"));
assert_eq!(layout.categories[1].priority, Some(2));
let ln = &layout.layout_node_xml;
assert!(ln.contains("<dgm:layoutNode"), "layout_node_xml: {}", ln);
assert!(ln.contains("name=\"root\""), "layout_node_xml: {}", ln);
assert!(ln.contains("<dgm:alg"), "layout_node_xml: {}", ln);
}
#[test]
fn layout_def_round_trip() {
let xml = sample_layout_def_xml();
let layout = LayoutDef::parse_from_xml(xml).expect("parse");
let out = layout.to_xml();
assert!(out.contains("<dgm:layoutDef"), "out: {}", out);
assert!(out.contains("uniqueId=\"process1\""), "out: {}", out);
assert!(out.contains("val=\"Process\""), "out: {}", out);
assert!(out.contains("<dgm:catLst>"), "out: {}", out);
assert!(out.contains("type=\"process\""), "out: {}", out);
assert!(out.contains("<dgm:layoutNode"), "out: {}", out);
assert!(out.contains("name=\"root\""), "out: {}", out);
}
fn sample_quick_style_xml() -> &'static str {
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n\
<dgm:styleData xmlns:dgm=\"http://schemas.openxmlformats.org/drawingml/2006/diagram\">\
<dgm:styleLbl name=\"node0\">\
<dgm:spPr/>\
</dgm:styleLbl>\
<dgm:styleLbl name=\"node1\">\
<dgm:spPr/>\
</dgm:styleLbl>\
<dgm:extLst/>\
</dgm:styleData>"
}
#[test]
fn quick_style_parse_basic() {
let xml = sample_quick_style_xml();
let qs = QuickStyleDef::parse_from_xml(xml).expect("parse quickStyle");
assert_eq!(qs.style_labels.len(), 2, "should have 2 styleLbl");
assert_eq!(qs.style_labels[0].name.as_deref(), Some("node0"));
assert_eq!(qs.style_labels[1].name.as_deref(), Some("node1"));
assert!(qs.style_labels[0].raw_xml.contains("<dgm:styleLbl"));
}
#[test]
fn quick_style_round_trip() {
let xml = sample_quick_style_xml();
let qs = QuickStyleDef::parse_from_xml(xml).expect("parse");
let out = qs.to_xml();
assert!(out.contains("<dgm:styleData"), "out: {}", out);
assert!(out.contains("name=\"node0\""), "out: {}", out);
assert!(out.contains("name=\"node1\""), "out: {}", out);
}
fn sample_colors_def_xml() -> &'static str {
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n\
<dgm:colorsDef xmlns:dgm=\"http://schemas.openxmlformats.org/drawingml/2006/diagram\" uniqueId=\"color1\">\
<dgm:title val=\"Primary Colors\"/>\
<dgm:desc val=\"Theme color variants\"/>\
<dgm:catLst><dgm:cat type=\"primary\" pri=\"1\"/></dgm:catLst>\
<dgm:styleClrData>\
<dgm:styleLbl name=\"node0\">\
<a:effectClrLst xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\"><a:schemeClr val=\"accent1\"/></a:effectClrLst>\
<a:fillClrLst xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\"><a:schemeClr val=\"accent1\"/></a:fillClrLst>\
</dgm:styleLbl>\
</dgm:styleClrData>\
</dgm:colorsDef>"
}
#[test]
fn colors_def_parse_basic() {
let xml = sample_colors_def_xml();
let colors = ColorsDef::parse_from_xml(xml).expect("parse colorsDef");
assert_eq!(colors.unique_id.as_deref(), Some("color1"));
assert_eq!(colors.title.as_deref(), Some("Primary Colors"));
assert_eq!(colors.desc.as_deref(), Some("Theme color variants"));
assert_eq!(colors.style_color_labels.len(), 1);
assert_eq!(colors.style_color_labels[0].name.as_deref(), Some("node0"));
let raw = &colors.style_color_labels[0].raw_xml;
assert!(raw.contains("<dgm:styleLbl"), "raw: {}", raw);
assert!(
raw.contains("accent1"),
"raw should contain color ref: {}",
raw
);
}
#[test]
fn colors_def_round_trip() {
let xml = sample_colors_def_xml();
let colors = ColorsDef::parse_from_xml(xml).expect("parse");
let out = colors.to_xml();
assert!(out.contains("<dgm:colorsDef"), "out: {}", out);
assert!(out.contains("uniqueId=\"color1\""), "out: {}", out);
assert!(out.contains("val=\"Primary Colors\""), "out: {}", out);
assert!(out.contains("<dgm:styleClrData>"), "out: {}", out);
assert!(out.contains("name=\"node0\""), "out: {}", out);
}
#[test]
fn local_name_strips_prefix() {
assert_eq!(local_name(b"dgm:pt"), b"pt");
assert_eq!(local_name(b"pt"), b"pt");
assert_eq!(local_name(b"a:t"), b"t");
}
#[test]
fn point_set_text_updates_both_fields() {
let xml = sample_data_model_xml();
let mut dm = DataModel::parse_from_xml(xml).expect("parse");
let pt = dm.point_mut(1).expect("point 1 should exist");
pt.set_text("新文本");
assert_eq!(pt.text.as_deref(), Some("新文本"));
assert!(
pt.raw_xml.contains("<a:t>新文本</a:t>"),
"raw_xml: {}",
pt.raw_xml
);
assert!(
!pt.raw_xml.contains("根节点"),
"old text should be replaced: {}",
pt.raw_xml
);
}
#[test]
fn point_set_text_on_doc_node_no_a_t() {
let xml = sample_data_model_xml();
let mut dm = DataModel::parse_from_xml(xml).expect("parse");
let pt = dm.point_mut(0).expect("point 0 should exist");
pt.set_text("doc文本");
assert_eq!(pt.text.as_deref(), Some("doc文本"));
assert!(
!pt.raw_xml.contains("doc文本"),
"raw_xml should not change: {}",
pt.raw_xml
);
}
#[test]
fn point_clear_text_empties_both_fields() {
let xml = sample_data_model_xml();
let mut dm = DataModel::parse_from_xml(xml).expect("parse");
let pt = dm.point_mut(1).expect("point 1 should exist");
assert!(pt.text.is_some());
pt.clear_text();
assert!(pt.text.is_none());
assert!(
pt.raw_xml.contains("<a:t></a:t>"),
"raw_xml: {}",
pt.raw_xml
);
assert!(!pt.raw_xml.contains("根节点"), "raw_xml: {}", pt.raw_xml);
}
#[test]
fn point_set_text_escapes_xml_special_chars() {
let xml = sample_data_model_xml();
let mut dm = DataModel::parse_from_xml(xml).expect("parse");
let pt = dm.point_mut(1).expect("point 1 should exist");
pt.set_text("a<b>&c\"d'e");
assert!(
pt.raw_xml.contains("a<b>&c"d'e"),
"raw_xml: {}",
pt.raw_xml
);
assert_eq!(pt.text.as_deref(), Some("a<b>&c\"d'e"));
}
#[test]
fn data_model_set_point_text_by_id() {
let xml = sample_data_model_xml();
let mut dm = DataModel::parse_from_xml(xml).expect("parse");
assert!(dm.set_point_text(1, "节点1新文本"));
assert_eq!(dm.point(1).unwrap().text.as_deref(), Some("节点1新文本"));
assert!(!dm.set_point_text(999, "不存在"));
}
#[test]
fn data_model_to_xml_structured_rebuild() {
let mut dm = DataModel::default();
dm.points.push(DataModelPoint {
model_id: 1,
pt_type: Some("par".to_string()),
text: Some("新节点".to_string()),
raw_xml: String::new(), });
let xml = dm.to_xml();
assert!(
xml.contains(r#"<dgm:pt modelId="1" type="par">"#),
"xml: {}",
xml
);
assert!(xml.contains("<dgm:t>"), "xml: {}", xml);
assert!(xml.contains("<a:bodyPr/>"), "xml: {}", xml);
assert!(xml.contains("<a:lstStyle/>"), "xml: {}", xml);
assert!(xml.contains("<a:t>新节点</a:t>"), "xml: {}", xml);
}
#[test]
fn data_model_to_xml_structured_rebuild_no_text() {
let mut dm = DataModel::default();
dm.points.push(DataModelPoint {
model_id: 0,
pt_type: Some("doc".to_string()),
text: None,
raw_xml: String::new(),
});
let xml = dm.to_xml();
assert!(
xml.contains(r#"<dgm:pt modelId="0" type="doc"/>"#),
"xml: {}",
xml
);
assert!(
!xml.contains("<dgm:t>"),
"xml should not have dgm:t: {}",
xml
);
}
#[test]
fn escape_xml_text_all_special_chars() {
assert_eq!(escape_xml_text("&"), "&");
assert_eq!(escape_xml_text("<"), "<");
assert_eq!(escape_xml_text(">"), ">");
assert_eq!(escape_xml_text("'"), "'");
assert_eq!(escape_xml_text("\""), """);
assert_eq!(
escape_xml_text("a&b<c>d'e\"f"),
"a&b<c>d'e"f"
);
assert_eq!(escape_xml_text("普通文本"), "普通文本");
}
#[test]
fn point_is_type_query() {
let xml = sample_data_model_xml();
let dm = DataModel::parse_from_xml(xml).expect("parse");
assert!(dm.point(0).unwrap().is_type("doc"));
assert!(dm.point(1).unwrap().is_type("par"));
assert!(!dm.point(0).unwrap().is_type("par"));
}
}