Recursive AI

Getting started with OneDocument

All onedocument docs

Install OneDocument 1.0.0 for .NET 10, read a Word document, convert it to PDF, read and recalculate an Excel workbook, and use the onedocument command-line tool. Every sample on this page was run on the 1.0.0 packages from NuGet and shows its real output. The program makes its own input files, so you need nothing on disk.

OneDocument was called OneOffice before its first release. It is not affiliated with Microsoft; Word, Excel and PowerPoint name the file formats.

Install

The packages target net10.0, so you need the .NET 10 SDK.

Terminal
dotnet new console -o OneDocumentStart
cd OneDocumentStart
dotnet add package RecursiveAI.OneDocument.Documents --version 1.0.0
dotnet add package RecursiveAI.OneDocument.Spreadsheets --version 1.0.0
dotnet add package RecursiveAI.OneDocument.Rendering --version 1.0.0

Eight packages are on NuGet at 1.0.0, all on one version. Reference only the kinds you use; each one registers its formats at build time, with no setup call.

  • RecursiveAI.OneDocument: the core. Containers, the markup tree, the format registry, formulas and number formats, templates, text extraction, RTF and VBA. The others bring it in.
  • RecursiveAI.OneDocument.Documents: Word-processing documents: DOCX, DOC, RTF, ODT, 2003 XML, HTML and Markdown, tracked changes, mail merge, operations.
  • RecursiveAI.OneDocument.Spreadsheets: workbooks: XLSX, XLSB, XLS, ODS, CSV and TSV, 2003 XML, calculation, pivots, streaming, typed rows.
  • RecursiveAI.OneDocument.Presentations: PPTX, PPT and ODP.
  • RecursiveAI.OneDocument.Schema: typed views over the OOXML schemas.
  • RecursiveAI.OneDocument.Rendering: layout, charts, SmartArt and equations; PDF and page images (PNG, JPEG, TIFF, WebP, SVG).
  • RecursiveAI.OneDocument.All: every package above.
  • RecursiveAI.OneDocument.Tool: the onedocument command-line tool.

None of them uses native code. A ninth, RecursiveAI.OneDocument.Cryptography (signatures and encryption on write), isn’t on NuGet: in the changelog’s words, “it follows in a 1.x release.”

Make two files to work with

The samples below are one Program.cs, run top to bottom. This first part writes a short report and a small budget, so the rest has real files to open. A new document starts with one empty paragraph, as Word’s blank document does.

C#
using RecursiveAI.OneDocument;
using RecursiveAI.OneDocument.Documents;
using RecursiveAI.OneDocument.Spreadsheets;
using RecursiveAI.OneDocument.Spreadsheets.Binding;
 
using (Document made = Document.Create())
{
    made.Styles.AddBuiltIn("Title");
    made.Styles.AddBuiltIn("Heading 1");
    made.Body.AddParagraph("Site visit report").Style = "Title";
    made.Body.AddParagraph("Findings").Style = "Heading 1";
    made.Body.AddParagraph("The roof drains are clear. The north stairwell light is out.");
    made.Body.AddParagraph("Next steps").Style = "Heading 1";
    made.Body.AddParagraph("Replace the stairwell light by Friday.");
    made.SaveAs("report.docx");
}
 
using (Document made = Document.Create())
{
    Paragraph body = made.Body.AddParagraph("The supplier delivers the goods.");
    made.Revisions.CreateAuthor("Dana Reviewer", null).InsertText(body, 0, "Within 30 days, ");
    made.SaveAs("draft.docx");      // a tracked change, for the command-line section
}
 
using (Workbook made = Workbook.Create())
{
    Worksheet sheet = made.Sheets[0];
    sheet.Rename("Budget");
    sheet.Range("A1:B1").Values = new object?[,] { { "Item", "Cost" } };
    sheet.Range("A2:B4").Values = new object?[,] { { "Ladder", 189.5 }, { "Bulbs", 42 }, { "Labor", 360 } };
    sheet["D1"].SetString("Total");
    sheet["D2"].Formula = "SUM(B2:B4)";
    sheet.Range("B2:B4").NumberFormat = "$#,##0.00";
    sheet.Range("D2:D2").NumberFormat = "$#,##0.00";
    made.Recalculate();             // stores the formula’s result in the file, as Excel does
    made.SaveAs("budget.xlsx");
}

Read a .docx

Document.Open finds the format from the content, never the extension. Paragraphs carry their text and style name; Office.ExtractText gives the text of any supported file in one call.

C#
using (Document doc = Document.Open("report.docx"))
{
    foreach (Paragraph p in doc.Paragraphs)
        Console.WriteLine($"[{p.Style}] {p.Text}");
}
 
string text = Office.ExtractText("report.docx");
Console.WriteLine($"{text.Length} characters: {text.Trim().Split('\n')[0]} ...");
Text
[Normal] 
[Title] Site visit report
[Heading 1] Findings
[Normal] The roof drains are clear. The north stairwell light is out.
[Heading 1] Next steps
[Normal] Replace the stairwell light by Friday.
139 characters: Site visit report ...

To change a document in place, edit and call doc.Save(): parts you didn’t touch are written back byte-identical. On this report, setting the text of the title paragraph and saving changed word/document.xml and nothing else. See authoring and tracked changes.

Convert a .docx to PDF

One line, with the Rendering package referenced. The PDF is tagged.

C#
Office.Convert("report.docx", "report.pdf");
byte[] pdf = File.ReadAllBytes("report.pdf");
Console.WriteLine($"report.pdf: {pdf.Length:N0} bytes, starts {System.Text.Encoding.ASCII.GetString(pdf, 0, 8)}");
Text
report.pdf: 14,544 bytes, starts %PDF-1.7

The same call converts a workbook or a presentation, and picks the output by extension: .png, .jpg, .tif, .webp and .svg write page images, and .odt, .rtf, .html or .md convert the document. By default the layout uses the open fonts that come with OneRender (Liberation, Carlito and Caladea), with a metric-compatible stand-in where the document names a font it doesn’t have: Times New Roman becomes Liberation Serif, Calibri becomes Carlito. Your own fonts can be added. See layout and PDF and page images and SVG.

Read an .xlsx

Cells give their value, their formula and the text Excel would display. Recalculate runs the calculation engine after an edit.

C#
using (Workbook wb = Workbook.Open("budget.xlsx"))
{
    Worksheet sheet = wb.Sheets["Budget"];
    for (int row = 2; row <= 4; row++)
        Console.WriteLine($"{sheet[$"A{row}"].Text,-7} {sheet[$"B{row}"].FormattedText,9}");
    Cell total = sheet["D2"];
    Console.WriteLine($"Total   {total.FormattedText,9}  ={total.Formula}");
    sheet["B3"].Value = 58;
    wb.Recalculate();
    Console.WriteLine($"Total   {total.FormattedText,9}  after B3 = 58");
}
Text
Ladder    $189.50
Bulbs      $42.00
Labor     $360.00
Total     $591.50  =SUM(B2:B4)
Total     $607.50  after B3 = 58

For rows of data, Spreadsheet.Read<T> binds each row to a record by its header names. A source generator writes the binding code, so mark the type partial:

C#
foreach (Expense e in Spreadsheet.Read<Expense>("budget.xlsx"))
    Console.WriteLine(e);
 
[SpreadsheetRow(Sheet = "Budget")]
internal sealed partial record Expense(string Item, decimal Cost);
Text
Expense { Item = Ladder, Cost = 189.5 }
Expense { Item = Bulbs, Cost = 42 }
Expense { Item = Labor, Cost = 360 }

Spreadsheet.Write(path, rows) is the other direction. Formula text is en-US only; in the README’s words, “localized function names are not supported.” See the workbook model, rows of plain objects and recalculation.

The onedocument tool

Terminal
dotnet tool install --global RecursiveAI.OneDocument.Tool --version 1.0.0
onedocument --help

Run in the folder the program wrote to:

Terminal
onedocument detect budget.xlsx
onedocument extract report.docx
onedocument convert report.docx report.odt
onedocument render report.docx -o report.png --dpi 96
onedocument recalc budget.xlsx -o budget-recalc.xlsx
Text
Xlsx
 
Site visit report
Findings
The roof drains are clear. The north stairwell light is out.
Next steps
Replace the stairwell light by Friday.
wrote report.odt
warning font-substitution: Font "Times New Roman" is not available; "Liberation Serif" has the same glyph widths and was used instead.
1 of 1 page(s) exported; wrote report.png
1 of 1 formulas calculated in 1 levels; 0 circular, 0 #SPILL!

The warning goes to standard error. render also writes PDF, with --profile ua or --profile a2a for PDF/UA-1 or PDF/A-2a, and takes --pages for a range.

inspect lists what a file holds that a reader doesn’t see, and sanitize writes a copy without it. Here, the tracked change in draft.docx:

Terminal
onedocument inspect draft.docx
onedocument sanitize draft.docx clean.docx
onedocument extract clean.docx
Text
Document (Docx)
revisions: 1
  /word/document.xml: tracked changes [authors: Dana Reviewer]
Docx -> Docx
revisions: Removed, 1 removed, 1 before, 0 after
  removed /word/document.xml: tracked changes accepted
 
Within 30 days, The supplier delivers the goods.

Sanitizing re-inspects what it wrote before it saves. It hasn’t been independently audited; the coverage review in the docs is the project’s own, and some categories are reported but not removed. See inspect and sanitize for every category and what is kept.

Next

  • Overview: every format, every check with its source, and the gaps.
  • Coming from another library: the Open XML SDK, ClosedXML, EPPlus, NPOI, MiniExcel, ShapeCrawler and Xceed DocX, call by call.
  • Templates: one template language for Word, Excel and PowerPoint files.
  • Redaction: removing text from the file, not only from view, with the output re-read to verify.