diff --git a/src/officecli/Core/HtmlScreenshot.cs b/src/officecli/Core/HtmlScreenshot.cs
index 787e77288..c6cd1e735 100644
--- a/src/officecli/Core/HtmlScreenshot.cs
+++ b/src/officecli/Core/HtmlScreenshot.cs
@@ -411,7 +411,7 @@ private static (bool, string?) TryChrome(string url, string outPath, int w, int
private static string? FindChrome()
{
- string[] names = ["google-chrome", "google-chrome-stable", "chromium", "chromium-browser",
+ string[] names = ["chrome-headless-shell", "google-chrome", "google-chrome-stable", "chromium", "chromium-browser",
"chrome", "microsoft-edge", "microsoft-edge-stable", "msedge"];
var pathHit = WhichFirst(names);
if (pathHit != null) return pathHit;
@@ -437,12 +437,32 @@ private static (bool, string?) TryChrome(string url, string outPath, int w, int
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
+ string localAppData = Environment.GetEnvironmentVariable("LOCALAPPDATA") ?? "";
+ string userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
+
+ // Search ms-playwright / puppeteer installation directories for chrome-headless-shell.exe
+ foreach (var baseDir in new[] { localAppData, userProfile })
+ {
+ if (string.IsNullOrEmpty(baseDir)) continue;
+ var pwDir = Path.Combine(baseDir, "ms-playwright");
+ if (Directory.Exists(pwDir))
+ {
+ try
+ {
+ foreach (var exe in Directory.GetFiles(pwDir, "chrome-headless-shell.exe", SearchOption.AllDirectories))
+ abs.Add(exe);
+ }
+ catch { }
+ }
+ }
+
string[] roots = [
Environment.GetEnvironmentVariable("PROGRAMFILES") ?? @"C:\Program Files",
Environment.GetEnvironmentVariable("PROGRAMFILES(X86)") ?? @"C:\Program Files (x86)",
- Environment.GetEnvironmentVariable("LOCALAPPDATA") ?? "",
+ localAppData,
];
string[] suffixes = [
+ @"Google\Chrome\Application\chrome-headless-shell.exe",
@"Google\Chrome\Application\chrome.exe",
@"Chromium\Application\chrome.exe",
@"Microsoft\Edge\Application\msedge.exe",
diff --git a/src/officecli/Handlers/Excel/ExcelHandler.Add.Cells.cs b/src/officecli/Handlers/Excel/ExcelHandler.Add.Cells.cs
index a9630db70..2e73c2bd5 100644
--- a/src/officecli/Handlers/Excel/ExcelHandler.Add.Cells.cs
+++ b/src/officecli/Handlers/Excel/ExcelHandler.Add.Cells.cs
@@ -1245,6 +1245,7 @@ private string AddRun(string parentPath, string type, InsertPosition? position,
new Text(runExistingText) { Space = SpaceProcessingModeValues.Preserve }));
runCell.RemoveAllChildren();
runSst.AppendChild(runSsi);
+ InvalidateSharedStringCache();
var newSstIdx = runSst.Elements().Count() - 1;
runCell.CellValue = new CellValue(newSstIdx.ToString());
runCell.DataType = new EnumValue(CellValues.SharedString);
@@ -1563,6 +1564,7 @@ private void ApplyRichTextToCell(Cell cell, Dictionary propertie
}
sst.AppendChild(ssi);
+ InvalidateSharedStringCache();
sst.Count = (uint)sst.Elements().Count();
sst.UniqueCount = sst.Count;
@@ -1621,6 +1623,7 @@ private void ApplyPhoneticToCell(Cell cell, WorksheetPart wsPart,
ssi.AppendChild(rPh);
sst.AppendChild(ssi);
+ InvalidateSharedStringCache();
sst.Count = (uint)sst.Elements().Count();
sst.UniqueCount = sst.Count;
diff --git a/src/officecli/Handlers/Excel/ExcelHandler.Helpers.Cell.cs b/src/officecli/Handlers/Excel/ExcelHandler.Helpers.Cell.cs
index d6967b0c9..741bcc6b6 100644
--- a/src/officecli/Handlers/Excel/ExcelHandler.Helpers.Cell.cs
+++ b/src/officecli/Handlers/Excel/ExcelHandler.Helpers.Cell.cs
@@ -38,6 +38,48 @@ public partial class ExcelHandler
$"Unknown totals-row function '{tok}'. Valid: sum, average, count, countNums, max, min, stdDev, var, none, custom.")
};
+ private List? _sharedStringItemCache;
+ private List? _sharedStringCache;
+
+ private List GetSharedStringItemCache()
+ {
+ if (_sharedStringItemCache != null) return _sharedStringItemCache;
+ var sst = _doc.WorkbookPart?.GetPartsOfType().FirstOrDefault();
+ if (sst?.SharedStringTable == null) return _sharedStringItemCache = new List();
+
+ var list = new List();
+ foreach (var item in sst.SharedStringTable.Elements())
+ {
+ list.Add(item);
+ }
+ return _sharedStringItemCache = list;
+ }
+
+ private List GetSharedStringCache()
+ {
+ if (_sharedStringCache != null) return _sharedStringCache;
+ var items = GetSharedStringItemCache();
+ var list = new List(items.Count);
+ foreach (var item in items)
+ {
+ list.Add(item.InnerText ?? "");
+ }
+ return _sharedStringCache = list;
+ }
+
+ ///
+ /// Must be called whenever a new SharedStringItem is appended to or removed
+ /// from the SharedStringTable so that the next GetCellDisplayValue call
+ /// rebuilds the cache from the updated table.
+ ///
+ private void InvalidateSharedStringCache()
+ {
+ _sharedStringCache = null;
+ _sharedStringItemCache = null;
+ }
+
+
+
private string GetCellDisplayValue(Cell cell, Core.FormulaEvaluator? evaluator = null)
{
if (cell.DataType?.Value == CellValues.InlineString)
@@ -49,12 +91,13 @@ private string GetCellDisplayValue(Cell cell, Core.FormulaEvaluator? evaluator =
if (cell.DataType?.Value == CellValues.SharedString)
{
- var sst = _doc.WorkbookPart?.GetPartsOfType().FirstOrDefault();
- if (sst?.SharedStringTable != null && int.TryParse(value, out int idx))
+ if (int.TryParse(value, out int idx))
{
- var item = sst.SharedStringTable.Elements().ElementAtOrDefault(idx);
- return item?.InnerText ?? value;
+ var cache = GetSharedStringCache();
+ if (idx >= 0 && idx < cache.Count)
+ return cache[idx];
}
+ return value;
}
// Boolean cells store 0/1 in per the OOXML spec, but Excel displays
diff --git a/src/officecli/Handlers/Excel/ExcelHandler.HtmlPreview.cs b/src/officecli/Handlers/Excel/ExcelHandler.HtmlPreview.cs
index 9ed645732..8f1359a1b 100644
--- a/src/officecli/Handlers/Excel/ExcelHandler.HtmlPreview.cs
+++ b/src/officecli/Handlers/Excel/ExcelHandler.HtmlPreview.cs
@@ -2935,9 +2935,10 @@ private static (double mantissa, int exp) NormalizeScientific(double value)
var value = cell.CellValue?.Text;
if (value == null || !int.TryParse(value, out int idx)) return null;
- var sst = _doc.WorkbookPart?.GetPartsOfType().FirstOrDefault();
- var item = sst?.SharedStringTable?.Elements().ElementAtOrDefault(idx);
- if (item == null) return null;
+ var items = GetSharedStringItemCache();
+ if (idx < 0 || idx >= items.Count) return null;
+ var item = items[idx];
+
var runs = item.Elements().ToList();
// Only worth wrapping when at least one run carries explicit run-properties;