OfficeIMO.Markdown
0.5.2
Prefix Reserved
dotnet add package OfficeIMO.Markdown --version 0.5.2
NuGet\Install-Package OfficeIMO.Markdown -Version 0.5.2
<PackageReference Include="OfficeIMO.Markdown" Version="0.5.2" />
<PackageVersion Include="OfficeIMO.Markdown" Version="0.5.2" />
<PackageReference Include="OfficeIMO.Markdown" />
paket add OfficeIMO.Markdown --version 0.5.2
#r "nuget: OfficeIMO.Markdown, 0.5.2"
#:package OfficeIMO.Markdown@0.5.2
#addin nuget:?package=OfficeIMO.Markdown&version=0.5.2
#tool nuget:?package=OfficeIMO.Markdown&version=0.5.2
OfficeIMO.Markdown
Fluent and object‑model Markdown builder for .NET with CommonMark/GFM‑style output. Zero runtime dependencies, rich table/list helpers, HTML export (fragment or full document) with built‑in styles and Prism highlighting.
Why OfficeIMO.Markdown
- Pure .NET, cross‑platform — no native renderers required
- Fluent API or explicit object model — compose documents predictably
- CommonMark/GFM‑style output that renders well on GitHub, wikis, and docs sites
- HTML export with clean themes (Clean, GitHub Light/Dark/Auto), CDN/offline assets, and Prism highlighting
- Designed for reporting — tables from sequences/objects, callouts, TOC, and front matter
Design & Expectations
- Deterministic output — stable ordering and formatting make diffs/snapshots easy
- Extensible blocks — add custom blocks with small helpers instead of string concatenation
- Good defaults — header transforms (Pretty + acronyms), numeric/date alignment heuristics
- Performance — string builders and pooled buffers; no exceptions in hot loops (e.g., header lookups)
Badges
Highlights
- No external dependencies for Markdown/HTML generation.
- Fluent API and explicit object model.
- Core blocks: headings, paragraphs, links, images (with captions), lists (UL/OL/task/definition), tables, code blocks, callouts, front matter (YAML).
- Tables from objects/sequences: include/exclude/order/rename/formatters, alignment presets, date/number heuristics.
- Table of Contents: at top, here, or scoped to sections; GitHub‑style anchors.
- Table of Contents: at top, here, or scoped to sections; GitHub‑style anchors; HTML layouts (panel/sidebar) with optional ScrollSpy.
- HTML: fragment or full document; styles (Clean, GitHub Light/Dark/Auto, Word), CSS delivery (inline/link/external file), Online/Offline asset modes.
- Prism highlighting: CDN link or offline inline; manifest for safe dedupe across fragments.
- Reader (experimental): parse Markdown back into the typed object model you can traverse.
AOT / Trimming notes
FromAny(...)uses reflection for arbitrary runtime types (handy for PowerShell/Expando/etc.).- For NativeAOT/trimming, prefer typed overloads or explicit selectors.
var doc = MarkdownDoc.Create();
// AOT-safe: typed sequence
doc.Table(t => t.FromSequenceAuto(people));
// AOT-safe: typed object -> front matter
public sealed class BuildInfo { public string Version { get; set; } = "1.0"; }
doc.FrontMatter(FrontMatterBlock.FromObject(new BuildInfo()));
Supported Markdown
Blocks
- Headings: ATX
#..######with space; levels 1–6 - Paragraphs and hard breaks: two spaces or explicit
\nfrom composed content - Fenced code blocks: triple backticks with optional language; optional
_caption_line below - Images:
with optional{width=.. height=..}hints - Lists: unordered
-/*/+, ordered1.(single level); task items- [ ]/- [x] - Tables: GitHub pipe tables with per‑column alignment markers (
:---,:---:,---:) - Block quotes:
>lines (single level) - Callouts:
> [!info] Titlelines (Docs‑style), followed by body paragraphs - Horizontal rule:
--- - Footnotes: references
[^id]and definitions[^id]:with continuation lines - Front matter: top‑of‑file YAML between
---fences
Inlines
- Text, bold
**..**, italic*..*, bold+italic***..*** - Strikethrough
~~..~~ - Underline via
<u>text</u> - Code spans: backtick‑delimited; supports multi‑backtick fences when content contains backticks
- Links: inline
[text](url "title"), autolinks, and reference‑style[text][label]with separate definitions - Images:
and linked images[](href)
Writer guarantees
- Deterministic formatting and spacing for stable diffs
- GitHub‑friendly output (tables, footnotes, task lists)
Install
dotnet add package OfficeIMO.Markdown
Example
using OfficeIMO.Markdown;
var md = MarkdownDoc
.Create()
.FrontMatter(new { title = "DomainDetective", tags = new[] { "dns", "email", "security" } })
.H1("DomainDetective")
.P("All-in-one DNS, email, and TLS analyzer with rich reports.")
.Callout("info", "Early access", "APIs may change before 1.0.")
.H2("Install")
.Code("bash", "dotnet tool install -g DomainDetective")
.H2("Quick start")
.Code("powershell",
"Test-DDMailDomainClassification -DomainName 'evotec.pl','evotec.xyz' -ExportFormat Word")
.H2("Features")
.Ul(ul => ul
.Item("SPF/DKIM/DMARC scoring")
.Item("TLS/SSL tests and cipher hints")
.Item("WHOIS, MX, PTR, DNSSEC, BIMI")
.Item("Exports: Word, HTML, PDF, Markdown"))
.H2("Links")
.Ul(ul => ul
.ItemLink("Docs", "https://evotec.xyz/hub/")
.ItemLink("Issues", "https://github.com/EvotecIT/DomainDetective/issues"));
var markdown = md.ToMarkdown();
var htmlFrag = md.ToHtmlFragment();
var htmlDoc = md.ToHtmlDocument();
Status: early preview. API may evolve before 1.0.
Advanced usage
using OfficeIMO.Markdown;
using System.Globalization;
var people = new[] {
new { Name = "Alice", Role = "Dev", Score = 98.5, Joined = "2024-01-10" },
new { Name = "Bob", Role = "Ops", Score = 91.0, Joined = "2023-08-22" }
};
// 1) Control header casing + acronyms (user-provided)
var headerTx = HeaderTransforms.PrettyWithAcronyms(new[] { "ID", "DMARC", "SPF" });
MarkdownDoc.Create()
.H2("FromAny with header transform")
.Table(t => t.Columns(headerTx).FromAny(new { DmarcPolicy = "p=none", SpfAligned = true }))
// 2) FromSequence with explicit columns + numeric/date alignment
.H2("FromSequence with selectors")
.Table(t => t.FromSequence(people,
("Name", x => x.Name),
("Role", x => x.Role),
("Score", x => x.Score),
("Joined", x => x.Joined))
.AlignNumericRight() // right-align numeric columns
.AlignDatesCenter()) // center-align date-like columns
// 3) TOC at top + scoped TOC inside a section
.H2("Usage")
.H3("Tables")
.H3("Lists")
.TocAtTop("Contents", min: 2, max: 3)
.H2("Appendix").H3("Extra")
.TocForPreviousHeading("Appendix Contents", min: 3, max: 3);
// HTML rendering
var htmlFragment = md.ToHtmlFragment(new HtmlOptions { Style = HtmlStyle.GithubAuto });
var htmlDocument = md.ToHtmlDocument(new HtmlOptions { Title = "Report", Style = HtmlStyle.Clean, CssDelivery = CssDelivery.Inline });
md.SaveHtml("Report.html", new HtmlOptions { Style = HtmlStyle.Clean, CssDelivery = CssDelivery.ExternalFile }); // writes Report.html + Report.css
// CDN link online vs offline (download and inline)
var cdn = "https://cdn.jsdelivr.net/npm/github-markdown-css@5.5.1/github-markdown.min.css";
var htmlCdnOnline = md.ToHtmlDocument(new HtmlOptions { CssDelivery = CssDelivery.LinkHref, CssHref = cdn, AssetMode = AssetMode.Online, BodyClass = "markdown-body" });
var htmlCdnOffline = md.ToHtmlDocument(new HtmlOptions { CssDelivery = CssDelivery.LinkHref, CssHref = cdn, AssetMode = AssetMode.Offline, BodyClass = "markdown-body" });
Reader (experimental)
// Parse Markdown back into typed blocks/inlines
var doc = MarkdownReader.Parse(File.ReadAllText("README.md"));
// Inspect blocks
foreach (var h2 in doc.Blocks.OfType<HeadingBlock>().Where(h => h.Level == 2)) {
Console.WriteLine($"Section: {h2.Text}");
}
// Feature toggles align with OfficeIMO blocks/inlines
var parsed = MarkdownReader.Parse(markdown, new MarkdownReaderOptions { Tables = true, Callouts = true });
// TOC placeholders in Markdown are recognized and rendered:
// [TOC] or [[TOC]] or {:toc} or
// Parameterized form: [TOC min=2 max=3 layout=sidebar-right sticky=true scrollspy=true title="On this page"]
Header transforms and acronyms
// Provide your own acronyms to uppercase in headers
var tx = HeaderTransforms.PrettyWithAcronyms(new[] { "ID", "DMARC", "SPF" });
MarkdownDoc.Create().Table(t => t.Columns(tx).FromAny(new { DmarcPolicy = "p=none", SpfAligned = true, Id = 25 }));
// Or inline via options
MarkdownDoc.Create().Table(t => t.FromAny(new { DmarcPolicy = "p=none" }, o => o.HeaderTransform = HeaderTransforms.PrettyWithAcronyms(new[] { "DMARC" })));
TOC helpers
var md = MarkdownDoc.Create()
.H1("Report")
.H2("Intro").P("...")
.H2("Usage").H3("Tables").H3("Lists")
.TocAtTop("Contents", min: 2, max: 3) // top-level TOC (plain list)
.H2("Appendix").H3("Extra")
.TocHere(o => { o.MinLevel = 3; o.MaxLevel = 3; }) // TOC at current position
.TocForSection("Usage", "Usage Contents", min: 3, max: 3); // scoped to a named section
// TOC HTML styling (new)
MarkdownDoc.Create()
.H1("Guide").H2("Install").H2("Usage").H2("FAQ")
// 1) Compact panel card with title
.TocAtTop("Contents", min: 2, max: 3)
.TocHere(o => {
o.MinLevel = 2; o.MaxLevel = 3;
o.IncludeTitle = true; o.Title = "On this page"; o.Layout = TocLayout.Panel; o.Collapsible = false;
})
// 2) Right sidebar, sticky + ScrollSpy (highlights current section)
.TocAtTop("On this page", min: 2, max: 3)
.TocHere(o => {
o.MinLevel = 2; o.MaxLevel = 3;
o.Layout = TocLayout.SidebarRight; o.Sticky = true; o.ScrollSpy = true; o.IncludeTitle = true; o.Title = "On this page";
});
Column Options (FromAny)
| Option | Type | Purpose |
|---|---|---|
| Include | HashSet<string> | Only include these properties |
| Exclude | HashSet<string> | Exclude these properties |
| Order | List<string> | Order of headers (others follow) |
| HeaderRenames | Dictionary<string,string> | Map property name → header text |
| HeaderTransform | Func<string,string> | Transform header when no rename specified |
| Formatters | Dictionary<string, Func<object?,string>> | Per‑property cell formatting |
| Alignments | List<ColumnAlignment> | Per‑column alignment for header/cells |
Heuristics
- AlignNumericRight(threshold=0.8): right‑align columns that look numeric.
- AlignDatesCenter(threshold=0.6): center‑align columns that look like dates.
Doc‑level helpers
- TableAuto(build, alignNumeric=true, alignDates=true)
- TableFromAuto(data, configure?, alignNumeric=true, alignDates=true)
HTML options
- Kind: Fragment | Document
- Style: Plain | Clean | GithubLight | GithubDark | GithubAuto | Word
- CssDelivery: Inline | ExternalFile | LinkHref | None
- AssetMode: Online (link) | Offline (download+inline)
- Title, BodyClass (default "markdown-body"), IncludeAnchorLinks, ThemeToggle
- EmitMode: Emit (default) | ManifestOnly for host-side asset merging
- Prism: Enabled, Theme (Prism/Okaidia/GithubDark/GithubAuto), Languages, Plugins, CdnBase TOC HTML options (via TocOptions in TocAtTop/TocHere/TocFor*)
- Layout: List (default, plain nested list), Panel (card), SidebarRight/SidebarLeft
- Collapsible: wrap in <details>; Collapsed: default state
- ScrollSpy: highlight active heading while scrolling; Sticky: keep TOC visible (position: sticky)
Word style
- HtmlStyle.Word gives a document‑like look (Calibri/Cambria headings), Wordish tables (header shading, banded rows, borders), and comfortable spacing.
- Table cells support inline markdown (code, links, emphasis, images) and
<br>tags become line breaks.
Embedding fragments with CSS
// Get a self‑contained fragment with CSS and tiny scripts inlined
var frag = md.ToHtmlFragmentWithCss(new HtmlOptions { Style = HtmlStyle.Word });
De‑duping assets
- ToHtmlParts returns Assets: a list of { Id, Kind (Css/Js), Href or Inline }.
- Tags we emit include data-asset-id for easy deduplication if you concatenate HTML.
- Set EmitMode = ManifestOnly to suppress emitting <link>/<script> tags and merge Assets yourself.
Targets
- netstandard2.0 (library)
- net8.0, net9.0
License
MIT — see LICENSE.
Contributing
Issues and PRs welcome. Please keep one‑class/enum per file, avoid external runtime deps, and add targeted tests for new features.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 is compatible. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETFramework 4.7.2
- No dependencies.
-
.NETStandard 2.0
- No dependencies.
-
net8.0
- No dependencies.
-
net9.0
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on OfficeIMO.Markdown:
| Package | Downloads |
|---|---|
|
PowerForge.Blazor
Blazor components for displaying API documentation. Supports C# XML docs, PowerShell help, and custom markdown pages. Part of the PowerForge ecosystem. |
GitHub repositories
This package is not used by any popular GitHub repositories.