📑 فهرست مطالب
📦 گام ۱: نصب پکیجهای NuGet مورد نیاز
برای استخراج متن از فایلهای Office بدون نیاز به نصب نرمافزار Microsoft Office روی سرور، از کتابخانههای سبک و قدرتمند زیر استفاده میکنیم:
# برای پردازش فایلهای Word (DOCX) Install-Package DocumentFormat.OpenXml # برای پردازش فایلهای Excel (XLSX و XLS) Install-Package ExcelDataReader Install-Package ExcelDataReader.DataSet
DocumentFormat.OpenXml استاندارد رسمی مایکروسافت است و بسیار پایدار میباشد. ExcelDataReader فوقالعاده سریع است و نیاز به COM Interop ندارد، که آن را برای محیطهای سروری (Server-side) ایدهآل میکند.
📝 گام ۲: پیادهسازی DocxContentExtractor
این کلاس مسئول استخراج متن از فایلهای .docx است. نکته مهم در اینجا این است که استریم ورودی را به MemoryStream تبدیل میکنیم، زیرا OpenXml به یک استریم قابل جستجو (Seekable) نیاز دارد.
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using xAiService.Interfaces;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
namespace xAiService.Providers
{
/// <summary>
/// Extracts text content from DOCX files using DocumentFormat.OpenXml ...
/// </summary>
public class DocxContentExtractor : IFileContentExtractor
{
private const string DocxMimeType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
public bool CanExtract(string mimeType)
{
return string.Equals(mimeType, DocxMimeType, StringComparison.OrdinalIgnoreCase);
}
public async Task<string> ExtractAsync(
Stream fileStream,
string mimeType,
CancellationToken cancellationToken = default
)
{
// نکته حیاتی: OpenXml به استریم قابل جستجو (Seekable) نیاز دارد.
// استریم IFormFile ممکن است Seekable نباشد، بنابراین آن را در حافظه کپی میکنیم.
using var memoryStream = new MemoryStream();
await fileStream.CopyToAsync(memoryStream, cancellationToken);
memoryStream.Position = 0;
var stringBuilder = new StringBuilder();
// باز کردن سند Word به حالت فقط خواندنی
using (var wordDocument = WordprocessingDocument.Open(memoryStream, false))
{
var body = wordDocument.MainDocumentPart?.Document?.Body;
if (body != null)
{
// استخراج پاراگرافها و joining آنها با خط جدید برای حفظ ساختار برای LLM
var paragraphs = body.Elements<Paragraph>();
foreach (var paragraph in paragraphs)
{
var text = paragraph.InnerText?.Trim();
if (!string.IsNullOrEmpty(text))
{
stringBuilder.AppendLine(text);
}
}
}
}
return stringBuilder.ToString();
}
}
}
📊 گام ۳: پیادهسازی ExcelContentExtractor
این کلاس فایلهای .xlsx و .xls را خوانده و محتوای آن را به فرمت متنی ساختاریافته (شبیه CSV) تبدیل میکند تا مدل زبانی (LLM) بتواند به راحتی روابط سطرها و ستونها را درک کند.
using System;
using System.Data;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using xAiService.Interfaces;
using ExcelDataReader;
namespace xAiService.Providers
{
/// <summary>
/// Extracts tabular content from Excel files (XLSX, XLS) using ExcelDataReader ...
/// </summary>
public class ExcelContentExtractor : IFileContentExtractor
{
private static readonly string[] SupportedMimeTypes = new[]
{
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", // .xlsx
"application/vnd.ms-excel" // .xls
};
public bool CanExtract(string mimeType)
{
return Array.Exists(SupportedMimeTypes, type =>
string.Equals(type, mimeType, StringComparison.OrdinalIgnoreCase)
);
}
public async Task<string> ExtractAsync(
Stream fileStream,
string mimeType,
CancellationToken cancellationToken = default
)
{
// ثبت Encoding Provider برای پشتیبانی از فرمتهای قدیمیتر Excel
System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
// کپی در MemoryStream برای اطمینان از Seekable بودن
using var memoryStream = new MemoryStream();
await fileStream.CopyToAsync(memoryStream, cancellationToken);
memoryStream.Position = 0;
var stringBuilder = new StringBuilder();
// ایجاد Reader به صورت خودکار بر اساس فرمت فایل
using (var reader = ExcelReaderFactory.CreateReader(memoryStream))
{
var result = reader.AsDataSet();
foreach (DataTable table in result.Tables)
{
stringBuilder.AppendLine($"--- Sheet: {table.TableName} ---");
// افزودن هدر ستونها
var headers = new string[table.Columns.Count];
for (int i = 0; i < table.Columns.Count; i++)
{
headers[i] = table.Columns[i].ColumnName;
}
stringBuilder.AppendLine(string.Join(" | ", headers));
stringBuilder.AppendLine(new string('-', 50));
// افزودن دادههای سطرها
foreach (DataRow row in table.Rows)
{
var rowValues = new string[row.ItemArray.Length];
for (int i = 0; i < row.ItemArray.Length; i++)
{
rowValues[i] = row.ItemArray[i]?.ToString()?.Trim() ?? string.Empty;
}
stringBuilder.AppendLine(string.Join(" | ", rowValues));
}
stringBuilder.AppendLine();
}
}
return stringBuilder.ToString();
}
}
}
🔗 گام ۴: ثبت سرویسها در Dependency Injection
اکنون باید Extractor های جدید را به کانتینر DI اضافه کنیم. CompositeFileContentExtractor که قبلاً طراحی شده بود، به صورت خودکار تمام پیادهسازیهای IFileContentExtractor را از طریق تزریق IEnumerable<IFileContentExtractor> دریافت کرده و مدیریت میکند.
public void ConfigureServices(IServiceCollection services)
{
// ... (سایر ثبتهای سرویس)
// ✅ ثبت Extractor های فایل (الگوی Composite)
services.AddSingleton<IFileContentExtractor, PlainTextContentExtractor>();
services.AddSingleton<IFileContentExtractor, PdfContentExtractor>();
// ✅ افزودن Extractor های جدید آفیس
services.AddSingleton<IFileContentExtractor, DocxContentExtractor>();
services.AddSingleton<IFileContentExtractor, ExcelContentExtractor>();
// ✅ ثبت Composite Extractor (این کلاس لیست بالا را در Constructor دریافت میکند)
services.AddSingleton<IFileContentExtractor, CompositeFileContentExtractor>();
// ... (سایر ثبتهای سرویس)
}
⚠️ گام ۵: نکات کلیدی و ملاحظات عملکردی
🧠 محدودیت Context Window مدل
فایلهای Excel بزرگ میتوانند هزاران خط متن تولید کنند. قبل از ارسال به LLM، حتماً طول رشته استخراج شده را بررسی کنید و در صورت نیاز، آن را خلاصه (Chunk) کنید یا فقط چند سطر اول را ارسال نمایید.
💾 مدیریت حافظه (Memory)
استفاده از MemoryStream برای فایلهای بسیار بزرگ (مثلاً بالای 50 مگابایت) ممکن است باعث OutOfMemoryException شود. در appsettings.json حداکثر حجم فایل آپلودی را محدود کنید.
🔒 امنیت و اعتبارسنجی
کتابخانه ExcelDataReader در برابر فایلهای مخرب مقاوم است، اما همیشه قبل از پردازش، نوع فایل (MIME Type) و پسوند آن را در لایه Controller اعتبارسنجی کنید.
🌐 پشتیبانی از Encoding
خط Encoding.RegisterProvider(CodePagesEncodingProvider.Instance) در Extractor اکسل حیاتی است. بدون آن، خواندن فایلهای .xls قدیمی با کاراکترهای فارسی با خطا مواجه میشود.
با افزودن این دو کلاس، معماری
FileContentExtractor شما اکنون به طور کامل از فرمتهای متنی، PDF، Word و Excel پشتیبانی میکند. این تغییرات کاملاً با اصل Open/Closed (باز برای توسعه، بسته برای تغییر) در SOLID همخوانی دارد، زیرا برای افزودن فرمت جدید (مثلاً PowerPoint در آینده)، فقط کافی است یک کلاس جدید اضافه و در DI ثبت کنید، بدون اینکه کدهای موجود را تغییر دهید.