mirror of
https://gitee.com/dotnetchina/MiniExcel.git
synced 2024-12-02 03:47:41 +08:00
334 lines
11 KiB
C#
334 lines
11 KiB
C#
<Query Kind="Program">
|
|
<NuGetReference>Dapper</NuGetReference>
|
|
<NuGetReference>MiniExcel</NuGetReference>
|
|
<NuGetReference>Newtonsoft.Json</NuGetReference>
|
|
<NuGetReference>System.Data.SqlClient</NuGetReference>
|
|
<Namespace>Dapper</Namespace>
|
|
<Namespace>MiniExcelLibs</Namespace>
|
|
<Namespace>Newtonsoft.Json</Namespace>
|
|
<Namespace>MiniExcelLibs.OpenXml</Namespace>
|
|
<RemoveNamespace>System.Data</RemoveNamespace>
|
|
<RemoveNamespace>System.Linq.Expressions</RemoveNamespace>
|
|
<RemoveNamespace>System.Transactions</RemoveNamespace>
|
|
<RemoveNamespace>System.Xml.XPath</RemoveNamespace>
|
|
</Query>
|
|
|
|
void Main()
|
|
{
|
|
var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid().ToString()}.xml");
|
|
Dictionary<string, object> Maps = new Dictionary<string, object>()
|
|
{
|
|
["title"] = "FooCompany",
|
|
["date"] = DateTime.Now,
|
|
["managers"] = new[] {
|
|
new {name="Jack",department="HR"},
|
|
new {name="Loan",department="IT"}
|
|
},
|
|
["employees"] = new[] {
|
|
new {name="Wade",department="HR"},
|
|
new {name="Felix",department="HR"},
|
|
new {name="Eric",department="IT"},
|
|
new {name="Keaton",department="IT"}
|
|
}
|
|
};
|
|
using (var stream = File.Create(path))
|
|
{
|
|
ExcelOpenXmlTemplate.GenerateSheetXml(stream, xml, Maps);
|
|
Console.WriteLine(File.ReadAllText(path));
|
|
}
|
|
}
|
|
|
|
namespace MiniExcelLibs.OpenXml
|
|
{
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Xml;
|
|
internal class ExcelOpenXmlTemplate
|
|
{
|
|
private const string _ns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
|
|
private static readonly Regex _isExpressionRegex;
|
|
static ExcelOpenXmlTemplate()
|
|
{
|
|
_isExpressionRegex = new Regex("(?<={{).*?(?=}})");
|
|
}
|
|
public static void GenerateSheetXml(Stream stream, string sheetXml, Dictionary<string, object> inputMaps, XmlWriterSettings xmlWriterSettings = null)
|
|
{
|
|
var doc = new XmlDocument();
|
|
doc.LoadXml(sheetXml);
|
|
|
|
XmlNamespaceManager ns = new XmlNamespaceManager(doc.NameTable);
|
|
ns.AddNamespace("x", "http://schemas.openxmlformats.org/spreadsheetml/2006/main");
|
|
|
|
var worksheet = doc.SelectSingleNode("/x:worksheet", ns);
|
|
|
|
var dimension = doc.SelectSingleNode("/x:worksheet/x:dimension", ns);
|
|
XmlElement newDimension = null;
|
|
if (dimension != null)
|
|
{
|
|
newDimension = dimension.Clone() as XmlElement;
|
|
worksheet.RemoveChild(dimension);
|
|
}
|
|
|
|
|
|
var sheetData = doc.SelectSingleNode("/x:worksheet/x:sheetData", ns);
|
|
var newSheetData = sheetData.Clone();
|
|
sheetData.RemoveAll();
|
|
sheetData.InnerText = "{{{{{{split}}}}}}";
|
|
|
|
var contents = doc.InnerXml.Split(new string[] { "<sheetData>{{{{{{split}}}}}}</sheetData>" }, StringSplitOptions.None); ;
|
|
|
|
using (var writer = new StreamWriter(stream, Encoding.UTF8))
|
|
{
|
|
writer.Write(contents[0]);
|
|
var rows = newSheetData.SelectNodes($"x:row", ns);
|
|
|
|
//update dimension
|
|
if (newDimension != null)
|
|
{
|
|
var maxRowIndexDiff = 0;
|
|
foreach (XmlElement row in rows)
|
|
{
|
|
IEnumerable ienumerable = null;
|
|
|
|
foreach (XmlElement c in row.SelectNodes($"x:c", ns))
|
|
{
|
|
var v = c.SelectSingleNode("x:v", ns);
|
|
if (v?.InnerText == null)
|
|
continue;
|
|
|
|
var matchs = (_isExpressionRegex.Matches(v.InnerText).Cast<Match>().GroupBy(x => x.Value).Select(varGroup => varGroup.First().Value));
|
|
foreach (var item in matchs)
|
|
{
|
|
var keys = item.Split('.');
|
|
var value = inputMaps[keys[0]];
|
|
if (value is IEnumerable && !(value is string))
|
|
{
|
|
ienumerable = value as IEnumerable;
|
|
break;
|
|
}
|
|
}
|
|
if (ienumerable != null)
|
|
break;
|
|
}
|
|
if (ienumerable != null)
|
|
{
|
|
var first = true;
|
|
foreach (var element in ienumerable)
|
|
{
|
|
if (!first)
|
|
maxRowIndexDiff++;
|
|
first = false;
|
|
}
|
|
}
|
|
}
|
|
// e.g <dimension ref=\"A1:B6\" /> only need to update B6 to BMaxRowIndex
|
|
var @refs = newDimension.GetAttribute("ref").Split(':');
|
|
var letter = new String(refs[1].Where(Char.IsLetter).ToArray());
|
|
var digit = int.Parse(new String(refs[1].Where(Char.IsDigit).ToArray()));
|
|
|
|
newDimension.SetAttribute("ref", $"{refs[0]}:{letter}{digit + maxRowIndexDiff}");
|
|
writer.Write(CleanXml(newDimension.OuterXml));
|
|
}
|
|
|
|
//render sheetData
|
|
writer.Write("<sheetData>");
|
|
int originRowIndex;
|
|
int rowIndexDiff = 0;
|
|
foreach (XmlElement row in rows)
|
|
{
|
|
var rowCotainIEnumerable = false;
|
|
IEnumerable ienumerable = null;
|
|
List<string> propKeys = null;
|
|
Type ienumerableGenricType = null;
|
|
string ienumerableKey = null;
|
|
|
|
//TODO: some xlsx without r
|
|
originRowIndex = int.Parse(row.GetAttribute("r"));
|
|
|
|
//TODO: need to remove namespace
|
|
|
|
// check if contains IEnumerble row
|
|
{
|
|
var cs = row.SelectNodes($"x:c", ns);
|
|
foreach (XmlElement c in cs)
|
|
{
|
|
var cr = c.GetAttribute("r");
|
|
var letter = new String(cr.Where(Char.IsLetter).ToArray());
|
|
c.SetAttribute("r", $"{letter}{{{{{{MiniExcel_RowIndex}}}}}}");
|
|
|
|
var v = c.SelectSingleNode("x:v", ns);
|
|
if (v?.InnerText == null)
|
|
continue;
|
|
|
|
var matchs = (_isExpressionRegex.Matches(v.InnerText).Cast<Match>().GroupBy(x => x.Value).Select(varGroup => varGroup.First().Value));
|
|
foreach (var item in matchs)
|
|
{
|
|
var keys = item.Split('.');
|
|
var value = inputMaps[keys[0]];
|
|
if (value is IEnumerable && !(value is string))
|
|
{
|
|
|
|
|
|
if (propKeys == null)
|
|
propKeys = new List<string>();
|
|
propKeys.Add(keys[1]); //TODO: check if not contain 1 index
|
|
//only check first one match IEnumerable, so only render one collection at same row
|
|
if (rowCotainIEnumerable == false)
|
|
{
|
|
ienumerableKey = keys[0];
|
|
// get ienumerable runtime type
|
|
foreach (var element in value as IEnumerable)
|
|
{
|
|
if (element != null)
|
|
{
|
|
ienumerableGenricType = element.GetType();
|
|
break;
|
|
}
|
|
}
|
|
|
|
ienumerable = value as IEnumerable;
|
|
rowCotainIEnumerable = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
v.InnerText = v.InnerText.Replace($"{{{{{keys[0]}}}}}", value.ToString()); //TODO: auto check type and set value
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
var newRowIndex = originRowIndex + rowIndexDiff;
|
|
if (rowCotainIEnumerable && ienumerable != null)
|
|
{
|
|
var first = true;
|
|
foreach (var element in ienumerable)
|
|
{
|
|
var newRow = row.Clone() as XmlElement;
|
|
newRow.SetAttribute("r", newRowIndex.ToString());
|
|
newRow.InnerXml = row.InnerXml.Replace($"{{{{{{MiniExcel_RowIndex}}}}}}", newRowIndex.ToString());
|
|
|
|
foreach (var key in propKeys)
|
|
{
|
|
var prop = ienumerableGenricType.GetProperty(key);
|
|
newRow.InnerXml = newRow.InnerXml.Replace($"{{{{{ienumerableKey}.{key}}}}}", prop.GetValue(element).ToString());
|
|
}
|
|
|
|
// note: only first time need add diff ![image](https://user-images.githubusercontent.com/12729184/114494728-6bceda80-9c4f-11eb-9685-8b5ed054eabe.png)
|
|
if (!first)
|
|
rowIndexDiff++;
|
|
first = false;
|
|
|
|
newRowIndex++;
|
|
writer.Write(CleanXml(newRow.OuterXml));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
row.SetAttribute("r", newRowIndex.ToString());
|
|
row.InnerXml = row.InnerXml.Replace($"{{{{{{MiniExcel_RowIndex}}}}}}", newRowIndex.ToString());
|
|
writer.Write(CleanXml(row.OuterXml));
|
|
}
|
|
|
|
}
|
|
writer.Write("</sheetData>");
|
|
writer.Write(contents[1]);
|
|
}
|
|
}
|
|
private static string CleanXml(string xml)
|
|
{
|
|
//TODO: need to optimize
|
|
return xml.Replace("xmlns:x14ac=\"http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac\"", "").Replace("xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\"", "");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const string xml = @"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?>
|
|
<worksheet xmlns=""http://schemas.openxmlformats.org/spreadsheetml/2006/main""
|
|
xmlns:r=""http://schemas.openxmlformats.org/officeDocument/2006/relationships""
|
|
xmlns:mc=""http://schemas.openxmlformats.org/markup-compatibility/2006"" mc:Ignorable=""x14ac xr xr2 xr3""
|
|
xmlns:x14ac=""http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac""
|
|
xmlns:xr=""http://schemas.microsoft.com/office/spreadsheetml/2014/revision""
|
|
xmlns:xr2=""http://schemas.microsoft.com/office/spreadsheetml/2015/revision2""
|
|
xmlns:xr3=""http://schemas.microsoft.com/office/spreadsheetml/2016/revision3"" xr:uid=""{00000000-0001-0000-0000-000000000000}"">
|
|
<dimension ref=""A1:B100"" />
|
|
<sheetViews>
|
|
<sheetView tabSelected=""1"" zoomScaleNormal=""100"" workbookViewId=""0"">
|
|
<selection activeCell=""C8"" sqref=""C8"" />
|
|
</sheetView>
|
|
</sheetViews>
|
|
<sheetFormatPr defaultColWidth=""11.5546875"" defaultRowHeight=""13.2"" x14ac:dyDescent=""0.25"" />
|
|
<cols>
|
|
<col min=""1"" max=""1"" width=""17.109375"" customWidth=""1"" />
|
|
<col min=""2"" max=""2"" width=""21.77734375"" customWidth=""1"" />
|
|
</cols>
|
|
<sheetData>
|
|
<row r=""1"" spans=""1:2"" x14ac:dyDescent=""0.25"">
|
|
<c r=""A1"" t=""str"">
|
|
<v>Sheet1</v>
|
|
</c>
|
|
</row>
|
|
<row r=""2"" spans=""1:2"" x14ac:dyDescent=""0.25"">
|
|
<c r=""A2"" t=""str"">
|
|
<v>{{title}}-{{date}}</v>
|
|
</c>
|
|
<c r=""B2"" s=""2"" />
|
|
</row>
|
|
<row r=""3"" spans=""1:2"" x14ac:dyDescent=""0.25"">
|
|
<c r=""A3"" t=""str"">
|
|
<v>Managers</v>
|
|
</c>
|
|
</row>
|
|
<row r=""4"" spans=""1:2"" x14ac:dyDescent=""0.25"">
|
|
<c r=""A4"" t=""str"">
|
|
<v>{{managers.name}}</v>
|
|
</c>
|
|
<c r=""B4"" t=""str"">
|
|
<v>{{managers.department}}</v>
|
|
</c>
|
|
<c r=""C4"" t=""str"">
|
|
<v>{{title}}</v>
|
|
</c>
|
|
</row>
|
|
<row r=""5"" spans=""1:2"" x14ac:dyDescent=""0.25"">
|
|
<c r=""A5"" t=""str"">
|
|
<v>Employees</v>
|
|
</c>
|
|
</row>
|
|
<row r=""6"" spans=""1:2"" x14ac:dyDescent=""0.25"">
|
|
<c r=""A6"" t=""str"">
|
|
<v>{{employees.name}}</v>
|
|
</c>
|
|
<c r=""B6"" t=""str"">
|
|
<v>{{employees.department}}</v>
|
|
</c>
|
|
</row>
|
|
<row r=""100"" spans=""1:2"" x14ac:dyDescent=""0.25"">
|
|
<c r=""A100"" t=""str"">
|
|
<v>{{employees.name}}</v>
|
|
</c>
|
|
<c r=""B100"" t=""str"">
|
|
<v>{{employees.department}}</v>
|
|
</c>
|
|
</row>
|
|
</sheetData>
|
|
<mergeCells count=""1"">
|
|
<mergeCell ref=""A2:B2"" />
|
|
</mergeCells>
|
|
<pageMargins left=""0.78749999999999998"" right=""0.78749999999999998"" top=""1.05277777777778"" bottom=""1.05277777777778"" header=""0.78749999999999998"" footer=""0.78749999999999998"" />
|
|
<pageSetup orientation=""portrait"" useFirstPageNumber=""1"" horizontalDpi=""300"" verticalDpi=""300"" />
|
|
<headerFooter>
|
|
<oddHeader>&C&""Times New Roman,Regular""&12&A</oddHeader>
|
|
<oddFooter>&C&""Times New Roman,Regular""&12Page &P</oddFooter>
|
|
</headerFooter>
|
|
</worksheet>"; |