Добавьте файлы проекта.
This commit is contained in:
217
QWERTYkez.ExcelProcessor/ReplaceStringExtensions.cs
Normal file
217
QWERTYkez.ExcelProcessor/ReplaceStringExtensions.cs
Normal file
@@ -0,0 +1,217 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user