mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-14 01:25:40 -05:00
Save As Excel (#279)
* Fix dispose pattern usage in SaveAsWriterBase * Add SaveAsExcel feature This adds the save as excel function to the backend. To reduce large dependency and run on dotnet core now, this implementation use a raw excel writer (the SaveAsExcelFileStreamWriterHelper.cs) instrad of popular excel library, such as EPPlus or OpenXmlSdk. * Fix can not open the generated excel file in google sheet For the file name inside excel, google uses a case sensitive path while Excel doesn't. This change fix the case, so that the file name matches the one in x1/_rels/workbook.xml.rels * Fix datetime doesn't recognized by google sheet Google doesn't support cell type t="d" with ISO 8601 date. (From stackoverflow thread and testing), thus use the old way of excel datetime, which uses double to present datetime * update to use xmlwriter * Add basic unit tests for SaveAsExcelFileStreamWriterHelper * refactor: simplify the public interface of the SaveAsExcelFileStreamWriterHelper * update private fields names based on the name convention * Add comments to classes of SaveAsExcel feature * clean up SaveAsExcelFileStreamWriterHelper - change SaveAsExcelFileStreamWriterHelper from public to internal - remove the PenddingRowEndTag function from referenceManager - change the SaveAsExcelFileStreamWriterHelper(stream) to default leaveOpen to false to match the normal behavior - change the rowreference to use XmlConvert to convert int to string - rename writeSetting to writerSetting and add private * fix CI test error for SaveAsExcel * remove ExporterException in SaveAsExcel * fix lefe over CSV to Excel in the comments * refactor to be consistent with JsonWriter and remove the comment * remove commented out test The test is too slow to run * fix typo in comment * refactor SaveAsExcel to the coding standard * refactor rewrite the WriteStyle with XmlWriter * Add licence header * reverse mistakenly checked-in changes * fix: left-over CSV in commets * remove duplicate check The check was done in the IncreaseColumnReference, but that check is too late in case of too many columns. All the addCell do the check at the begining now * fix TimeSpan more than 24 hours * fix AddRowMustBeCalledBeforeAddCellException test This is due to remove duplicate call to AssureColumnReference in WriteAndIncreaseColumnReference * fix: TimeSpan will write twice * style: change retun in the switch to break * Add bool format * remove todo in comment This provides extra safeguard in the cost of one memory access when null.
This commit is contained in:
committed by
Benjamin Russell
parent
8d017368f9
commit
8d47d5c7b3
@@ -0,0 +1,241 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage;
|
||||
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
||||
using Moq;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.DataStorage
|
||||
{
|
||||
public class SaveAsExcelFileStreamWriterHelperTests : IDisposable
|
||||
{
|
||||
private Stream _stream;
|
||||
public SaveAsExcelFileStreamWriterHelperTests()
|
||||
{
|
||||
_stream = new MemoryStream();
|
||||
using (var helper = new SaveAsExcelFileStreamWriterHelper(_stream, true))
|
||||
using (var sheet = helper.AddSheet())
|
||||
{
|
||||
DbCellValue value = new DbCellValue();
|
||||
sheet.AddRow();
|
||||
|
||||
value.IsNull = true;
|
||||
sheet.AddCell(value);
|
||||
|
||||
value.IsNull = false;
|
||||
value.RawObject = "";
|
||||
sheet.AddCell(value);
|
||||
|
||||
value.RawObject = "test string";
|
||||
sheet.AddCell(value);
|
||||
|
||||
value.RawObject = 3;
|
||||
sheet.AddCell(value);
|
||||
|
||||
value.RawObject = 3.5;
|
||||
sheet.AddCell(value);
|
||||
|
||||
value.RawObject = false;
|
||||
sheet.AddCell(value);
|
||||
|
||||
value.RawObject = true;
|
||||
sheet.AddCell(value);
|
||||
|
||||
sheet.AddRow();
|
||||
|
||||
value.RawObject = new DateTime(1900, 2, 28);
|
||||
sheet.AddCell(value);
|
||||
|
||||
value.RawObject = new DateTime(1900, 3, 1);
|
||||
sheet.AddCell(value);
|
||||
|
||||
value.RawObject = new DateTime(1900, 3, 1, 15, 00, 00);
|
||||
sheet.AddCell(value);
|
||||
|
||||
value.RawObject = new DateTime(1, 1, 1, 15, 00, 00);
|
||||
sheet.AddCell(value);
|
||||
|
||||
value.RawObject = new TimeSpan(15, 00, 00);
|
||||
sheet.AddCell(value);
|
||||
|
||||
value.RawObject = new TimeSpan(24, 00, 00);
|
||||
sheet.AddCell(value);
|
||||
}
|
||||
}
|
||||
Regex contentRemoveLinebreakLeadingSpace = new Regex(@"\r?\n\s*");
|
||||
private void ContentMatch(string fileName)
|
||||
{
|
||||
string referencePath = Path.Combine(RunEnvironmentInfo.GetTestDataLocation(),
|
||||
"DataStorage",
|
||||
"SaveAsExcelFileStreamWriterHelperTests",
|
||||
fileName);
|
||||
string referenceContent = File.ReadAllText(referencePath);
|
||||
referenceContent = contentRemoveLinebreakLeadingSpace.Replace(referenceContent, "");
|
||||
|
||||
using (ZipArchive zip = new ZipArchive(_stream, ZipArchiveMode.Read, true))
|
||||
{
|
||||
using (var reader = new StreamReader(zip.GetEntry(fileName).Open()))
|
||||
{
|
||||
string realContent = reader.ReadToEnd();
|
||||
Assert.Equal(referenceContent, realContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
[Fact]
|
||||
public void CheckContentType()
|
||||
{
|
||||
ContentMatch("[Content_Types].xml");
|
||||
}
|
||||
[Fact]
|
||||
public void CheckTopRels()
|
||||
{
|
||||
ContentMatch("_rels/.rels");
|
||||
}
|
||||
[Fact]
|
||||
public void CheckWorkbookRels()
|
||||
{
|
||||
ContentMatch("xl/_rels/workbook.xml.rels");
|
||||
}
|
||||
[Fact]
|
||||
public void CheckStyles()
|
||||
{
|
||||
ContentMatch("xl/styles.xml");
|
||||
}
|
||||
[Fact]
|
||||
public void CheckWorkbook()
|
||||
{
|
||||
ContentMatch("xl/workbook.xml");
|
||||
}
|
||||
[Fact]
|
||||
public void CheckSheet1()
|
||||
{
|
||||
ContentMatch("xl/worksheets/sheet1.xml");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_stream.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public class SaveAsExcelFileStreamWriterHelperReferenceManagerTests
|
||||
{
|
||||
private Mock<XmlWriter> _xmlWriterMock;
|
||||
private string LastWrittenReference { get; set; }
|
||||
private int LastWrittenRow { get; set; }
|
||||
|
||||
public SaveAsExcelFileStreamWriterHelperReferenceManagerTests()
|
||||
{
|
||||
_xmlWriterMock = new Mock<XmlWriter>(MockBehavior.Strict);
|
||||
_xmlWriterMock
|
||||
.Setup(_xmlWriter => _xmlWriter.WriteChars(
|
||||
It.IsAny<char[]>(), It.IsAny<int>(), It.IsAny<int>()))
|
||||
.Callback<char[], int, int>((array, index, count) =>
|
||||
LastWrittenReference = new string(array, index, count));
|
||||
_xmlWriterMock.Setup(a => a.WriteStartAttribute(null, "r", null));
|
||||
_xmlWriterMock.Setup(a => a.WriteEndAttribute());
|
||||
_xmlWriterMock.Setup(a => a.WriteValue(It.IsAny<int>()))
|
||||
.Callback<int>(row => LastWrittenRow = row);
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReferenceA1()
|
||||
{
|
||||
var xmlWriter = _xmlWriterMock.Object;
|
||||
var manager = new SaveAsExcelFileStreamWriterHelper.ReferenceManager(xmlWriter);
|
||||
manager.WriteAndIncreaseRowReference();
|
||||
manager.WriteAndIncreaseColumnReference();
|
||||
Assert.Equal("A1", LastWrittenReference);
|
||||
}
|
||||
[Fact]
|
||||
public void ReferenceZ1()
|
||||
{
|
||||
var xmlWriter = _xmlWriterMock.Object;
|
||||
var manager = new SaveAsExcelFileStreamWriterHelper.ReferenceManager(xmlWriter);
|
||||
manager.WriteAndIncreaseRowReference();
|
||||
for (int i = 0; i < 26 - 1; i++)
|
||||
{
|
||||
manager.IncreaseColumnReference();
|
||||
}
|
||||
manager.WriteAndIncreaseColumnReference();
|
||||
Assert.Equal("Z1", LastWrittenReference);
|
||||
manager.WriteAndIncreaseColumnReference();
|
||||
Assert.Equal("AA1", LastWrittenReference);
|
||||
}
|
||||
[Fact]
|
||||
public void ReferenceZZ1()
|
||||
{
|
||||
var xmlWriter = _xmlWriterMock.Object;
|
||||
var manager = new SaveAsExcelFileStreamWriterHelper.ReferenceManager(xmlWriter);
|
||||
manager.WriteAndIncreaseRowReference();
|
||||
|
||||
for (int i = 0; i < 27 * 26 - 1; i++)
|
||||
{
|
||||
manager.IncreaseColumnReference();
|
||||
}
|
||||
manager.WriteAndIncreaseColumnReference();
|
||||
Assert.Equal("ZZ1", LastWrittenReference);
|
||||
manager.WriteAndIncreaseColumnReference();
|
||||
Assert.Equal("AAA1", LastWrittenReference);
|
||||
}
|
||||
[Fact]
|
||||
public void ReferenceXFD()
|
||||
{
|
||||
var xmlWriter = _xmlWriterMock.Object;
|
||||
var manager = new SaveAsExcelFileStreamWriterHelper.ReferenceManager(xmlWriter);
|
||||
manager.WriteAndIncreaseRowReference();
|
||||
|
||||
for (int i = 0; i < 16384 - 1; i++)
|
||||
{
|
||||
manager.IncreaseColumnReference();
|
||||
}
|
||||
//The 16384 should be the maximal column and not throw
|
||||
manager.AssureColumnReference();
|
||||
manager.WriteAndIncreaseColumnReference();
|
||||
Assert.Equal("XFD1", LastWrittenReference);
|
||||
var ex = Assert.Throws<InvalidOperationException>(
|
||||
() => manager.AssureColumnReference());
|
||||
Assert.Contains("max column number is 16384", ex.Message);
|
||||
}
|
||||
[Fact]
|
||||
public void ReferenceRowReset()
|
||||
{
|
||||
var xmlWriter = _xmlWriterMock.Object;
|
||||
var manager = new SaveAsExcelFileStreamWriterHelper.ReferenceManager(xmlWriter);
|
||||
manager.WriteAndIncreaseRowReference();
|
||||
Assert.Equal(1, LastWrittenRow);
|
||||
manager.WriteAndIncreaseColumnReference();
|
||||
Assert.Equal("A1", LastWrittenReference);
|
||||
manager.WriteAndIncreaseColumnReference();
|
||||
Assert.Equal("B1", LastWrittenReference);
|
||||
|
||||
//add row should reset column reference
|
||||
manager.WriteAndIncreaseRowReference();
|
||||
Assert.Equal(2, LastWrittenRow);
|
||||
manager.WriteAndIncreaseColumnReference();
|
||||
Assert.Equal("A2", LastWrittenReference);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRowMustBeCalledBeforeAddCellException()
|
||||
{
|
||||
var xmlWriter = _xmlWriterMock.Object;
|
||||
var manager = new SaveAsExcelFileStreamWriterHelper.ReferenceManager(xmlWriter);
|
||||
|
||||
var ex = Assert.Throws<InvalidOperationException>(
|
||||
() => manager.AssureColumnReference());
|
||||
Assert.Contains("AddRow must be called before AddCell", ex.Message);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user