From bd02f88cf32e8aa4a9ea533efd228bb9324be94b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 24 Feb 2026 03:18:26 +0000
Subject: [PATCH 1/2] Initial plan
From e6147e44650c8848cfeef70d4d34c801103d7753 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 24 Feb 2026 03:23:17 +0000
Subject: [PATCH 2/2] Add SheetIndices and SheetNames options for multi-sheet
filtering (#11)
Co-authored-by: shps951023 <12729184+shps951023@users.noreply.github.com>
---
README.md | 21 ++
src/MiniPdf/ExcelToPdfConverter.cs | 26 ++-
.../MiniPdf.Tests/ExcelToPdfConverterTests.cs | 179 ++++++++++++++++++
3 files changed, 225 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index e281c99..427ac54 100644
--- a/README.md
+++ b/README.md
@@ -86,6 +86,27 @@ var doc = ExcelToPdfConverter.Convert("data.xlsx", options);
doc.Save("data.pdf");
```
+### Multi-Sheet Handling
+
+By default all sheets are converted. Use `SheetIndices` (zero-based) or `SheetNames` (case-insensitive) to select a subset:
+
+```csharp
+// Convert only the first and third sheet (by index)
+var options = new ExcelToPdfConverter.ConversionOptions
+{
+ SheetIndices = new[] { 0, 2 },
+};
+
+// Convert only sheets named "Sales" and "Summary" (case-insensitive)
+var options2 = new ExcelToPdfConverter.ConversionOptions
+{
+ SheetNames = new[] { "Sales", "Summary" },
+};
+```
+
+When both `SheetIndices` and `SheetNames` are specified, `SheetIndices` takes precedence.
+```
+
### Save to Stream or Byte Array
```csharp
diff --git a/src/MiniPdf/ExcelToPdfConverter.cs b/src/MiniPdf/ExcelToPdfConverter.cs
index 8072fc7..c2d3a52 100644
--- a/src/MiniPdf/ExcelToPdfConverter.cs
+++ b/src/MiniPdf/ExcelToPdfConverter.cs
@@ -42,6 +42,17 @@ public sealed class ConversionOptions
/// Whether to include sheet name as a header (default: true).
public bool IncludeSheetName { get; set; } = true;
+
+ ///
+ /// Zero-based indices of the sheets to include. When null or empty, all sheets are included.
+ ///
+ public IReadOnlyList? SheetIndices { get; set; }
+
+ ///
+ /// Names of the sheets to include (case-insensitive). When null or empty, all sheets are included.
+ /// Ignored if is also specified.
+ ///
+ public IReadOnlyList? SheetNames { get; set; }
}
///
@@ -68,7 +79,20 @@ public static PdfDocument Convert(Stream excelStream, ConversionOptions? options
var sheets = ExcelReader.ReadSheets(excelStream);
var doc = new PdfDocument();
- foreach (var sheet in sheets)
+ // Filter sheets by index or name if specified
+ IEnumerable sheetsToRender = sheets;
+ if (options.SheetIndices is { Count: > 0 })
+ {
+ var indexSet = new HashSet(options.SheetIndices);
+ sheetsToRender = sheets.Where((_, i) => indexSet.Contains(i));
+ }
+ else if (options.SheetNames is { Count: > 0 })
+ {
+ var nameSet = new HashSet(options.SheetNames, StringComparer.OrdinalIgnoreCase);
+ sheetsToRender = sheets.Where(s => nameSet.Contains(s.Name));
+ }
+
+ foreach (var sheet in sheetsToRender)
{
RenderSheet(doc, sheet, options);
}
diff --git a/tests/MiniPdf.Tests/ExcelToPdfConverterTests.cs b/tests/MiniPdf.Tests/ExcelToPdfConverterTests.cs
index f74b68e..79dc3c4 100644
--- a/tests/MiniPdf.Tests/ExcelToPdfConverterTests.cs
+++ b/tests/MiniPdf.Tests/ExcelToPdfConverterTests.cs
@@ -127,6 +127,83 @@ public void Convert_WithTextColor_PreservesColorInPdf()
Assert.Contains("0.000 0.000 1.000 rg", content);
}
+ [Fact]
+ public void Convert_WithSheetIndices_OnlyRendersSpecifiedSheets()
+ {
+ using var excelStream = CreateMultiSheetExcel(
+ ("Alpha", new[] { new[] { "AlphaCell" } }),
+ ("Beta", new[] { new[] { "BetaCell" } }),
+ ("Gamma", new[] { new[] { "GammaCell" } }));
+
+ var options = new ExcelToPdfConverter.ConversionOptions
+ {
+ SheetIndices = new[] { 0, 2 }, // Alpha and Gamma only
+ };
+
+ var doc = ExcelToPdfConverter.Convert(excelStream, options);
+ var content = Encoding.ASCII.GetString(doc.ToArray());
+
+ Assert.Contains("AlphaCell", content);
+ Assert.DoesNotContain("BetaCell", content);
+ Assert.Contains("GammaCell", content);
+ }
+
+ [Fact]
+ public void Convert_WithSheetNames_OnlyRendersSpecifiedSheets()
+ {
+ using var excelStream = CreateMultiSheetExcel(
+ ("Sales", new[] { new[] { "SalesData" } }),
+ ("Costs", new[] { new[] { "CostsData" } }),
+ ("Summary", new[] { new[] { "SummaryData" } }));
+
+ var options = new ExcelToPdfConverter.ConversionOptions
+ {
+ SheetNames = new[] { "costs", "Summary" }, // case-insensitive
+ };
+
+ var doc = ExcelToPdfConverter.Convert(excelStream, options);
+ var content = Encoding.ASCII.GetString(doc.ToArray());
+
+ Assert.DoesNotContain("SalesData", content);
+ Assert.Contains("CostsData", content);
+ Assert.Contains("SummaryData", content);
+ }
+
+ [Fact]
+ public void Convert_SheetIndices_TakesPrecedenceOverSheetNames()
+ {
+ using var excelStream = CreateMultiSheetExcel(
+ ("First", new[] { new[] { "FirstCell" } }),
+ ("Second", new[] { new[] { "SecondCell" } }));
+
+ var options = new ExcelToPdfConverter.ConversionOptions
+ {
+ SheetIndices = new[] { 0 }, // First only
+ SheetNames = new[] { "Second" }, // would select Second, but ignored
+ };
+
+ var doc = ExcelToPdfConverter.Convert(excelStream, options);
+ var content = Encoding.ASCII.GetString(doc.ToArray());
+
+ Assert.Contains("FirstCell", content);
+ Assert.DoesNotContain("SecondCell", content);
+ }
+
+ [Fact]
+ public void Convert_NoMatchingSheets_ProducesAtLeastOnePage()
+ {
+ using var excelStream = CreateMultiSheetExcel(
+ ("Sheet1", new[] { new[] { "Data" } }));
+
+ var options = new ExcelToPdfConverter.ConversionOptions
+ {
+ SheetNames = new[] { "DoesNotExist" },
+ };
+
+ var doc = ExcelToPdfConverter.Convert(excelStream, options);
+ Assert.True(doc.Pages.Count >= 1);
+ }
+
///
/// Creates a minimal valid .xlsx file in memory with the given data.
///
@@ -405,4 +482,106 @@ int GetStringIndex(string value)
ms.Position = 0;
return ms;
}
+
+ ///
+ /// Creates a minimal .xlsx with multiple named sheets, each containing string rows.
+ ///
+ private static MemoryStream CreateMultiSheetExcel(params (string name, string[][] rows)[] sheets)
+ {
+ var ms = new MemoryStream();
+
+ using (var archive = new ZipArchive(ms, ZipArchiveMode.Create, leaveOpen: true))
+ {
+ // [Content_Types].xml
+ var ctSb = new StringBuilder();
+ ctSb.AppendLine("");
+ ctSb.AppendLine("");
+ ctSb.AppendLine(" ");
+ ctSb.AppendLine(" ");
+ ctSb.AppendLine(" ");
+ for (var i = 0; i < sheets.Length; i++)
+ ctSb.AppendLine($" ");
+ ctSb.AppendLine(" ");
+ ctSb.AppendLine("");
+ AddEntry(archive, "[Content_Types].xml", ctSb.ToString());
+
+ AddEntry(archive, "_rels/.rels",
+ """
+
+
+
+
+ """);
+
+ // xl/_rels/workbook.xml.rels
+ var relsSb = new StringBuilder();
+ relsSb.AppendLine("");
+ relsSb.AppendLine("");
+ for (var i = 0; i < sheets.Length; i++)
+ relsSb.AppendLine($" ");
+ relsSb.AppendLine($" ");
+ relsSb.AppendLine("");
+ AddEntry(archive, "xl/_rels/workbook.xml.rels", relsSb.ToString());
+
+ // xl/workbook.xml
+ var wbSb = new StringBuilder();
+ wbSb.AppendLine("");
+ wbSb.AppendLine("");
+ wbSb.AppendLine(" ");
+ for (var i = 0; i < sheets.Length; i++)
+ wbSb.AppendLine($" ");
+ wbSb.AppendLine(" ");
+ wbSb.AppendLine("");
+ AddEntry(archive, "xl/workbook.xml", wbSb.ToString());
+
+ // Shared strings (global across all sheets)
+ var sharedStrings = new List();
+ var sharedStringIndex = new Dictionary();
+
+ int GetStringIndex(string value)
+ {
+ if (!sharedStringIndex.TryGetValue(value, out var idx))
+ {
+ idx = sharedStrings.Count;
+ sharedStrings.Add(value);
+ sharedStringIndex[value] = idx;
+ }
+ return idx;
+ }
+
+ for (var i = 0; i < sheets.Length; i++)
+ {
+ var (_, rows) = sheets[i];
+ var sheetSb = new StringBuilder();
+ sheetSb.AppendLine("");
+ sheetSb.AppendLine("");
+ sheetSb.AppendLine("");
+ for (var r = 0; r < rows.Length; r++)
+ {
+ sheetSb.AppendLine($" ");
+ for (var c = 0; c < rows[r].Length; c++)
+ {
+ var colLetter = (char)('A' + c);
+ var idx = GetStringIndex(rows[r][c]);
+ sheetSb.AppendLine($" {idx}");
+ }
+ sheetSb.AppendLine("
");
+ }
+ sheetSb.AppendLine("");
+ sheetSb.AppendLine("");
+ AddEntry(archive, $"xl/worksheets/sheet{i + 1}.xml", sheetSb.ToString());
+ }
+
+ var ssSb = new StringBuilder();
+ ssSb.AppendLine("");
+ ssSb.AppendLine($"");
+ foreach (var s in sharedStrings)
+ ssSb.AppendLine($" {EscapeXml(s)}");
+ ssSb.AppendLine("");
+ AddEntry(archive, "xl/sharedStrings.xml", ssSb.ToString());
+ }
+
+ ms.Position = 0;
+ return ms;
+ }
}