Files
QWERTYkez.OpenXmlProcessors/QWERTYkez.ExcelProcessor/ReplaceStringExtensions.cs

217 lines
10 KiB
C#
Raw Normal View History

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<string, string> { [oldValue] = newValue };
ReplaceCore(doc, worksheets, replacement, comparisonType);
}
// --- Публичный метод для множественной замены ---
internal static void Replace(this SpreadsheetDocument doc, IEnumerable<KeyValuePair<string, string>> 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<string, string>(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<string, string> replacementDict, StringComparison comparisonType)
{
var workbookPart = doc.WorkbookPart!;
// 1. Собрать все изменения для ячеек
var cellChanges = new Dictionary<Cell, string>();
var allSharedStrings = new List<string>();
var sharedStringTable = workbookPart.SharedStringTablePart?.SharedStringTable;
if (sharedStringTable is not null)
{
foreach (var item in sharedStringTable.Elements<SharedStringItem>())
allSharedStrings.Add(string.Concat(item.Descendants<Text>().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<string> 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<string, string> replacementDict, StringComparison comparisonType, Dictionary<Cell, string> cellChanges, List<string> allSharedStrings)
{
var worksheet = worksheetPart.Worksheet;
if (worksheet is null) return;
var sheetData = worksheet.GetFirstChild<SheetData>();
if (sheetData is null) return;
foreach (var row in sheetData.Elements<Row>())
foreach (var cell in row.Elements<Cell>())
{
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<Cell, string> cellChanges, List<string> 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<Text>().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>(CellValues.SharedString);
cell.CellValue = new CellValue(newIndex.ToString());
}
}
}
private static void UpdateSharedStringTable(WorkbookPart workbookPart, List<string> allSharedStrings)
{
var ssPart = workbookPart.SharedStringTablePart;
ssPart ??= workbookPart.AddNewPart<SharedStringTablePart>();
var sharedStringTable = ssPart.SharedStringTable ?? new SharedStringTable();
sharedStringTable.RemoveAllChildren<SharedStringItem>();
foreach (var str in allSharedStrings)
sharedStringTable.AppendChild(new SharedStringItem(new Text(str)));
sharedStringTable.Save();
}
private static string ProcessReplacements(string input, Dictionary<string, string> 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<string> allSharedStrings)
{
if (cell?.CellValue is null) return string.Empty;
if (cell.InlineString is not null)
return string.Concat(cell.InlineString.Descendants<Text>().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<string> 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<string, string> replacementDict, StringComparison comparisonType)
{
var worksheet = worksheetPart.Worksheet;
if (worksheet is null) return;
var headerFooter = worksheet.Descendants<HeaderFooter>().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<string, string> 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<string, string> replacementDict, StringComparison comparisonType)
{
var commentsPart = worksheetPart.WorksheetCommentsPart;
if (commentsPart?.Comments is null) return;
foreach (var comment in commentsPart.Comments.Elements<Comment>())
{
var textElement = comment.Descendants<CommentText>().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;
}
}
}