|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Globalization;
using System.IO;
using System.Text;
using MS.Internal.Text;
namespace System.Windows.Controls
{
internal static class DataGridClipboardHelper
{
internal static void FormatCell(object cellValue, bool firstCell, bool lastCell, StringBuilder sb, string format)
{
bool csv = string.Equals(format, DataFormats.CommaSeparatedValue, StringComparison.OrdinalIgnoreCase);
if (csv || string.Equals(format, DataFormats.Text, StringComparison.OrdinalIgnoreCase)
|| string.Equals(format, DataFormats.UnicodeText, StringComparison.OrdinalIgnoreCase))
{
if (cellValue != null)
{
bool escapeApplied = false;
int length = sb.Length;
FormatPlainText(cellValue.ToString(), csv, new StringWriter(sb, CultureInfo.CurrentCulture), ref escapeApplied);
if (escapeApplied)
{
sb.Insert(length, '"');
}
}
if (lastCell)
{
// Last cell
sb.Append('\r');
sb.Append('\n');
}
else
{
sb.Append(csv ? ',' : '\t');
}
}
else if (string.Equals(format, DataFormats.Html, StringComparison.OrdinalIgnoreCase))
{
if (firstCell)
{
// First cell - append start of row
sb.Append("<TR>");
}
sb.Append("<TD>"); // Start cell
if (cellValue != null)
{
FormatPlainTextAsHtml(cellValue.ToString(), new StringWriter(sb, CultureInfo.CurrentCulture));
}
else
{
sb.Append(" ");
}
sb.Append("</TD>"); // End cell
if (lastCell)
{
// Last cell - append end of row
sb.Append("</TR>");
}
}
}
internal static void GetClipboardContentForHtml(StringBuilder content)
{
const int bytecountPrefixContext = 135; // Byte count of context before the content in the HTML format
const int bytecountSuffixContext = 36; // Byte count of context after the content in the HTML format
content.Insert(0, "<TABLE>");
content.Append("</TABLE>");
// The character set supported by the clipboard is Unicode in its UTF-8 encoding.
// There are characters in Asian languages which require more than 2 bytes for encoding into UTF-8
// Marshal.SystemDefaultCharSize is 2 and would not be appropriate in all cases. We have to explicitly calculate the number of bytes.
byte[] sourceBytes = Encoding.Unicode.GetBytes(content.ToString());
byte[] destinationBytes = InternalEncoding.Convert(Encoding.Unicode, Encoding.UTF8, sourceBytes);
int bytecountEndOfFragment = bytecountPrefixContext + destinationBytes.Length;
int bytecountEndOfHtml = bytecountEndOfFragment + bytecountSuffixContext;
string prefix = string.Create(CultureInfo.InvariantCulture,
$"""
Version:1.0
StartHTML:00000097
EndHTML:{bytecountEndOfHtml:00000000}
StartFragment:00000133
EndFragment:{bytecountEndOfFragment:00000000}
<HTML>
<BODY>
<!--StartFragment-->
""");
content.Insert(0, prefix);
content.Append(
"""
<!--EndFragment-->
</BODY>
</HTML>
""");
}
private static void FormatPlainText(string s, bool csv, TextWriter output, ref bool escapeApplied)
{
if (s != null)
{
int length = s.Length;
for (int i = 0; i < length; i++)
{
char ch = s[i];
switch (ch)
{
case '\t':
if (!csv)
{
output.Write(' ');
}
else
{
output.Write('\t');
}
break;
case '"':
if (csv)
{
output.Write("\"\"");
escapeApplied = true;
}
else
{
output.Write('"');
}
break;
case ',':
if (csv)
{
escapeApplied = true;
}
output.Write(',');
break;
default:
output.Write(ch);
break;
}
}
if (escapeApplied)
{
output.Write('"');
}
}
}
// Code taken from ASP.NET file xsp\System\Web\httpserverutility.cs; same in DataGridViewCell.cs
private static void FormatPlainTextAsHtml(string s, TextWriter output)
{
if (s == null)
{
return;
}
int cb = s.Length;
char prevCh = '\0';
for (int i = 0; i < cb; i++)
{
char ch = s[i];
switch (ch)
{
case '<':
output.Write("<");
break;
case '>':
output.Write(">");
break;
case '"':
output.Write(""");
break;
case '&':
output.Write("&");
break;
case ' ':
if (prevCh == ' ')
{
output.Write(" ");
}
else
{
output.Write(ch);
}
break;
case '\r':
// Ignore \r, only handle \n
break;
case '\n':
output.Write("<br>");
break;
// REVIEW: what to do with tabs? See original code in xsp\System\Web\httpserverutility.cs
default:
// The seemingly arbitrary 160 comes from RFC
if (ch >= 160 && ch < 256)
{
output.Write("&#");
output.Write(((int)ch).ToString(NumberFormatInfo.InvariantInfo));
output.Write(';');
}
else
{
output.Write(ch);
}
break;
}
prevCh = ch;
}
}
}
}
|