using ScadaLink.Commons.Entities.Templates; namespace ScadaLink.TemplateEngine; /// /// Resolves the hierarchical ("qualified") name of a composition-derived /// template. A derived template stores only its contained name — the /// owning composition slot's InstanceName, unique only within that owner. /// The qualified path (Owner.Slot.Slot…) is computed on demand by /// walking the chain up to the base /// template. /// public static class TemplateNaming { /// /// Returns the dotted hierarchical name of . For /// a base (non-derived) template this is just its stored name. The walk is /// null-safe: if any owner link is missing from the supplied lookups it /// stops and falls back to the stored contained name, and a cycle (which /// the composition graph should never contain) is broken defensively. /// public static string QualifiedName( Template template, IReadOnlyDictionary byId, IReadOnlyDictionary compById) { ArgumentNullException.ThrowIfNull(template); ArgumentNullException.ThrowIfNull(byId); ArgumentNullException.ThrowIfNull(compById); return Resolve(template, byId, compById, new HashSet()); } private static string Resolve( Template template, IReadOnlyDictionary byId, IReadOnlyDictionary compById, HashSet visited) { // Base template, broken owner link, or a cycle → the stored name is the // best (and contained) answer. if (!template.IsDerived || template.OwnerCompositionId is not { } compId || !compById.TryGetValue(compId, out var composition) || !byId.TryGetValue(composition.TemplateId, out var owner) || !visited.Add(template.Id)) { return template.Name; } return $"{Resolve(owner, byId, compById, visited)}.{composition.InstanceName}"; } }