diff --git a/src/MiniWord/MiniWord.Implment.cs b/src/MiniWord/MiniWord.Implment.cs index 6e48862..a99bf8d 100644 --- a/src/MiniWord/MiniWord.Implment.cs +++ b/src/MiniWord/MiniWord.Implment.cs @@ -25,7 +25,7 @@ public static partial class MiniWord { private static void SaveAsByTemplateImpl(Stream stream, byte[] template, Dictionary data) { - SaveAsByTemplateImplAsync(stream,template,data).GetAwaiter().GetResult(); + SaveAsByTemplateImplAsync(stream, template, data).GetAwaiter().GetResult(); } private static async Task SaveAsByTemplateImplAsync(Stream stream, byte[] template, Dictionary data, CancellationToken token = default(CancellationToken)) @@ -81,6 +81,7 @@ private static void Generate(this OpenXmlElement xmlElement, WordprocessingDocum ReplaceText(xmlElement, docx, tags); } + /// /// 渲染Table /// @@ -90,7 +91,11 @@ private static void Generate(this OpenXmlElement xmlElement, WordprocessingDocum /// private static void GenerateTable(Table table, WordprocessingDocument docx, Dictionary tags) { - var trs = table.Descendants().ToArray(); // remember toarray or system will loop OOM; + var trs = table.Elements().ToArray(); // remember toarray or system will loop OOM; + var regexStr = "(?<={{).*?\\..*?(?=}})"; + //计算是否只有一个cell存在指令,如果超过1个则纵向渲染,否则横向渲染。 + var cellList = table.Elements().SelectMany(s => s.Elements()).ToList(); + bool isHorizontal = cellList.Count > 1 && cellList.Count(w => Regex.Matches(w.InnerText, regexStr).Count > 0) <= 1; foreach (var tr in trs) { @@ -98,8 +103,9 @@ private static void GenerateTable(Table table, WordprocessingDocument docx, Dict .Replace("{{if(", "").Replace(")if", "").Replace("endif}}", ""); // 匹配list数据,格式“Items.PropName” - var matchs = (Regex.Matches(innerText, "(?<={{).*?\\..*?(?=}})") + var matchs = (Regex.Matches(innerText, regexStr) .Cast().GroupBy(x => x.Value).Select(varGroup => varGroup.First().Value)).ToArray(); + if (matchs.Length > 0) { //var listKeys = matchs.Select(s => s.Split('.')[0]).Distinct().ToArray(); @@ -124,12 +130,18 @@ private static void GenerateTable(Table table, WordprocessingDocument docx, Dict var attributeKey = matchs[0].Split('.')[0]; var list = tagObj as IEnumerable; + int num = tr.Elements().Count(); + int index = 0; + List elementList = new List(); + int totalCount = list.Cast().Count(); + //int rowCount = (totalCount + num - 1) / (num == 0 ? 1 : num); // 计算总行数 + foreach (var item in list) { var dic = new Dictionary(); //TODO: optimize - - + var newTr = tr.CloneNode(true); + if (item is IDictionary) { var es = (Dictionary)item; @@ -153,16 +165,43 @@ private static void GenerateTable(Table table, WordprocessingDocument docx, Dict ReplaceIfStatements(newTr, tags: dic); ReplaceText(newTr, docx, tags: dic); - //Fix #47 The table should be inserted at the template tag position instead of the last row - if (table.Contains(tr)) + + if (isHorizontal) { - table.InsertBefore(newTr, tr); + var newTable = newTr.Descendants().FirstOrDefault(); + if (newTable != null) + elementList.Add(newTable.CloneNode(true)); + + if (index > 0 && ((index + 1) % num == 0 || (index == totalCount - 1)) && elementList.Count > 0) + { + var templateTr = tr.CloneNode(true); + for (var i = 0; i < elementList.Count; i++) + { + var tCell = templateTr.Elements().ToList()[i]; + + tCell.RemoveAllChildren(); + tCell.Append(elementList[i]); + } + newTr = templateTr; + elementList = new List(); + + table.Append(newTr); + } } else { - // If it is a nested table, temporarily append it to the end according to the original plan. - table.Append(newTr); + //Fix #47 The table should be inserted at the template tag position instead of the last row + if (table.Contains(tr)) + { + table.InsertBefore(newTr, tr); + } + else + { + // If it is a nested table, temporarily append it to the end according to the original plan. + table.Append(newTr); + } } + index++; } tr.Remove(); @@ -194,6 +233,7 @@ private static void GenerateTable(Table table, WordprocessingDocument docx, Dict } + /// /// 获取Obj对象指定的值 /// @@ -648,7 +688,7 @@ private static void ReplaceText(Paragraph p, WordprocessingDocument docx, Dictio { AddPicture(run, mainPart.GetIdOfPart(imagePart), pic); } - + } t.Remove(); @@ -1023,9 +1063,9 @@ private static void AddPicture(OpenXmlElement appendElement, string relationship new A.PresetGeometry( new A.AdjustValueList() ) - { Preset = A.ShapeTypeValues.Rectangle })) + { Preset = A.ShapeTypeValues.Rectangle })) ) - { Uri = "http://schemas.openxmlformats.org/drawingml/2006/picture" }) + { Uri = "http://schemas.openxmlformats.org/drawingml/2006/picture" }) ) { DistanceFromTop = (UInt32Value)0U, @@ -1043,22 +1083,30 @@ private static void AddPictureAnchor(OpenXmlElement appendElement, string relati DW.Anchor anchor3 = new DW.Anchor() { - DistanceFromTop = (UInt32Value)0U, DistanceFromBottom = (UInt32Value)0U, - DistanceFromLeft = (UInt32Value)114300U, DistanceFromRight = (UInt32Value)114300U, - SimplePos = false, RelativeHeight = (UInt32Value)0U, BehindDoc = pic.BehindDoc, Locked = false, - LayoutInCell = true, AllowOverlap = pic.AllowOverlap, EditId = "1EACBECC", AnchorId = "5AF073F3" + DistanceFromTop = (UInt32Value)0U, + DistanceFromBottom = (UInt32Value)0U, + DistanceFromLeft = (UInt32Value)114300U, + DistanceFromRight = (UInt32Value)114300U, + SimplePos = false, + RelativeHeight = (UInt32Value)0U, + BehindDoc = pic.BehindDoc, + Locked = false, + LayoutInCell = true, + AllowOverlap = pic.AllowOverlap, + EditId = "1EACBECC", + AnchorId = "5AF073F3" }; DW.SimplePosition simplePosition3 = new DW.SimplePosition() { X = 0L, Y = 0L }; DW.HorizontalPosition horizontalPosition3 = new DW.HorizontalPosition() - { RelativeFrom = DW.HorizontalRelativePositionValues.Column }; + { RelativeFrom = DW.HorizontalRelativePositionValues.Column }; DW.PositionOffset positionOffset5 = new DW.PositionOffset(); positionOffset5.Text = $"{pic.HorizontalPositionOffset * 9525}"; horizontalPosition3.Append(positionOffset5); DW.VerticalPosition verticalPosition3 = new DW.VerticalPosition() - { RelativeFrom = DW.VerticalRelativePositionValues.Paragraph }; + { RelativeFrom = DW.VerticalRelativePositionValues.Paragraph }; DW.PositionOffset positionOffset6 = new DW.PositionOffset(); positionOffset6.Text = $"{pic.VerticalPositionOffset * 9525}"; @@ -1067,12 +1115,12 @@ private static void AddPictureAnchor(OpenXmlElement appendElement, string relati DW.EffectExtent effectExtent13 = new DW.EffectExtent() - { LeftEdge = 0L, TopEdge = 0L, RightEdge = 0L, BottomEdge = 0L }; + { LeftEdge = 0L, TopEdge = 0L, RightEdge = 0L, BottomEdge = 0L }; DW.WrapNone wrapNone3 = new DW.WrapNone(); DW.DocProperties docProperties13 = new DW.DocProperties() - { Id = (UInt32Value)774829591U, Name = $"Picture {Guid.NewGuid().ToString()}" }; + { Id = (UInt32Value)774829591U, Name = $"Picture {Guid.NewGuid().ToString()}" }; DW.NonVisualGraphicFrameDrawingProperties nonVisualGraphicFrameDrawingProperties13 = new DW.NonVisualGraphicFrameDrawingProperties(); @@ -1087,14 +1135,14 @@ private static void AddPictureAnchor(OpenXmlElement appendElement, string relati graphic13.AddNamespaceDeclaration("a", "http://schemas.openxmlformats.org/drawingml/2006/main"); A.GraphicData graphicData13 = new A.GraphicData() - { Uri = "http://schemas.openxmlformats.org/drawingml/2006/picture" }; + { Uri = "http://schemas.openxmlformats.org/drawingml/2006/picture" }; PIC.Picture picture13 = new PIC.Picture(); picture13.AddNamespaceDeclaration("pic", "http://schemas.openxmlformats.org/drawingml/2006/picture"); PIC.NonVisualPictureProperties nonVisualPictureProperties13 = new PIC.NonVisualPictureProperties(); PIC.NonVisualDrawingProperties nonVisualDrawingProperties13 = new PIC.NonVisualDrawingProperties() - { Id = (UInt32Value)0U, Name = $"Image {Guid.NewGuid().ToString()}.{pic.Extension}" }; + { Id = (UInt32Value)0U, Name = $"Image {Guid.NewGuid().ToString()}.{pic.Extension}" }; PIC.NonVisualPictureDrawingProperties nonVisualPictureDrawingProperties13 = new PIC.NonVisualPictureDrawingProperties(); @@ -1104,7 +1152,7 @@ private static void AddPictureAnchor(OpenXmlElement appendElement, string relati PIC.BlipFill blipFill13 = new PIC.BlipFill(); A.Blip blip13 = new A.Blip() - { Embed = relationshipId, CompressionState = A.BlipCompressionValues.Print }; + { Embed = relationshipId, CompressionState = A.BlipCompressionValues.Print }; A.BlipExtensionList blipExtensionList11 = new A.BlipExtensionList(); A.BlipExtension blipExtension11 = new A.BlipExtension() { Uri = $"{{{Guid.NewGuid().ToString("n")}}}" }; diff --git a/tests/MiniWordTests/MiniWordTest4MeAsync.cs b/tests/MiniWordTests/MiniWordTest4MeAsync.cs new file mode 100644 index 0000000..e5296e4 --- /dev/null +++ b/tests/MiniWordTests/MiniWordTest4MeAsync.cs @@ -0,0 +1,242 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using DocumentFormat.OpenXml.Office2010.ExcelAc; +using MiniSoftware; +using Xunit; + +namespace MiniWordTests; + +public class MiniWordTest4MeAsync +{ + [Fact] + public async void TestForeachLoopInTablesAsync() + { + var path = "C:\\Users\\Administrator\\Downloads\\";// PathHelper.GetTempFilePath(); + var templatePath = "D:\\工作文档\\毅智科技\\导出模板\\信息标签模板A4_2.docx";//PathHelper.GetFile("TestForeachInTablesDemo.docx"); + + + var list = new List>(); + + foreach (var i in Enumerable.Range(1, 8)) + { + var values = new Dictionary() + { + ["u:name"] = "李哈哈" + i, + ["u:sex"] = "男", + ["r:userGroupParentName"] = "广州人才凯盛幼儿园", + ["r:userGroupName"] = $"大{i}班", + ["e:2613220640424581"] = i % 2 == 0 ? "男" : "女", + ["e:2613186883781253"] = i % 2 == 0 ? "5" : "6", + ["u:months"] = i, + ["e:2660464484401477"] = i, + ["s:2642862487273797"] = "维生素D", + ["s:2642861958398277"] = "乙肝两对半", + ["s:2642862864793925"] = "铁蛋白", + + ["qc1"] = "sdsdfds", + }; + list.Add(values); + } + + var value = new Dictionary(); + value["list"] = list; + + + await MiniWord.SaveAsByTemplateAsync(path + DateTime.Now.ToFileTimeUtc() + ".docx", templatePath, value); + //System.Diagnostics.Process.Start("explorer.exe", path); + //var xml = Helpers.GetZipFileContent(path + DateTime.Now.ToFileTimeUtc() + ".docx", "word/document.xml"); + //Assert.Contains(@"Discussion requirement part2 and development", xml); + //Assert.Contains(@"Discussion requirement part1", xml); + //Assert.Contains( + // "Air way to the Airplane | Parking way to the Car / Hotel way to the Room, Food way to the Plate", xml); + } + + [Fact] + public async void TestForeachLoopInTables2Async() + { + + string path = "D:\\工作文档\\毅智科技\\导出模板\\3-6岁体检表2026_列表.docx"; + string outputPath = "C:\\Users\\Administrator\\Downloads\\"; + List streamList = new List(); + List> valueList = new List>(); + byte[] buffer = File.ReadAllBytes("C:\\Users\\Administrator\\Downloads\\qcode.png"); + + foreach (var index in Enumerable.Range(1, 6)) + { + var values = new Dictionary() + { + ["u:name"] = "李哈哈_" + index, + ["u:sex"] = "男", + ["r:userGroupParentName"] = "广州人才凯盛幼儿园", + ["r:userGroupName"] = "大一班", + ["u:sex"] = "男", + ["u:age"] = "5", + ["u:months"] = "4", + ["e:ti200020"] = "110", + ["s:2642862487273797"] = "维生素D", + ["s:ei100008"] = "乙肝两对半", + ["s:ei100009"] = "铁蛋白", + ["qc1"] = new MiniSoftware.MiniWordPicture(buffer, MiniSoftware.Common.Enums.Extension.Png, 50, 50) + }; + //MemoryStream stream = new MemoryStream(); + //await MiniSoftware.MiniWord.SaveAsByTemplateAsync(stream, path, values); + //streamList.Add(stream); + valueList.Add(values); + }; + Dictionary listData = new Dictionary() + { + }; + listData["list"] = valueList; + MemoryStream stream = new MemoryStream(); + await MiniSoftware.MiniWord.SaveAsByTemplateAsync(outputPath + (DateTime.Now.ToFileTimeUtc()) + ".docx", path, listData); + } + + [Fact] + public async void MiniWordIfStatement_FirstIfAsync() + { + var path = PathHelper.GetTempFilePath(); + var templatePath = PathHelper.GetFile("TestIfStatement.docx"); + var value = new Dictionary() + { + ["Name"] = new List(){ + new MiniWordHyperLink(){ + Url = "https://google.com", + Text = "測試連結22!!" + }, + new MiniWordHyperLink(){ + Url = "https://google1.com", + Text = "測試連結11!!" + } + }, + ["Company_Name"] = "MiniSofteware", + ["CreateDate"] = new DateTime(2021, 01, 01), + ["VIP"] = true, + ["Points"] = 123, + ["APP"] = "Demo APP", + }; + await MiniWord.SaveAsByTemplateAsync(path, templatePath, value); + //Console.WriteLine(path); + var docXml = Helpers.GetZipFileContent(path, "word/document.xml"); + Assert.Contains("First if chosen: MiniSofteware", docXml); + Assert.DoesNotContain("Second if chosen: MaxiSoftware", docXml); + Assert.Contains("Points are greater than 100", docXml); + Assert.Contains("CreateDate is not less than 2021", docXml); + Assert.DoesNotContain("CreateDate is not greater than 2021", docXml); + } + + [Fact] + public async void TestForeachLoopInTablesWithIfStatementAsync() + { + var path = PathHelper.GetTempFilePath(); + var templatePath = PathHelper.GetFile("TestForeachInTablesWithIfStatementDemo.docx"); + var value = new Dictionary() + { + ["TripHs"] = new List> + { + new Dictionary + { + { "sDate", DateTime.Parse("2022-09-08 08:30:00") }, + { "eDate", DateTime.Parse("2022-09-08 15:00:00") }, + { "How", "Discussion requirement part1" }, + { + "Details", new List() + { + new MiniWordForeach() + { + Value = new Dictionary() + { + {"Text", "Air"}, + {"Value", "Airplane"} + }, + Separator = " | " + }, + new MiniWordForeach() + { + Value = new Dictionary() + { + {"Text", "Parking"}, + {"Value", "Car"} + }, + Separator = " / " + }, + new MiniWordForeach() + { + Value = new Dictionary() + { + {"Text", "Hotel"}, + {"Value", "Room"} + }, + Separator = ", " + }, + new MiniWordForeach() + { + Value = new Dictionary() + { + {"Text", "Food"}, + {"Value", "Plate"} + }, + Separator = "" + } + } + } + }, + new Dictionary + { + { "sDate", DateTime.Parse("2022-09-09 08:30:00") }, + { "eDate", DateTime.Parse("2022-09-09 17:00:00") }, + { "How", "Discussion requirement part2 and development" }, + { + "Details", new List() + { + new MiniWordForeach() + { + Value = new Dictionary() + { + {"Text", "Air"}, + {"Value", "Airplane"} + }, + Separator = " | " + }, + new MiniWordForeach() + { + Value = new Dictionary() + { + {"Text", "Parking"}, + {"Value", "Car"} + }, + Separator = " / " + }, + new MiniWordForeach() + { + Value = new Dictionary() + { + {"Text", "Hotel"}, + {"Value", "Room"} + }, + Separator = ", " + }, + new MiniWordForeach() + { + Value = new Dictionary() + { + {"Text", "Food"}, + {"Value", "Plate"} + }, + Separator = "" + } + } + } + } + } + }; + await MiniWord.SaveAsByTemplateAsync(path, templatePath, value); + //System.Diagnostics.Process.Start("explorer.exe", path); + var xml = Helpers.GetZipFileContent(path, "word/document.xml"); + Assert.Contains(@"Discussion requirement part2 and development", xml); + Assert.Contains(@"Discussion requirement part1", xml); + Assert.Contains("Air way to the Airplane | Hotel way to the Room", xml); + } +} \ No newline at end of file