• 正则表达式匹配EXCEL地址字符串


    为了实现数据库与EXCEL导入导出功能,需要写一个EXCEL访问类,遇到单元范围地址处理的问题。考虑几天最终决定用正则表达式来判断单元格行列地址字符串。网上找了半天也没找到相关详细的描述,只能自己DIY。

    这天书一样的规则实在令人却步,不过考虑到掌握之后前途无量,以前很多字符串解析都是用程序循环逻辑判断的方法实现,用正则表达式会变得很简单。花了2天时间研究正则表达式,终于实现了要求。在研究过程中还了解到小括号在正则表达式中的作用。把研究结果写出来的目的也是为了复习一遍以加深印象,同时也做个备忘。

    先给出EXCEL单元及范围地址的正则表达式:

    单地址: 

    (^(\$?)[a-zA-Z]+\2[0-9]*$)|(^\$?[0-9]+$)

    范围地址:

    ((^(\$?)[a-zA-Z]+\3[0-9]*)|(^(\$?)[0-9]+)):(((\3)[a-zA-Z]+\3[0-9]*$)|(\5[0-9]+$))

    并编写相应的方法,C#中要注意“\”符号的转义,要写成“\\”。

    下面是C#实现方法。


      using System.Text.RegularExpressions;
    //其他代码略
    //匹配单地址
    public static bool IsXlsCell(string input)
    {
        return Regex.IsMatch(input, "(^(\\$?)[a-zA-Z]+\\2[0-9]*$)|(^\\$?[0-9]+$)");
    }
    
    // 匹配范围地址
    public static bool IsXlsRange(string input)
    {
        return Regex.IsMatch(input, "((^(\\$?)[a-zA-Z]+\\3[0-9]*)|(^(\\$?)[0-9]+)):(((\\3)[a-zA-Z]+\\3[0-9]*$)|(\\5[0-9]+$))");
    }
    

    分析并创建基本零件:

    以单地址为例。在EXCEL中单个单元格地址的格式由行列参数组成,格式是“$列号$行号”,例如“$A$10”,也可以省略"$",如“A10”,该字符串表示1列10行的单元格。两种格式EXCEL都支持。然后分析单个单元格字符串的其他两种情况。EXCEL中另两种特殊情况是单列和单行,格式是“$列号”和“$行号”,比如“$A”和“$10”,或者“A”和“10”,分别表示列号为1的整个列和行号为10的整个行。

    EXCEL中的对象的方法参数顺序与之相反“CELL[int row,int col].Value”,是行在前列在后。而地址描述中字母总是表示列号,数字总是表示行号,有关字母转换成数值另外写文章说明。

    经过如此分析,可见单个单元地址就三种情况,外加“$”符号组合共6种组合。即“[$][COLUMN][$][ROW]”,这个就是模型,而且列只能是字母(不区分大小写),行只能是数字,方括号代表可省略。虽然这个写法不是很准确,但是基本已经能看出点正则表达式的影子了,也清楚地看出,除了"$"符号的修饰,单元地址的组成就是两部分,列项与行项。

    下面一步步来组装基本的正则表达式,为了条理清晰,先给出每一项的结果,每一项你可以理解为一个零部件。

    • “$”符号的正则表达式“\$?

    “$”符号可有可无,而为了完整性也不能丢下它不管,也为了字符串的美观,更是为了后面测试正则表达式中小括号的缓存作用,我做了这样的规则,一旦第一个匹配项带“$”,后面的匹配项必须有,否则视为无效,即不匹配(简直多此一举,哈哈,其实在实际应用时,匹配之前可以先Replace掉)。匹配单个可有可无的符号是“\$?”,由于“$”在正则表达式中式结束限定符,所以必须要转义,转义符和C-LIKE语言一样,都是“\”符,后面的“?”代表之前的匹配项匹配0个或1个,即可有可无啦。

    • COLUMN列的正则表达式的基本格式“[a-zA-Z]

    “[a-zA-Z]”表示匹配一个大写或小写的字母,注意,只是匹配一个字母,方括号对里面是范围,这里限定了匹配字母范围,即小写的a到z和大写的A到Z,z和A之间不能有其他字符,包括空格。好了,现在进行扩展,列号可能是多个字母组成,因此在方括号后面加个限定符,如果需要匹配1个或连续多个,用“+”符号,即“[a-zA-Z]+”,如果要匹配0个或多个,用“*”符号,即“[a-zA-Z]*”。

    • ROW行的正则表达式的基本格式“[0-9]

    这个就不用说了,和上面一样,0-9是限定范围,意思就是匹配单个数字符号,同理,要匹配1个或多个数字符号就是“[0-9]+”,要匹配0个或多个数字符号用“[0-9]*”。

    小结一下,“+”和“*”都是用来限定前面的匹配项的,你可以理解“+”为至少有一个,同样可以理解“*”为可以是连续多个,也可以没有。

    组装:

    以上三项是单元地址匹配所需要的主要零件,为了描述方便,这里为这些零件按顺序分别称为X、Y、Z作为代号,下面开始组装。

    从前面的分析看,单个单元格地址有三种情况,列行(单个单元格)、单列、单行,下面一个个来。

    • 单个单元格的正则表达式“^\$?[a-zA-Z]+\$?[0-9]+$

    这种情况下,列字母之前只允许出现“$”或没有其他字符,因此需要用到“^”开始限定符,该符号代表右边的匹配项是字符串开始,不存在其他符号,即^X,也就是在第一个零件前面加个“^”,展开就是“^\$?”。注意这里的“?”的含义。接下来就是列字母序列,那么就是^XY,而且在单个单元格地址中列字母必须有一个且可能为多个字母,因此后面加个“+”,即^XY+,展开就是“^\$?[a-zA-Z]+”。再来组装行的表达式零件,代码是Z,好吧,那就是^XY+Z,等等,好像少了什么,对了,数字前面可能还有个“$”要匹配,插入之^XY+XZ,在这里行的数字符号也是必须有一个且可能为多个数字符号,因此后面也加个“+”,即^XY+XZ+,然后,对了,数字后面不能有其他非数字符号,加个结束限定符“$”(前面说过),加上后就是“^XY+XZ+$,展开后就是“^\$?[a-zA-Z]+\$?[0-9]+$”。

    • 单列的正则表达式“^\$?[a-zA-Z]+$

    这个就简单了,根据上面的说明,很容易组装起来,“^XY+$,展开“^\$?[a-zA-Z]+$”,就这么简单。

    • 单行的正则表达式“^\$?[0-9]+$

    这就不用说了,“^XZ+$,展开“^\$?[0-9]+$”。

    注意X,Y,Z都代表上面三个基本零件,像做代数一样要代入表达式。而“^”符号代表开始,“$”符号代表结束,如果不加,就无法屏蔽掉非法字符,像“12AB34”这样的字符串,就也会认为单元格地址匹配,所以是不允许的。

    到此为止似乎是完成了,但是这样要写三个方法分别去匹配三种情况,而且判断时要分三次调用,这可还不够完美也很麻烦,需要进行整合优化。


    优化:

    为了方便理解,还是用代数式来描述,我们已经有了三个对应不同情况的表达式,他们是“^XY+XZ+$”、“^XY+$”、“^XZ+$”,可以看出,后两种分别是前一种的子集。

    去掉头尾限定符,进行集合运算。

    交集

    “XY+XZ+” ∩  “XY+” =“XY+”。单列与单元格交集。

    “XY+XZ+” ∩  “XZ+” =“XZ+”。单行与单元格交集。

    补集(忘记什么符号了,不管了用减号代替着)

    “XY+XZ+” -  “XY+” =“XZ+”。单列与单元格补集。

    “XY+XZ+” -  “XZ+” =“XY+”。单行与单元格补集。

    规则:左对齐,先看单列与单元格的关系。可以看出单列和单元格的交集的一部分“XY+”,补集是“XZ+”,不同之处是行部分,对于单列来说行号是缺失的,可以没有的。因此把“+”号换成“*”号,表示可以没有行。
    加上头尾限定符,记为:“^XY+XZ*$“,这样即可以匹配单元格也可以匹配单列。
    一下子解决了2种情况,还有一种单行情况只能单独列出,即"^XZ+$"。
    然后组合“(^XY+XZ*$)|(^XZ+$)”,恩,不错,经过这样子优化,应该是最优了,好了可以做代数了,代入扩展开来:
    "(^\$?[a-zA-Z]+\$?[0-9]*$)|(^\$?[0-9]+$)"
    感觉差不多了,最后强制"$"符号一致性(发烧一下)。由于"?"代表可有可无,因此“$A10”的单个单元格地址也能匹配,但是为了测试小括号的缓存机制,要求统一使用“$”符号,要么都不带,要么都带。
    把匹配“$”符号的匹配项加括号(这里不涉及单列和单行的情况),见标红色部分:

    "(^(\$?)[a-zA-Z]+(\$?)[0-9]*$)|(^\$?[0-9]+$)"

    无论前面一个匹配是否成功,要求后面一个要与之一致,根据正则表达式规则,小括号都会做匹配结果缓存,“\n”可以引用缓存中的结果,其中n代表缓存号,缓存号从1-99编号,按层次按先后编号,估计就是堆栈的原理。那么把括号分解看看第一个匹配项是几号。

    (  //缓存1#
      ^
      (\$?) //缓存2#
      [a-zA-Z]+
      (\$?) //缓存3#
      [0-9]*$
    )
    |
    (//缓存4#
      ^\$?[0-9]+$
    ) 
    

    这样清楚了,第一个“$”匹配项是2#缓存,OK,动个小手术,见红字,把“\$?”改成“\2”:

    "(^(\$?)[a-zA-Z]+(\$?)[0-9]*$)|(^\$?[0-9]+$)"=>"(^(\$?)[a-zA-Z]+\2[0-9]*$)|(^\$?[0-9]+$)".

    最后加上C#的转义符"\",这就是最终单地址的正则表达式。

    "(^(\\$?)[a-zA-Z]+\\2[0-9]*$)|(^\\$?[0-9]+$)"

     范围地址和此类似,就是结构上更复杂些。以上表达式在VS2008的C#中调试通过,无BUG。

     

     

     

  • 相关阅读:
    Codeforces Round #436 C. Bus
    Codeforces Round #436 B. Polycarp and Letters
    Codeforces Round #436 A. Fair Game
    Codeforces Round #439 C. The Intriguing Obsession
    Codeforces Round #438 C. Qualification Rounds
    Codeforces Round #438 B. Race Against Time
    hdu 1394 Minimum Inversion Number (树状数组求逆序对)
    hdu 6318 Swaps and Inversions (线段树求逆序对数)
    ACM/ICPC 2018亚洲区预选赛北京赛站网络赛 D 80 Days (线段树查询最小值)
    hdu 6299 Balanced Sequence (贪心)
  • 原文地址:https://www.cnblogs.com/CodeBlove/p/2132748.html
Copyright © 2020-2023  润新知