diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..f9eb3d6 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "Bash(dotnet build *)", + "Bash(dotnet test *)" + ] + } +} diff --git a/.github/workflows/dotnet-linux.yml b/.github/workflows/dotnet-linux.yml index 0fde9f2..06aee17 100644 --- a/.github/workflows/dotnet-linux.yml +++ b/.github/workflows/dotnet-linux.yml @@ -16,12 +16,12 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v1 with: - dotnet-version: 8.0.x + dotnet-version: 9.0.x - name: Install mscorefonts run: sudo apt-get -y install ttf-mscorefonts-installer - name: Install dependencies run: dotnet restore - name: Build net8.0 - run: dotnet build --configuration Release --no-restore --framework net8.0 + run: dotnet build --configuration Release --no-restore --framework net9.0 - name: Test net8.0 - run: dotnet test --no-restore --verbosity minimal --framework net8.0 + run: dotnet test --no-restore --verbosity minimal --framework net9.0 diff --git a/.github/workflows/dotnet-mac.yml b/.github/workflows/dotnet-mac.yml index 43a5d41..f2457c2 100644 --- a/.github/workflows/dotnet-mac.yml +++ b/.github/workflows/dotnet-mac.yml @@ -14,10 +14,10 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v1 with: - dotnet-version: 8.0.x + dotnet-version: 9.0.x - name: Install dependencies run: dotnet restore - name: Build net8.0 - run: dotnet build --configuration Release --no-restore --framework net8.0 + run: dotnet build --configuration Release --no-restore --framework net9.0 - name: Test net8.0 - run: dotnet test --no-restore --verbosity minimal --framework net8.0 + run: dotnet test --no-restore --verbosity minimal --framework net9.0 diff --git a/.github/workflows/dotnet-windows.yml b/.github/workflows/dotnet-windows.yml index e527dbc..bfc74d5 100644 --- a/.github/workflows/dotnet-windows.yml +++ b/.github/workflows/dotnet-windows.yml @@ -14,7 +14,7 @@ jobs: - name: Setup .NET Core uses: actions/setup-dotnet@v1 with: - dotnet-version: 8.0.x + dotnet-version: 9.0.x - name: Install dependencies run: dotnet restore - name: Build diff --git a/README.md b/README.md index 7a56f0c..ecfd7b2 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ![Linux](https://github.com/BobLd/tabula-sharp/workflows/Linux/badge.svg) ![Mac OS](https://github.com/BobLd/tabula-sharp/workflows/Mac%20OS/badge.svg) -- Supports netstandard2.0, net462, net471, net6.0, net8.0 +- Supports netstandard2.0 / 2.1, net462, net471, net6.0, net8.0 and net9.0 - No java bindings NuGet packages available on the [releases](https://github.com/BobLd/tabula-sharp/releases) page and on www.nuget.org: diff --git a/Tabula.Csv/Tabula.Csv.csproj b/Tabula.Csv/Tabula.Csv.csproj index 9f8ccae..603eaac 100644 --- a/Tabula.Csv/Tabula.Csv.csproj +++ b/Tabula.Csv/Tabula.Csv.csproj @@ -1,32 +1,32 @@  - - netstandard2.0;net462;net471;net6.0;net8.0 - Extract tables from PDF files (port of tabula-java using PdfPig). Csv and Tsv writers. - https://github.com/BobLd/tabula-sharp - 0.1.5 - BobLd - pdf, extract, table, tabula, pdfpig, parse, extraction, csv, tsv, excel, export - MIT - https://github.com/BobLd/tabula-sharp - True - True - README.md - + + netstandard2.0;netstandard2.1;net462;net471;net6.0;net8.0;net9.0 + Extract tables from PDF files (port of tabula-java using PdfPig). Csv and Tsv writers. + https://github.com/BobLd/tabula-sharp + 1.0.0 + BobLd + pdf, extract, table, tabula, pdfpig, parse, extraction, csv, tsv, excel, export + MIT + https://github.com/BobLd/tabula-sharp + True + True + README.md + - - - True - \ - - + + + True + \ + + - - - + + + - - - + + + - + \ No newline at end of file diff --git a/Tabula.Json/Tabula.Json.csproj b/Tabula.Json/Tabula.Json.csproj index 06cb3aa..9e94d05 100644 --- a/Tabula.Json/Tabula.Json.csproj +++ b/Tabula.Json/Tabula.Json.csproj @@ -1,32 +1,32 @@  - - netstandard2.0;net462;net471;net6.0;net8.0 - Extract tables from PDF files (port of tabula-java using PdfPig). Json writer. - https://github.com/BobLd/tabula-sharp - 0.1.5 - BobLd - BobLd - pdf, extract, table, tabula, pdfpig, parse, extraction, json, export - MIT - https://github.com/BobLd/tabula-sharp - True - README.md - + + netstandard2.0;netstandard2.1;net462;net471;net6.0;net8.0;net9.0 + Extract tables from PDF files (port of tabula-java using PdfPig). Json writer. + https://github.com/BobLd/tabula-sharp + 1.0.0 + BobLd + BobLd + pdf, extract, table, tabula, pdfpig, parse, extraction, json, export + MIT + https://github.com/BobLd/tabula-sharp + True + README.md + - - - True - \ - - + + + True + \ + + - - - + + + - - - + + + - + \ No newline at end of file diff --git a/Tabula.Tests/GitHubIssues.cs b/Tabula.Tests/GitHubIssues.cs new file mode 100644 index 0000000..c81a555 --- /dev/null +++ b/Tabula.Tests/GitHubIssues.cs @@ -0,0 +1,22 @@ +using Tabula.Detectors; +using UglyToad.PdfPig; +using Xunit; + +namespace Tabula.Tests +{ + public class GithubIssues + { + [Fact] + public void Issue30() + { + using (PdfDocument document = PdfDocument.Open("Resources/issue.pdf", new ParsingOptions() { ClipPaths = true })) + { + PageArea page = ObjectExtractor.Extract(document, 1); + + SimpleNurminenDetectionAlgorithm detector = new SimpleNurminenDetectionAlgorithm(); + var regions = detector.Detect(page); + Assert.Single(regions); + } + } + } +} diff --git a/Tabula.Tests/Resources/issue.pdf b/Tabula.Tests/Resources/issue.pdf new file mode 100644 index 0000000..57b0cea Binary files /dev/null and b/Tabula.Tests/Resources/issue.pdf differ diff --git a/Tabula.Tests/Tabula.Tests.csproj b/Tabula.Tests/Tabula.Tests.csproj index 5816e9b..c905667 100644 --- a/Tabula.Tests/Tabula.Tests.csproj +++ b/Tabula.Tests/Tabula.Tests.csproj @@ -1,1052 +1,1055 @@  - - net8.0 + + net9.0 - false + false - 0.1.2 - + 0.1.2 + - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + - - - - - + + + + + - - - Always - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - Always - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - + + + Always + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + Always + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + diff --git a/Tabula/Detectors/SimpleNurminenDetectionAlgorithm.cs b/Tabula/Detectors/SimpleNurminenDetectionAlgorithm.cs index e7bef85..9006338 100644 --- a/Tabula/Detectors/SimpleNurminenDetectionAlgorithm.cs +++ b/Tabula/Detectors/SimpleNurminenDetectionAlgorithm.cs @@ -110,7 +110,7 @@ public IReadOnlyList Detect(PageArea page) List allEdges = new List(horizontalRulings); allEdges.AddRange(verticalRulings); - List tableAreas = new List(); + HashSet tableAreas = new HashSet(); // if we found some edges, try to find some tables based on them if (allEdges.Count > 0) @@ -236,13 +236,14 @@ public IReadOnlyList Detect(PageArea page) // get rid of any text lines contained within existing tables, this allows us to find more tables //for (Iterator iterator = lines.iterator(); iterator.hasNext();) - foreach (var textRow in lines.ToList()) + for (int i = lines.Count - 1; i >= 0; i--) { - foreach (TableRectangle table in tableAreas) + var textRow = lines[i]; + foreach (var table in tableAreas) { if (table.Contains(textRow)) { - lines.Remove(textRow); + lines.RemoveAt(i); break; } } @@ -278,8 +279,7 @@ public IReadOnlyList Detect(PageArea page) if (table != null) { - foundTable = true; - tableAreas.Add(table); + foundTable = tableAreas.Add(table); } } } while (foundTable); @@ -725,7 +725,7 @@ private TextEdges getTextEdges(List lines) return new TextEdges(leftTextEdges, midTextEdges, rightTextEdges); } - private List getTableAreasFromCells(List cells) + private HashSet getTableAreasFromCells(List cells) { List> cellGroups = new List>(); foreach (TableRectangle cell in cells) @@ -764,7 +764,7 @@ private List getTableAreasFromCells(List cells) } // create table areas based on cell group - List tableAreas = new List(); + HashSet tableAreas = new HashSet(); foreach (List cellGroup in cellGroups) { // less than four cells should not make a table diff --git a/Tabula/Extractors/SpreadsheetExtractionAlgorithm.cs b/Tabula/Extractors/SpreadsheetExtractionAlgorithm.cs index a24e9df..4cca586 100644 --- a/Tabula/Extractors/SpreadsheetExtractionAlgorithm.cs +++ b/Tabula/Extractors/SpreadsheetExtractionAlgorithm.cs @@ -287,7 +287,6 @@ public static List FindSpreadsheetsFromCells(List pointSet = new HashSet(); Dictionary edgesH = new Dictionary(); Dictionary edgesV = new Dictionary(); - int i = 0; cells = new List(new HashSet(cells)); @@ -316,26 +315,27 @@ public static List FindSpreadsheetsFromCells(List pointsSortY = new List(pointSet); pointsSortY.Sort(new POINT_COMPARER()); - while (i < pointSet.Count) + // Pair consecutive points sharing a Y / X coordinate. Using a for-loop with an + // unconditional increment (rather than the upstream tabula-java nested while) + // prevents an infinite loop when a coordinate is NaN or Infinity, since Feq + // returns false for any NaN comparison and would otherwise leave i unchanged. + for (int i = 0; i + 1 < pointSet.Count; i++) { - float currY = (float)pointsSortY[i].Y; - while (i < pointSet.Count && Utils.Feq(pointsSortY[i].Y, currY)) + if (Utils.Feq(pointsSortY[i].Y, pointsSortY[i + 1].Y)) { edgesH[pointsSortY[i]] = pointsSortY[i + 1]; edgesH[pointsSortY[i + 1]] = pointsSortY[i]; - i += 2; + i++; } } - i = 0; - while (i < pointSet.Count) + for (int i = 0; i + 1 < pointSet.Count; i++) { - float currX = (float)pointsSortX[i].X; - while (i < pointSet.Count && Utils.Feq(pointsSortX[i].X, currX)) + if (Utils.Feq(pointsSortX[i].X, pointsSortX[i + 1].X)) { edgesV[pointsSortX[i]] = pointsSortX[i + 1]; edgesV[pointsSortX[i + 1]] = pointsSortX[i]; - i += 2; + i++; } } diff --git a/Tabula/ObjectExtractor.cs b/Tabula/ObjectExtractor.cs index ab45c9e..7fd5dbb 100644 --- a/Tabula/ObjectExtractor.cs +++ b/Tabula/ObjectExtractor.cs @@ -75,7 +75,7 @@ public static PageArea ExtractPage(Page page) // se.processPage(p); var rulings = new List(); - foreach (var path in page.ExperimentalAccess.Paths) + foreach (var path in page.Paths) { if (!path.IsFilled && !path.IsStroked) continue; // strokeOrFillPath operator => filter stroke and filled diff --git a/Tabula/PageIterator.cs b/Tabula/PageIterator.cs index 9b3c0cd..334cfdb 100644 --- a/Tabula/PageIterator.cs +++ b/Tabula/PageIterator.cs @@ -28,20 +28,7 @@ public PageIterator(PdfDocument pdfDocument, IEnumerable pages) : base() } /// - public PageArea Current - { - get - { - try - { - return ObjectExtractor.ExtractPage(this.pdfDocument, pageIndexIterator.Current); - } - catch (Exception ex) - { - throw ex; - } - } - } + public PageArea Current => ObjectExtractor.ExtractPage(this.pdfDocument, pageIndexIterator.Current); /// object IEnumerator.Current => Current; diff --git a/Tabula/TableRectangle.cs b/Tabula/TableRectangle.cs index e1e1245..7687277 100644 --- a/Tabula/TableRectangle.cs +++ b/Tabula/TableRectangle.cs @@ -11,7 +11,7 @@ namespace Tabula /// /// A tabula rectangle. /// - public class TableRectangle : IComparable + public class TableRectangle : IComparable, IEquatable { /// /// Sort top to bottom (as in reading order). @@ -212,30 +212,26 @@ public void SetBottom(double bottom) public override int GetHashCode() { - int prime = 31; - int result = base.GetHashCode(); - // need to implement hash and equal in PdfPig's PdfRectangle - result = prime * result + BoundingBox.BottomLeft.GetHashCode(); - result = prime * result + BoundingBox.TopLeft.GetHashCode(); - result = prime * result + BoundingBox.TopRight.GetHashCode(); - result = prime * result + BoundingBox.BottomRight.GetHashCode(); - result = prime * result + BitConverter.ToInt32(BitConverter.GetBytes(BoundingBox.Rotation), 0); - return result; + return BoundingBox.GetHashCode(); } public override bool Equals(object obj) { if (obj is TableRectangle other) { - if (!this.BoundingBox.BottomLeft.Equals(other.BoundingBox.BottomLeft)) return false; - if (!this.BoundingBox.TopLeft.Equals(other.BoundingBox.TopLeft)) return false; - if (!this.BoundingBox.TopRight.Equals(other.BoundingBox.TopRight)) return false; - if (!this.BoundingBox.BottomRight.Equals(other.BoundingBox.BottomRight)) return false; - return true; + return Equals(other); } + return false; } + public bool Equals(TableRectangle other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return BoundingBox.Equals(other.BoundingBox); + } + public override string ToString() { StringBuilder sb = new StringBuilder(); diff --git a/Tabula/Tabula.csproj b/Tabula/Tabula.csproj index fa30e1e..6a3490e 100644 --- a/Tabula/Tabula.csproj +++ b/Tabula/Tabula.csproj @@ -1,31 +1,31 @@  - - netstandard2.0;net462;net471;net6.0;net8.0 - Extract tables from PDF files (port of tabula-java using PdfPig). - https://github.com/BobLd/tabula-sharp - 0.1.5 - BobLd - BobLd - pdf, extract, table, tabula, pdfpig, parse, extraction, export - MIT - https://github.com/BobLd/tabula-sharp - README.md - True - True - True - Tabula.snk - + + netstandard2.0;netstandard2.1;net462;net471;net6.0;net8.0;net9.0 + Extract tables from PDF files (port of tabula-java using PdfPig). + https://github.com/BobLd/tabula-sharp + 1.0.0 + BobLd + BobLd + pdf, extract, table, tabula, pdfpig, parse, extraction, export + MIT + https://github.com/BobLd/tabula-sharp + README.md + True + True + True + Tabula.snk + - - - True - \ - - + + + True + \ + + - - - + + + - + \ No newline at end of file diff --git a/Tabula/TextElement.cs b/Tabula/TextElement.cs index ca4c806..833b9ec 100644 --- a/Tabula/TextElement.cs +++ b/Tabula/TextElement.cs @@ -39,6 +39,11 @@ public TextElement(PdfRectangle pdfRectangle, FontDetails font, double fontSize, } public string GetText() => text; + /// + /// Gets the PdfPig letter associated. + /// + public Letter Letter => letter; + /// /// The direction of the text (0, 90, 180, or 270). Can be any number with PdfPig. /// diff --git a/Tabula/TextStripper.cs b/Tabula/TextStripper.cs index b449b0d..1a528aa 100644 --- a/Tabula/TextStripper.cs +++ b/Tabula/TextStripper.cs @@ -14,6 +14,8 @@ public sealed class TextStripper { private const string NBSP = "\u00A0"; private static float AVG_HEIGHT_MULT_THRESHOLD = 6.0f; + private static float MAX_BLANK_FONT_SIZE = 40.0f; + private static float MIN_BLANK_FONT_SIZE = 2.0f; /// /// Process the page. @@ -42,7 +44,7 @@ public static TextStripperResult Process(Page page) double wos = GetExpectedWhitespaceSize(letter); - TextElement te = new TextElement(GetBbox(letter), letter.Font, letter.PointSize, c, wos, letter.GlyphRectangle.Rotation) + TextElement te = new TextElement(GetBbox(letter), letter.FontDetails, letter.PointSize, c, wos, letter.BoundingBox.Rotation) { letter = letter }; @@ -54,9 +56,17 @@ public static TextStripperResult Process(Page page) totalHeight += Math.Max(te.Height, 1); // added by bobld: min height value to 1 double avgHeight = totalHeight / countHeight; - if (avgHeight > 0 && te.Height >= (avgHeight * AVG_HEIGHT_MULT_THRESHOLD) && (te.GetText()?.Trim().Equals("") != false)) + if (te.GetText()?.Trim().Equals("") != false) { - continue; + if (avgHeight > 0 && te.Height >= (avgHeight * AVG_HEIGHT_MULT_THRESHOLD)) + { + continue; + } + + if (letter.PointSize > MAX_BLANK_FONT_SIZE || letter.PointSize < MIN_BLANK_FONT_SIZE) + { + continue; + } } textElements.Add(te); @@ -80,10 +90,10 @@ private static bool IsPrintable(string s) private static bool IsSpecial(char c) { -#if NETCOREAPP3_1 - return c >= System.Text.Unicode.UnicodeRanges.Specials.FirstCodePoint && c < (System.Text.Unicode.UnicodeRanges.Specials.FirstCodePoint + System.Text.Unicode.UnicodeRanges.Specials.Length); -#else +#if NETSTANDARD || NETFRAMEWORK return c >= '\uFFF0' && c <= '\uFFFF'; +#else + return c >= System.Text.Unicode.UnicodeRanges.Specials.FirstCodePoint && c < (System.Text.Unicode.UnicodeRanges.Specials.FirstCodePoint + System.Text.Unicode.UnicodeRanges.Specials.Length); #endif } @@ -102,39 +112,39 @@ private static PdfRectangle GetBbox(Letter letter) { case TextOrientation.Horizontal: double add = 0; - if (letter.GlyphRectangle.Height == 0) + if (letter.BoundingBox.Height == 0) { add = 1; // force minimum height to be 1 } else if (letter.Value == " ") { - add = -(letter.GlyphRectangle.Height - 1); // force height of space to be 1 + add = -(letter.BoundingBox.Height - 1); // force height of space to be 1 } return new PdfRectangle(Utils.Round(letter.StartBaseLine.X, 2), Utils.Round(letter.StartBaseLine.Y, 2), - Utils.Round(letter.StartBaseLine.X + Math.Max(letter.Width, letter.GlyphRectangle.Width), 2), - Utils.Round(letter.GlyphRectangle.TopLeft.Y + add, 2)); + Utils.Round(letter.StartBaseLine.X + Math.Max(letter.Width, letter.BoundingBox.Width), 2), + Utils.Round(letter.BoundingBox.TopLeft.Y + add, 2)); case TextOrientation.Rotate180: // need to force min height = 1 and height of space to be 1 return new PdfRectangle(Utils.Round(letter.StartBaseLine.X, 2), Utils.Round(letter.StartBaseLine.Y, 2), - Utils.Round(letter.StartBaseLine.X - Math.Max(letter.Width, letter.GlyphRectangle.Width), 2), - Utils.Round(letter.GlyphRectangle.TopRight.Y, 2)); + Utils.Round(letter.StartBaseLine.X - Math.Max(letter.Width, letter.BoundingBox.Width), 2), + Utils.Round(letter.BoundingBox.TopRight.Y, 2)); case TextOrientation.Rotate90: // need to force min height = 1 and height of space to be 1 - return new PdfRectangle(new PdfPoint(Utils.Round(letter.StartBaseLine.X + letter.GlyphRectangle.Height, 2), Utils.Round(letter.GlyphRectangle.BottomLeft.Y, 2)), - new PdfPoint(Utils.Round(letter.StartBaseLine.X + letter.GlyphRectangle.Height, 2), Utils.Round(letter.EndBaseLine.Y, 2)), - new PdfPoint(Utils.Round(letter.StartBaseLine.X, 2), Utils.Round(letter.GlyphRectangle.BottomLeft.Y, 2)), + return new PdfRectangle(new PdfPoint(Utils.Round(letter.StartBaseLine.X + letter.BoundingBox.Height, 2), Utils.Round(letter.BoundingBox.BottomLeft.Y, 2)), + new PdfPoint(Utils.Round(letter.StartBaseLine.X + letter.BoundingBox.Height, 2), Utils.Round(letter.EndBaseLine.Y, 2)), + new PdfPoint(Utils.Round(letter.StartBaseLine.X, 2), Utils.Round(letter.BoundingBox.BottomLeft.Y, 2)), new PdfPoint(Utils.Round(letter.StartBaseLine.X, 2), Utils.Round(letter.EndBaseLine.Y, 2))); case TextOrientation.Rotate270: // need to force min height = 1 and height of space to be 1 - return new PdfRectangle(new PdfPoint(Utils.Round(letter.StartBaseLine.X - letter.GlyphRectangle.Height, 2), Utils.Round(letter.StartBaseLine.Y, 2)), - new PdfPoint(Utils.Round(letter.StartBaseLine.X - letter.GlyphRectangle.Height, 2), Utils.Round(letter.GlyphRectangle.BottomRight.Y, 2)), + return new PdfRectangle(new PdfPoint(Utils.Round(letter.StartBaseLine.X - letter.BoundingBox.Height, 2), Utils.Round(letter.StartBaseLine.Y, 2)), + new PdfPoint(Utils.Round(letter.StartBaseLine.X - letter.BoundingBox.Height, 2), Utils.Round(letter.BoundingBox.BottomRight.Y, 2)), new PdfPoint(Utils.Round(letter.StartBaseLine.X, 2), Utils.Round(letter.StartBaseLine.Y, 2)), - new PdfPoint(Utils.Round(letter.StartBaseLine.X, 2), Utils.Round(letter.GlyphRectangle.BottomRight.Y, 2))); + new PdfPoint(Utils.Round(letter.StartBaseLine.X, 2), Utils.Round(letter.BoundingBox.BottomRight.Y, 2))); case TextOrientation.Other: default: @@ -151,8 +161,8 @@ private static PdfRectangle GetBoundingBoxOther(Letter letter) { letter.StartBaseLine, letter.EndBaseLine, - letter.GlyphRectangle.TopLeft, - letter.GlyphRectangle.TopRight + letter.BoundingBox.TopLeft, + letter.BoundingBox.TopRight }; // Candidates bounding boxes @@ -164,7 +174,7 @@ private static PdfRectangle GetBoundingBoxOther(Letter letter) // Find the orientation of the OBB, using the baseline angle // Assumes line order is correct - var baseLineAngle = Distances.BoundAngle180(Distances.Angle(letter.GlyphRectangle.BottomLeft, letter.GlyphRectangle.BottomRight)); + var baseLineAngle = Distances.BoundAngle180(Distances.Angle(letter.BoundingBox.BottomLeft, letter.BoundingBox.BottomRight)); double deltaAngle = Math.Abs(Distances.BoundAngle180(obb.Rotation - baseLineAngle)); double deltaAngle1 = Math.Abs(Distances.BoundAngle180(obb1.Rotation - baseLineAngle));