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