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;