using System.Text.RegularExpressions; using StandardsWorkbench.Core; using UglyToad.PdfPig; namespace StandardsWorkbench.Pdf; public sealed class PdfStandardService : IStandardPdfService { public Task<(StandardMetadata Metadata, IReadOnlyList Pages, IReadOnlyList Clauses)> ImportAsync(string pdfPath, string code, string title, CancellationToken cancellationToken) { if (!File.Exists(pdfPath)) throw new FileNotFoundException("PDF ГОСТа не найден.", pdfPath); return Task.Run(() => { using var pdf = PdfDocument.Open(pdfPath); var pages = new List(); foreach (var page in pdf.GetPages()) { cancellationToken.ThrowIfCancellationRequested(); var text = page.Text ?? ""; pages.Add(new PdfPageText(page.Number, text, text.Trim().Length > 0, text.Trim().Length > 0 ? "Extracted" : "OCR_REQUIRED", text.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries))); } var standardId = Regex.Replace(code.ToUpperInvariant(), "[^A-ZА-Я0-9]+", "-").Trim('-'); var clauses = ExtractClauses(standardId, pages); var meta = new StandardMetadata { StandardId = standardId, Code = code, Title = title, SourceFileSha256 = ProfileStore.Sha256(pdfPath), PageCount = pages.Count, TextLayerDetected = pages.Any(p => p.TextLayerDetected), ImportedAt = DateTime.UtcNow }; return (meta, (IReadOnlyList)pages, (IReadOnlyList)clauses); }, cancellationToken); } private static List ExtractClauses(string id, IReadOnlyList pages) { var result = new List(); string? currentId = null; var currentText = new List(); var start = 1; void Flush(int end) { if (currentId is not null) result.Add(new StandardClause(id, currentId, "", string.Join(Environment.NewLine, currentText), start, end, 0.85)); } foreach (var page in pages) foreach (var line in page.Text.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) { var match = Regex.Match(line, "^(\\d+(?:\\.\\d+){0,5})[.)]?\\s+(.+)$"); if (match.Success) { Flush(page.PageNumber - 1); currentId = match.Groups[1].Value; currentText = [line]; start = page.PageNumber; } else if (currentId is not null) currentText.Add(line); } Flush(pages.Count); return result; } } public sealed class NoOpOcrService : IPdfOcrService { public Task RecognizePageAsync(string pdfPath, int pageNumber, CancellationToken cancellationToken) => Task.FromResult(new PdfPageText(pageNumber, "", false, "OCR_REQUIRED", [])); }