Excel列名与uint的相互转换

        ”Excel列名与uint的相互转换“,这是一个挺有意思的命题,也是很多用程序与Excel打交道的程序员感兴趣的话题。

        在话题展开之前,要先统一一个重要概念:单独的A-Z对应的uint为1-26。我的实现方法也是将Excel列名的进制认定为26进制。好了,我们言归正传(代码中用到了自己写的一个char的扩展方法)。

        #region Join 字符串拼接扩展方法

        /// 
        /// 字符串拼接
        /// 
        /// 要操作的Char集合
        /// 按照参数合并后的字符串
        public static string Join(this IEnumerable iCharEnumerable)
        {
            StringBuilder tempReturnValue = new StringBuilder();
            foreach (var tempChar in iCharEnumerable)
            {
                tempReturnValue.Append(tempChar);
            }
            return tempReturnValue.ToString();
        }

        #endregion

        这个命题的难度就在于Excel的列名是从字母A到Z,然后AA、ZZ、AAA……,你说它是26进制也行,说它是27进制也未必没有道理。最关键的一点是:在这个进制中没有传统进制中”0“的概念。(单独的A代表1、Z代表26,AA代表27,那么”AA“中第一个A和第二个A分别代表什么呢?好吧,第一个A代表27,第二个A代表0^_^,读者可以考虑一下AAA中第二个A代表多少?)

       推理的过程我就不讲了,我讲一下结论:如果列名的长度>=2,则处理第一个字母的时候要+1,处理最后一个字母的时候要-1(从左往右),中间的不特殊处理。

       列名转换成uint

        /// 
        /// 将Excel列名转换为int
        /// 
        /// 
        /// 
        public static uint ExcelColumnToUInt32(this string iColumnName)
        {
            var tempColumn = iColumnName.Reverse().Join().ToUpper();
            var tempReturnValue = 0D;
            var tempStep = 26U;
            for (int i = 0, tempMax = tempColumn.Length - 1; i <= tempMax; i++)
            {
                tempReturnValue += Math.Pow(tempStep, i) * ((uint)tempColumn[i] - 64);
                if (i == tempMax && i > 0)
                {
                    tempReturnValue++;
                }
                else if (i == 0 && i < tempMax)
                {
                    tempReturnValue--;
                }
            }
            if (tempReturnValue > uint.MaxValue || tempReturnValue < uint.MinValue)
            {
                throw new Exception("Excel列名溢出:" + iColumnName);
            }
            return (uint)tempReturnValue;
        }

        uint转换成Excel列名

        /// 
        /// 将uint转换成Excel列名
        /// 
        /// 
        /// 
        public static string ToExcelColumnName(this uint iColumnIndex)
        {
            var tempColumnIndex = iColumnIndex;
            if (tempColumnIndex == 0)
            {
                throw new ArgumentOutOfRangeException("ColumnIndex必须大于0");
            }
            StringBuilder tempColumnName = new StringBuilder();
            uint tempMode = 0;
            uint tempRemainder = 0;
            uint tempCurrentPow = 26;
            for (int i = 0; ; i++)
            {
                if (i == 0)
                {
                    tempRemainder = (uint)Math.Floor((double)(tempColumnIndex - 1u) / tempCurrentPow);
                    tempMode = (tempColumnIndex - 1u) % tempCurrentPow;
                    tempColumnName.Append((char)(64 + tempMode + 1));
                    if (tempRemainder == 0)
                    {
                        break;
                    }
                    tempColumnIndex = tempRemainder;
                }
                else
                {
                    tempRemainder = (uint)Math.Floor((double)(tempColumnIndex) / tempCurrentPow);
                    tempMode = tempColumnIndex % tempCurrentPow;
                    if (tempMode == 0)
                    {
                        tempRemainder--;
                        tempMode = 26;
                    }
                    tempColumnName.Insert(0, (char)(64 + tempMode));
                    if (tempRemainder == 0)
                    { break; }
                    tempColumnIndex = tempRemainder;
                    
                }
            }
            return tempColumnName.ToString();
        }

        为了验证上述方法,我做了如下测试,先上测试代码(一个命令控制台代码,很简单,就没写什么注释),后上论证过程

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace TestExcelColumnNameConverter
{
    class Program
    {
        static void Main(string[] args)
        {
            var tempMax = 27;
            List tempList = new List();
            for (int i = 0; i < tempMax; i++)
            {
                Console.WriteLine(i);
                for (int j = 0; j < tempMax; j++)
                {
                    if (i > 0 && j == 0)
                    { j++; }
                    for (int k = 0; k < tempMax; k++)
                    {
                        if (j > 0 && k == 0)
                        { k++; }
                        for (int x = 0; x < tempMax; x++)
                        {
                            if (k > 0 && x == 0)
                            { x++; }
                            for (int y = 1; y < tempMax; y++)
                            {
                                var tempItem = new ColumnNameAndUint();
                                tempList.Add(tempItem);
                                var tempColumnName = new StringBuilder();
                                tempColumnName.Append(i > 0 ? ((char)(64 + i)).ToString() : string.Empty)
                                    .Append(j > 0 ? ((char)(64 + j)).ToString() : string.Empty)
                                    .Append(k > 0 ? ((char)(64 + k)).ToString() : string.Empty)
                                    .Append(x > 0 ? ((char)(64 + x)).ToString() : string.Empty)
                                    .Append((char)(64 + y));
                                tempItem.OriginalStrName = tempColumnName.ToString();
                                tempItem.UintName = tempItem.OriginalStrName.ExcelColumnToUInt32();
                                tempItem.StrName = tempItem.UintName.ToExcelColumnName();
                            }
                        }
                    }
                }
            }
            tempList.Where(s => s.OriginalStrName != s.StrName)
                .ToList().ForEach(s =>
                {
                    Console.WriteLine("{0}\t{1}\t{2}", s.OriginalStrName, s.StrName, s.UintName);
                });

            Console.WriteLine("正在写入文件");            
            using (var tempFs = new StreamWriter(String.Format("../../{0}.txt", DateTime.Now.ToString("yyyMMddHHmmss")), true, Encoding.UTF8))
            {
                tempList.ForEach(s =>
                {
                    tempFs.WriteLine("{0}\t{1}\t{2}", s.OriginalStrName, s.StrName, s.UintName);
                });
                tempFs.Flush();
                tempFs.Close();
            }
            Console.WriteLine("写入完毕,输入任意字符退出程序!");
            Console.ReadKey();
        }
    }

    /// 
    /// 用于承载测试内容的载体,没有其他的意义
    /// 
    public class ColumnNameAndUint
    {
        public string OriginalStrName { get; set; }
        public String StrName { get; set; }
        public uint UintName { get; set; }
    } 

    /// 
    /// Excel列名和Uint转换功能的扩展类
    /// 
    public static class ExcelExtension
    {
        #region Join 字符串拼接扩展方法

        /// 
        /// 字符串拼接
        /// 
        /// 要操作的Char集合
        /// 按照参数合并后的字符串
        public static string Join(this IEnumerable iCharEnumerable)
        {
            StringBuilder tempReturnValue = new StringBuilder();
            foreach (var tempChar in iCharEnumerable)
            {
                tempReturnValue.Append(tempChar);
            }
            return tempReturnValue.ToString();
        }

        #endregion

        #region ExcelColumnToUInt32

        /// 
        /// 将Excel列名转换为int
        /// 
        /// 
        /// 
        public static uint ExcelColumnToUInt32(this string iColumnName)
        {
            var tempColumn = iColumnName.Reverse().Join().ToUpper();
            var tempReturnValue = 0D;
            var tempStep = 26U;
            for (int i = 0, tempMax = tempColumn.Length - 1; i <= tempMax; i++)
            {
                tempReturnValue += Math.Pow(tempStep, i) * ((uint)tempColumn[i] - 64);
                if (i == tempMax && i > 0)
                {
                    tempReturnValue++;
                }
                else if (i == 0 && i < tempMax)
                {
                    tempReturnValue--;
                }
            }
            if (tempReturnValue > uint.MaxValue || tempReturnValue < uint.MinValue)
            {
                throw new Exception("Excel列名溢出:" + iColumnName);
            }
            return (uint)tempReturnValue;
        }

        #endregion

        #region ToExcelColumnName

        /// 
        /// 将uint转换成Excel列名
        /// 
        /// 
        /// 
        public static string ToExcelColumnName(this uint iColumnIndex)
        {
            var tempColumnIndex = iColumnIndex;
            if (tempColumnIndex == 0)
            {
                throw new ArgumentOutOfRangeException("ColumnIndex必须大于0");
            }
            StringBuilder tempColumnName = new StringBuilder();
            uint tempMode = 0;
            uint tempRemainder = 0;
            uint tempCurrentPow = 26;
            for (int i = 0; ; i++)
            {
                if (i == 0)
                {
                    tempRemainder = (uint)Math.Floor((double)(tempColumnIndex - 1u) / tempCurrentPow);
                    tempMode = (tempColumnIndex - 1u) % tempCurrentPow;
                    tempColumnName.Append((char)(64 + tempMode + 1));
                    if (tempRemainder == 0)
                    {
                        break;
                    }
                    tempColumnIndex = tempRemainder;
                }
                else
                {
                    tempRemainder = (uint)Math.Floor((double)(tempColumnIndex) / tempCurrentPow);
                    tempMode = tempColumnIndex % tempCurrentPow;
                    if (tempMode == 0)
                    {
                        tempRemainder--;
                        tempMode = 26;
                    }
                    tempColumnName.Insert(0, (char)(64 + tempMode));
                    if (tempRemainder == 0)
                    { break; }
                    tempColumnIndex = tempRemainder;

                }
            }
            return tempColumnName.ToString();
        }

        #endregion
    }
}

        上述代码会测试从A到ZZZZZ(5个Z)的区间内的所有列名转换,生成了一个247M的txt文件,我将文件内容导入到Sql server2008r2进行数据分析,以证明转换方法是正确的。

        txt文件我就不上传了o(╯□╰)o,贴个截图吧

 

创建用于分析数据的物理表

CREATE TABLE ExcelColumnInfo
    (
      OriginalName NVARCHAR(20) ,
      StrName NVARCHAR(20) ,
      UintName BIGINT
    )
--导入数据后创建三个蹩脚的索引
CREATE INDEX aa ON dbo.ExcelColumnInfo(OriginalName)
CREATE INDEX bb ON dbo.ExcelColumnInfo(StrName)
CREATE INDEX cc ON dbo.ExcelColumnInfo(UintName)

 

        将文本文件内容导入到Sql中

       

 

--查看最小值
SELECT  MIN(OriginalName) AS OriginalName ,
        MIN(StrName) AS StrName ,
        MIN(UintName) AS UintName
FROM    dbo.ExcelColumnInfo

结果集

 

 
--查看最大值
SELECT  MAX(OriginalName) AS OriginalName ,
        MAX(StrName) AS StrName ,
        MAX(UintName) AS UintName
FROM    dbo.ExcelColumnInfo

结果集

 

--查看通过转换函数生成列名是否存在于原始列名不存在的值
SELECT  *
FROM    dbo.ExcelColumnInfo
WHERE   OriginalName <> StrName ;

结果集

 

--查询UnintName是否存在数据孤岛
SELECT  *
FROM    dbo.ExcelColumnInfo T1
WHERE   NOT EXISTS ( SELECT 1
                     FROM   dbo.ExcelColumnInfo T2
                     WHERE  t1.UintName + 1 = t2.UintName
                            OR t1.UintName - 1 = t2.UintName ) 

结果集(查询的时候有点慢)


 通过上述验证,可以证明转换规则的正确性。如果发现bug可以联系我。

 

你可能感兴趣的:(OpenXml,C#,列名转换,Excel,OpenXml)