DataTable: Add units for columns
This commit is contained in:
@ -81,7 +81,7 @@ namespace Elwig.Helpers.Export {
|
||||
Content = new StreamWriter(content.Open(), Utils.UTF8);
|
||||
await Content.WriteAsync("""
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<office:document-content xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" office:version="1.3">
|
||||
<office:document-content xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" office:version="1.3">
|
||||
<office:automatic-styles>
|
||||
<style:default-style style:family="table-cell">
|
||||
<style:text-properties fo:language="de" fo:country="AT"/>
|
||||
@ -108,10 +108,14 @@ namespace Elwig.Helpers.Export {
|
||||
<style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold" fo:font-size="16pt"/>
|
||||
</style:style>
|
||||
<style:style style:name="th" style:family="table-cell" style:parent-style-name="default">
|
||||
<style:table-cell-properties style:text-align-source="fix" style:repeat-content="false"/>
|
||||
<style:table-cell-properties style:text-align-source="fix" style:repeat-content="false" style:vertical-align="middle"/>
|
||||
<style:paragraph-properties fo:text-align="center"/>
|
||||
<style:text-properties fo:font-weight="bold" style:font-weight-asian="bold" style:font-weight-complex="bold"/>
|
||||
</style:style>
|
||||
<number:number-style style:name="nperc">
|
||||
<number:number number:decimal-places="1" number:min-decimal-places="1" number:min-integer-digits="1"/>
|
||||
</number:number-style>
|
||||
<style:style style:name="perc" style:family="table-cell" style:parent-style-name="default" style:data-style-name="nperc"/>
|
||||
</office:automatic-styles>
|
||||
<office:body>
|
||||
<office:spreadsheet>
|
||||
@ -151,8 +155,8 @@ namespace Elwig.Helpers.Export {
|
||||
await writer.WriteAsync($"""
|
||||
<config:config-item-map-entry config:name="{tbl}">
|
||||
<config:config-item config:name="VerticalSplitMode" config:type="short">2</config:config-item>
|
||||
<config:config-item config:name="VerticalSplitPosition" config:type="int">3</config:config-item>
|
||||
<config:config-item config:name="PositionBottom" config:type="int">3</config:config-item>
|
||||
<config:config-item config:name="VerticalSplitPosition" config:type="int">4</config:config-item>
|
||||
<config:config-item config:name="PositionBottom" config:type="int">4</config:config-item>
|
||||
</config:config-item-map-entry>
|
||||
|
||||
""");
|
||||
@ -177,8 +181,10 @@ namespace Elwig.Helpers.Export {
|
||||
|
||||
_tables.Add(table.Name);
|
||||
await Content.WriteAsync($" <table:table table:name=\"{table.Name}\" table:default-cell-style-name=\"default\">\r\n");
|
||||
foreach (var w in table.ColumnWidths) {
|
||||
await Content.WriteAsync(" <table:table-column" + (w != null ? $" table:style-name=\"colw{w}\"" : "") + "/>\r\n");
|
||||
foreach (var (s, w) in table.ColumnSpans.Zip(table.ColumnWidths)) {
|
||||
for (int i = 0; i < s; i++) {
|
||||
await Content.WriteAsync(" <table:table-column" + (w != null ? $" table:style-name=\"colw{w / s}\"" : "") + "/>\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
await Content.WriteAsync(
|
||||
@ -189,48 +195,76 @@ namespace Elwig.Helpers.Export {
|
||||
$" <table:table-cell table:number-columns-repeated=\"{totalSpan}\"/>\r\n" +
|
||||
$" </table:table-row>\r\n" +
|
||||
$" <table:table-row>\r\n");
|
||||
foreach (var (name, span) in table.ColumnNames.Zip(table.ColumnSpans)) {
|
||||
await Content.WriteAsync(FormatCell(name, colSpan: span, style: "th"));
|
||||
foreach (var (name, span, units) in table.ColumnNames.Zip(table.ColumnSpans, table.ColumnUnits)) {
|
||||
var hasUnits = units.Length > 0;
|
||||
await Content.WriteAsync(FormatCell(name, colSpan: span, rowSpan: hasUnits ? 1 : 2, style: "th"));
|
||||
}
|
||||
await Content.WriteAsync(" </table:table-row>\r\n <table:table-row>\r\n");
|
||||
foreach (var (span, units) in table.ColumnSpans.Zip(table.ColumnUnits)) {
|
||||
if (units.Length == 0) {
|
||||
await Content.WriteAsync($" <table:covered-table-cell table:number-columns-repeated=\"{span}\"/>\r\n");
|
||||
continue;
|
||||
}
|
||||
foreach (var u in units) {
|
||||
await Content.WriteAsync(FormatCell(u == null ? null : $"[{u}]", style: "th"));
|
||||
}
|
||||
}
|
||||
await Content.WriteAsync(" </table:table-row>\r\n");
|
||||
|
||||
foreach (var row in table.GetData()) {
|
||||
await FormatRow(row);
|
||||
await FormatRow(row, table.ColumnUnits);
|
||||
}
|
||||
|
||||
await Content.WriteAsync(" </table:table>\r\n");
|
||||
}
|
||||
|
||||
protected async Task FormatRow(IEnumerable<object?> row) {
|
||||
protected async Task FormatRow(IEnumerable<object?> row, IEnumerable<string?[]?> colUnits) {
|
||||
if (Content == null) throw new InvalidOperationException();
|
||||
var arrays = row.Where(c => c is Array).Cast<Array>().Select(c => c.Length).ToArray();
|
||||
int rowNum = Math.Max(1, arrays.Length > 0 ? arrays.Max() : 0);
|
||||
for (int i = 0; i < rowNum; i++) {
|
||||
await Content.WriteAsync(" <table:table-row>\r\n");
|
||||
foreach (var data in row) {
|
||||
foreach (var (data, units) in row.Zip(colUnits)) {
|
||||
if (data is Array a) {
|
||||
await Content.WriteAsync(i < a.Length ? FormatCell(a.GetValue(i)) : " <table:table-cell/>\r\n");
|
||||
await Content.WriteAsync(i < a.Length ? FormatCell(a.GetValue(i), units: units) : $" <table:table-cell tables:number-columns-repeated=\"{GetSubCols(a.GetType().GetElementType())}\"/>\r\n");
|
||||
} else {
|
||||
await Content.WriteAsync(i == 0 ? FormatCell(data, rowNum) : " <table:covered-table-cell/>\r\n");
|
||||
await Content.WriteAsync(FormatCell(data, rowSpan: i == 0 ? rowNum : 1, isCovered: i > 0, units: units));
|
||||
}
|
||||
}
|
||||
await Content.WriteAsync(" </table:table-row>\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
protected static string FormatCell(object? data, int rowSpan = 1, int colSpan = 1, string? style = "default", bool forceString = false) {
|
||||
private static int GetSubCols(Type? type) {
|
||||
if (type != null && type.IsValueType == true && type.Name.StartsWith("ValueTuple"))
|
||||
return type.GetFields().Length;
|
||||
return 1;
|
||||
}
|
||||
|
||||
protected static string FormatCell(object? data, int rowSpan = 1, int colSpan = 1, string? style = "default", bool isCovered = false, string?[]? units = null) {
|
||||
if (data?.GetType().IsValueType == true && data.GetType().Name.StartsWith("ValueTuple"))
|
||||
return string.Join("", data.GetType().GetFields().Select(p => FormatCell(p.GetValue(data), rowSpan, colSpan, style, forceString)));
|
||||
return string.Join("", data.GetType().GetFields().Zip(units ?? Array.Empty<string?>())
|
||||
.Select(p => FormatCell(p.First.GetValue(data), rowSpan, colSpan, style, isCovered, new[] { p.Second }))
|
||||
);
|
||||
|
||||
var add = (style != null ? $" table:style-name=\"{style}\"" : "") + (rowSpan > 1 || colSpan > 1 ? $" table:number-rows-spanned=\"{rowSpan}\" table:number-columns-spanned=\"{colSpan}\"" : "");
|
||||
string ct = isCovered ? "table:covered-table-cell" : "table:table-cell";
|
||||
var isPercent = units != null && units.Length > 0 && units[0] == "%";
|
||||
|
||||
string c;
|
||||
if (!forceString && data == null) {
|
||||
c = $"<table:table-cell{add}/>";
|
||||
} else if (!forceString && (data is float || data is double || data is byte || data is char ||
|
||||
data is short || data is ushort || data is int || data is uint || data is long || data is ulong)) {
|
||||
c = $"<table:table-cell office:value-type=\"float\" office:value=\"{data.ToString()?.Replace(",", ".")}\"{add}><text:p>{data}</text:p></table:table-cell>";
|
||||
if (data == null) {
|
||||
c = $"<{ct}{add}/>";
|
||||
} else if (data is float || data is double || data is byte || data is char ||
|
||||
data is short || data is ushort || data is int || data is uint || data is long || data is ulong) {
|
||||
|
||||
double v = double.Parse(data?.ToString() ?? "0");
|
||||
if (isPercent) {
|
||||
data = $"{v:N1}";
|
||||
add = string.Join(" ", add.Split(" ").Select(p => p.StartsWith("table:style-name=") ? "table:style-name=\"perc\"" : p));
|
||||
}
|
||||
c = $"<{ct} office:value-type=\"float\" calcext:value-type=\"float\" office:value=\"{v.ToString()?.Replace(",", ".")}\"{add}><text:p>{data}</text:p></{ct}>";
|
||||
} else {
|
||||
c = $"<table:table-cell office:value-type=\"string\"{add}><text:p>{data}</text:p></table:table-cell>";
|
||||
c = $"<{ct} office:value-type=\"string\" calcext:value-type=\"string\"{add}><text:p>{data}</text:p></{ct}>";
|
||||
}
|
||||
|
||||
return $" {c}\r\n" + (colSpan > 1 ? $" <table:covered-table-cell table:number-rows-repeated=\"{colSpan - 1}\"/>\r\n" : "");
|
||||
|
Reference in New Issue
Block a user