namespace QWERTYkez.ExcelProcessor; internal static class ReplaceStringExtensions { // --- Публичный метод для одиночной замены --- internal static void Replace(this SpreadsheetDocument doc, string oldValue, string newValue, StringComparison comparisonType = StringComparison.OrdinalIgnoreCase) { if (string.IsNullOrEmpty(oldValue)) return; WorksheetPart[] worksheets = [.. doc.WorkbookPart?.WorksheetParts!]; if (worksheets.Length < 1) return; var replacement = new Dictionary { [oldValue] = newValue }; ReplaceCore(doc, worksheets, replacement, comparisonType); } // --- Публичный метод для множественной замены --- internal static void Replace(this SpreadsheetDocument doc, IEnumerable> replacements, StringComparison comparisonType = StringComparison.OrdinalIgnoreCase) { if (replacements is null) return; WorksheetPart[] worksheets = [.. doc.WorkbookPart?.WorksheetParts!]; if (worksheets.Length < 1) return; var comparer = GetComparerForStringComparison(comparisonType); var replacementDict = new Dictionary(comparer); foreach (var kvp in replacements) if (!string.IsNullOrEmpty(kvp.Key)) replacementDict[kvp.Key] = kvp.Value; if (replacementDict.Count == 0) return; ReplaceCore(doc, worksheets, replacementDict, comparisonType); } // --- Общий приватный метод, содержащий всю логику замены --- private static void ReplaceCore(SpreadsheetDocument doc, WorksheetPart[] worksheets, Dictionary replacementDict, StringComparison comparisonType) { var workbookPart = doc.WorkbookPart!; // 1. Собрать все изменения для ячеек var cellChanges = new Dictionary(); var allSharedStrings = new List(); var sharedStringTable = workbookPart.SharedStringTablePart?.SharedStringTable; if (sharedStringTable is not null) { foreach (var item in sharedStringTable.Elements()) allSharedStrings.Add(string.Concat(item.Descendants().Select(t => t.Text))); } foreach (var worksheetPart in worksheets) CollectCellChanges(worksheetPart, replacementDict, comparisonType, cellChanges, allSharedStrings); // 2. Применить изменения к ячейкам ApplyCellChanges(cellChanges, allSharedStrings); // 3. Обновить SharedStringTable UpdateSharedStringTable(workbookPart, allSharedStrings); // 4. Обработать колонтитулы foreach (var worksheetPart in worksheets) ReplaceInHeadersFooters(worksheetPart, replacementDict, comparisonType); // 5. Обработать комментарии foreach (var worksheetPart in worksheets) ReplaceInComments(worksheetPart, replacementDict, comparisonType); } // --- Остальные вспомогательные методы (без изменений) --- private static IEqualityComparer GetComparerForStringComparison(StringComparison comparisonType) => comparisonType switch { StringComparison.Ordinal => StringComparer.Ordinal, StringComparison.OrdinalIgnoreCase => StringComparer.OrdinalIgnoreCase, StringComparison.InvariantCulture => StringComparer.InvariantCulture, StringComparison.InvariantCultureIgnoreCase => StringComparer.InvariantCultureIgnoreCase, StringComparison.CurrentCulture => StringComparer.CurrentCulture, StringComparison.CurrentCultureIgnoreCase => StringComparer.CurrentCultureIgnoreCase, _ => StringComparer.OrdinalIgnoreCase, }; private static void CollectCellChanges(WorksheetPart worksheetPart, Dictionary replacementDict, StringComparison comparisonType, Dictionary cellChanges, List allSharedStrings) { var worksheet = worksheetPart.Worksheet; if (worksheet is null) return; var sheetData = worksheet.GetFirstChild(); if (sheetData is null) return; foreach (var row in sheetData.Elements()) foreach (var cell in row.Elements()) { string originalText = GetCellText(cell, allSharedStrings); if (string.IsNullOrEmpty(originalText)) continue; string newText = ProcessReplacements(originalText, replacementDict, comparisonType); if (newText != originalText) cellChanges[cell] = newText; } } private static void ApplyCellChanges(Dictionary cellChanges, List allSharedStrings) { foreach (var kvp in cellChanges) { var cell = kvp.Key; var newText = kvp.Value; if (cell.InlineString is not null) { var texts = cell.InlineString.Descendants().ToList(); foreach (var t in texts) t.Remove(); cell.InlineString.AppendChild(new Text(newText)); } else { int newIndex = AddOrFindStringIndex(allSharedStrings, newText); cell.DataType = new EnumValue(CellValues.SharedString); cell.CellValue = new CellValue(newIndex.ToString()); } } } private static void UpdateSharedStringTable(WorkbookPart workbookPart, List allSharedStrings) { var ssPart = workbookPart.SharedStringTablePart; ssPart ??= workbookPart.AddNewPart(); var sharedStringTable = ssPart.SharedStringTable ?? new SharedStringTable(); sharedStringTable.RemoveAllChildren(); foreach (var str in allSharedStrings) sharedStringTable.AppendChild(new SharedStringItem(new Text(str))); sharedStringTable.Save(); } private static string ProcessReplacements(string input, Dictionary replacementDict, StringComparison comparisonType) { if (string.IsNullOrEmpty(input) || replacementDict.Count == 0) return input; string result = input; foreach (string key in replacementDict.Keys.OrderByDescending(k => k.Length)) { string value = replacementDict[key]; result = ReplaceInString(result, key, value, comparisonType); } return result; } private static string ReplaceInString(string original, string oldValue, string newValue, StringComparison comparisonType) { int idx = original.IndexOf(oldValue, comparisonType); if (idx < 0) return original; var sb = new StringBuilder(original.Length + newValue.Length - oldValue.Length); int last = 0; while (idx >= 0) { sb.Append(original, last, idx - last); sb.Append(newValue); last = idx + oldValue.Length; idx = original.IndexOf(oldValue, last, comparisonType); } sb.Append(original, last, original.Length - last); return sb.ToString(); } private static string GetCellText(Cell cell, List allSharedStrings) { if (cell?.CellValue is null) return string.Empty; if (cell.InlineString is not null) return string.Concat(cell.InlineString.Descendants().Select(t => t.Text)); string value = cell.CellValue.InnerText; if (cell.DataType?.Value == CellValues.SharedString) { if (int.TryParse(value, out int idx) && idx >= 0 && idx < allSharedStrings.Count) return allSharedStrings[idx]; return string.Empty; } return value; } private static int AddOrFindStringIndex(List allSharedStrings, string text) { int idx = allSharedStrings.IndexOf(text); if (idx >= 0) return idx; allSharedStrings.Add(text); return allSharedStrings.Count - 1; } // --- Колонтитулы (обобщённый метод) --- private static void ReplaceInHeadersFooters(WorksheetPart worksheetPart, Dictionary replacementDict, StringComparison comparisonType) { var worksheet = worksheetPart.Worksheet; if (worksheet is null) return; var headerFooter = worksheet.Descendants().FirstOrDefault(); if (headerFooter is null) return; foreach (var elem in new OpenXmlLeafTextElement?[] { headerFooter.OddHeader, headerFooter.OddFooter, headerFooter.EvenHeader, headerFooter.EvenFooter, headerFooter.FirstHeader, headerFooter.FirstFooter }) ReplaceHeaderFooter(elem, replacementDict, comparisonType); } private static void ReplaceHeaderFooter(OpenXmlLeafTextElement? element, Dictionary replacementDict, StringComparison comparisonType) { if (element?.Text is null) return; string original = element.Text; string processed = ProcessReplacements(original, replacementDict, comparisonType); if (processed != original) element.Text = processed; } // --- Комментарии --- private static void ReplaceInComments(WorksheetPart worksheetPart, Dictionary replacementDict, StringComparison comparisonType) { var commentsPart = worksheetPart.WorksheetCommentsPart; if (commentsPart?.Comments is null) return; foreach (var comment in commentsPart.Comments.Elements()) { var textElement = comment.Descendants().FirstOrDefault(); if (textElement?.Text is null) continue; string original = textElement.Text.Text; if (string.IsNullOrEmpty(original)) continue; string processed = ProcessReplacements(original, replacementDict, comparisonType); if (processed != original) textElement.Text.Text = processed; } } }