Добавьте файлы проекта.
This commit is contained in:
175
QWERTYkez.WordProcessor/PlaceholderFinder.cs
Normal file
175
QWERTYkez.WordProcessor/PlaceholderFinder.cs
Normal file
@@ -0,0 +1,175 @@
|
||||
namespace QWERTYkez.WordProcessor;
|
||||
|
||||
internal static class PlaceholderFinder
|
||||
{
|
||||
public static ISet<string> FindInDocument(WordprocessingDocument doc, Body body)
|
||||
{
|
||||
ISet<string> result = new NormalizedSet();
|
||||
|
||||
// 1. Основной текст
|
||||
FindInParagraphs(body.Elements<Paragraph>(), result);
|
||||
|
||||
// 2. Таблицы в основном тексте
|
||||
foreach (var table in body.Descendants<Table>())
|
||||
{
|
||||
FindInParagraphs(table.Descendants<Paragraph>(), result);
|
||||
}
|
||||
|
||||
// 3. Колонтитулы
|
||||
FindInHeadersAndFooters(doc, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void FindInParagraphs(IEnumerable<Paragraph> paragraphs, ISet<string> result)
|
||||
{
|
||||
// Локальная коллекция для каждого вызова - оптимизация для уменьшения аллокаций
|
||||
List<string>? tempList = null;
|
||||
|
||||
foreach (var paragraph in paragraphs)
|
||||
{
|
||||
var text = GetParagraphText(paragraph);
|
||||
if (string.IsNullOrEmpty(text)) continue;
|
||||
|
||||
// Откладываем создание списка до первого найденного плейсхолдера
|
||||
bool hasPlaceholders = FindPlaceholdersInText(text, ref tempList);
|
||||
|
||||
if (hasPlaceholders && tempList is not null)
|
||||
{
|
||||
// Добавляем найденные плейсхолдеры с учетом регистра
|
||||
foreach (var placeholder in tempList)
|
||||
{
|
||||
result.Add(placeholder);
|
||||
}
|
||||
tempList.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static unsafe bool FindPlaceholdersInText(string text, ref List<string>? output)
|
||||
{
|
||||
fixed (char* pText = text)
|
||||
{
|
||||
char* start = pText;
|
||||
char* end = pText + text.Length;
|
||||
bool foundAny = false;
|
||||
|
||||
while (start < end)
|
||||
{
|
||||
// Ищем начало плейсхолдера
|
||||
char* dollarStart = null;
|
||||
while (start < end)
|
||||
{
|
||||
if (*start == '$')
|
||||
{
|
||||
dollarStart = start;
|
||||
start++;
|
||||
break;
|
||||
}
|
||||
start++;
|
||||
}
|
||||
|
||||
if (dollarStart is null || start >= end) break;
|
||||
|
||||
// Ищем конец плейсхолдера
|
||||
char* dollarEnd = null;
|
||||
char* contentStart = start; // Начало содержимого (после первого $)
|
||||
|
||||
while (start < end)
|
||||
{
|
||||
if (*start == '$')
|
||||
{
|
||||
dollarEnd = start;
|
||||
start++;
|
||||
break;
|
||||
}
|
||||
start++;
|
||||
}
|
||||
|
||||
if (dollarEnd is null) break;
|
||||
|
||||
// Извлекаем содержимое между долларами
|
||||
int contentLength = (int)(dollarEnd - contentStart);
|
||||
if (contentLength > 0) // Игнорируем пустые "$$"
|
||||
{
|
||||
foundAny = true;
|
||||
output ??= [];
|
||||
|
||||
string content = new(contentStart, 0, contentLength);
|
||||
output.Add(content);
|
||||
}
|
||||
}
|
||||
|
||||
return foundAny;
|
||||
}
|
||||
}
|
||||
|
||||
private static void FindInHeadersAndFooters(WordprocessingDocument doc, ISet<string> result)
|
||||
{
|
||||
if (doc.MainDocumentPart is null) return;
|
||||
|
||||
// Верхние колонтитулы
|
||||
foreach (var headerPart in doc.MainDocumentPart.HeaderParts)
|
||||
{
|
||||
if (headerPart?.Header is not null)
|
||||
{
|
||||
FindInParagraphs(headerPart.Header.Descendants<Paragraph>(), result);
|
||||
}
|
||||
}
|
||||
|
||||
// Нижние колонтитулы
|
||||
foreach (var footerPart in doc.MainDocumentPart.FooterParts)
|
||||
{
|
||||
if (footerPart?.Footer is not null)
|
||||
{
|
||||
FindInParagraphs(footerPart.Footer.Descendants<Paragraph>(), result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetParagraphText(Paragraph paragraph)
|
||||
{
|
||||
if (paragraph is null) return string.Empty;
|
||||
|
||||
// Используем Span для минимальных аллокаций
|
||||
var texts = paragraph.Descendants<Text>();
|
||||
|
||||
// Быстрая проверка: если всего один WordText элемент
|
||||
if (texts is ICollection<Text> collection && collection.Count == 1)
|
||||
{
|
||||
foreach (var text in collection)
|
||||
{
|
||||
return text.Text ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
// Для нескольких WordText элементов
|
||||
int totalLength = 0;
|
||||
|
||||
// Первый проход: подсчет общей длины
|
||||
foreach (var text in texts)
|
||||
{
|
||||
if (text.Text is not null)
|
||||
{
|
||||
totalLength += text.Text.Length;
|
||||
}
|
||||
}
|
||||
|
||||
if (totalLength == 0) return string.Empty;
|
||||
|
||||
// Второй проход: копирование
|
||||
var chars = new char[totalLength];
|
||||
int position = 0;
|
||||
|
||||
foreach (var text in texts)
|
||||
{
|
||||
if (text.Text is not null)
|
||||
{
|
||||
text.Text.CopyTo(0, chars, position, text.Text.Length);
|
||||
position += text.Text.Length;
|
||||
}
|
||||
}
|
||||
|
||||
return new string(chars);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user