Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Workitems v2.0.0 #9

Merged
merged 14 commits into from
Feb 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ dotnet_diagnostic.IDE0045.severity = suggestion
# IDE0301: Simplify collection initialization
dotnet_diagnostic.IDE0301.severity = suggestion

# IDE0305: Simplify collection initialization
dotnet_diagnostic.IDE0305.severity = suggestion

[{*Test}/**.cs]
#Inline variable declaration (IDE0018)
dotnet_diagnostic.IDE0018.severity = none
Expand Down
83 changes: 47 additions & 36 deletions DocxTemplater.Images/ImageFormatter.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
using System;
using System.IO;
using System.Linq;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using DocxTemplater.Formatter;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Metadata;
using System;
using System.IO;
using System.Linq;
using A = DocumentFormat.OpenXml.Drawing;
using DW = DocumentFormat.OpenXml.Drawing.Wordprocessing;
using PIC = DocumentFormat.OpenXml.Drawing.Pictures; // http://schemas.openxmlformats.org/drawingml/2006/picture"
Expand Down Expand Up @@ -39,37 +39,37 @@ public void ApplyFormat(FormatterContext context, Text target)
using var image = Image.Load(imageBytes);
var imagePartType = DetectPartTypeInfo(context.Placeholder, image.Metadata);
var root = target.GetRoot();
string impagepartRelationShipId = null;
string imagePartRelId = null;
uint maxPropertyId = 0;
if (root is OpenXmlPartRootElement openXmlPartRootElement && openXmlPartRootElement.OpenXmlPart != null)
{
maxPropertyId = openXmlPartRootElement.OpenXmlPart.GetMaxDocPropertyId();
if (openXmlPartRootElement.OpenXmlPart is HeaderPart headerPart)
{
impagepartRelationShipId = CreateImagePart(headerPart, imageBytes, imagePartType);
imagePartRelId = CreateImagePart(headerPart, imageBytes, imagePartType);
}
else if (openXmlPartRootElement.OpenXmlPart is FooterPart footerPart)
{
impagepartRelationShipId = CreateImagePart(footerPart, imageBytes, imagePartType);
imagePartRelId = CreateImagePart(footerPart, imageBytes, imagePartType);
}
else if (openXmlPartRootElement.OpenXmlPart is MainDocumentPart mainDocumentPart)
{
impagepartRelationShipId = CreateImagePart(mainDocumentPart, imageBytes, imagePartType);
imagePartRelId = CreateImagePart(mainDocumentPart, imageBytes, imagePartType);
}
}

if (impagepartRelationShipId == null)
if (imagePartRelId == null)
{
throw new OpenXmlTemplateException("Could not find a valid image part");
}

// case 1. Image ist the only child element of a <wps:wsp> (TextBox)
if (TryHandleImageInWordprocessingShape(target, impagepartRelationShipId, image, context.Args.FirstOrDefault() ?? string.Empty, maxPropertyId))
if (TryHandleImageInWordprocessingShape(target, imagePartRelId, image, context.Args.FirstOrDefault() ?? string.Empty, maxPropertyId))
{
return;
}

AddInlineGraphicToRun(target, impagepartRelationShipId, image, maxPropertyId);
AddInlineGraphicToRun(target, imagePartRelId, image, maxPropertyId);
}
catch (Exception e) when (e is InvalidImageContentException or UnknownImageFormatException)
{
Expand Down Expand Up @@ -103,13 +103,8 @@ private static bool TryHandleImageInWordprocessingShape(Text target, string impa
return false;
}

var anchor = target.GetFirstAncestor<DW.Anchor>();
if (anchor == null)
{
return false;
}

var targetExtent = anchor.GetFirstChild<DW.Extent>();
// get extent of the drawing either from the anchor or inline
var targetExtent = target.GetFirstAncestor<DW.Anchor>()?.GetFirstChild<DW.Extent>() ?? target.GetFirstAncestor<DW.Inline>()?.GetFirstChild<DW.Extent>();
if (targetExtent != null)
{
double scale = 0;
Expand Down Expand Up @@ -142,29 +137,48 @@ private static bool TryHandleImageInWordprocessingShape(Text target, string impa
}


private static void ReplaceAnchorContentWithPicture(string impagepartRelationShipId, uint maxDocumentPropertyId, Drawing original)
private static void ReplaceAnchorContentWithPicture(string impagepartRelationShipId, uint maxDocumentPropertyId,
Drawing original)
{
var propertyId = maxDocumentPropertyId + 1;
var originalAnchor = original.GetFirstChild<DW.Anchor>();
var originaleExtent = originalAnchor.GetFirstChild<DW.Extent>();
var inlineOrAnchor = (OpenXmlElement)original.GetFirstChild<DW.Anchor>() ??
(OpenXmlElement)original.GetFirstChild<DW.Inline>();
var originaleExtent = inlineOrAnchor.GetFirstChild<DW.Extent>();

var horzPosition = originalAnchor.GetFirstChild<DW.HorizontalPosition>().CloneNode(true);
var vertPosition = originalAnchor.GetFirstChild<DW.VerticalPosition>().CloneNode(true);
var clonedInlineOrAnchor = inlineOrAnchor.CloneNode(false);

var anchorChildElments = new OpenXmlElement[]
if (inlineOrAnchor is DW.Anchor anchor)
{
new DW.SimplePosition {X = 0L, Y = 0L},
horzPosition,
vertPosition,
new DW.Extent {Cx = originaleExtent.Cx, Cy = originaleExtent.Cy},
new DW.EffectExtent
clonedInlineOrAnchor.Append(new DW.SimplePosition { X = 0L, Y = 0L });
var horzPosition = anchor.GetFirstChild<DW.HorizontalPosition>().CloneNode(true);
var vertPosition = inlineOrAnchor.GetFirstChild<DW.VerticalPosition>().CloneNode(true);
clonedInlineOrAnchor.Append(horzPosition);
clonedInlineOrAnchor.Append(vertPosition);
clonedInlineOrAnchor.Append(new DW.Extent { Cx = originaleExtent.Cx, Cy = originaleExtent.Cy });
clonedInlineOrAnchor.Append(new DW.EffectExtent
{
LeftEdge = 0L,
TopEdge = 0L,
RightEdge = 0L,
BottomEdge = 0L
},
new DW.WrapNone(),
});
clonedInlineOrAnchor.Append(new DW.WrapNone());
}
else if (inlineOrAnchor is DW.Inline)
{
clonedInlineOrAnchor.Append(new DW.Extent { Cx = originaleExtent.Cx, Cy = originaleExtent.Cy });
clonedInlineOrAnchor.Append(new DW.EffectExtent
{
LeftEdge = 0L,
TopEdge = 0L,
RightEdge = 0L,
BottomEdge = 0L
});
}

clonedInlineOrAnchor.Append(new OpenXmlElement[]
{

new DW.DocProperties
{
Id = propertyId,
Expand All @@ -177,11 +191,8 @@ private static void ReplaceAnchorContentWithPicture(string impagepartRelationShi
CreatePicture(impagepartRelationShipId, propertyId, originaleExtent.Cx, originaleExtent.Cy)
)
{Uri = "http://schemas.openxmlformats.org/drawingml/2006/picture"})
};

var anchor = originalAnchor.CloneNode(false);
anchor.Append(anchorChildElments);
var dw = new Drawing(anchor);
});
var dw = new Drawing(clonedInlineOrAnchor);
original.InsertAfterSelf(dw);
original.Remove();
}
Expand Down
128 changes: 128 additions & 0 deletions DocxTemplater.Test/ComplexTemplateTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
using DocxTemplater.Images;

namespace DocxTemplater.Test
{
internal class ComplexTemplateTest
{
[Test]
public void ProcessComplexTemplate()
{

var imageBytes = File.ReadAllBytes("Resources/testImage.jpg");
using var fileStream = File.OpenRead("Resources/ComplexTemplate.docx");
var docTemplate = new DocxTemplate(fileStream);
docTemplate.RegisterFormatter(new ImageFormatter());

var model = CreateModel(imageBytes);
docTemplate.BindModel("ds", model);

var result = docTemplate.Process();
docTemplate.Validate();
result.Position = 0;
result.SaveAsFileAndOpenInWord();
}

private object CreateModel(byte[] imageBytes)
{
var items = new List<WarehouseItem>
{
new()
{
IsHw = true,
Name = "Item 1",
HardwareRevisions = new List<VersionInfo>
{
new() {IsMajor = true, Version = "1.0"},
new() {IsMajor = false, Version = "1.1"},
new() {IsMajor = false, Version = "1.2"},
new() {IsMajor = false, Version = "1.3"},
}
},
new()
{
IsHw = true,
Name = "Item 2",
SoftwareVersions = new List<VersionInfo>
{
new() {IsMajor = true, Version = "1.0"},
new() {IsMajor = false, Version = "1.1"},
new() {IsMajor = false, Version = "1.2"},
new() {IsMajor = false, Version = "1.3"},
}
},
new()
{
IsHw = false,
Name = "Item 3",
SoftwareVersions = new List<VersionInfo>
{
new() {IsMajor = true, Version = "1.0"},
new() {IsMajor = false, Version = "1.1"},
new() {IsMajor = false, Version = "1.2"},
new() {IsMajor = false, Version = "1.3"},
}
},
new()
{
IsHw = false,
Name = "Item 4",
SoftwareVersions = new List<VersionInfo>
{
new() {IsMajor = true, Version = "42.0"},
},
HardwareRevisions = new List<VersionInfo>
{
new() {IsMajor = true, Version = "1.0"},
new() {IsMajor = false, Version = "1.1"},
new() {IsMajor = false, Version = "1.2"},
new() {IsMajor = false, Version = "1.3"},
}
}
};

var images = new List<byte[]>
{
imageBytes,
imageBytes,
imageBytes
};

return new ComplexTemplateModel
{
Items = items,
Images = images
};
}

private class ComplexTemplateModel
{
public IReadOnlyCollection<WarehouseItem> Items { get; set; }

public IReadOnlyCollection<byte[]> Images { get; set; }
}

private class WarehouseItem
{
public bool IsHw { get; set; }

public string Name { get; set; }

public IReadOnlyCollection<VersionInfo> HardwareRevisions { get; set; }

public IReadOnlyCollection<VersionInfo> SoftwareVersions { get; set; }

}

private class VersionInfo
{
public bool IsMajor { get; set; }

public string Version { get; set; }

public override string ToString()
{
return IsMajor ? $"Major Version: {Version}" : $"Minor Version: {Version}";
}
}
}
}
71 changes: 69 additions & 2 deletions DocxTemplater.Test/DocxTemplateTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,24 @@ public void DynamicTable()
Assert.That(rows[4].InnerText, Is.EqualTo("Value7Value8Value9"));
}

[Test]
public void EmptyDynamicTable()
{
using var fileStream = File.OpenRead("Resources/DynamicTable.docx");
var docTemplate = new DocxTemplate(fileStream);
var tableModel = new DynamicTable();
docTemplate.BindModel("ds", tableModel);
var result = docTemplate.Process();
docTemplate.Validate();
result.Position = 0;
result.SaveAsFileAndOpenInWord();
result.Position = 0;
var document = WordprocessingDocument.Open(result, false);
var body = document.MainDocumentPart.Document.Body;
var table = body.Descendants<Table>().FirstOrDefault();
Assert.That(table, Is.Null);
}

/// <summary>
/// Dynamic tables are only required if the number of columns is not known at design time.
/// otherwise a simple table bound to a collection of objects is sufficient.
Expand Down Expand Up @@ -205,6 +223,53 @@ public void InsertHtmlInLoop()
Assert.That(altChunks.Count, Is.EqualTo(2));
}

[Test]
public void ConditionalBlockInLoop()
{
var content = "{{#Educations}}{?{.HasTeacher}}{{.ChecklistName}}{{:}}noTeacher {{.ChecklistName}}{{/}}{{:s:}}, {{/Educations}}";
using var memStream = new MemoryStream();
using var wpDocument = WordprocessingDocument.Create(memStream, WordprocessingDocumentType.Document);
MainDocumentPart mainPart = wpDocument.AddMainDocumentPart();
mainPart.Document = new Document(new Body(new Paragraph(new Run(new Text(content)))));
wpDocument.Save();
memStream.Position = 0;
var docTemplate = new DocxTemplate(memStream);
docTemplate.BindModel("Educations", new[]
{
new { HasTeacher = true, ChecklistName = "ChecklistName1" },
new { HasTeacher = false, ChecklistName = "ChecklistName2" },
new { HasTeacher = true, ChecklistName = "ChecklistName3" }
});
var result = docTemplate.Process();
docTemplate.Validate();
Assert.IsNotNull(result);
// validate content
var document = WordprocessingDocument.Open(result, false);
var body = document.MainDocumentPart.Document.Body;
Assert.That(body.InnerText, Is.EqualTo("ChecklistName1, noTeacher ChecklistName2, ChecklistName3"));
}

[Test]
public void NullValueHandlingForNesteObjects()
{
using var memStream = new MemoryStream();
using var wpDocument = WordprocessingDocument.Create(memStream, WordprocessingDocumentType.Document);
MainDocumentPart mainPart = wpDocument.AddMainDocumentPart();
mainPart.Document = new Document(new Body(new Paragraph(new Run(new Text("Test{{ds.Model.Outer.BillName}}")))));
wpDocument.Save();
memStream.Position = 0;
var docTemplate = new DocxTemplate(memStream);
docTemplate.Settings.BindingErrorHandling = BindingErrorHandling.SkipBindingAndRemoveContent;
docTemplate.BindModel("ds", new { Model = new { Outer = (LessonReportModel)null } });
var result = docTemplate.Process();
docTemplate.Validate();
Assert.IsNotNull(result);
var document = WordprocessingDocument.Open(result, false);
var body = document.MainDocumentPart.Document.Body;
//check values have been replaced
Assert.That(body.InnerText, Is.EqualTo("Test"));
}

[Test]
public void MissingVariableWithSkipErrorHandling()
{
Expand Down Expand Up @@ -274,6 +339,7 @@ public void CollectionSeparatorTest()
Assert.That(body.InnerText, Is.EqualTo("Item1,Item2,Item3"));
}


[Test]
public void ConditionsWithAndWithoutPrefix()
{
Expand All @@ -285,8 +351,9 @@ public void ConditionsWithAndWithoutPrefix()
new Paragraph(new Run(new Text("{?{ ds.Test > 5}}Test2{{else}}else2{{/}}"))),
new Paragraph(new Run(new Text("{?{ ds2.Test > 5}}Test3{{else}}else3{{/}}"))),
new Paragraph(new Run(new Text("{?{ds3.MyBool}}Test4{{:}}else4{{/}}"))),
new Paragraph(new Run(new Text("{?{!ds4.MyBool}}Test5{{:}}else4{{/}}")))
));
new Paragraph(new Run(new Text("{?{!ds4.MyBool}}Test5{{:}}else4{{/}}"))),
new Paragraph(new Run(new Text("{?{!ds3.MyBool}}NoElse{{/}}")))
));
wpDocument.Save();
memStream.Position = 0;
var docTemplate = new DocxTemplate(memStream);
Expand Down
Loading
Loading