using System.Globalization; using SheetMe.Core.Models; namespace SheetMe.Designer.Services; /// 컨트롤 Id 생성 — 문서 전체에서 유일한 "TextBox1" 식 이름 부여(레거시 관행). public static class IdGenerator { #region Methods /// 문서의 사용 중 Id 집합 수집 public static HashSet CollectUsed(FormDocument document) { var used = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (var page in document.Pages) { Collect(page.Root, used); } return used; } /// /// 타입 기반 유일 Id 생성 — 같은 타입의 최대 번호 + 1(레거시 clsNameCreationService 규약). /// 발급한 Id 는 used 집합에 추가된다(연속 발급 안전). /// /// 빈 번호를 메우지 않는 것이 핵심이다. TextBox3 을 지우고 새 TextBox 를 놓으면 /// 최소 빈칸 방식은 다시 TextBox3 을 발급하는데, 이름은 저장된 답변(E_SctDta.SctObjNam)이 /// 붙는 열쇠라서 지운 칸의 과거 입력이 새 칸에 그대로 나타난다. 액션 대상과 /// CalcBox 수식도 이름으로 서로를 가리키므로 같은 이름을 재사용하면 조용히 오배선된다. /// public static string NextId(HashSet used, string type) { var next = 1; foreach (var id in used) { if (id.Length <= type.Length || !id.StartsWith(type, StringComparison.OrdinalIgnoreCase)) { continue; } // 접미사가 순수 숫자일 때만 번호로 본다("TextBox1a" 나 "TextBoxTotal" 은 무시) if (int.TryParse(id.AsSpan(type.Length), NumberStyles.None, CultureInfo.InvariantCulture, out var number) && number >= next) { next = number + 1; } } var candidate = type + next.ToString(CultureInfo.InvariantCulture); // 방어: "TextBox0" 같은 값이 이미 있으면 next 가 1 로 남아 충돌할 수 있다 while (!used.Add(candidate)) { next++; candidate = type + next.ToString(CultureInfo.InvariantCulture); } return candidate; } /// 단건 발급 편의 — 문서 스캔 후 1개 생성 public static string NextId(FormDocument document, string type) => NextId(CollectUsed(document), type); private static void Collect(ControlElement element, HashSet used) { if (element.Id.Length > 0) { used.Add(element.Id); } foreach (var child in element.Children) { Collect(child, used); } } #endregion }