Skip to content

Commit

Permalink
Merge pull request #9 from Amberg/workitems/v2.0.0
Browse files Browse the repository at this point in the history
Workitems v2.0.0
  • Loading branch information
Amberg authored Feb 5, 2024
2 parents 5640a5e + 6aef5f2 commit 765bbf2
Show file tree
Hide file tree
Showing 23 changed files with 584 additions and 141 deletions.
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

0 comments on commit 765bbf2

Please sign in to comment.