Files
QWERTYkez.OpenXmlProcessors/QWERTYkez.ExcelProcessor/PlaceholderFinder.cs

143 lines
4.9 KiB
C#
Raw Normal View History

namespace QWERTYkez.ExcelProcessor;
internal static class PlaceholderFinder
{
/// <summary>Находит плейсхолдеры $...$ в указанных листах.</summary>
public static ISet<string> FindInDocument(SpreadsheetDocument doc, params WorksheetPart[] worksheets)
{
var result = new NormalizedSet();
if (doc.WorkbookPart is null) return result;
var sharedStringTable = doc.WorkbookPart.SharedStringTablePart?.SharedStringTable;
foreach (var ws in worksheets)
ProcessWorksheet(ws, sharedStringTable, result);
return result;
}
/// <summary>Находит плейсхолдеры во всех листах документа.</summary>
public static ISet<string> FindInDocument(SpreadsheetDocument doc)
{
if (doc.WorkbookPart is null) return new NormalizedSet();
return FindInDocument(doc, [.. doc.WorkbookPart.WorksheetParts]);
}
private static void ProcessWorksheet(WorksheetPart wsPart, SharedStringTable? sharedStrings, ISet<string> result)
{
var worksheet = wsPart.Worksheet;
if (worksheet is null) return;
ProcessCells(worksheet, sharedStrings, result);
ProcessHeaderFooter(worksheet, result);
}
private static void ProcessCells(Worksheet worksheet, SharedStringTable? sharedStrings, ISet<string> result)
{
var sheetData = worksheet.GetFirstChild<SheetData>();
if (sheetData is null) return;
List<string>? tempList = null; // буфер для плейсхолдеров из одного текста
foreach (var row in sheetData.Elements<Row>())
{
foreach (var cell in row.Elements<Cell>())
{
string text = GetCellValue(cell, sharedStrings);
if (string.IsNullOrEmpty(text)) continue;
#if DEBUG
if (text.Contains('$'))
Debug.WriteLine($"[PlaceholderFinder] Cell {cell.CellReference}: '{text}'");
#endif
if (FindPlaceholdersInText(text, ref tempList))
{
foreach (var ph in tempList!)
result.Add(ph);
tempList.Clear();
}
}
}
}
private static void ProcessHeaderFooter(Worksheet worksheet, ISet<string> result)
{
var hf = worksheet.Descendants<HeaderFooter>().FirstOrDefault();
if (hf is null) return;
var texts = new[]
{
hf.OddHeader?.Text, hf.OddFooter?.Text,
hf.EvenHeader?.Text, hf.EvenFooter?.Text,
hf.FirstHeader?.Text, hf.FirstFooter?.Text
};
List<string>? tempList = null;
foreach (var text in texts)
{
if (string.IsNullOrEmpty(text)) continue;
if (FindPlaceholdersInText(text!, ref tempList))
{
foreach (var ph in tempList!)
result.Add(ph);
tempList.Clear();
}
}
}
private static string GetCellValue(Cell cell, SharedStringTable? sharedStrings)
{
if (cell?.CellValue is null && cell?.InlineString is null)
return string.Empty;
// Встроенная строка
if (cell.InlineString is not null)
return string.Concat(cell.InlineString.Descendants<Text>().Select(t => t.Text));
// Обычное значение или общая строка
string raw = cell.CellValue!.InnerText;
if (cell.DataType?.Value == CellValues.SharedString)
{
if (sharedStrings is not null && int.TryParse(raw, out int idx) && idx >= 0 && idx < sharedStrings.Count!)
{
var si = sharedStrings.ElementAt(idx);
return string.Concat(si.Descendants<Text>().Select(t => t.Text));
}
return string.Empty;
}
return raw;
}
private static unsafe bool FindPlaceholdersInText(string text, ref List<string>? output)
{
fixed (char* pText = text)
{
char* start = pText;
char* end = pText + text.Length;
bool found = false;
while (start < end)
{
// Ищем '$'
while (start < end && *start != '$') start++;
if (start >= end) break;
char* open = start++;
if (start >= end) break;
// Ищем закрывающий '$'
char* contentStart = start;
while (start < end && *start != '$') start++;
if (start >= end) break;
char* close = start++;
int len = (int)(close - contentStart);
if (len > 0)
{
found = true;
output ??= [];
output.Add(new string(contentStart, 0, len));
}
}
return found;
}
}
}