Skip to content

Commit

Permalink
Allow ampersand in sheet names (#574)
Browse files Browse the repository at this point in the history
* Write out XML encoded sheet names

* Fix culture errors when running unit tests

* Force dot as decimal separator column width, fixes bug triggered by TestIssueI4ZYUU

---------

Co-authored-by: Jef Van den Brandt <[email protected]>
  • Loading branch information
ofthelit and Jef Van den Brandt authored Mar 24, 2024
1 parent 7894e28 commit 567d70b
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 98 deletions.
28 changes: 14 additions & 14 deletions src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ private void GenerateSheetByEnumerable(MiniExcelStreamWriter writer, IEnumerable
{
mode = "IDictionary";
props = GetDictionaryColumnInfo(null, dic);
//maxColumnIndex = dic.Keys.Count;
//maxColumnIndex = dic.Keys.Count;
maxColumnIndex = props.Count; // why not using keys, because ignore attribute ![image](https://user-images.githubusercontent.com/12729184/163686902-286abb70-877b-4e84-bd3b-001ad339a84a.png)
}
else
Expand All @@ -189,7 +189,7 @@ private void GenerateSheetByEnumerable(MiniExcelStreamWriter writer, IEnumerable
writer.Write($@"<?xml version=""1.0"" encoding=""utf-8""?><x:worksheet xmlns:r=""http://schemas.openxmlformats.org/officeDocument/2006/relationships"" xmlns:x=""http://schemas.openxmlformats.org/spreadsheetml/2006/main"" >");

long dimensionWritePosition = 0;

// We can write the dimensions directly if the row count is known
if (_configuration.FastMode && rowCount == null)
{
Expand Down Expand Up @@ -327,7 +327,7 @@ private void SetDictionaryColumnInfo(List<ExcelColumnInfo> _props, object key)
p.ExcelColumnName = key?.ToString();
p.Key = key;
// TODO:Dictionary value type is not fiexed
//var _t =
//var _t =
//var gt = Nullable.GetUnderlyingType(p.PropertyType);
var isIgnore = false;
if (_configuration.DynamicColumns != null && _configuration.DynamicColumns.Length > 0)
Expand Down Expand Up @@ -465,7 +465,7 @@ private Tuple<string, string, string> GetCellValue(int rowIndex, int cellIndex,
Type type = null;
if (p == null || p.Key != null)
{
// TODO: need to optimize
// TODO: need to optimize
// Dictionary need to check type every time, so it's slow..
type = value.GetType();
type = Nullable.GetUnderlyingType(type) ?? type;
Expand Down Expand Up @@ -667,7 +667,7 @@ private void GenerateSheetByIDataReader(MiniExcelStreamWriter writer, IDataReade
maxColumnIndex = props.Count;

WriteColumnsWidths(writer, props);

writer.Write("<x:sheetData>");
int fieldCount = reader.FieldCount;
if (_printHeader)
Expand Down Expand Up @@ -713,9 +713,9 @@ private ExcelColumnInfo GetColumnInfosFromDynamicConfiguration(string columnName
Key = columnName
};

if (_configuration.DynamicColumns == null || _configuration.DynamicColumns.Length <= 0)
if (_configuration.DynamicColumns == null || _configuration.DynamicColumns.Length <= 0)
return prop;

var dynamicColumn = _configuration.DynamicColumns.SingleOrDefault(_ => _.Key == columnName);
if (dynamicColumn == null || dynamicColumn.Ignore)
{
Expand Down Expand Up @@ -772,12 +772,12 @@ private static void WriteColumnsWidths(MiniExcelStreamWriter writer, IEnumerable
foreach (var p in ecwProps)
{
writer.Write(
$@"<x:col min=""{p.ExcelColumnIndex + 1}"" max=""{p.ExcelColumnIndex + 1}"" width=""{p.ExcelColumnWidth}"" customWidth=""1"" />");
$@"<x:col min=""{p.ExcelColumnIndex + 1}"" max=""{p.ExcelColumnIndex + 1}"" width=""{p.ExcelColumnWidth?.ToString(CultureInfo.InvariantCulture)}"" customWidth=""1"" />");
}

writer.Write($@"</x:cols>");
}

private static void WriteC(MiniExcelStreamWriter writer, string r, string columnName)
{
writer.Write($"<x:c r=\"{r}\" t=\"str\" s=\"1\">");
Expand All @@ -796,7 +796,7 @@ private void GenerateEndXml()
}
}

// styles.xml
// styles.xml
{
var styleXml = string.Empty;

Expand Down Expand Up @@ -882,15 +882,15 @@ private void GenerateEndXml()
sheetId++;
if (string.IsNullOrEmpty(s.State))
{
workbookXml.AppendLine($@"<x:sheet name=""{s.Name}"" sheetId=""{sheetId}"" r:id=""{s.ID}"" />");
workbookXml.AppendLine($@"<x:sheet name=""{ExcelOpenXmlUtils.EncodeXML(s.Name)}"" sheetId=""{sheetId}"" r:id=""{s.ID}"" />");
}
else
{
workbookXml.AppendLine($@"<x:sheet name=""{s.Name}"" sheetId=""{sheetId}"" state=""{s.State}"" r:id=""{s.ID}"" />");
workbookXml.AppendLine($@"<x:sheet name=""{ExcelOpenXmlUtils.EncodeXML(s.Name)}"" sheetId=""{sheetId}"" state=""{s.State}"" r:id=""{s.ID}"" />");
}
workbookRelsXml.AppendLine($@"<Relationship Type=""http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"" Target=""/{s.Path}"" Id=""{s.ID}"" />");

//TODO: support multiple drawing
//TODO: support multiple drawing
//TODO: ../drawings/drawing1.xml or /xl/drawings/drawing1.xml
var sheetRelsXml = $@"<Relationship Type=""http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing"" Target=""../drawings/drawing{sheetId}.xml"" Id=""drawing{sheetId}"" />";
CreateZipEntry($"xl/worksheets/_rels/sheet{s.SheetIdx}.xml.rels", "",
Expand All @@ -902,7 +902,7 @@ private void GenerateEndXml()
_defaultWorkbookXmlRels.Replace("{{sheets}}", workbookRelsXml.ToString()));
}

//[Content_Types].xml
//[Content_Types].xml
{
var sb = new StringBuilder(@"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?><Types xmlns=""http://schemas.openxmlformats.org/package/2006/content-types""><Default ContentType=""application/vnd.openxmlformats-officedocument.spreadsheetml.printerSettings"" Extension=""bin""/><Default ContentType=""application/xml"" Extension=""xml""/><Default ContentType=""image/jpeg"" Extension=""jpg""/><Default ContentType=""image/png"" Extension=""png""/><Default ContentType=""image/gif"" Extension=""gif""/><Default ContentType=""application/vnd.openxmlformats-package.relationships+xml"" Extension=""rels""/>");
foreach (var p in _zipDictionary)
Expand Down
60 changes: 29 additions & 31 deletions tests/MiniExcelTests/MiniExcelIssueAsyncTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public class Issue255DTO
{
[ExcelFormat("yyyy")]
public DateTime Time { get; set; }

[ExcelColumn(Format = "yyyy")]
public DateTime Time2 { get; set; }
}
Expand Down Expand Up @@ -229,7 +229,7 @@ public async Task Issue241()

{
var q = await MiniExcel.QueryAsync<Issue241Dto>(path);
var rows = q.ToList();
var rows = q.ToList();
Assert.Equal(rows[0].InDate, new DateTime(2021, 01, 04));
Assert.Equal(rows[1].InDate, new DateTime(2020, 04, 05));
}
Expand Down Expand Up @@ -548,35 +548,33 @@ public async Task Issue227()
{
var q = await MiniExcel.QueryAsync<UserAccount>(path);
var rows = q.ToList();
Assert.Equal(100, rows.Count());
Assert.Equal(100, rows.Count);

Assert.Equal(Guid.Parse("78DE23D2-DCB6-BD3D-EC67-C112BBC322A2"), rows[0].ID);
Assert.Equal("Wade", rows[0].Name);
Assert.Equal(DateTime.ParseExact("27/09/2020", "dd/MM/yyyy", CultureInfo.InvariantCulture), rows[0].BoD);
Assert.Equal(36, rows[0].Age);
Assert.False(rows[0].VIP);
Assert.Equal(decimal.Parse("5019.12"), rows[0].Points);
Assert.Equal(5019.12m, rows[0].Points);
Assert.Equal(1, rows[0].IgnoredProperty);
}
{
using (var stream = File.OpenRead(path))
{
var q = await stream.QueryAsync<UserAccount>();
var rows = q.ToList();
Assert.Equal(100, rows.Count());
Assert.Equal(100, rows.Count);

Assert.Equal(Guid.Parse("78DE23D2-DCB6-BD3D-EC67-C112BBC322A2"), rows[0].ID);
Assert.Equal("Wade", rows[0].Name);
Assert.Equal(DateTime.ParseExact("27/09/2020", "dd/MM/yyyy", CultureInfo.InvariantCulture), rows[0].BoD);
Assert.Equal(36, rows[0].Age);
Assert.False(rows[0].VIP);
Assert.Equal(decimal.Parse("5019.12"), rows[0].Points);
Assert.Equal(5019.12m, rows[0].Points);
Assert.Equal(1, rows[0].IgnoredProperty);
}
}
}


}

/// <summary>
Expand Down Expand Up @@ -613,7 +611,7 @@ public async Task Issue223()
Assert.Equal(typeof(object), columns[0].DataType);
Assert.Equal(typeof(object), columns[1].DataType);

Assert.Equal((double)123, dt.Rows[1]["A"]);
Assert.Equal(123.0, dt.Rows[1]["A"]);
Assert.Equal("HelloWorld", dt.Rows[2]["B"]);
}

Expand Down Expand Up @@ -698,10 +696,10 @@ public async Task Issue211()

var q = await MiniExcel.QueryAsync(path, true);
var rows = q.ToList();
Assert.Equal((double)1, rows[0].Test1);
Assert.Equal((double)2, rows[0].Test2);
Assert.Equal((double)3, rows[1].Test1);
Assert.Equal((double)4, rows[1].Test2);
Assert.Equal(1.0, rows[0].Test1);
Assert.Equal(2.0, rows[0].Test2);
Assert.Equal(3.0, rows[1].Test1);
Assert.Equal(4.0, rows[1].Test2);
}
}

Expand All @@ -721,19 +719,19 @@ public async Task Issue216()
Assert.Equal("Test1", table.Columns[0].ColumnName);
Assert.Equal("Test2", table.Columns[1].ColumnName);
Assert.Equal("1", table.Rows[0]["Test1"]);
Assert.Equal((double)2, table.Rows[0]["Test2"]);
Assert.Equal(2.0, table.Rows[0]["Test2"]);
Assert.Equal("3", table.Rows[1]["Test1"]);
Assert.Equal((double)4, table.Rows[1]["Test2"]);
Assert.Equal(4.0, table.Rows[1]["Test2"]);
}

{
var dt = await MiniExcel.QueryAsDataTableAsync(path, false);

Check warning on line 728 in tests/MiniExcelTests/MiniExcelIssueAsyncTests.cs

View workflow job for this annotation

GitHub Actions / build

'MiniExcel.QueryAsDataTableAsync(string, bool, string, ExcelType, string, IConfiguration, CancellationToken)' is obsolete: 'QueryAsDataTable is not recommended, because it'll load all data into memory.'
Assert.Equal("Test1", dt.Rows[0]["A"]);
Assert.Equal("Test2", dt.Rows[0]["B"]);
Assert.Equal("1", dt.Rows[1]["A"]);
Assert.Equal((double)2, dt.Rows[1]["B"]);
Assert.Equal(2.0, dt.Rows[1]["B"]);
Assert.Equal("3", dt.Rows[2]["A"]);
Assert.Equal((double)4, dt.Rows[2]["B"]);
Assert.Equal(4.0, dt.Rows[2]["B"]);
}
}

Expand Down Expand Up @@ -794,7 +792,7 @@ group s by s.PRT_ID into g
}

/// <summary>
/// Optimize stream excel type check
/// Optimize stream excel type check
/// https://github.com/shps951023/MiniExcel/issues/215
/// </summary>
[Fact]
Expand Down Expand Up @@ -1292,7 +1290,7 @@ public async Task Issue142_Query()
var path = @"../../../../../samples/xlsx/TestIssue142.xlsx";
await Assert.ThrowsAsync<ArgumentException>(async () =>
{
var q = (await MiniExcel.QueryAsync<Issue142VoOverIndex>(path)).ToList();
var q = (await MiniExcel.QueryAsync<Issue142VoOverIndex>(path)).ToList();
});
}

Expand Down Expand Up @@ -1413,7 +1411,7 @@ public async Task Issue157()
{
var q = await MiniExcel.QueryAsync(path, sheetName: "Sheet1");
var rows = q.ToList();
Assert.Equal(6, rows.Count());
Assert.Equal(6, rows.Count);
Assert.Equal("Sheet1", MiniExcel.GetSheetNames(path).First());
}
using (var p = new ExcelPackage(new FileInfo(path)))
Expand All @@ -1427,13 +1425,13 @@ public async Task Issue157()
{
var q = await MiniExcel.QueryAsync<UserAccount>(path, sheetName: "Sheet1");
var rows = q.ToList();
Assert.Equal(5, rows.Count());
Assert.Equal(5, rows.Count);

Assert.Equal(Guid.Parse("78DE23D2-DCB6-BD3D-EC67-C112BBC322A2"), rows[0].ID);
Assert.Equal("Wade", rows[0].Name);
Assert.Equal(DateTime.ParseExact("27/09/2020", "dd/MM/yyyy", CultureInfo.InvariantCulture), rows[0].BoD);
Assert.False(rows[0].VIP);
Assert.Equal(decimal.Parse("5019"), rows[0].Points);
Assert.Equal(5019m, rows[0].Points);
Assert.Equal(1, rows[0].IgnoredProperty);
}
}
Expand Down Expand Up @@ -1491,7 +1489,7 @@ public async Task Issue149()
await MiniExcel.SaveAsAsync(path, input);

var q = await MiniExcel.QueryAsync<Issue149VO>(path);
var rows = q.Select(s => (string)s.Test).ToList();
var rows = q.Select(s => s.Test).ToList();
for (int i = 0; i < chars.Length; i++)
{
output.WriteLine($"{i} , {chars[i]} , {rows[i]}");
Expand All @@ -1516,7 +1514,7 @@ public async Task Issue153()
var path = @"../../../../../samples/xlsx/TestIssue153.xlsx";
var q = await MiniExcel.QueryAsync(path, true);
var rows = q.First() as IDictionary<string, object>;

Assert.Equal(new[] { "序号", "代号", "新代号", "名称", "XXX", "部门名称", "单位", "ERP工时 (小时)A", "工时(秒) A/3600", "标准人工工时(秒)", "生产标准机器工时(秒)", "财务、标准机器工时(秒)", "更新日期", "产品机种", "备注", "最近一次修改前的标准工时(秒)", "最近一次修改前的标准机时(秒)", "备注1" }
, rows.Keys);
}
Expand All @@ -1543,14 +1541,14 @@ public async Task Issue137()
Assert.Equal(" ", row["D"]);
Assert.Equal(" ", row["E"]);
Assert.Equal(" ", row["F"]);
Assert.Equal(Double.Parse("0"), row["G"]);
Assert.Equal(0.0, row["G"]);
Assert.Equal("1為港幣 0為台幣", row["H"]);
}
{
var row = rows[1] as IDictionary<string, object>;
Assert.Equal(double.Parse("1"), row["A"]);
Assert.Equal(1.0, row["A"]);
Assert.Equal("MTX", row["B"]);
Assert.Equal(double.Parse("10"), row["C"]);
Assert.Equal(10.0, row["C"]);
Assert.Null(row["D"]);
Assert.Null(row["E"]);
Assert.Null(row["F"]);
Expand All @@ -1559,7 +1557,7 @@ public async Task Issue137()
}
{
var row = rows[2] as IDictionary<string, object>;
Assert.Equal(double.Parse("0.95"), row["A"]);
Assert.Equal(0.95, row["A"]);
}
}

Expand All @@ -1572,16 +1570,16 @@ public async Task Issue137()
Assert.Equal(10, rows.Count);
{
var row = rows[0] as IDictionary<string, object>;
Assert.Equal(double.Parse("1"), row["比例"]);
Assert.Equal(1.0, row["比例"]);
Assert.Equal("MTX", row["商品"]);
Assert.Equal(double.Parse("10"), row["滿倉口數"]);
Assert.Equal(10.0, row["滿倉口數"]);
Assert.Null(row["0"]);
Assert.Null(row["1為港幣 0為台幣"]);
}

{
var row = rows[1] as IDictionary<string, object>;
Assert.Equal(double.Parse("0.95"), row["比例"]);
Assert.Equal(0.95, row["比例"]);
}
}

Expand Down
Loading

0 comments on commit 567d70b

Please sign in to comment.