mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-13 17:23:02 -05:00
Improves "save as Excel" functionality (#2266)
This commit is contained in:
@@ -57,6 +57,8 @@
|
|||||||
<PackageReference Update="coverlet.collector" Version="6.0.0" />
|
<PackageReference Update="coverlet.collector" Version="6.0.0" />
|
||||||
<PackageReference Update="coverlet.msbuild" Version="6.0.0" />
|
<PackageReference Update="coverlet.msbuild" Version="6.0.0" />
|
||||||
<PackageReference Update="Microsoft.SqlServer.XEvent.XELite" Version="2023.1.30.3" />
|
<PackageReference Update="Microsoft.SqlServer.XEvent.XELite" Version="2023.1.30.3" />
|
||||||
|
<PackageReference Update="SkiaSharp" Version="2.88.6" />
|
||||||
|
<PackageReference Update="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="2.88.6" Condition="$([MSBuild]::IsOsPlatform('Linux'))" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- When updating version of Dependencies in the below section, please also update the version in the following files:
|
<!-- When updating version of Dependencies in the below section, please also update the version in the following files:
|
||||||
|
|||||||
@@ -50,6 +50,8 @@
|
|||||||
<PackageReference Include="Microsoft.SqlServer.Management.SmoMetadataProvider" />
|
<PackageReference Include="Microsoft.SqlServer.Management.SmoMetadataProvider" />
|
||||||
<PackageReference Include="Microsoft.SqlServer.SqlManagementObjects" />
|
<PackageReference Include="Microsoft.SqlServer.SqlManagementObjects" />
|
||||||
<PackageReference Include="Microsoft.SqlServer.Management.SqlParser" />
|
<PackageReference Include="Microsoft.SqlServer.Management.SqlParser" />
|
||||||
|
<PackageReference Include="SkiaSharp" />
|
||||||
|
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Condition="$([MSBuild]::IsOsPlatform('Linux'))" />
|
||||||
<PackageReference Include="System.Configuration.ConfigurationManager" />
|
<PackageReference Include="System.Configuration.ConfigurationManager" />
|
||||||
<PackageReference Include="System.Text.Encoding.CodePages" />
|
<PackageReference Include="System.Text.Encoding.CodePages" />
|
||||||
<PackageReference Include="Microsoft.SqlServer.XEvent.XELite" />
|
<PackageReference Include="Microsoft.SqlServer.XEvent.XELite" />
|
||||||
|
|||||||
@@ -115,6 +115,26 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
|||||||
/// Include headers of columns in Excel
|
/// Include headers of columns in Excel
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IncludeHeaders { get; set; }
|
public bool IncludeHeaders { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Freeze header row in Excel
|
||||||
|
/// </summary>
|
||||||
|
public bool FreezeHeaderRow { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bold header row in Excel
|
||||||
|
/// </summary>
|
||||||
|
public bool BoldHeaderRow { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enable auto filter on header row in Excel
|
||||||
|
/// </summary>
|
||||||
|
public bool AutoFilterHeaderRow { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Auto size columns in Excel
|
||||||
|
/// </summary>
|
||||||
|
public bool AutoSizeColumns { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -157,6 +157,30 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
|||||||
get { return this.GetOptionValue<int>(SerializationOptionsHelper.MaxCharsToStore); }
|
get { return this.GetOptionValue<int>(SerializationOptionsHelper.MaxCharsToStore); }
|
||||||
set { this.SetOptionValue<int>(SerializationOptionsHelper.Formatted, value); }
|
set { this.SetOptionValue<int>(SerializationOptionsHelper.Formatted, value); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool FreezeHeaderRow
|
||||||
|
{
|
||||||
|
get { return this.GetOptionValue<bool>(SerializationOptionsHelper.FreezeHeaderRow); }
|
||||||
|
set { this.SetOptionValue<bool>(SerializationOptionsHelper.FreezeHeaderRow, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool BoldHeaderRow
|
||||||
|
{
|
||||||
|
get { return this.GetOptionValue<bool>(SerializationOptionsHelper.BoldHeaderRow); }
|
||||||
|
set { this.SetOptionValue<bool>(SerializationOptionsHelper.BoldHeaderRow, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AutoFilterHeaderRow
|
||||||
|
{
|
||||||
|
get { return this.GetOptionValue<bool>(SerializationOptionsHelper.AutoFilterHeaderRow); }
|
||||||
|
set { this.SetOptionValue<bool>(SerializationOptionsHelper.AutoFilterHeaderRow, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AutoSizeColumns
|
||||||
|
{
|
||||||
|
get { return this.GetOptionValue<bool>(SerializationOptionsHelper.AutoSizeColumns); }
|
||||||
|
set { this.SetOptionValue<bool>(SerializationOptionsHelper.AutoSizeColumns, value); }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SerializeDataResult
|
public class SerializeDataResult
|
||||||
@@ -184,5 +208,9 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts
|
|||||||
internal const string Encoding = "encoding";
|
internal const string Encoding = "encoding";
|
||||||
internal const string Formatted = "formatted";
|
internal const string Formatted = "formatted";
|
||||||
internal const string MaxCharsToStore = "maxchars";
|
internal const string MaxCharsToStore = "maxchars";
|
||||||
|
internal const string FreezeHeaderRow = "freezeHeaderRow";
|
||||||
|
internal const string BoldHeaderRow = "boldHeaderRow";
|
||||||
|
internal const string AutoFilterHeaderRow = "autoFilterHeaderRow";
|
||||||
|
internal const string AutoSizeColumns = "autoSizeColumns";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,11 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts;
|
||||||
|
using SkiaSharp;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
||||||
{
|
{
|
||||||
@@ -16,14 +18,28 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class SaveAsExcelFileStreamWriter : SaveAsStreamWriter
|
public class SaveAsExcelFileStreamWriter : SaveAsStreamWriter
|
||||||
{
|
{
|
||||||
|
// Font family used in Excel sheet
|
||||||
|
private const string FontFamily = "Calibri";
|
||||||
|
|
||||||
|
// Font size in Excel sheet (points with conversion to pixels)
|
||||||
|
private const float FontSizePixels = 11F * (96F / 72F);
|
||||||
|
|
||||||
|
// Pixel width of auto-filter button
|
||||||
|
private const float AutoFilterPixelWidth = 17F;
|
||||||
|
|
||||||
#region Member Variables
|
#region Member Variables
|
||||||
|
|
||||||
private readonly SaveResultsAsExcelRequestParams saveParams;
|
private readonly SaveResultsAsExcelRequestParams saveParams;
|
||||||
|
private readonly float[] columnWidths;
|
||||||
|
private readonly int columnEndIndex;
|
||||||
|
private readonly int columnStartIndex;
|
||||||
|
private readonly SaveAsExcelFileStreamWriterHelper helper;
|
||||||
|
|
||||||
private bool headerWritten;
|
private bool headerWritten;
|
||||||
private SaveAsExcelFileStreamWriterHelper helper;
|
|
||||||
private SaveAsExcelFileStreamWriterHelper.ExcelSheet sheet;
|
private SaveAsExcelFileStreamWriterHelper.ExcelSheet sheet;
|
||||||
|
|
||||||
|
private SKPaint paint;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -41,7 +57,89 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
|||||||
{
|
{
|
||||||
saveParams = requestParams;
|
saveParams = requestParams;
|
||||||
helper = new SaveAsExcelFileStreamWriterHelper(stream);
|
helper = new SaveAsExcelFileStreamWriterHelper(stream);
|
||||||
sheet = helper.AddSheet();
|
|
||||||
|
// Do some setup if the caller requested automatically sized columns
|
||||||
|
if (requestParams.AutoSizeColumns)
|
||||||
|
{
|
||||||
|
// Set column counts depending on whether save request is for entire set or a subset
|
||||||
|
columnEndIndex = columns.Count;
|
||||||
|
columnStartIndex = 0;
|
||||||
|
var columnCount = columns.Count;
|
||||||
|
|
||||||
|
if (requestParams.IsSaveSelection)
|
||||||
|
{
|
||||||
|
// ReSharper disable PossibleInvalidOperationException IsSaveSelection verifies these values exist
|
||||||
|
columnEndIndex = requestParams.ColumnEndIndex.Value + 1;
|
||||||
|
columnStartIndex = requestParams.ColumnStartIndex.Value;
|
||||||
|
columnCount = columnEndIndex - columnStartIndex;
|
||||||
|
// ReSharper restore PossibleInvalidOperationException
|
||||||
|
}
|
||||||
|
|
||||||
|
columnWidths = new float[columnCount];
|
||||||
|
|
||||||
|
// If the caller requested headers the column widths can be initially set based on the header values
|
||||||
|
if (requestParams.IncludeHeaders)
|
||||||
|
{
|
||||||
|
// Setup for measuring the header, set font style based on whether the header should be bold or not
|
||||||
|
using var headerPaint = new SKPaint();
|
||||||
|
headerPaint.Typeface = SKTypeface.FromFamilyName(FontFamily, requestParams.BoldHeaderRow ? SKFontStyle.Bold : SKFontStyle.Normal);
|
||||||
|
headerPaint.TextSize = FontSizePixels;
|
||||||
|
var skBounds = SKRect.Empty;
|
||||||
|
|
||||||
|
// Loop over all the columns
|
||||||
|
for (int columnIndex = columnStartIndex; columnIndex < columnEndIndex; ++columnIndex)
|
||||||
|
{
|
||||||
|
var columnNumber = columnIndex - columnStartIndex;
|
||||||
|
|
||||||
|
// Measure the header text
|
||||||
|
var textWidth = headerPaint.MeasureText(columns[columnIndex].ColumnName.AsSpan(), ref skBounds);
|
||||||
|
|
||||||
|
// Add extra for the auto filter button if requested
|
||||||
|
if (requestParams.AutoFilterHeaderRow)
|
||||||
|
{
|
||||||
|
textWidth += AutoFilterPixelWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just store the width as a starting point
|
||||||
|
columnWidths[columnNumber] = textWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Excel supports specifying column widths so measure if the user wants the columns automatically sized
|
||||||
|
/// </summary>
|
||||||
|
public override bool ShouldMeasureRowColumns => saveParams.AutoSizeColumns;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Measures each column of a row of data and stores updates the maximum width of the column if needed
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="row">The row of data to measure</param>
|
||||||
|
public override void MeasureRowColumns(IList<DbCellValue> row)
|
||||||
|
{
|
||||||
|
// Create the paint object if not done already
|
||||||
|
if (paint == null)
|
||||||
|
{
|
||||||
|
paint = new SKPaint();
|
||||||
|
|
||||||
|
paint.Typeface = SKTypeface.FromFamilyName(FontFamily);
|
||||||
|
paint.TextSize = FontSizePixels;
|
||||||
|
}
|
||||||
|
|
||||||
|
var skBounds = SKRect.Empty;
|
||||||
|
|
||||||
|
// Loop over all the columns
|
||||||
|
for (int columnIndex = columnStartIndex; columnIndex < columnEndIndex; ++columnIndex)
|
||||||
|
{
|
||||||
|
var columnNumber = columnIndex - columnStartIndex;
|
||||||
|
|
||||||
|
// Measure the width of the text
|
||||||
|
var textWidth = paint.MeasureText(row[columnIndex].DisplayValue.AsSpan(), ref skBounds);
|
||||||
|
|
||||||
|
// Update the max if the new width is greater
|
||||||
|
columnWidths[columnNumber] = Math.Max(columnWidths[columnNumber], textWidth);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -55,13 +153,44 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
|||||||
/// </param>
|
/// </param>
|
||||||
public override void WriteRow(IList<DbCellValue> row, IReadOnlyList<DbColumnWrapper> columns)
|
public override void WriteRow(IList<DbCellValue> row, IReadOnlyList<DbColumnWrapper> columns)
|
||||||
{
|
{
|
||||||
|
// Check to make sure the sheet has been created
|
||||||
|
if (sheet == null)
|
||||||
|
{
|
||||||
|
// Get rid of any paint object from the auto-sizing
|
||||||
|
paint?.Dispose();
|
||||||
|
|
||||||
|
// Create the blank sheet
|
||||||
|
sheet = helper.AddSheet(null, columns.Count);
|
||||||
|
|
||||||
|
// The XLSX format has strict ordering requirements so these must be done in the proper order
|
||||||
|
|
||||||
|
// First freeze the header row if the caller has requested header rows and that the header should be frozen
|
||||||
|
if (saveParams.IncludeHeaders && saveParams.FreezeHeaderRow)
|
||||||
|
{
|
||||||
|
sheet.FreezeHeaderRow();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next if column widths have been specified they should be saved to the sheet
|
||||||
|
if (columnWidths != null)
|
||||||
|
{
|
||||||
|
sheet.WriteColumnInformation(columnWidths);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lastly enable auto filter if the caller has requested header rows and that the header should be frozen
|
||||||
|
if (saveParams.IncludeHeaders && saveParams.AutoFilterHeaderRow)
|
||||||
|
{
|
||||||
|
sheet.EnableAutoFilter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Write out the header if we haven't already and the user chose to have it
|
// Write out the header if we haven't already and the user chose to have it
|
||||||
if (saveParams.IncludeHeaders && !headerWritten)
|
if (saveParams.IncludeHeaders && !headerWritten)
|
||||||
{
|
{
|
||||||
sheet.AddRow();
|
sheet.AddRow();
|
||||||
for (int i = ColumnStartIndex; i <= ColumnEndIndex; i++)
|
for (int i = ColumnStartIndex; i <= ColumnEndIndex; i++)
|
||||||
{
|
{
|
||||||
sheet.AddCell(columns[i].ColumnName);
|
// Add the header text and bold if requested
|
||||||
|
sheet.AddCell(columns[i].ColumnName, saveParams.BoldHeaderRow);
|
||||||
}
|
}
|
||||||
headerWritten = true;
|
headerWritten = true;
|
||||||
}
|
}
|
||||||
@@ -74,7 +203,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
|||||||
}
|
}
|
||||||
|
|
||||||
private bool disposed;
|
private bool disposed;
|
||||||
override protected void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (disposed)
|
if (disposed)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data.SqlTypes;
|
using System.Data.SqlTypes;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
@@ -75,21 +76,31 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
|||||||
// new TimeSpan(24,0,0).Ticks
|
// new TimeSpan(24,0,0).Ticks
|
||||||
private const long TicksPerDay = 864000000000L;
|
private const long TicksPerDay = 864000000000L;
|
||||||
|
|
||||||
|
// Digit pixel width for 11 point Calibri
|
||||||
|
private const float FontPixelWidth = 7;
|
||||||
|
|
||||||
private XmlWriter writer;
|
private XmlWriter writer;
|
||||||
private ReferenceManager referenceManager;
|
private ReferenceManager referenceManager;
|
||||||
private bool hasOpenRowTag;
|
private bool hasOpenRowTag;
|
||||||
|
|
||||||
|
private readonly int columnCount;
|
||||||
|
private bool autoFilterColumns;
|
||||||
|
private bool hasStartedSheetData;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the ExcelSheet class.
|
/// Initializes a new instance of the ExcelSheet class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="writer">XmlWriter to write the sheet data</param>
|
/// <param name="writer">XmlWriter to write the sheet data</param>
|
||||||
internal ExcelSheet(XmlWriter writer)
|
/// <param name="columnCount">Number of columns in the new sheet</param>
|
||||||
|
internal ExcelSheet(XmlWriter writer, int columnCount)
|
||||||
{
|
{
|
||||||
this.writer = writer;
|
this.writer = writer;
|
||||||
|
this.columnCount = columnCount;
|
||||||
|
|
||||||
writer.WriteStartDocument();
|
writer.WriteStartDocument();
|
||||||
writer.WriteStartElement("worksheet", "http://schemas.openxmlformats.org/spreadsheetml/2006/main");
|
writer.WriteStartElement("worksheet", "http://schemas.openxmlformats.org/spreadsheetml/2006/main");
|
||||||
writer.WriteAttributeString("xmlns", "r", null, "http://schemas.openxmlformats.org/officeDocument/2006/relationships");
|
writer.WriteAttributeString("xmlns", "r", null, "http://schemas.openxmlformats.org/officeDocument/2006/relationships");
|
||||||
writer.WriteStartElement("sheetData");
|
|
||||||
referenceManager = new ReferenceManager(writer);
|
referenceManager = new ReferenceManager(writer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,6 +109,13 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void AddRow()
|
public void AddRow()
|
||||||
{
|
{
|
||||||
|
// Write the open tag for sheetData if it hasn't been written yet
|
||||||
|
if (!hasStartedSheetData)
|
||||||
|
{
|
||||||
|
writer.WriteStartElement("sheetData");
|
||||||
|
hasStartedSheetData = true;
|
||||||
|
}
|
||||||
|
|
||||||
EndRowIfNeeded();
|
EndRowIfNeeded();
|
||||||
hasOpenRowTag = true;
|
hasOpenRowTag = true;
|
||||||
|
|
||||||
@@ -110,8 +128,9 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Write a string cell
|
/// Write a string cell
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="value">string value to write</param>
|
/// <param name="value">String value to write</param>
|
||||||
public void AddCell(string value)
|
/// <param name="bold">Whether the cell should be bold, defaults to false</param>
|
||||||
|
public void AddCell(string value, bool bold = false)
|
||||||
{
|
{
|
||||||
// string needs <c t="inlineStr"><is><t>string</t></is></c>
|
// string needs <c t="inlineStr"><is><t>string</t></is></c>
|
||||||
// This class uses inlineStr instead of more common shared string table
|
// This class uses inlineStr instead of more common shared string table
|
||||||
@@ -129,21 +148,28 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
|||||||
|
|
||||||
writer.WriteAttributeString("t", "inlineStr");
|
writer.WriteAttributeString("t", "inlineStr");
|
||||||
|
|
||||||
|
// Write the style attribute set to the bold font if requested
|
||||||
|
if (bold)
|
||||||
|
{
|
||||||
|
writer.WriteAttributeString("s", "5");
|
||||||
|
}
|
||||||
|
|
||||||
writer.WriteStartElement("is");
|
writer.WriteStartElement("is");
|
||||||
writer.WriteStartElement("t");
|
writer.WriteStartElement("t");
|
||||||
writer.WriteValue(value);
|
writer.WriteValue(value);
|
||||||
writer.WriteEndElement();
|
writer.WriteEndElement(); // <t>
|
||||||
writer.WriteEndElement();
|
writer.WriteEndElement(); // <is>
|
||||||
|
|
||||||
writer.WriteEndElement();
|
writer.WriteEndElement(); // <c>
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Write a object cell
|
/// Write a object cell
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// The program will try to output number/datetime, otherwise, call the ToString
|
/// The program will try to output number/datetime, otherwise, call the ToString
|
||||||
/// <param name="o"></param>
|
/// <param name="dbCellValue">DbCellValue to write based on data type</param>
|
||||||
public void AddCell(DbCellValue dbCellValue)
|
/// <param name="bold">Whether the cell should be bold, defaults to false</param>
|
||||||
|
public void AddCell(DbCellValue dbCellValue, bool bold = false)
|
||||||
{
|
{
|
||||||
object o = dbCellValue.RawObject;
|
object o = dbCellValue.RawObject;
|
||||||
if (dbCellValue.IsNull || o == null)
|
if (dbCellValue.IsNull || o == null)
|
||||||
@@ -169,26 +195,101 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
|||||||
AddCell((DateTime)o);
|
AddCell((DateTime)o);
|
||||||
break;
|
break;
|
||||||
case TypeCode.String:
|
case TypeCode.String:
|
||||||
AddCell((string)o);
|
AddCell((string)o, bold);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (o is TimeSpan span) //TimeSpan doesn't have TypeCode
|
if (o is TimeSpan span) //TimeSpan doesn't have TypeCode
|
||||||
{
|
{
|
||||||
AddCell(span);
|
AddCell(span);
|
||||||
}
|
}
|
||||||
// We need to handle SqlDecimal and SqlMoney types here because we can't convert them to .NET types due to different precisons in SQL Server and .NET.
|
// We need to handle SqlDecimal and SqlMoney types here because we can't convert them to .NET types due to different precisions in SQL Server and .NET.
|
||||||
else if (o is SqlDecimal || o is SqlMoney)
|
else if (o is SqlDecimal || o is SqlMoney)
|
||||||
{
|
{
|
||||||
AddCellBoxedNumber(dbCellValue.DisplayValue);
|
AddCellBoxedNumber(dbCellValue.DisplayValue);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AddCell(dbCellValue.DisplayValue);
|
AddCell(dbCellValue.DisplayValue, bold);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write a sheetView that freezes the top row. Must be called before any rows have been added.
|
||||||
|
/// </summary>
|
||||||
|
/// <exception cref="InvalidOperationException">Thrown if called after any rows have been added.</exception>
|
||||||
|
public void FreezeHeaderRow()
|
||||||
|
{
|
||||||
|
if (hasStartedSheetData)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Must be called before calling AddRow");
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.WriteStartElement("sheetViews");
|
||||||
|
|
||||||
|
writer.WriteStartElement("sheetView");
|
||||||
|
writer.WriteAttributeString("tabSelected", "1");
|
||||||
|
writer.WriteAttributeString("workbookViewId", "0");
|
||||||
|
|
||||||
|
writer.WriteStartElement("pane");
|
||||||
|
writer.WriteAttributeString("ySplit", "1");
|
||||||
|
writer.WriteAttributeString("topLeftCell", "A2");
|
||||||
|
writer.WriteAttributeString("activePane", "bottomLeft");
|
||||||
|
writer.WriteAttributeString("state", "frozen");
|
||||||
|
writer.WriteEndElement(); // <pane>
|
||||||
|
|
||||||
|
writer.WriteStartElement("selection");
|
||||||
|
writer.WriteAttributeString("pane", "bottomLeft");
|
||||||
|
writer.WriteEndElement(); // <selection>
|
||||||
|
|
||||||
|
writer.WriteEndElement(); // <sheetView>
|
||||||
|
writer.WriteEndElement(); // <sheetViews>
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enable auto filtering, the XML will be written when closing the sheet later
|
||||||
|
/// </summary>
|
||||||
|
public void EnableAutoFilter()
|
||||||
|
{
|
||||||
|
autoFilterColumns = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write the columns widths. Must be called before any rows have been added.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="columnWidths">Array with the widths of each column.</param>
|
||||||
|
/// <exception cref="InvalidOperationException">Thrown if called after any rows have been added.</exception>
|
||||||
|
public void WriteColumnInformation(float[] columnWidths)
|
||||||
|
{
|
||||||
|
if (hasStartedSheetData)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Must be called before calling AddRow");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (columnWidths.Length != this.columnCount)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Column count mismatch");
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.WriteStartElement("cols");
|
||||||
|
|
||||||
|
for (int columnIndex = 0; columnIndex < columnWidths.Length; columnIndex++)
|
||||||
|
{
|
||||||
|
var columnWidth = Math.Truncate((columnWidths[columnIndex] + 15) / FontPixelWidth * 256) / 256;
|
||||||
|
|
||||||
|
writer.WriteStartElement("col");
|
||||||
|
writer.WriteAttributeString("min", (columnIndex + 1).ToString());
|
||||||
|
writer.WriteAttributeString("max", (columnIndex + 1).ToString());
|
||||||
|
writer.WriteAttributeString("width", columnWidth.ToString(CultureInfo.InvariantCulture));
|
||||||
|
writer.WriteAttributeString("bestFit", "1");
|
||||||
|
writer.WriteAttributeString("customWidth", "1");
|
||||||
|
writer.WriteEndElement(); // <col>
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.WriteEndElement(); // <cols>
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Close the <row><sheetData><worksheet> tags and close the stream
|
/// Close the <row><sheetData><worksheet> tags and close the stream
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -196,6 +297,15 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
|||||||
{
|
{
|
||||||
EndRowIfNeeded();
|
EndRowIfNeeded();
|
||||||
writer.WriteEndElement(); // <sheetData>
|
writer.WriteEndElement(); // <sheetData>
|
||||||
|
|
||||||
|
// Write the auto filter XML if requested
|
||||||
|
if (autoFilterColumns)
|
||||||
|
{
|
||||||
|
writer.WriteStartElement("autoFilter");
|
||||||
|
writer.WriteAttributeString("ref", $"A1:{ReferenceManager.GetColumnName(this.columnCount)}1");
|
||||||
|
writer.WriteEndElement(); // <autoFilter>
|
||||||
|
}
|
||||||
|
|
||||||
writer.WriteEndElement(); // <worksheet>
|
writer.WriteEndElement(); // <worksheet>
|
||||||
writer.Dispose();
|
writer.Dispose();
|
||||||
}
|
}
|
||||||
@@ -203,7 +313,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Write a empty cell
|
/// Write a empty cell
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// This only increases the internal bookmark and doesn't arcturally write out anything.
|
/// This only increases the internal bookmark and doesn't actually write out anything.
|
||||||
private void AddCellEmpty()
|
private void AddCellEmpty()
|
||||||
{
|
{
|
||||||
referenceManager.IncreaseColumnReference();
|
referenceManager.IncreaseColumnReference();
|
||||||
@@ -212,7 +322,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Write a bool cell.
|
/// Write a bool cell.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="time"></param>
|
/// <param name="value">Boolean value to write</param>
|
||||||
private void AddCell(bool value)
|
private void AddCell(bool value)
|
||||||
{
|
{
|
||||||
// Excel FALSE: <c r="A1" t="b"><v>0</v></c>
|
// Excel FALSE: <c r="A1" t="b"><v>0</v></c>
|
||||||
@@ -234,15 +344,15 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
|||||||
{
|
{
|
||||||
writer.WriteValue("0");
|
writer.WriteValue("0");
|
||||||
}
|
}
|
||||||
writer.WriteEndElement();
|
writer.WriteEndElement(); // <v>
|
||||||
|
|
||||||
writer.WriteEndElement();
|
writer.WriteEndElement(); // <c>
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Write a TimeSpan cell.
|
/// Write a TimeSpan cell.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="time"></param>
|
/// <param name="time">TimeSpan value to write</param>
|
||||||
private void AddCell(TimeSpan time)
|
private void AddCell(TimeSpan time)
|
||||||
{
|
{
|
||||||
referenceManager.AssureColumnReference();
|
referenceManager.AssureColumnReference();
|
||||||
@@ -262,7 +372,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Write a DateTime cell.
|
/// Write a DateTime cell.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dateTime">Datetime</param>
|
/// <param name="dateTime">DateTime value to write</param>
|
||||||
/// <remark>
|
/// <remark>
|
||||||
/// If the DateTime does not have date part, it will be written as datetime and show as time only
|
/// If the DateTime does not have date part, it will be written as datetime and show as time only
|
||||||
/// If the DateTime is before 1900-03-01, save as string because excel doesn't support them.
|
/// If the DateTime is before 1900-03-01, save as string because excel doesn't support them.
|
||||||
@@ -307,9 +417,9 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
|||||||
|
|
||||||
writer.WriteStartElement("v");
|
writer.WriteStartElement("v");
|
||||||
writer.WriteValue(number);
|
writer.WriteValue(number);
|
||||||
writer.WriteEndElement();
|
writer.WriteEndElement(); // <v>
|
||||||
|
|
||||||
writer.WriteEndElement();
|
writer.WriteEndElement(); // <c>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -326,9 +436,9 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
|||||||
|
|
||||||
writer.WriteStartElement("v");
|
writer.WriteStartElement("v");
|
||||||
writer.WriteValue(excelDate);
|
writer.WriteValue(excelDate);
|
||||||
writer.WriteEndElement();
|
writer.WriteEndElement(); // <v>
|
||||||
|
|
||||||
writer.WriteEndElement();
|
writer.WriteEndElement(); // <c>
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EndRowIfNeeded()
|
private void EndRowIfNeeded()
|
||||||
@@ -338,8 +448,6 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
|||||||
writer.WriteEndElement(); // <row>
|
writer.WriteEndElement(); // <row>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -429,6 +537,26 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the column name (letters) from a column number
|
||||||
|
/// https://stackoverflow.com/questions/181596/how-to-convert-a-column-number-e-g-127-into-an-excel-column-e-g-aa
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="columnNumber">The column number.</param>
|
||||||
|
/// <returns>The column name.</returns>
|
||||||
|
public static string GetColumnName(int columnNumber)
|
||||||
|
{
|
||||||
|
string columnName = "";
|
||||||
|
|
||||||
|
while (columnNumber > 0)
|
||||||
|
{
|
||||||
|
int modulo = (columnNumber - 1) % 26;
|
||||||
|
columnName = Convert.ToChar('A' + modulo) + columnName;
|
||||||
|
columnNumber = (columnNumber - modulo) / 26;
|
||||||
|
}
|
||||||
|
|
||||||
|
return columnName;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check that we have not write too many rows. (xlsx has a limit of 1048576 rows)
|
/// Check that we have not write too many rows. (xlsx has a limit of 1048576 rows)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -511,11 +639,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
|||||||
/// <param name="sheetName">Sheet name</param>
|
/// <param name="sheetName">Sheet name</param>
|
||||||
/// <returns>ExcelSheet for writing the sheet content</returns>
|
/// <returns>ExcelSheet for writing the sheet content</returns>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// When the sheetName is null, sheet1,shhet2,..., will be used.
|
/// When the sheetName is null, sheet1,sheet2,..., will be used.
|
||||||
/// The following charactors are not allowed in the sheetName
|
/// The following characters are not allowed in the sheetName
|
||||||
/// '\', '/','*','[',']',':','?'
|
/// '\', '/','*','[',']',':','?'
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public ExcelSheet AddSheet(string sheetName = null)
|
public ExcelSheet AddSheet(string sheetName, int columnCount)
|
||||||
{
|
{
|
||||||
string sheetFileName = "sheet" + (sheetNames.Count + 1);
|
string sheetFileName = "sheet" + (sheetNames.Count + 1);
|
||||||
sheetName ??= sheetFileName;
|
sheetName ??= sheetFileName;
|
||||||
@@ -523,7 +651,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
|||||||
|
|
||||||
sheetNames.Add(sheetName);
|
sheetNames.Add(sheetName);
|
||||||
XmlWriter sheetWriter = AddEntry($"xl/worksheets/{sheetFileName}.xml");
|
XmlWriter sheetWriter = AddEntry($"xl/worksheets/{sheetFileName}.xml");
|
||||||
return new ExcelSheet(sheetWriter);
|
return new ExcelSheet(sheetWriter, columnCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -568,26 +696,26 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
|||||||
xw.WriteStartElement("Default");
|
xw.WriteStartElement("Default");
|
||||||
xw.WriteAttributeString("Extension", "rels");
|
xw.WriteAttributeString("Extension", "rels");
|
||||||
xw.WriteAttributeString("ContentType", "application/vnd.openxmlformats-package.relationships+xml");
|
xw.WriteAttributeString("ContentType", "application/vnd.openxmlformats-package.relationships+xml");
|
||||||
xw.WriteEndElement();
|
xw.WriteEndElement(); // <Default>
|
||||||
|
|
||||||
xw.WriteStartElement("Override");
|
xw.WriteStartElement("Override");
|
||||||
xw.WriteAttributeString("PartName", "/xl/workbook.xml");
|
xw.WriteAttributeString("PartName", "/xl/workbook.xml");
|
||||||
xw.WriteAttributeString("ContentType", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml");
|
xw.WriteAttributeString("ContentType", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml");
|
||||||
xw.WriteEndElement();
|
xw.WriteEndElement(); // <Override>
|
||||||
|
|
||||||
xw.WriteStartElement("Override");
|
xw.WriteStartElement("Override");
|
||||||
xw.WriteAttributeString("PartName", "/xl/styles.xml");
|
xw.WriteAttributeString("PartName", "/xl/styles.xml");
|
||||||
xw.WriteAttributeString("ContentType", "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml");
|
xw.WriteAttributeString("ContentType", "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml");
|
||||||
xw.WriteEndElement();
|
xw.WriteEndElement(); // <Override>
|
||||||
|
|
||||||
for (int i = 1; i <= sheetNames.Count; ++i)
|
for (int i = 1; i <= sheetNames.Count; ++i)
|
||||||
{
|
{
|
||||||
xw.WriteStartElement("Override");
|
xw.WriteStartElement("Override");
|
||||||
xw.WriteAttributeString("PartName", "/xl/worksheets/sheet" + i + ".xml");
|
xw.WriteAttributeString("PartName", "/xl/worksheets/sheet" + i + ".xml");
|
||||||
xw.WriteAttributeString("ContentType", "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml");
|
xw.WriteAttributeString("ContentType", "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml");
|
||||||
xw.WriteEndElement();
|
xw.WriteEndElement(); // <Override>
|
||||||
}
|
}
|
||||||
xw.WriteEndElement();
|
xw.WriteEndElement(); // <Types>
|
||||||
xw.WriteEndDocument();
|
xw.WriteEndDocument();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -607,9 +735,9 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
|||||||
xw.WriteAttributeString("Id", "rId1");
|
xw.WriteAttributeString("Id", "rId1");
|
||||||
xw.WriteAttributeString("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument");
|
xw.WriteAttributeString("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument");
|
||||||
xw.WriteAttributeString("Target", "xl/workbook.xml");
|
xw.WriteAttributeString("Target", "xl/workbook.xml");
|
||||||
xw.WriteEndElement();
|
xw.WriteEndElement(); // <Relationship>
|
||||||
|
|
||||||
xw.WriteEndElement();
|
xw.WriteEndElement(); // <Relationships>
|
||||||
|
|
||||||
xw.WriteEndDocument();
|
xw.WriteEndDocument();
|
||||||
}
|
}
|
||||||
@@ -648,7 +776,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
|||||||
xw.WriteAttributeString("name", sheetNames[i - 1]);
|
xw.WriteAttributeString("name", sheetNames[i - 1]);
|
||||||
xw.WriteAttributeString("sheetId", i.ToString());
|
xw.WriteAttributeString("sheetId", i.ToString());
|
||||||
xw.WriteAttributeString("r", "id", null, "rId" + i);
|
xw.WriteAttributeString("r", "id", null, "rId" + i);
|
||||||
xw.WriteEndElement();
|
xw.WriteEndElement(); // <sheet>
|
||||||
}
|
}
|
||||||
xw.WriteEndDocument();
|
xw.WriteEndDocument();
|
||||||
}
|
}
|
||||||
@@ -668,7 +796,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
|||||||
xw.WriteAttributeString("Id", "rId0");
|
xw.WriteAttributeString("Id", "rId0");
|
||||||
xw.WriteAttributeString("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles");
|
xw.WriteAttributeString("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles");
|
||||||
xw.WriteAttributeString("Target", "styles.xml");
|
xw.WriteAttributeString("Target", "styles.xml");
|
||||||
xw.WriteEndElement();
|
xw.WriteEndElement(); // <Relationship>
|
||||||
|
|
||||||
for (int i = 1; i <= sheetNames.Count; i++)
|
for (int i = 1; i <= sheetNames.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -676,9 +804,9 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
|||||||
xw.WriteAttributeString("Id", "rId" + i);
|
xw.WriteAttributeString("Id", "rId" + i);
|
||||||
xw.WriteAttributeString("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet");
|
xw.WriteAttributeString("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet");
|
||||||
xw.WriteAttributeString("Target", "worksheets/sheet" + i + ".xml");
|
xw.WriteAttributeString("Target", "worksheets/sheet" + i + ".xml");
|
||||||
xw.WriteEndElement();
|
xw.WriteEndElement(); // <Relationship>
|
||||||
}
|
}
|
||||||
xw.WriteEndElement();
|
xw.WriteEndElement(); // <Relationships>
|
||||||
xw.WriteEndDocument();
|
xw.WriteEndDocument();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -702,41 +830,63 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
|||||||
xw.WriteStartElement("numFmt");
|
xw.WriteStartElement("numFmt");
|
||||||
xw.WriteAttributeString("numFmtId", "166");
|
xw.WriteAttributeString("numFmtId", "166");
|
||||||
xw.WriteAttributeString("formatCode", "yyyy-mm-dd");
|
xw.WriteAttributeString("formatCode", "yyyy-mm-dd");
|
||||||
xw.WriteEndElement();
|
xw.WriteEndElement(); // <numFmt>
|
||||||
xw.WriteStartElement("numFmt");
|
xw.WriteStartElement("numFmt");
|
||||||
xw.WriteAttributeString("numFmtId", "167");
|
xw.WriteAttributeString("numFmtId", "167");
|
||||||
xw.WriteAttributeString("formatCode", "hh:mm:ss");
|
xw.WriteAttributeString("formatCode", "hh:mm:ss");
|
||||||
xw.WriteEndElement();
|
xw.WriteEndElement(); // <numFmt>
|
||||||
xw.WriteStartElement("numFmt");
|
xw.WriteStartElement("numFmt");
|
||||||
xw.WriteAttributeString("numFmtId", "168");
|
xw.WriteAttributeString("numFmtId", "168");
|
||||||
xw.WriteAttributeString("formatCode", "yyyy-mm-dd hh:mm:ss");
|
xw.WriteAttributeString("formatCode", "yyyy-mm-dd hh:mm:ss");
|
||||||
xw.WriteEndElement();
|
xw.WriteEndElement(); // <numFmt>
|
||||||
xw.WriteStartElement("numFmt");
|
xw.WriteStartElement("numFmt");
|
||||||
xw.WriteAttributeString("numFmtId", "169");
|
xw.WriteAttributeString("numFmtId", "169");
|
||||||
xw.WriteAttributeString("formatCode", "[h]:mm:ss");
|
xw.WriteAttributeString("formatCode", "[h]:mm:ss");
|
||||||
xw.WriteEndElement();
|
xw.WriteEndElement(); // <numFmt>
|
||||||
xw.WriteEndElement(); //mumFmts
|
xw.WriteEndElement(); // <numFmts>
|
||||||
|
|
||||||
|
|
||||||
xw.WriteStartElement("fonts");
|
xw.WriteStartElement("fonts");
|
||||||
xw.WriteAttributeString("count", "1");
|
xw.WriteAttributeString("count", "2");
|
||||||
|
|
||||||
xw.WriteStartElement("font");
|
xw.WriteStartElement("font");
|
||||||
xw.WriteStartElement("sz");
|
xw.WriteStartElement("sz");
|
||||||
xw.WriteAttributeString("val", "11");
|
xw.WriteAttributeString("val", "11");
|
||||||
xw.WriteEndElement();
|
xw.WriteEndElement(); // <sz>
|
||||||
xw.WriteStartElement("color");
|
xw.WriteStartElement("color");
|
||||||
xw.WriteAttributeString("theme", "1");
|
xw.WriteAttributeString("theme", "1");
|
||||||
xw.WriteEndElement();
|
xw.WriteEndElement(); // <color>
|
||||||
xw.WriteStartElement("name");
|
xw.WriteStartElement("name");
|
||||||
xw.WriteAttributeString("val", "Calibri");
|
xw.WriteAttributeString("val", "Calibri");
|
||||||
xw.WriteEndElement();
|
xw.WriteEndElement(); // <name>
|
||||||
xw.WriteStartElement("family");
|
xw.WriteStartElement("family");
|
||||||
xw.WriteAttributeString("val", "2");
|
xw.WriteAttributeString("val", "2");
|
||||||
xw.WriteEndElement();
|
xw.WriteEndElement(); // <family>
|
||||||
xw.WriteStartElement("scheme");
|
xw.WriteStartElement("scheme");
|
||||||
xw.WriteAttributeString("val", "minor");
|
xw.WriteAttributeString("val", "minor");
|
||||||
xw.WriteEndElement();
|
xw.WriteEndElement(); // <scheme>
|
||||||
xw.WriteEndElement(); // font
|
xw.WriteEndElement(); // <font>
|
||||||
|
|
||||||
|
xw.WriteStartElement("font");
|
||||||
|
xw.WriteStartElement("b");
|
||||||
|
xw.WriteEndElement(); // <b>
|
||||||
|
xw.WriteStartElement("sz");
|
||||||
|
xw.WriteAttributeString("val", "11");
|
||||||
|
xw.WriteEndElement(); // <sz>
|
||||||
|
xw.WriteStartElement("color");
|
||||||
|
xw.WriteAttributeString("theme", "1");
|
||||||
|
xw.WriteEndElement(); // <color>
|
||||||
|
xw.WriteStartElement("name");
|
||||||
|
xw.WriteAttributeString("val", "Calibri");
|
||||||
|
xw.WriteEndElement(); // <name>
|
||||||
|
xw.WriteStartElement("family");
|
||||||
|
xw.WriteAttributeString("val", "2");
|
||||||
|
xw.WriteEndElement(); // <family>
|
||||||
|
xw.WriteStartElement("scheme");
|
||||||
|
xw.WriteAttributeString("val", "minor");
|
||||||
|
xw.WriteEndElement(); // <scheme>
|
||||||
|
xw.WriteEndElement(); // <font>
|
||||||
|
|
||||||
xw.WriteEndElement(); // fonts
|
xw.WriteEndElement(); // fonts
|
||||||
|
|
||||||
xw.WriteStartElement("fills");
|
xw.WriteStartElement("fills");
|
||||||
@@ -744,9 +894,9 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
|||||||
xw.WriteStartElement("fill");
|
xw.WriteStartElement("fill");
|
||||||
xw.WriteStartElement("patternFill");
|
xw.WriteStartElement("patternFill");
|
||||||
xw.WriteAttributeString("patternType", "none");
|
xw.WriteAttributeString("patternType", "none");
|
||||||
xw.WriteEndElement(); // patternFill
|
xw.WriteEndElement(); // <patternFill>
|
||||||
xw.WriteEndElement(); // fill
|
xw.WriteEndElement(); // <fill>
|
||||||
xw.WriteEndElement(); // fills
|
xw.WriteEndElement(); // <fills>
|
||||||
|
|
||||||
xw.WriteStartElement("borders");
|
xw.WriteStartElement("borders");
|
||||||
xw.WriteAttributeString("count", "1");
|
xw.WriteAttributeString("count", "1");
|
||||||
@@ -756,8 +906,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
|||||||
xw.WriteElementString("top", null);
|
xw.WriteElementString("top", null);
|
||||||
xw.WriteElementString("bottom", null);
|
xw.WriteElementString("bottom", null);
|
||||||
xw.WriteElementString("diagonal", null);
|
xw.WriteElementString("diagonal", null);
|
||||||
xw.WriteEndElement(); // board
|
xw.WriteEndElement(); // <border>
|
||||||
xw.WriteEndElement(); // borders
|
xw.WriteEndElement(); // <borders>
|
||||||
|
|
||||||
xw.WriteStartElement("cellStyleXfs");
|
xw.WriteStartElement("cellStyleXfs");
|
||||||
xw.WriteAttributeString("count", "1");
|
xw.WriteAttributeString("count", "1");
|
||||||
@@ -766,35 +916,39 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
|||||||
xw.WriteAttributeString("fontId", "0");
|
xw.WriteAttributeString("fontId", "0");
|
||||||
xw.WriteAttributeString("fillId", "0");
|
xw.WriteAttributeString("fillId", "0");
|
||||||
xw.WriteAttributeString("borderId", "0");
|
xw.WriteAttributeString("borderId", "0");
|
||||||
xw.WriteEndElement(); // xf
|
xw.WriteEndElement(); // <xf>
|
||||||
xw.WriteEndElement(); // cellStyleXfs
|
xw.WriteEndElement(); // <cellStyleXfs>
|
||||||
|
|
||||||
xw.WriteStartElement("cellXfs");
|
xw.WriteStartElement("cellXfs");
|
||||||
xw.WriteAttributeString("count", "5");
|
xw.WriteAttributeString("count", "6");
|
||||||
xw.WriteStartElement("xf");
|
xw.WriteStartElement("xf");
|
||||||
xw.WriteAttributeString("xfId", "0");
|
xw.WriteAttributeString("xfId", "0");
|
||||||
xw.WriteEndElement();
|
xw.WriteEndElement(); // <xf>
|
||||||
xw.WriteStartElement("xf");
|
xw.WriteStartElement("xf");
|
||||||
xw.WriteAttributeString("numFmtId", "166");
|
xw.WriteAttributeString("numFmtId", "166");
|
||||||
xw.WriteAttributeString("xfId", "0");
|
xw.WriteAttributeString("xfId", "0");
|
||||||
xw.WriteAttributeString("applyNumberFormat", "1");
|
xw.WriteAttributeString("applyNumberFormat", "1");
|
||||||
xw.WriteEndElement();
|
xw.WriteEndElement(); // <xf>
|
||||||
xw.WriteStartElement("xf");
|
xw.WriteStartElement("xf");
|
||||||
xw.WriteAttributeString("numFmtId", "167");
|
xw.WriteAttributeString("numFmtId", "167");
|
||||||
xw.WriteAttributeString("xfId", "0");
|
xw.WriteAttributeString("xfId", "0");
|
||||||
xw.WriteAttributeString("applyNumberFormat", "1");
|
xw.WriteAttributeString("applyNumberFormat", "1");
|
||||||
xw.WriteEndElement();
|
xw.WriteEndElement(); // <xf>
|
||||||
xw.WriteStartElement("xf");
|
xw.WriteStartElement("xf");
|
||||||
xw.WriteAttributeString("numFmtId", "168");
|
xw.WriteAttributeString("numFmtId", "168");
|
||||||
xw.WriteAttributeString("xfId", "0");
|
xw.WriteAttributeString("xfId", "0");
|
||||||
xw.WriteAttributeString("applyNumberFormat", "1");
|
xw.WriteAttributeString("applyNumberFormat", "1");
|
||||||
xw.WriteEndElement();
|
xw.WriteEndElement(); // <xf>
|
||||||
xw.WriteStartElement("xf");
|
xw.WriteStartElement("xf");
|
||||||
xw.WriteAttributeString("numFmtId", "169");
|
xw.WriteAttributeString("numFmtId", "169");
|
||||||
xw.WriteAttributeString("xfId", "0");
|
xw.WriteAttributeString("xfId", "0");
|
||||||
xw.WriteAttributeString("applyNumberFormat", "1");
|
xw.WriteAttributeString("applyNumberFormat", "1");
|
||||||
xw.WriteEndElement();
|
xw.WriteEndElement(); // <xf>
|
||||||
xw.WriteEndElement(); // cellXfs
|
xw.WriteStartElement("xf");
|
||||||
|
xw.WriteAttributeString("xfId", "0");
|
||||||
|
xw.WriteAttributeString("fontId", "1");
|
||||||
|
xw.WriteEndElement(); // <xf>
|
||||||
|
xw.WriteEndElement(); // <cellXfs>
|
||||||
|
|
||||||
xw.WriteStartElement("cellStyles");
|
xw.WriteStartElement("cellStyles");
|
||||||
xw.WriteAttributeString("count", "1");
|
xw.WriteAttributeString("count", "1");
|
||||||
@@ -802,8 +956,8 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
|||||||
xw.WriteAttributeString("name", "Normal");
|
xw.WriteAttributeString("name", "Normal");
|
||||||
xw.WriteAttributeString("builtinId", "0");
|
xw.WriteAttributeString("builtinId", "0");
|
||||||
xw.WriteAttributeString("xfId", "0");
|
xw.WriteAttributeString("xfId", "0");
|
||||||
xw.WriteEndElement(); // cellStyle
|
xw.WriteEndElement(); // <cellStyle>
|
||||||
xw.WriteEndElement(); // cellStyles
|
xw.WriteEndElement(); // <cellStyles>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,6 +85,17 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage
|
|||||||
throw new InvalidOperationException("This type of writer is meant to write values from a list of cell values only.");
|
throw new InvalidOperationException("This type of writer is meant to write values from a list of cell values only.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates whether columns should be measured based on whether the output format supports it and if the caller wants the columns automatically sized
|
||||||
|
/// </summary>
|
||||||
|
public virtual bool ShouldMeasureRowColumns => false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Measures the columns in a row of data as part of determining automatic column widths
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="row">The row of data to measure</param>
|
||||||
|
public virtual void MeasureRowColumns(IList<DbCellValue> row) { }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writes a row of data to the output file using the format provided by the implementing class.
|
/// Writes a row of data to the output file using the format provided by the implementing class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -533,8 +533,30 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
using (var fileReader = fileFactory.GetReader(outputFileName))
|
using (var fileReader = fileFactory.GetReader(outputFileName))
|
||||||
using (var fileWriter = fileFactory.GetWriter(saveParams.FilePath, Columns))
|
using (var fileWriter = fileFactory.GetWriter(saveParams.FilePath, Columns))
|
||||||
{
|
{
|
||||||
|
DateTime recentLogTime;
|
||||||
|
|
||||||
|
// Some writers (like Excel) require the column widths before any of the rows have been written so we need to loop over
|
||||||
|
// all the rows of data in order to calculate what the column widths should be before the rows are actually written
|
||||||
|
if (fileWriter is SaveAsStreamWriter { ShouldMeasureRowColumns: true } saveAsStreamWriter)
|
||||||
|
{
|
||||||
|
Logger.Verbose($"Started measuring {RowCount} rows");
|
||||||
|
recentLogTime = DateTime.Now;
|
||||||
|
// Iterate over the rows that are in the selected row set
|
||||||
|
for (long i = rowStartIndex; i < rowEndIndex; ++i)
|
||||||
|
{
|
||||||
|
var row = fileReader.ReadRow(fileOffsets[i], i, Columns);
|
||||||
|
saveAsStreamWriter.MeasureRowColumns(row);
|
||||||
|
if (DateTime.Now.Subtract(recentLogTime).TotalSeconds > 1)
|
||||||
|
{
|
||||||
|
Logger.Verbose($"Measure progress: {i - rowStartIndex + 1}/{RowCount}.");
|
||||||
|
recentLogTime = DateTime.Now;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Logger.Verbose($"Measured {RowCount} rows");
|
||||||
|
}
|
||||||
|
|
||||||
Logger.Verbose($"Started exporting {RowCount} rows to file: {outputFileName}");
|
Logger.Verbose($"Started exporting {RowCount} rows to file: {outputFileName}");
|
||||||
var recentLogTime = DateTime.Now;
|
recentLogTime = DateTime.Now;
|
||||||
// Iterate over the rows that are in the selected row set
|
// Iterate over the rows that are in the selected row set
|
||||||
for (long i = rowStartIndex; i < rowEndIndex; ++i)
|
for (long i = rowStartIndex; i < rowEndIndex; ++i)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -290,7 +290,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution
|
|||||||
FilePath = this.requestParams.FilePath,
|
FilePath = this.requestParams.FilePath,
|
||||||
BatchIndex = 0,
|
BatchIndex = 0,
|
||||||
ResultSetIndex = 0,
|
ResultSetIndex = 0,
|
||||||
IncludeHeaders = this.requestParams.IncludeHeaders
|
IncludeHeaders = this.requestParams.IncludeHeaders,
|
||||||
|
FreezeHeaderRow = this.requestParams.FreezeHeaderRow,
|
||||||
|
BoldHeaderRow = this.requestParams.BoldHeaderRow,
|
||||||
|
AutoFilterHeaderRow = this.requestParams.AutoFilterHeaderRow,
|
||||||
|
AutoSizeColumns = this.requestParams.AutoSizeColumns
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
private SaveResultsAsCsvRequestParams CreateCsvRequestParams()
|
private SaveResultsAsCsvRequestParams CreateCsvRequestParams()
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<numFmt numFmtId="168" formatCode="yyyy-mm-dd hh:mm:ss" />
|
<numFmt numFmtId="168" formatCode="yyyy-mm-dd hh:mm:ss" />
|
||||||
<numFmt numFmtId="169" formatCode="[h]:mm:ss" />
|
<numFmt numFmtId="169" formatCode="[h]:mm:ss" />
|
||||||
</numFmts>
|
</numFmts>
|
||||||
<fonts count="1">
|
<fonts count="2">
|
||||||
<font>
|
<font>
|
||||||
<sz val="11" />
|
<sz val="11" />
|
||||||
<color theme="1" />
|
<color theme="1" />
|
||||||
@@ -14,6 +14,14 @@
|
|||||||
<family val="2" />
|
<family val="2" />
|
||||||
<scheme val="minor" />
|
<scheme val="minor" />
|
||||||
</font>
|
</font>
|
||||||
|
<font>
|
||||||
|
<b />
|
||||||
|
<sz val="11" />
|
||||||
|
<color theme="1" />
|
||||||
|
<name val="Calibri" />
|
||||||
|
<family val="2" />
|
||||||
|
<scheme val="minor" />
|
||||||
|
</font>
|
||||||
</fonts>
|
</fonts>
|
||||||
<fills count="1">
|
<fills count="1">
|
||||||
<fill>
|
<fill>
|
||||||
@@ -32,12 +40,13 @@
|
|||||||
<cellStyleXfs count="1">
|
<cellStyleXfs count="1">
|
||||||
<xf numFmtId="0" fontId="0" fillId="0" borderId="0" />
|
<xf numFmtId="0" fontId="0" fillId="0" borderId="0" />
|
||||||
</cellStyleXfs>
|
</cellStyleXfs>
|
||||||
<cellXfs count="5">
|
<cellXfs count="6">
|
||||||
<xf xfId="0" />
|
<xf xfId="0" />
|
||||||
<xf numFmtId="166" xfId="0" applyNumberFormat="1" />
|
<xf numFmtId="166" xfId="0" applyNumberFormat="1" />
|
||||||
<xf numFmtId="167" xfId="0" applyNumberFormat="1" />
|
<xf numFmtId="167" xfId="0" applyNumberFormat="1" />
|
||||||
<xf numFmtId="168" xfId="0" applyNumberFormat="1" />
|
<xf numFmtId="168" xfId="0" applyNumberFormat="1" />
|
||||||
<xf numFmtId="169" xfId="0" applyNumberFormat="1" />
|
<xf numFmtId="169" xfId="0" applyNumberFormat="1" />
|
||||||
|
<xf xfId="0" fontId="1" />
|
||||||
</cellXfs>
|
</cellXfs>
|
||||||
<cellStyles count="1">
|
<cellStyles count="1">
|
||||||
<cellStyle name="Normal" builtinId="0" xfId="0" />
|
<cellStyle name="Normal" builtinId="0" xfId="0" />
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<worksheet xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
|
||||||
|
<sheetData>
|
||||||
|
<row r="1">
|
||||||
|
<c r="B1" t="inlineStr"><is><t></t></is></c>
|
||||||
|
<c r="C1" t="inlineStr"><is><t>test string</t></is></c>
|
||||||
|
<c r="D1"><v>3</v></c>
|
||||||
|
<c r="E1"><v>3.5</v></c>
|
||||||
|
<c r="F1" t="b"><v>0</v></c>
|
||||||
|
<c r="G1" t="b"><v>1</v></c>
|
||||||
|
</row>
|
||||||
|
<row r="2">
|
||||||
|
<c r="A2" t="inlineStr"><is><t>1900-02-28</t></is></c>
|
||||||
|
<c r="B2" s="1"><v>61</v></c>
|
||||||
|
<c r="C2" s="3"><v>61.625</v></c>
|
||||||
|
<c r="D2" s="2"><v>0.625</v></c>
|
||||||
|
<c r="E2" s="2"><v>0.625</v></c>
|
||||||
|
<c r="F2" s="4"><v>1</v></c>
|
||||||
|
</row>
|
||||||
|
</sheetData>
|
||||||
|
<autoFilter ref="A1:G1" />
|
||||||
|
</worksheet>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<worksheet xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
|
||||||
|
<sheetData>
|
||||||
|
<row r="1">
|
||||||
|
<c r="B1" t="inlineStr"><is><t></t></is></c>
|
||||||
|
<c r="C1" t="inlineStr"><is><t>test string</t></is></c>
|
||||||
|
<c r="D1"><v>3</v></c>
|
||||||
|
<c r="E1"><v>3.5</v></c>
|
||||||
|
<c r="F1" t="b"><v>0</v></c>
|
||||||
|
<c r="G1" t="b"><v>1</v></c>
|
||||||
|
</row>
|
||||||
|
<row r="2">
|
||||||
|
<c r="A2" t="inlineStr"><is><t>1900-02-28</t></is></c>
|
||||||
|
<c r="B2" s="1"><v>61</v></c>
|
||||||
|
<c r="C2" s="3"><v>61.625</v></c>
|
||||||
|
<c r="D2" s="2"><v>0.625</v></c>
|
||||||
|
<c r="E2" s="2"><v>0.625</v></c>
|
||||||
|
<c r="F2" s="4"><v>1</v></c>
|
||||||
|
</row>
|
||||||
|
</sheetData>
|
||||||
|
</worksheet>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<worksheet xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
|
||||||
|
<sheetViews>
|
||||||
|
<sheetView tabSelected="1" workbookViewId="0">
|
||||||
|
<pane ySplit="1" topLeftCell="A2" activePane="bottomLeft" state="frozen" />
|
||||||
|
<selection pane="bottomLeft" />
|
||||||
|
</sheetView>
|
||||||
|
</sheetViews>
|
||||||
|
<sheetData>
|
||||||
|
<row r="1">
|
||||||
|
<c r="B1" t="inlineStr"><is><t></t></is></c>
|
||||||
|
<c r="C1" t="inlineStr"><is><t>test string</t></is></c>
|
||||||
|
<c r="D1"><v>3</v></c>
|
||||||
|
<c r="E1"><v>3.5</v></c>
|
||||||
|
<c r="F1" t="b"><v>0</v></c>
|
||||||
|
<c r="G1" t="b"><v>1</v></c>
|
||||||
|
</row>
|
||||||
|
<row r="2">
|
||||||
|
<c r="A2" t="inlineStr"><is><t>1900-02-28</t></is></c>
|
||||||
|
<c r="B2" s="1"><v>61</v></c>
|
||||||
|
<c r="C2" s="3"><v>61.625</v></c>
|
||||||
|
<c r="D2" s="2"><v>0.625</v></c>
|
||||||
|
<c r="E2" s="2"><v>0.625</v></c>
|
||||||
|
<c r="F2" s="4"><v>1</v></c>
|
||||||
|
</row>
|
||||||
|
</sheetData>
|
||||||
|
</worksheet>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<worksheet xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
|
||||||
|
<cols>
|
||||||
|
<col min="1" max="1" width="2.28515625" bestFit="1" customWidth="1" />
|
||||||
|
<col min="2" max="2" width="2.42578125" bestFit="1" customWidth="1" />
|
||||||
|
<col min="3" max="3" width="2.5703125" bestFit="1" customWidth="1" />
|
||||||
|
<col min="4" max="4" width="2.7109375" bestFit="1" customWidth="1" />
|
||||||
|
<col min="5" max="5" width="2.85546875" bestFit="1" customWidth="1" />
|
||||||
|
<col min="6" max="6" width="3" bestFit="1" customWidth="1" />
|
||||||
|
<col min="7" max="7" width="3.140625" bestFit="1" customWidth="1" />
|
||||||
|
</cols>
|
||||||
|
<sheetData>
|
||||||
|
<row r="1">
|
||||||
|
<c r="B1" t="inlineStr"><is><t></t></is></c>
|
||||||
|
<c r="C1" t="inlineStr"><is><t>test string</t></is></c>
|
||||||
|
<c r="D1"><v>3</v></c>
|
||||||
|
<c r="E1"><v>3.5</v></c>
|
||||||
|
<c r="F1" t="b"><v>0</v></c>
|
||||||
|
<c r="G1" t="b"><v>1</v></c>
|
||||||
|
</row>
|
||||||
|
<row r="2">
|
||||||
|
<c r="A2" t="inlineStr"><is><t>1900-02-28</t></is></c>
|
||||||
|
<c r="B2" s="1"><v>61</v></c>
|
||||||
|
<c r="C2" s="3"><v>61.625</v></c>
|
||||||
|
<c r="D2" s="2"><v>0.625</v></c>
|
||||||
|
<c r="E2" s="2"><v>0.625</v></c>
|
||||||
|
<c r="F2" s="4"><v>1</v></c>
|
||||||
|
</row>
|
||||||
|
</sheetData>
|
||||||
|
</worksheet>
|
||||||
@@ -16,14 +16,14 @@ using NUnit.Framework;
|
|||||||
|
|
||||||
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.DataStorage
|
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.DataStorage
|
||||||
{
|
{
|
||||||
public partial class SaveAsExcelFileStreamWriterHelperTests : IDisposable
|
public partial class SaveAsExcelFileStreamWriterHelperTests
|
||||||
{
|
{
|
||||||
private Stream _stream;
|
[Test]
|
||||||
public SaveAsExcelFileStreamWriterHelperTests()
|
public void DefaultSheet()
|
||||||
{
|
{
|
||||||
_stream = new MemoryStream();
|
var stream = new MemoryStream();
|
||||||
using (var helper = new SaveAsExcelFileStreamWriterHelper(_stream, true))
|
using (var helper = new SaveAsExcelFileStreamWriterHelper(stream, true))
|
||||||
using (var sheet = helper.AddSheet())
|
using (var sheet = helper.AddSheet(null, 7))
|
||||||
{
|
{
|
||||||
var value = new DbCellValue();
|
var value = new DbCellValue();
|
||||||
sheet.AddRow();
|
sheet.AddRow();
|
||||||
@@ -70,64 +70,431 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.DataStorage
|
|||||||
value.RawObject = new TimeSpan(24, 00, 00);
|
value.RawObject = new TimeSpan(24, 00, 00);
|
||||||
sheet.AddCell(value);
|
sheet.AddCell(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ContentMatch(stream, "[Content_Types].xml");
|
||||||
|
ContentMatch(stream, "_rels/.rels");
|
||||||
|
ContentMatch(stream, "xl/_rels/workbook.xml.rels");
|
||||||
|
ContentMatch(stream, "xl/styles.xml");
|
||||||
|
ContentMatch(stream, "xl/workbook.xml");
|
||||||
|
ContentMatch(stream, "xl/worksheets/sheet1.xml");
|
||||||
|
|
||||||
|
stream.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void SheetWithFrozenHeaderRow()
|
||||||
|
{
|
||||||
|
var stream = new MemoryStream();
|
||||||
|
using (var helper = new SaveAsExcelFileStreamWriterHelper(stream, true))
|
||||||
|
using (var sheet = helper.AddSheet(null, 7))
|
||||||
|
{
|
||||||
|
sheet.FreezeHeaderRow();
|
||||||
|
|
||||||
|
var 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentMatch(stream, "[Content_Types].xml");
|
||||||
|
ContentMatch(stream, "_rels/.rels");
|
||||||
|
ContentMatch(stream, "xl/_rels/workbook.xml.rels");
|
||||||
|
ContentMatch(stream, "xl/styles.xml");
|
||||||
|
ContentMatch(stream, "xl/workbook.xml");
|
||||||
|
ContentMatch(stream, "xl/worksheets/sheet1.xml", "xl/worksheets/sheet1-headerRowFrozen.xml");
|
||||||
|
|
||||||
|
stream.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void SheetWithFrozenHeaderRowCalledTooLate()
|
||||||
|
{
|
||||||
|
var expectedException = new InvalidOperationException("Must be called before calling AddRow");
|
||||||
|
var actualException = new Exception();
|
||||||
|
var stream = new MemoryStream();
|
||||||
|
using (var helper = new SaveAsExcelFileStreamWriterHelper(stream, true))
|
||||||
|
using (var sheet = helper.AddSheet(null, 7))
|
||||||
|
{
|
||||||
|
sheet.AddRow();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
sheet.FreezeHeaderRow();
|
||||||
|
|
||||||
|
Assert.Fail("Did not throw an exception when calling FreezeHeaderRow too late");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
actualException = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.AreEqual(expectedException.GetType(), actualException.GetType());
|
||||||
|
Assert.AreEqual(expectedException.Message, actualException.Message);
|
||||||
|
|
||||||
|
stream.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void SheetWithBoldHeaderRow()
|
||||||
|
{
|
||||||
|
var stream = new MemoryStream();
|
||||||
|
using (var helper = new SaveAsExcelFileStreamWriterHelper(stream, true))
|
||||||
|
using (var sheet = helper.AddSheet(null, 7))
|
||||||
|
{
|
||||||
|
var 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentMatch(stream, "[Content_Types].xml");
|
||||||
|
ContentMatch(stream, "_rels/.rels");
|
||||||
|
ContentMatch(stream, "xl/_rels/workbook.xml.rels");
|
||||||
|
ContentMatch(stream, "xl/styles.xml");
|
||||||
|
ContentMatch(stream, "xl/workbook.xml");
|
||||||
|
ContentMatch(stream, "xl/worksheets/sheet1.xml", "xl/worksheets/sheet1-headerRowBold.xml");
|
||||||
|
|
||||||
|
stream.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void SheetWithAutoFilterEnabled()
|
||||||
|
{
|
||||||
|
var stream = new MemoryStream();
|
||||||
|
using (var helper = new SaveAsExcelFileStreamWriterHelper(stream, true))
|
||||||
|
using (var sheet = helper.AddSheet(null, 7))
|
||||||
|
{
|
||||||
|
sheet.EnableAutoFilter();
|
||||||
|
|
||||||
|
var 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentMatch(stream, "[Content_Types].xml");
|
||||||
|
ContentMatch(stream, "_rels/.rels");
|
||||||
|
ContentMatch(stream, "xl/_rels/workbook.xml.rels");
|
||||||
|
ContentMatch(stream, "xl/styles.xml");
|
||||||
|
ContentMatch(stream, "xl/workbook.xml");
|
||||||
|
ContentMatch(stream, "xl/worksheets/sheet1.xml", "xl/worksheets/sheet1-autoFilterEnabled.xml");
|
||||||
|
|
||||||
|
stream.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void SheetWriteColumnInformationCalledTooLate()
|
||||||
|
{
|
||||||
|
var expectedException = new InvalidOperationException("Must be called before calling AddRow");
|
||||||
|
var actualException = new Exception();
|
||||||
|
var stream = new MemoryStream();
|
||||||
|
using (var helper = new SaveAsExcelFileStreamWriterHelper(stream, true))
|
||||||
|
using (var sheet = helper.AddSheet(null, 7))
|
||||||
|
{
|
||||||
|
sheet.AddRow();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
sheet.WriteColumnInformation(new []{ 1F, 2F, 3F, 4F, 5F, 6F, 7F });
|
||||||
|
|
||||||
|
Assert.Fail("Did not throw an exception when calling WriteColumnInformation too late");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
actualException = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.AreEqual(expectedException.GetType(), actualException.GetType());
|
||||||
|
Assert.AreEqual(expectedException.Message, actualException.Message);
|
||||||
|
|
||||||
|
stream.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void SheetWriteColumnInformationWithWrongAmount()
|
||||||
|
{
|
||||||
|
var expectedException = new InvalidOperationException("Column count mismatch");
|
||||||
|
var actualException = new Exception();
|
||||||
|
var stream = new MemoryStream();
|
||||||
|
using (var helper = new SaveAsExcelFileStreamWriterHelper(stream, true))
|
||||||
|
using (var sheet = helper.AddSheet(null, 7))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
sheet.WriteColumnInformation(new[] { 1F, 2F, 3F, 4F, 5F });
|
||||||
|
|
||||||
|
Assert.Fail("Did not throw an exception when calling WriteColumnInformation with the wrong number of columns");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
actualException = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
var 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.AreEqual(expectedException.GetType(), actualException.GetType());
|
||||||
|
Assert.AreEqual(expectedException.Message, actualException.Message);
|
||||||
|
|
||||||
|
stream.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void SheetWriteColumnInformation()
|
||||||
|
{
|
||||||
|
var stream = new MemoryStream();
|
||||||
|
using (var helper = new SaveAsExcelFileStreamWriterHelper(stream, true))
|
||||||
|
using (var sheet = helper.AddSheet(null, 7))
|
||||||
|
{
|
||||||
|
sheet.WriteColumnInformation(new[] { 1F, 2F, 3F, 4F, 5F, 6F, 7F });
|
||||||
|
|
||||||
|
var 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentMatch(stream, "[Content_Types].xml");
|
||||||
|
ContentMatch(stream, "_rels/.rels");
|
||||||
|
ContentMatch(stream, "xl/_rels/workbook.xml.rels");
|
||||||
|
ContentMatch(stream, "xl/styles.xml");
|
||||||
|
ContentMatch(stream, "xl/workbook.xml");
|
||||||
|
ContentMatch(stream, "xl/worksheets/sheet1.xml", "xl/worksheets/sheet1-withColumns.xml");
|
||||||
|
|
||||||
|
stream.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
[GeneratedRegex("\\r?\\n\\s*")]
|
[GeneratedRegex("\\r?\\n\\s*")]
|
||||||
private static partial Regex GetContentRemoveLinebreakLeadingSpaceRegex();
|
private static partial Regex GetContentRemoveLinebreakLeadingSpaceRegex();
|
||||||
|
|
||||||
private void ContentMatch(string fileName)
|
private void ContentMatch(Stream stream, string fileName)
|
||||||
|
{
|
||||||
|
ContentMatch(stream, fileName, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ContentMatch(Stream stream, string realFileName, string referenceFileName)
|
||||||
{
|
{
|
||||||
string referencePath = Path.Combine(RunEnvironmentInfo.GetTestDataLocation(),
|
string referencePath = Path.Combine(RunEnvironmentInfo.GetTestDataLocation(),
|
||||||
"DataStorage",
|
"DataStorage",
|
||||||
"SaveAsExcelFileStreamWriterHelperTests",
|
"SaveAsExcelFileStreamWriterHelperTests",
|
||||||
fileName);
|
referenceFileName);
|
||||||
string referenceContent = File.ReadAllText(referencePath);
|
string referenceContent = File.ReadAllText(referencePath);
|
||||||
referenceContent = GetContentRemoveLinebreakLeadingSpaceRegex().Replace(referenceContent, "");
|
referenceContent = GetContentRemoveLinebreakLeadingSpaceRegex().Replace(referenceContent, "");
|
||||||
|
|
||||||
using (var zip = new ZipArchive(_stream, ZipArchiveMode.Read, true))
|
using (var zip = new ZipArchive(stream, ZipArchiveMode.Read, true))
|
||||||
{
|
{
|
||||||
using (var reader = new StreamReader(zip?.GetEntry(fileName)?.Open()!))
|
using (var reader = new StreamReader(zip?.GetEntry(realFileName)?.Open()!))
|
||||||
{
|
{
|
||||||
string realContent = reader.ReadToEnd();
|
string realContent = reader.ReadToEnd();
|
||||||
Assert.AreEqual(referenceContent, realContent);
|
Assert.AreEqual(referenceContent, realContent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
[Test]
|
|
||||||
public void CheckContentType()
|
|
||||||
{
|
|
||||||
ContentMatch("[Content_Types].xml");
|
|
||||||
}
|
|
||||||
[Test]
|
|
||||||
public void CheckTopRels()
|
|
||||||
{
|
|
||||||
ContentMatch("_rels/.rels");
|
|
||||||
}
|
|
||||||
[Test]
|
|
||||||
public void CheckWorkbookRels()
|
|
||||||
{
|
|
||||||
ContentMatch("xl/_rels/workbook.xml.rels");
|
|
||||||
}
|
|
||||||
[Test]
|
|
||||||
public void CheckStyles()
|
|
||||||
{
|
|
||||||
ContentMatch("xl/styles.xml");
|
|
||||||
}
|
|
||||||
[Test]
|
|
||||||
public void CheckWorkbook()
|
|
||||||
{
|
|
||||||
ContentMatch("xl/workbook.xml");
|
|
||||||
}
|
|
||||||
[Test]
|
|
||||||
public void CheckSheet1()
|
|
||||||
{
|
|
||||||
ContentMatch("xl/worksheets/sheet1.xml");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_stream.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SaveAsExcelFileStreamWriterHelperReferenceManagerTests
|
public class SaveAsExcelFileStreamWriterHelperReferenceManagerTests
|
||||||
|
|||||||
Reference in New Issue
Block a user