How to use clipboard to copy data from Excel Sheet to DataTable?

11,336

Solution 1

I found a clean and bullet-proof solution. Here the code:

First, a extension to convert a XmlDocument to XElement:

/// <summary> Convert XML Document to XDocument </summary>
/// <param name="xmlDocument">Attached XML Document</param>
public static XDocument fwToXDocument(this XmlDocument xmlDocument)
{
    using (XmlNodeReader xmlNodeReader = new XmlNodeReader(xmlDocument))
    {
        xmlNodeReader.MoveToContent();
        return XDocument.Load(xmlNodeReader);
    }
}

The complete function:

private DataTable clipboardExcelToDataTable(bool blnFirstRowHasHeader = false)
{
    string strTime = "S " + DateTime.Now.ToString("mm:ss:fff");
    var clipboard = Clipboard.GetDataObject();
    if (!clipboard.GetDataPresent("XML Spreadsheet")) return null;

    strTime += "\r\nRead " + DateTime.Now.ToString("mm:ss:fff");
    StreamReader streamReader = new StreamReader((MemoryStream)clipboard.GetData("XML Spreadsheet"));
    strTime += "\r\nFinish read " + DateTime.Now.ToString("mm:ss:fff");
    streamReader.BaseStream.SetLength(streamReader.BaseStream.Length - 1);

    XmlDocument xmlDocument = new XmlDocument();
    xmlDocument.LoadXml(streamReader.ReadToEnd());
    strTime += "\r\nRead XML Document " + DateTime.Now.ToString("mm:ss:fff");

    XNamespace ssNs = "urn:schemas-microsoft-com:office:spreadsheet";
    DataTable dtData = new DataTable();

    var linqRows = xmlDocument.fwToXDocument().Descendants(ssNs + "Row").ToList<XElement>();

    for (int x = 0; x < linqRows.Max(a => a.Descendants(ssNs + "Cell").Count()); x++)
        dtData.Columns.Add("Column " + (x + 1).ToString());

    int intCol = 0;

    DataRow drCurrent;

    linqRows.ForEach(rowElement =>
        {
            intCol = 0;
            drCurrent = dtData.Rows.Add();
            rowElement.Descendants(ssNs + "Cell")
                .ToList<XElement>()
                .ForEach(cell => drCurrent[intCol++] = cell.Value);
        });

    if (blnFirstRowHasHeader)
    {
        int x = 0;
        foreach (DataColumn dcCurrent in dtData.Columns)
            dcCurrent.ColumnName = dtData.Rows[0][x++].ToString();

        dtData.Rows.RemoveAt(0);
    }

    strTime += "\r\nF " + DateTime.Now.ToString("mm:ss:fff");

    return dtData;
}

The process takes ~15 seconds to read ~25,000 rows.

Works perfectly for any kind of data. Basically, the method creates a grid with the same structure of the Excel WorkSheet. Merge of rows or columns will fill up the first cell able. All columns will be string DataType by default.

Solution 2

If they are flat data you could do it like this.

private class Field
{
        public string Valor { get; set; }
}

private class Row
{
        public List<Field> Fields { get; set; }

        public Row(string value)
        {
            Fields = new List<Field>();
            var fieldsString = value.Split(new char[] {'\t'});
            foreach (string f in fieldsString)
            {
                Fields.Add(new Field {Valor = f});
            }
    }
}

public Parse()
{
    var data = Clipboard.GetDataObject();
    var datos = (string)data.GetData(DataFormats.Text);
    var stringRows = datos.Split(new Char[] {'\r', '\n'}, StringSplitOptions.RemoveEmptyEntries);
    var table = new List<Row>(stringRows.Length) ;
    foreach (string stringRow in stringRows)
    {
        table.Add( new Row(stringRow) );
    }
}
Share:
11,336
MiBol
Author by

MiBol

An ambitious developer with a strong technical background and expertise in multiples languages like C#, VB.Net, JavaScript, Transact-SQL, among others; who possesses self-discipline and the ability to work with the minimum of supervision. Able to play a key role throughout the software development/support life cycle of a project to ensure that quality solutions meet business objectives. Possessing a good team spirit, deadline orientated and having the ability to organize and present complex solutions clearly and accurately. I possess more than 12 years of experience leading end-to-end development since understand the specific need of the business, document, present a plan with the expectations of the new system, a development the system, perform testing phases, put the tool on production and working in the maintenance and continual improvement. I have the experience to work in tandem with the final users, to understand their work and bring a quality, simple and fast systems that meet their needs and accomplish the business expectations. Along my career, I've been developing more than 150 solutions and systems (most of them worked without a technical partner). I’m working on implementation projects among Accounts Payable and Receivable, Treasury, Purchasing, Affiliates, Taxes, HR, etc. In my latest years, I’m been working as coordinator of tech team (11 members) providing technical consulting, trainer of new topics and technologies, sharing experiences, and advice about the best way to develop a system based on a specific need. I steward other technologies as BluePrism/RPA/Robotics, Microsoft Azure, Knime, SAP Lumira and others

Updated on June 04, 2022

Comments

  • MiBol
    MiBol almost 2 years

    I have a Winform project, created on Microsoft Framework 3.5. The users may have installed Windows 7 or Windows XP, and Office 2007 or above.

    I'm working on in a procedure to get the clipboard data and put in on a C# DataTable. I already created a method to get the raw data from the clipboard and upload it in a DataTable.

    But in some cases, the Excel data shows a value, but internally have another:

    enter image description here

    I'm investigating a method to get the raw data from Excel:

    string XmlFmt = "XML Spreadsheet";
    var clipboard = Clipboard.GetDataObject();
    
    if (clipboard.GetDataPresent(XmlFmt))
    {
        var clipData = clipboard.GetData(XmlFmt);
        StreamReader streamReader = new StreamReader((MemoryStream)clipData);
        streamReader.BaseStream.SetLength(streamReader.BaseStream.Length - 1);
    
        string xmlText = streamReader.ReadToEnd();
        var stream = new StringReader(xmlText);
    
        XmlDocument xmlDocument = new XmlDocument();
        xmlDocument.LoadXml(xmlText);
    
        DataSet dsExcelData = new DataSet();
        dsExcelData.ReadXml(new XmlNodeReader(xmlDocument));
    }
    

    But, this method retrieves me a DataSet with multiples tables with the configuration of each part of the Excel Data: enter image description here enter image description here

    Basically, I want to convert these structures to a simple DataTable with only the raw data. Someone could help me with a hint how achieve this? ...I don't want to use a third party library in this implementation.