// Copyright 2025 OfficeCli (officecli.ai) // SPDX-License-Identifier: Apache-2.0 using DocumentFormat.OpenXml; using DocumentFormat.OpenXml.Spreadsheet; using OfficeCli.Core; namespace OfficeCli.Handlers; public partial class ExcelHandler { /// /// Try to handle workbook-level settings. Returns true if handled. /// private bool TrySetWorkbookSetting(string key, string value) { switch (key) { // ==================== WorkbookProperties ==================== case "workbook.date1904" or "date1904": { var props = EnsureWorkbookProperties(); props.Date1904 = IsTruthy(value); SaveWorkbook(); return true; } case "workbook.codename" or "codename": { var props = EnsureWorkbookProperties(); props.CodeName = value; SaveWorkbook(); return true; } case "workbook.filterprivacy" or "filterprivacy": { var props = EnsureWorkbookProperties(); props.FilterPrivacy = IsTruthy(value); SaveWorkbook(); return true; } case "workbook.showobjects" or "showobjects": { var props = EnsureWorkbookProperties(); props.ShowObjects = value.ToLowerInvariant() switch { "all" => ObjectDisplayValues.All, "placeholders" => ObjectDisplayValues.Placeholders, "none" => ObjectDisplayValues.None, _ => throw new ArgumentException($"Invalid showObjects: '{value}'. Valid: all, placeholders, none") }; SaveWorkbook(); return true; } case "workbook.backupfile" or "backupfile": { var props = EnsureWorkbookProperties(); props.BackupFile = IsTruthy(value); SaveWorkbook(); return true; } case "workbook.datecompatibility" or "datecompatibility": { var props = EnsureWorkbookProperties(); props.DateCompatibility = IsTruthy(value); SaveWorkbook(); return true; } // ==================== CalculationProperties ==================== case "calc.mode" or "calcmode": { var calc = EnsureCalculationProperties(); calc.CalculationMode = value.ToLowerInvariant() switch { "auto" or "automatic" => CalculateModeValues.Auto, "manual" => CalculateModeValues.Manual, "autonoexcepttables" or "autoexcepttables" or "autonotable" => CalculateModeValues.AutoNoTable, _ => throw new ArgumentException($"Invalid calc.mode: '{value}'. Valid: auto, manual, autoExceptTables") }; SaveWorkbook(); return true; } case "calc.iterate" or "iterate": { var calc = EnsureCalculationProperties(); calc.Iterate = IsTruthy(value); SaveWorkbook(); return true; } case "calc.iteratecount" or "iteratecount": { var calc = EnsureCalculationProperties(); calc.IterateCount = ParseHelpers.SafeParseUint(value, "calc.iterateCount"); SaveWorkbook(); return true; } case "calc.iteratedelta" or "iteratedelta": { var calc = EnsureCalculationProperties(); calc.IterateDelta = ParseHelpers.SafeParseDouble(value, "calc.iterateDelta"); SaveWorkbook(); return true; } case "calc.fullprecision" or "fullprecision": { var calc = EnsureCalculationProperties(); calc.FullPrecision = IsTruthy(value); SaveWorkbook(); return true; } case "calc.fullcalconload" or "fullcalconload": { var calc = EnsureCalculationProperties(); calc.FullCalculationOnLoad = IsTruthy(value); SaveWorkbook(); return true; } case "calc.refmode" or "refmode": { var calc = EnsureCalculationProperties(); calc.ReferenceMode = value.ToLowerInvariant() switch { "a1" => ReferenceModeValues.A1, "r1c1" => ReferenceModeValues.R1C1, _ => throw new ArgumentException($"Invalid calc.refMode: '{value}'. Valid: A1, R1C1") }; SaveWorkbook(); return true; } // ==================== WorkbookProtection ==================== case "workbook.protection" or "workbookprotection": { var workbook = _doc.WorkbookPart!.Workbook; var existing = workbook.GetFirstChild(); existing?.Remove(); if (!string.Equals(value, "none", StringComparison.OrdinalIgnoreCase) && IsTruthy(value)) { var newProt = new WorkbookProtection { LockStructure = true, LockWindows = true }; var anchor = (DocumentFormat.OpenXml.OpenXmlElement?)workbook.GetFirstChild() ?? (DocumentFormat.OpenXml.OpenXmlElement?)workbook.GetFirstChild() ?? workbook.GetFirstChild(); if (anchor != null) anchor.InsertBeforeSelf(newProt); else workbook.AppendChild(newProt); } SaveWorkbook(); return true; } case "workbook.lockstructure" or "lockstructure": { var prot = EnsureWorkbookProtection(); prot.LockStructure = IsTruthy(value); SaveWorkbook(); return true; } case "workbook.lockwindows" or "lockwindows": { var prot = EnsureWorkbookProtection(); prot.LockWindows = IsTruthy(value); SaveWorkbook(); return true; } default: return false; } } // ==================== Helpers ==================== private WorkbookProperties EnsureWorkbookProperties() { var workbook = _doc.WorkbookPart!.Workbook; var props = workbook.GetFirstChild(); if (props == null) { props = new WorkbookProperties(); // Schema order: workbookPr must appear before Sheets, BookViews, etc. // Insert as the first child to maintain schema order. var firstChild = workbook.FirstChild; if (firstChild != null) firstChild.InsertBeforeSelf(props); else workbook.AppendChild(props); } return props; } private CalculationProperties EnsureCalculationProperties() { var workbook = _doc.WorkbookPart!.Workbook; var calc = workbook.GetFirstChild(); if (calc == null) { calc = new CalculationProperties(); workbook.AppendChild(calc); } return calc; } private WorkbookProtection EnsureWorkbookProtection() { var workbook = _doc.WorkbookPart!.Workbook; var prot = workbook.GetFirstChild(); if (prot == null) { prot = new WorkbookProtection(); // Schema order: workbookProtection must precede bookViews and sheets. // Insert before the first of BookViews, Sheets, or CalculationProperties if present. var anchor = (DocumentFormat.OpenXml.OpenXmlElement?)workbook.GetFirstChild() ?? (DocumentFormat.OpenXml.OpenXmlElement?)workbook.GetFirstChild() ?? workbook.GetFirstChild(); if (anchor != null) anchor.InsertBeforeSelf(prot); else workbook.AppendChild(prot); } return prot; } private void SaveWorkbook() { _doc.WorkbookPart?.Workbook?.Save(); } /// /// Read workbook-level settings into Format dictionary. /// private void PopulateWorkbookSettings(DocumentNode node) { var workbook = _doc.WorkbookPart?.Workbook; if (workbook == null) return; // WorkbookProperties var props = workbook.GetFirstChild(); if (props != null) { if (props.Date1904?.Value != null) node.Format["workbook.date1904"] = props.Date1904.Value; if (props.CodeName?.Value != null) node.Format["workbook.codeName"] = props.CodeName.Value; if (props.FilterPrivacy?.Value != null) node.Format["workbook.filterPrivacy"] = props.FilterPrivacy.Value; if (props.ShowObjects?.Value != null) node.Format["workbook.showObjects"] = props.ShowObjects.InnerText; if (props.BackupFile?.Value != null) node.Format["workbook.backupFile"] = props.BackupFile.Value; if (props.DateCompatibility?.Value != null) node.Format["workbook.dateCompatibility"] = props.DateCompatibility.Value; } // CalculationProperties var calc = workbook.GetFirstChild(); if (calc != null) { if (calc.CalculationMode?.Value != null) node.Format["calc.mode"] = calc.CalculationMode.InnerText; if (calc.Iterate?.Value != null) node.Format["calc.iterate"] = calc.Iterate.Value; if (calc.IterateCount?.Value != null) node.Format["calc.iterateCount"] = (int)calc.IterateCount.Value; if (calc.IterateDelta?.Value != null) node.Format["calc.iterateDelta"] = calc.IterateDelta.Value; if (calc.FullPrecision?.Value != null) node.Format["calc.fullPrecision"] = calc.FullPrecision.Value; if (calc.FullCalculationOnLoad?.Value != null) node.Format["calc.fullCalcOnLoad"] = calc.FullCalculationOnLoad.Value; if (calc.ReferenceMode?.Value != null) node.Format["calc.refMode"] = calc.ReferenceMode.InnerText; } // WorkbookProtection var prot = workbook.GetFirstChild(); if (prot != null) { if (prot.LockStructure?.Value != null) node.Format["workbook.lockStructure"] = prot.LockStructure.Value; if (prot.LockWindows?.Value != null) node.Format["workbook.lockWindows"] = prot.LockWindows.Value; } } }