File: System\Windows\Controls\DataGridClipboardHelper.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework)
// 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;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using System.Windows;
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("&nbsp;");
                }
 
                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("&lt;");
                        break;
                    case '>':
                        output.Write("&gt;");
                        break;
                    case '"':
                        output.Write("&quot;");
                        break;
                    case '&':
                        output.Write("&amp;");
                        break;
                    case ' ':
                        if (prevCh == ' ')
                        {
                            output.Write("&nbsp;");
                        }
                        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;
            }
        }
    }
}