// // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // #nullable disable using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Web; using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage { /// /// Writer for exporting results to a Markdown table. /// public class SaveAsMarkdownFileStreamWriter : SaveAsStreamWriter { private const string Delimiter = "|"; private static readonly Regex NewlineRegex = new Regex(@"(\r\n|\n|\r)", RegexOptions.Compiled); private readonly Encoding _encoding; private readonly string _lineSeparator; public SaveAsMarkdownFileStreamWriter( Stream stream, SaveResultsAsMarkdownRequestParams requestParams, IReadOnlyList columns) : base(stream, requestParams, columns) { // Parse the request params this._lineSeparator = string.IsNullOrEmpty(requestParams.LineSeparator) ? Environment.NewLine : requestParams.LineSeparator; this._encoding = ParseEncoding(requestParams.Encoding, Encoding.UTF8); // Output the header if requested if (requestParams.IncludeHeaders) { // Write the column header IEnumerable selectedColumnNames = columns.Skip(this.ColumnStartIndex) .Take(this.ColumnCount) .Select(c => EncodeMarkdownField(c.ColumnName)); string headerLine = string.Join(Delimiter, selectedColumnNames); this.WriteLine($"{Delimiter}{headerLine}{Delimiter}"); // Write the separator row var separatorBuilder = new StringBuilder(Delimiter); for (int i = 0; i < this.ColumnCount; i++) { separatorBuilder.Append($"---{Delimiter}"); } this.WriteLine(separatorBuilder.ToString()); } } /// public override void WriteRow(IList row, IReadOnlyList columns) { IEnumerable selectedCells = row.Skip(this.ColumnStartIndex) .Take(this.ColumnCount) .Select(c => EncodeMarkdownField(c.DisplayValue)); string rowLine = string.Join(Delimiter, selectedCells); this.WriteLine($"{Delimiter}{rowLine}{Delimiter}"); } internal static string EncodeMarkdownField(string? field) { // Special case for nulls if (field == null) { return "NULL"; } // Escape HTML entities, since Markdown supports inline HTML field = HttpUtility.HtmlEncode(field); // Escape pipe delimiters field = field.Replace(@"|", @"\|"); // @TODO: Allow option to encode multiple whitespace characters as   // Replace newlines with br tags, since cell values must be single line field = NewlineRegex.Replace(field, @"
"); return field; } private void WriteLine(string line) { byte[] bytes = this._encoding.GetBytes(line + this._lineSeparator); this.FileStream.Write(bytes); } } }