.NET core docx
This is an extension methods set for OpenXML SDK.
This mean that there aren't wrapping classes around document, paragraph, run, style, etc... but extension methods that cohexists within OpenXML SDK.
This allow you to use any OpenXML SDK infrastructure in addition to provided simplified extensions methods without these interfere with a custom approach.
example shows basic usage through sampledoc
You can open an existing doc or create an empty docx eventually as a copy of an existing prestyled template document:
using DocumentFormat.OpenXml.Wordprocessing;
using DColor = System.Drawing.Color;
using SearchAThing.DocX;
using SearchAThing;
namespace examples;
class Program
{
static void Main(string[] args)
{
var outputPathfilename = System.IO.Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "out.docx");
// note: initialize new empty document into given pathfilename saving for first time
using (var doc = DocXToolkit.Create(outputPathfilename))
{
doc.AddParagraph("Sample paragraph");
// note: remember to call doc.Finalize() to release associated dynamic resources
doc.Finalize();
}
var psi = new ProcessStartInfo(outputPathfilename);
psi.UseShellExecute = true;
Process.Start(psi);
}
}
- is highly suggested to call
Finalize()
method at the end of document usage to release resources allocated per document ( style libraries, state variables, ... ). DColor
is an alias forSystem.Drawing.Color
doc
.SetDocDefaults(runFontName: "Arial");
doc
.MainSectionProperties().SetPageSize(PaperSize.A4, PageOrientationValues.Portrait);
var titleStyle = doc.GetPredefinedStyle(LibraryStyleEnum.Title);
var subTitleStyle = doc.GetPredefinedStyle(LibraryStyleEnum.Subtitle);
var heading1Style = doc.GetPredefinedStyle(LibraryStyleEnum.Heading1);
var heading2Style = doc.GetPredefinedStyle(LibraryStyleEnum.Heading2);
var tableStyle = doc.GetPredefinedStyle(LibraryStyleEnum.Table);
var figureStyle = doc.GetPredefinedStyle(LibraryStyleEnum.Figure);
SetDocDefaults()
change default font, size, spacing, indentation, justificationMainSectionProperties()
allow access to base Section Properties; these will be used as backing store for actual section properties until you introduce a new oneGetPredefinedStyle()
retrieve one of predefined style ( see LibraryStyleEnum ) ; these styles came from a library that is instantiated foreach document through per document resources object ( because of specific number id, and other properties ).
doc
.AddParagraph("netcore-docx", titleStyle);
doc
.AddParagraph("Demo document", subTitleStyle);
- AddParagraph allow to insert a text and specify style to use for paragraph
note: TOC field updated from wordprocessor at first open ( not by the api )
doc
.AddParagraph()
.SectionProperties() // here a new section property will instantiated
.SetHeader(header => header.AddParagraph("netcore-docx - Demo document"))
.SetFooter(footer => footer
.AddParagraph("Page ")
.AddField(FieldEnum.PAGE)
.AddText(" of ")
.AddField(FieldEnum.NUMPAGES)
.SetJustification(JustificationValues.Center)
);
doc
.AddToc("Table of Contents")
.AddParagraph();
doc
.AddBreak();
AddToc()
insert a predefined sdt block ( seeGenerateSdtBlock()
) and triggerUpdateFieldsOnOpen
because the TOC couldn't created at this model level without having exact page number position of elements that depends on document renderer program.
doc
.AddParagraph("Font family, size, color, paragraph properties", heading1Style)
.EnableAutoNumbering()
.AddParagraph("some normal paragraph")
.AddParagraph("line with space before 5mm").SetSpacingBetweenLines(beforeMM: 5)
.AddParagraph("line with space after 5mm").SetSpacingBetweenLines(afterMM: 5)
.AddParagraph("bold mode").SetBold()
.AddParagraph("italic mode").SetItalic()
.AddParagraph("underline mode").SetUnderline()
.AddParagraph("some color ")
.AddRun("red", action: run => run.SetColor(DColor.Red))
.AddRun(" green", action: run => run.SetColor(DColor.Green))
.AddRun(" blue", action: run => run.SetColor(DColor.Blue))
.AddParagraph("paragraph shading")
.IncAutoNumbering()
.AddParagraph("orange, clear").SetShading(DColor.Orange)
.DecAutoNumbering()
.AddParagraph("run shading")
.IncAutoNumbering()
.AddParagraph("clear ")
.AddRun("red", action: run => run.SetShading(DColor.Red, ShadingPatternValues.Clear))
.AddSpace().AddRun("green", action: run => run.SetShading(DColor.Green, ShadingPatternValues.Clear))
.AddSpace().AddRun("blue", action: run => run.SetShading(DColor.Blue, ShadingPatternValues.Clear))
.DecAutoNumbering()
.DisableAutoNumbering();
doc
.AddBreak();
EnableAutoNumbering()
is an helper to auto set numbering mode on any paragraph inserted after this command succeed; this command flag a value into per document resources object allowing DocX methods extensions to know thatSetNumbering()
has to be applied silently untilDisableAutoNumbering()
clear this behavior; during auto numberingIncAutoNumbering()
andDecAutoNumbering()
allow to increase/decrease numbering level (indentation). If don't want to use auto numbering you can set per paragraph numbering usingSetNumbering()
.SetShading()
can applied with different behavior to a single Run or to an entire Paragraph.AddBreak()
adds a manual page break ( if want to change section use.SectionProperties()
instead )
doc
.AddParagraph("Custom paragraph properties", heading1Style);
{
var myStyle1 = doc.AddParagraphSyle("myStyle1",
runFontName: "Times New Roman",
runFontColor: DColor.Blue,
runFontSizePt: 14,
spacingBetweenLinesOpts: new SpacingBetweenLinesOptions { AfterMM = 5 },
indentationOpts: new IndentationOptions { },
justification: JustificationValues.Left);
doc.AddParagraph("paragraph with myStyle1", myStyle1);
var myStyle2 = doc.AddParagraphSyle("myStyle2",
runFontColor: DColor.Red,
basedOn: myStyle1);
doc.AddParagraph("paragraph with myStyle2 based on myStyle1", myStyle2);
}
- with
AddParagraphStyle()
you can add a new paragraph style from scratch ; if you can starts from a template docx may you find easily to prepare styles needed instead of creating them programmatically.
doc
.AddParagraph("Find replace", heading1Style);
Paragraph? pref = null;
doc
.AddParagraph("This ")
.AddRun("wor", run => run.SetColor(DColor.Red))
.AddRun("ld", run => run.SetColor(DColor.Green))
.AddRun("s", run => run.SetColor(DColor.Blue))
.AddRun(" text")
.Act(p => pref = p)
.AddParagraph($"Is composed by {pref!.GetRuns().Count()} runs")
.AddParagraph("where the word \"worlds\" is composed by 3 runs");
var search = doc.FindText("worlds");
doc
.AddParagraph(
$"Searching the word \"worlds\" in previous text results in {search.Count} occurrences:");
foreach (var match in search)
{
doc
.AddParagraph($"paragraph: [")
.AddRuns(match.Paragraph.GetRuns().Select(r => (Run)r.Clone()))
.AddRun($"] contains {match.Runs.Count} matching runs:")
.SetNumbering(0, NumberFormatValues.Decimal);
foreach (var matchingrun in match.Runs)
{
doc.AddParagraph()
.AddRun(((Run)matchingrun.Clone()).Act(run =>
run.SetShading(DColor.Yellow)))
.SetNumbering(1, NumberFormatValues.Bullet);
}
}
doc
.AddBreak();
FindText()
actually works by searching text occurrence in all the document and returns a list of paragraphs that contains occurrence.FindAndReplace()
like FindText() works over all document contents by searching text and replacing with text substitution specified; specifically it can replace existing text runs by maintaining their run properties until replaced text length not exceeded the existant one and using last run properties for remaining addictional text; an optionSplitAndCreateSingleRun
allow to replace matching multiple run into single run by trimming and stitching in order to ensure that replaced text will exists in a single run ( could used to uniform as a single run some template document keyword edited by hand causing multiple run across a single keyword to be used subsequently for an automated document creation )
doc
.AddParagraph("Images", heading1Style)
.AddParagraph("Image with original size", heading2Style)
.AddImage(img01pathfilename)
.AddParagraph()
.AddParagraph("Image keep aspect Width = 50mm", heading2Style)
.AddImage(img01pathfilename, widthMM: 50)
.AddParagraph()
.AddParagraph("Image keep aspect Height = 50mm", heading2Style)
.AddImage(img01pathfilename, heightMM: 50)
.AddParagraph()
.AddParagraph("Image unconstrained width:50mm height:50mm", heading2Style)
.AddImage(img01pathfilename, widthMM: 50, heightMM: 50)
.AddParagraph()
.AddTable()
.AddColumns(3, 1)
.AddRow(row =>
{
row.GetCell(0)
.SetParagraph("Left")
.AddImage(img01pathfilename, widthMM: 30)
.SetJustification(JustificationValues.Left);
row.GetCell(1)
.SetParagraph("Center")
.AddImage(img01pathfilename, widthMM: 30)
.SetJustification(JustificationValues.Center);
row.GetCell(2)
.SetParagraph("Right")
.AddImage(img01pathfilename, widthMM: 30)
.SetJustification(JustificationValues.Right);
var cellsCount = row.Elements<TableCell>().Count();
for (int i = 0; i < cellsCount; ++i) row.GetCell(i).SetPadding(2);
})
.SetBorderAll()
.AddBreak();
AddImage()
adds an inline image into a paragraph or a run ; if not specified the type if auto states type from file extension ;dpi
if present are used to state original dimension and aspect ratio ; can be inserted without specify width, height to use original dimension or constraining one of them keeping aspect ratio or constraining all of them to force stretched version.
doc
.AddParagraph("Table with Field types", heading1Style);
var tbl = doc
.AddTable(tableWidthPercent: 50)
.AddColumn(1).AddColumn(1);
tbl.AddRow(row =>
{
row.GetCell(0)
.SetShading(DColor.Navy)
.SetParagraph("FIELD NAME", run => run.SetBold().SetColor(DColor.White))
.SetUniformMargin(2);
row.GetCell(1)
.SetShading(DColor.Navy)
.SetParagraph("SAMPLE", run => run.SetBold().SetColor(DColor.White))
.SetUniformMargin(2);
});
foreach (var t in Enum.GetValues<FieldEnum>())
{
tbl.AddRow(row =>
{
row.GetCell(0).SetParagraph(t.ToString(), run => run.SetBold()).SetUniformMargin(2);
row.GetCell(1).GetFirstChild<Paragraph>()!.AddField(t).SetUniformMargin(2);
});
}
// short version
//tbl.SetBordersOutside(BorderValues.Single);
// custom version
tbl.SetBorders((args) =>
{
if (args.colIdx == 0)
args.leftBorder = new LeftBorder() { Val = BorderValues.Single };
if (args.rowIdx == 0)
args.topBorder = new TopBorder() { Val = BorderValues.Single };
if (args.colIdx == args.colCount - 1)
args.rightBorder = new RightBorder() { Val = BorderValues.Single };
if (args.rowIdx == args.rowCount - 1)
args.bottomBorder = new BottomBorder() { Val = BorderValues.Single };
});
doc
.AddBreak();
- paragraph
AddTable()
adds a table with specified % width or if null auto computed from columns - table
AddColumn()
adds column to the table specifying an exact MM measure if table work with fixed width ( tableWidthPercent: null ); instead of table uses a % width the column widthMM will normalized across all columns to reach table width extents. - table
AddRow()
adds an empty row already filled with enough cells to cover actual table columns - row
GetCell()
retrieve Table Cell from which you may use.SetParagraph()
in place of.AddParagraph()
because empty cells prepared with an empty paragraph.- an helper row
SetUniformMargin()
allow to set margin around cell contents
- an helper row
- table
SetBordersOutside()
sets outer table cells border to Single or specified Border type. AlternativelySetBordersAll()
orSetBorders()
can be used to set all cells outer/inner or custom border.
doc
.AddParagraph("Numbering", heading1Style)
.AddParagraph("Bullet list", heading2Style)
.AddParagraph("sample1").SetNumbering(0)
.AddParagraph("sample2").SetNumbering(1)
.AddParagraph("sample2").SetNumbering(0)
.AddParagraph("Decimal list", heading2Style)
.AddParagraph("sample1").SetNumbering(0, NumberFormatValues.Decimal, restartNumbering: true)
.AddParagraph("sample2x").SetNumbering(1, NumberFormatValues.Decimal)
.AddParagraph("sample2").SetNumbering(0, NumberFormatValues.Decimal)
.AddParagraph("Structured Decimal list", heading2Style)
.AddParagraph("sample1").SetNumbering(0, NumberFormatValues.Decimal, structured: true)
.AddParagraph("sample2").SetNumbering(1, NumberFormatValues.Decimal, structured: true)
.AddParagraph("sample2").SetNumbering(0, NumberFormatValues.Decimal, structured: true);
doc
.AddBreak();
SetNumbering(level, [format:bullet], [structured:false])
allow to specify that a paragraph is marked in its property to be a numbering ;level
specify indentation level 0...9 ;format
specify the type of numering ( actually library supports None, Bullet, Decimal ) ;structured: true
used in conjunction with Decimal format allow to enumerate inside levels with recall of parent levels.
- double check document validate errors
- useful tools : OpenXML SDK Tools 2.5
dotnet test
mkdir netcore-docx
cd netcore-docx
dotnet new sln
dotnet new classlib -n netcore-docx
cd netcore-docx
dotnet add package DocumentFormat.OpenXml --version 2.8.1
cd ..
dotnet sln netcore-docx.sln add netcore-docx/netcore-docx.csproj
dotnet restore
dotnet build