Skip to content

Commit 7c51683

Browse files
committed
feat: Add PDF header/footer filter and DOCX table complexity analyzer
- Add PdfHeaderFooterFilter for repetitive line detection and removal - Add DocxTableComplexityAnalyzer (Phase 1 of complex table roadmap) - Enhance PPTX slide title extraction with shape type analysis - Improve XLSX Markdown table output formatting - Add RefineOptions.FilterPdfHeaderFooter/PdfHeaderFooterThreshold - Bump version to 0.9.5
1 parent d06c68c commit 7c51683

8 files changed

Lines changed: 847 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## [Unreleased]
99

1010
### Added
11+
- **PDF Header/Footer Filter**: Repetitive line pattern detection and removal
12+
- `PdfHeaderFooterFilter`: Detects lines appearing on multiple pages (configurable threshold)
13+
- `RefineOptions.FilterPdfHeaderFooter`: Opt-in feature for PDF noise reduction
14+
- `RefineOptions.PdfHeaderFooterThreshold`: Configurable ratio (default 0.5 = 50% of pages)
15+
- **DOCX Table Complexity Analyzer**: Phase 1 of complex table handling roadmap
16+
- `DocxTableComplexityAnalyzer`: Detects merged cells, nested tables, irregular rows
17+
- `TableAnalysisResult`: Comprehensive metrics with complexity scoring (0.0-1.0)
18+
- `TableComplexityLevel`: Simple, Low, Medium, High, VeryHigh classification
19+
- Generates warnings for Markdown-incompatible table structures
20+
- **PPTX Slide Title Extraction**: Enhanced title detection in presentations
21+
- Improved title placeholder detection using shape type analysis
22+
- Better handling of slides without explicit title shapes
23+
- **XLSX Markdown Table Output**: Enhanced spreadsheet to Markdown conversion
24+
- Proper column alignment detection and formatting
25+
- Improved cell content handling for complex data
1126
- **Auto Chunking Strategy Selection**: Document structure-based strategy selection
1227
- `SelectChunkingStrategy()`: Analyzes document for headings and numbered sections
1328
- `AnalyzeDocumentStructure()`: Detects Markdown headings, numbered patterns

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<Project>
33
<!-- Version Information (can be overridden by build scripts) -->
44
<PropertyGroup>
5-
<VersionPrefix>0.9.4</VersionPrefix>
5+
<VersionPrefix>0.9.5</VersionPrefix>
66
<AssemblyVersion>$(VersionPrefix)</AssemblyVersion>
77
<FileVersion>$(VersionPrefix)</FileVersion>
88
<PackageVersion Condition="'$(PackageVersion)' == ''">$(VersionPrefix)</PackageVersion>

src/FileFlux.Core/Contracts/IDocumentProcessor.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,19 @@ public class RefineOptions
282282
/// </summary>
283283
public int? MaxLlmTokens { get; set; }
284284

285+
/// <summary>
286+
/// Enable PDF header/footer pattern detection and removal.
287+
/// When enabled, repetitive lines appearing on multiple pages are identified and filtered.
288+
/// Default: false (opt-in feature).
289+
/// </summary>
290+
public bool FilterPdfHeaderFooter { get; set; } = false;
291+
292+
/// <summary>
293+
/// Minimum ratio of pages a line must appear on to be considered header/footer.
294+
/// Range: 0.0-1.0. Default: 0.5 (50% of pages).
295+
/// </summary>
296+
public double PdfHeaderFooterThreshold { get; set; } = 0.5;
297+
285298
/// <summary>
286299
/// Default refinement options.
287300
/// </summary>

src/FileFlux.Core/Infrastructure/Readers/ExcelDocumentReader.cs

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,10 @@ private static (string Content, int RowCount, int CellCount) ExtractWorksheetCon
427427
try
428428
{
429429
var rows = worksheetPart.Worksheet.Descendants<Row>().ToList();
430+
var processedRows = new List<List<string>>();
431+
var maxColumns = 0;
430432

433+
// First pass: collect all rows and determine max columns
431434
foreach (var row in rows)
432435
{
433436
cancellationToken.ThrowIfCancellationRequested();
@@ -439,7 +442,9 @@ private static (string Content, int RowCount, int CellCount) ExtractWorksheetCon
439442
foreach (var cell in cells)
440443
{
441444
var cellValue = GetCellValue(cell, sharedStringTable);
442-
cellValues.Add(cellValue);
445+
// Escape pipe characters for Markdown table compatibility
446+
var escapedValue = cellValue.Replace("|", "\\|").Replace("\n", " ").Replace("\r", "");
447+
cellValues.Add(escapedValue);
443448

444449
if (!string.IsNullOrWhiteSpace(cellValue))
445450
{
@@ -450,13 +455,33 @@ private static (string Content, int RowCount, int CellCount) ExtractWorksheetCon
450455

451456
if (hasContent)
452457
{
453-
// 테이블 형식으로 셀 값들을 연결 (파이프 구분자 사용)
454-
var rowText = string.Join(" | ", cellValues.Where(v => !string.IsNullOrWhiteSpace(v)));
455-
if (!string.IsNullOrWhiteSpace(rowText))
456-
{
457-
contentBuilder.AppendLine(rowText);
458-
rowCount++;
459-
}
458+
processedRows.Add(cellValues);
459+
maxColumns = Math.Max(maxColumns, cellValues.Count);
460+
rowCount++;
461+
}
462+
}
463+
464+
// Generate Markdown table
465+
if (processedRows.Count > 0 && maxColumns > 0)
466+
{
467+
// Normalize all rows to have same column count
468+
foreach (var row in processedRows)
469+
{
470+
while (row.Count < maxColumns)
471+
row.Add(string.Empty);
472+
}
473+
474+
// Header row (first row)
475+
var headerRow = processedRows[0];
476+
contentBuilder.AppendLine("| " + string.Join(" | ", headerRow) + " |");
477+
478+
// Separator row
479+
contentBuilder.AppendLine("| " + string.Join(" | ", headerRow.Select(_ => "---")) + " |");
480+
481+
// Data rows (skip header)
482+
for (int i = 1; i < processedRows.Count; i++)
483+
{
484+
contentBuilder.AppendLine("| " + string.Join(" | ", processedRows[i]) + " |");
460485
}
461486
}
462487
}

src/FileFlux.Core/Infrastructure/Readers/PowerPointDocumentReader.cs

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,14 +404,68 @@ private static RawContent ExtractPowerPointContentFromStream(Stream stream, stri
404404
}
405405
}
406406

407+
/// <summary>
408+
/// Extracts the slide title from title placeholder shapes.
409+
/// Falls back to first text content or slide number if no title found.
410+
/// </summary>
411+
private static string ExtractSlideTitle(Slide slide, int slideNumber)
412+
{
413+
var shapeTree = slide.CommonSlideData?.ShapeTree;
414+
if (shapeTree == null)
415+
return $"Slide {slideNumber}";
416+
417+
// 1. Try to find title placeholder (Title or CenteredTitle)
418+
foreach (var shape in shapeTree.Elements<Shape>())
419+
{
420+
var placeholder = shape.NonVisualShapeProperties?
421+
.ApplicationNonVisualDrawingProperties?
422+
.GetFirstChild<PlaceholderShape>();
423+
424+
if (placeholder?.Type != null && placeholder.Type.HasValue)
425+
{
426+
var placeholderType = placeholder.Type.Value;
427+
if (placeholderType == PlaceholderValues.Title ||
428+
placeholderType == PlaceholderValues.CenteredTitle)
429+
{
430+
var titleText = ExtractTextFromShape(shape);
431+
if (!string.IsNullOrWhiteSpace(titleText))
432+
{
433+
// Clean up: remove newlines and trim
434+
return titleText.Replace("\r\n", " ").Replace("\n", " ").Trim();
435+
}
436+
}
437+
}
438+
}
439+
440+
// 2. Fallback: try first text block if it looks like a title (short text)
441+
foreach (var shape in shapeTree.Elements<Shape>())
442+
{
443+
var text = ExtractTextFromShape(shape);
444+
if (!string.IsNullOrWhiteSpace(text))
445+
{
446+
var cleanText = text.Replace("\r\n", " ").Replace("\n", " ").Trim();
447+
// Use as title only if reasonably short (under 100 chars)
448+
if (cleanText.Length > 0 && cleanText.Length < 100)
449+
{
450+
return cleanText;
451+
}
452+
}
453+
}
454+
455+
// 3. Final fallback
456+
return $"Slide {slideNumber}";
457+
}
458+
407459
private static (string Content, int ShapeCount) ExtractSlideContent(Slide slide, SlidePart slidePart, int slideNumber, List<string> warnings, CancellationToken cancellationToken)
408460
{
409461
var contentBuilder = new StringBuilder();
410462
var shapeCount = 0;
411463

412464
try
413465
{
414-
contentBuilder.AppendLine($"## Slide {slideNumber}");
466+
// Extract actual slide title from title placeholder
467+
var slideTitle = ExtractSlideTitle(slide, slideNumber);
468+
contentBuilder.AppendLine($"## {slideTitle}");
415469
contentBuilder.AppendLine();
416470

417471
var shapeTree = slide.CommonSlideData?.ShapeTree;

0 commit comments

Comments
 (0)