// 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();
if (IsTruthy(value)) props.Date1904 = true;
else props.Date1904 = null;
CleanupEmptyWorkbookProperties();
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();
if (IsTruthy(value)) props.FilterPrivacy = true;
else props.FilterPrivacy = null;
CleanupEmptyWorkbookProperties();
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();
if (IsTruthy(value)) props.BackupFile = true;
else props.BackupFile = null;
CleanupEmptyWorkbookProperties();
SaveWorkbook();
return true;
}
case "workbook.datecompatibility" or "datecompatibility":
{
var props = EnsureWorkbookProperties();
if (IsTruthy(value))
props.DateCompatibility = true;
else
props.DateCompatibility = null;
CleanupEmptyWorkbookProperties();
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();
if (IsTruthy(value))
calc.Iterate = true;
else
calc.Iterate = null;
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();
if (IsTruthy(value))
calc.FullPrecision = true;
else
calc.FullPrecision = null;
SaveWorkbook();
return true;
}
case "calc.fullcalconload" or "fullcalconload":
{
var calc = EnsureCalculationProperties();
if (IsTruthy(value))
calc.FullCalculationOnLoad = true;
else
calc.FullCalculationOnLoad = null;
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();
if (IsTruthy(value))
prot.LockStructure = true;
else
prot.LockStructure = null;
CleanupEmptyWorkbookProtection();
SaveWorkbook();
return true;
}
case "workbook.lockwindows" or "lockwindows":
{
var prot = EnsureWorkbookProtection();
if (IsTruthy(value))
prot.LockWindows = true;
else
prot.LockWindows = null;
CleanupEmptyWorkbookProtection();
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 CleanupEmptyWorkbookProperties()
{
var props = _doc.WorkbookPart?.Workbook?.GetFirstChild();
if (props != null && !props.HasAttributes && !props.HasChildren)
props.Remove();
}
private void CleanupEmptyWorkbookProtection()
{
var prot = _doc.WorkbookPart?.Workbook?.GetFirstChild();
if (prot != null && !prot.HasAttributes && !prot.HasChildren)
prot.Remove();
}
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 == true) node.Format["workbook.date1904"] = true;
if (props.CodeName?.Value != null) node.Format["workbook.codeName"] = props.CodeName.Value;
if (props.FilterPrivacy?.Value == true) node.Format["workbook.filterPrivacy"] = true;
if (props.ShowObjects?.Value != null) node.Format["workbook.showObjects"] = props.ShowObjects.InnerText;
if (props.BackupFile?.Value == true) node.Format["workbook.backupFile"] = true;
if (props.DateCompatibility?.Value == true) node.Format["workbook.dateCompatibility"] = true;
}
// CalculationProperties
var calc = workbook.GetFirstChild();
if (calc != null)
{
if (calc.CalculationMode?.Value != null) node.Format["calc.mode"] = calc.CalculationMode.InnerText;
if (calc.Iterate?.Value == true) node.Format["calc.iterate"] = true;
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 == true) node.Format["calc.fullPrecision"] = true;
if (calc.FullCalculationOnLoad?.Value == true) node.Format["calc.fullCalcOnLoad"] = true;
if (calc.ReferenceMode?.Value != null) node.Format["calc.refMode"] = calc.ReferenceMode.InnerText;
}
// WorkbookProtection
var prot = workbook.GetFirstChild();
if (prot != null)
{
if (prot.LockStructure?.Value == true) node.Format["workbook.lockStructure"] = true;
if (prot.LockWindows?.Value == true) node.Format["workbook.lockWindows"] = true;
}
}
}