use super::*;
pub(super) fn state_is_hidden(ctx: &StateRenderCtx<'_>, id: &str) -> bool {
ctx.hidden_prefixes
.iter()
.any(|p| id == p || id.starts_with(&format!("{p}----")))
}
pub(super) fn state_data_look<'a>(ctx: &'a StateRenderCtx<'_>) -> &'a str {
let look = ctx.diagram_look.trim();
if look.is_empty() { "classic" } else { look }
}
pub(super) fn state_note_owner_id(id: &str) -> &str {
id.rsplit_once("----parent")
.map(|(owner, _)| owner)
.filter(|owner| !owner.is_empty())
.unwrap_or(id)
}
#[derive(Debug, Clone, Copy)]
pub(super) struct StateScopedDomId<'a, 'b> {
diagram_id: SvgDiagramId<'a>,
id: &'b str,
}
impl std::fmt::Display for StateScopedDomId<'_, '_> {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
formatter,
"{}-{}",
self.diagram_id,
escape_xml_display(self.id)
)
}
}
#[derive(Debug, Clone, Copy)]
pub(super) struct StateScopedDomIdAttr<'a, 'b>(StateScopedDomId<'a, 'b>);
impl std::fmt::Display for StateScopedDomIdAttr<'_, '_> {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
formatter,
"{}-{}",
self.0.diagram_id,
escape_attr_display(self.0.id)
)
}
}
impl<'a, 'b> StateScopedDomId<'a, 'b> {
pub(super) fn attr(self) -> StateScopedDomIdAttr<'a, 'b> {
StateScopedDomIdAttr(self)
}
}
pub(super) fn state_scoped_dom_id<'a, 'b>(
ctx: &StateRenderCtx<'a>,
id: &'b str,
) -> StateScopedDomId<'a, 'b> {
StateScopedDomId {
diagram_id: ctx.diagram_id,
id,
}
}
pub(super) fn state_node_scoped_dom_id<'a, 'b>(
ctx: &'b StateRenderCtx<'a>,
node_id: &'b str,
) -> StateScopedDomId<'a, 'b> {
ctx.nodes_by_id
.get(node_id)
.map(|node| node.dom_id.trim())
.filter(|dom_id| !dom_id.is_empty())
.map(|dom_id| state_scoped_dom_id(ctx, dom_id))
.unwrap_or_else(|| state_scoped_dom_id(ctx, node_id))
}
fn state_leaf_context_impl<'a>(
ctx: &'a StateRenderCtx<'_>,
id: &str,
respect_nested_roots: bool,
) -> Option<&'a str> {
let mut p = ctx.parent.get(id).copied();
while p.is_some() {
let pid = state_strip_note_group(ctx, p)?;
let Some(pn) = ctx.nodes_by_id.get(pid).copied() else {
return Some(pid);
};
if pn.is_group && pn.shape != "noteGroup" {
if !respect_nested_roots || ctx.nested_roots.contains(pid) {
return Some(pid);
}
p = ctx.parent.get(pid).copied();
continue;
}
p = ctx.parent.get(pid).copied();
}
None
}
fn state_strip_note_group<'a>(
ctx: &'a StateRenderCtx<'_>,
mut parent: Option<&'a str>,
) -> Option<&'a str> {
while let Some(pid) = parent {
let Some(pn) = ctx.nodes_by_id.get(pid).copied() else {
return Some(pid);
};
if pn.shape == "noteGroup" {
parent = ctx.parent.get(pid).copied();
continue;
}
return Some(pid);
}
None
}
fn state_insertion_context_impl<'a>(
ctx: &'a StateRenderCtx<'_>,
cluster_id: &str,
respect_nested_roots: bool,
) -> Option<&'a str> {
state_leaf_context_impl(ctx, cluster_id, respect_nested_roots)
}
fn state_endpoint_context_impl<'a>(
ctx: &'a StateRenderCtx<'_>,
id: &str,
respect_nested_roots: bool,
) -> Option<&'a str> {
if let Some(n) = ctx.nodes_by_id.get(id).copied()
&& n.is_group
&& n.shape != "noteGroup"
{
return state_insertion_context_impl(ctx, id, respect_nested_roots);
}
state_leaf_context_impl(ctx, id, respect_nested_roots)
}
fn state_context_chain_impl<'a>(
ctx: &'a StateRenderCtx<'_>,
mut c: Option<&'a str>,
respect_nested_roots: bool,
) -> Vec<Option<&'a str>> {
let mut out = Vec::new();
loop {
out.push(c);
let Some(id) = c else {
break;
};
c = state_insertion_context_impl(ctx, id, respect_nested_roots);
}
out
}
fn state_edge_context_impl<'a>(
ctx: &'a StateRenderCtx<'_>,
edge: &StateSvgEdge,
respect_nested_roots: bool,
) -> Option<&'a str> {
let a = state_endpoint_context_impl(ctx, edge.start.as_str(), respect_nested_roots);
let b = state_endpoint_context_impl(ctx, edge.end.as_str(), respect_nested_roots);
let ca = state_context_chain_impl(ctx, a, respect_nested_roots);
let cb = state_context_chain_impl(ctx, b, respect_nested_roots);
for anc in cb {
if ca.contains(&anc) {
return anc;
}
}
None
}
pub(super) fn state_endpoint_context_raw<'a>(
ctx: &'a StateRenderCtx<'_>,
id: &str,
) -> Option<&'a str> {
state_endpoint_context_impl(ctx, id, false)
}
pub(super) fn state_context_chain_raw<'a>(
ctx: &'a StateRenderCtx<'_>,
c: Option<&'a str>,
) -> Vec<Option<&'a str>> {
state_context_chain_impl(ctx, c, false)
}
pub(super) fn state_edge_context_raw<'a>(
ctx: &'a StateRenderCtx<'_>,
edge: &StateSvgEdge,
) -> Option<&'a str> {
state_edge_context_impl(ctx, edge, false)
}
pub(super) fn state_leaf_context<'a>(ctx: &'a StateRenderCtx<'_>, id: &str) -> Option<&'a str> {
state_leaf_context_impl(ctx, id, true)
}
pub(super) fn state_insertion_context<'a>(
ctx: &'a StateRenderCtx<'_>,
cluster_id: &str,
) -> Option<&'a str> {
state_insertion_context_impl(ctx, cluster_id, true)
}
pub(super) fn state_edge_context<'a>(
ctx: &'a StateRenderCtx<'_>,
edge: &StateSvgEdge,
) -> Option<&'a str> {
state_edge_context_impl(ctx, edge, true)
}
pub(super) fn state_is_shadowed_self_loop_edge(
ctx: &StateRenderCtx<'_>,
edge_index: usize,
edge: &StateSvgEdge,
root: Option<&str>,
) -> bool {
if ctx.uses_elk_adapter_dom || edge.start != edge.end {
return false;
}
if state_edge_context(ctx, edge) != root {
return false;
}
for later in ctx.edges.iter().skip(edge_index + 1) {
if later.start != later.end {
continue;
}
if later.start != edge.start || later.end != edge.end {
continue;
}
if state_is_hidden(ctx, later.start.as_str())
|| state_is_hidden(ctx, later.end.as_str())
|| state_is_hidden(ctx, later.id.as_str())
{
continue;
}
if state_edge_context(ctx, later) != root {
continue;
}
return true;
}
false
}