Hello World
Spiga

趣味编程:从字符串中提取信息

2009-10-12 14:20 by 老赵, 20535 visits

字符串解析是程序员工作中非常重要的一部分,也是非常考验编程能力的工作。基本上我在面试程序员的时候,一定会出一道编程题目作为考察的一方面,而这道题目有很大的可能性是做字符串的解析。例如,给出一个模式规则,要求写程序判断某个字符串是否符合特定格式。例如,要求将BB Code转化为HTML。而现在这个趣味编程题,来自于我目前正在进行的项目。因此从实用角度来说,也有一定现实意义。

这个标题基本上没有包含多少信息,不过我也实在不知道该如何描述这个问题。这个任务是要从一个字符串中提取一些信息,于是我们先来定义概念:

首先是“token”,token是最小的信息单元,我们可以把它当作是一个字符串来处理。而多个token则组成了一个token group,token group之间的各token使用“-”进行分割。例如,以下便定义一个了token group:

jeffz-hello-world

一个token group可以用一个字符串数组或列表来表示,例如上面的字符串则表示一个包含三个token的token group,分别是jeffz,hello和world。多个token group则可以组成一整个字符串信息,我们把它称为text。一个text中的各个token group使用“--”进行分割,例如:

group1-hello--group2-world

一个text可以认为是token group的数组或列表。因此,最终从一个text中提取到的信息,则可以用一个字符串数组的列表来表示。例如,以上的text的信息其实就类似于:

new List<string[]> { new string[] { "group1", "hello" }, new string[] { "group2", "world" } }

不过您想到这样一个问题:“-”是作为分割符使用的,但如果一个token中本身需要包含“-”又该如何呢?于是,我们又引入了单引号,被一组单引号包裹的token,其中所有的“-”被当作是普通的字符处理,不作为分隔符。例如:

jeffz-'hello-world'

这样一个字符串所表示的text,它包含一个token group,其中有两个token:

new List<string[]> { new string[] { "jeffz", "hello-world" } }

但是,既然单引号也有特殊含义了,那么一个token中又如何表示一个单引号呢?于是乎,我们再定义一个规则,如果一个token中需要包含单引号的话,我们需要使用单引号来包含这个token,并且token中的单引号变成两个单引号。例如:

jeffz-'hello''''world'

它所表示的数据即为:

new List<string[]> { new string[] { "jeffz", "hello''world" } }

text中包含四个单引号,但是表示的数据中只有两个单引号,这就是我们的“转义”规则。还有值得注意的是,如果token中需要包含单引号或“-”,那么这个token在表示的时候一定需要用一对单引号包裹起来——这也是为了“简化规则”。

这次的“趣味编程”便是希望写一个方法,从text中提取出“数据”,也就是一个List<string[]>,我们假设所有的输入都是正确的。

那么,这个规则又有什么含义呢?在我的项目中,这个字符串被当作是产品查询页面的URL,表示的自然是产品的查询条件。由于查询条件非常的丰富,还会根据不同的分类有所改变,因此在URL中表现查询条件非常的麻烦。例如,淘宝的查询页面URL便是这样的:

http://search1.taobao.com/browse/0/n-g,geytami-g,geytami-------1------7------------------4----0--------------------g,ojsxgzlsozsv64dsnfrwkwzvgaydalbzhe4tsxi---g,whflzr5rxy-------2-------b--40--coefp-0-all-0.htm?search_multi_condition=1&ssid=s1#ListView

但是,这个URL对于某个人来说几乎没有任何可读性。普通用户对此的关注度自然小很多,但是这样的URL也会给开发人员的工作造成不小的麻烦。在我看来,有一个相对易读的规则还是很重要的。此外,据说URL中的关键字对于SEO也很有帮助(当然这点我不确定)。因此,我们设计了本文这种“自洽”的数据表示方式。如果您足够“敏感”的话,会发现作为特殊字符的单引号或是“-”符号,它们在URL上是不需要转义的——这也是我们为它们赋予特殊含义的原因。

于是现在,我们便可以使用这样的URL字符串来表示一个查询条件了:

cpu-3.0g--color-red-green-black--price-5000-8000--weight-'3-'--keywords-'levi''s'

这个text拆开后的得到的数据便是:

new List<string[]>
{
    new string[] { "cpu", "3.0g" },
    new string[] { "color", "red", "green", "black" },
    new string[] { "price", "5000", "8000" },
    new string[] { "weight", "3-" },
    new string[] { "keywords", "levi's" },
}

于是这个查询条件便是:CPU为3.0G,颜色为红、绿或黑,价格在5000到8000,重量在3千克内,并包含“levi's”关键字的……笔记本?

您也来试试看吧!(参考答案:上

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

110 条回复

  1. 温景良(Jason)
    *.*.*.*
    链接

    温景良(Jason) 2009-10-12 14:23:00

    没看明白想说明是的什么东西

  2. 伊牛娃
    *.*.*.*
    链接

    伊牛娃 2009-10-12 14:25:00

    SF不

  3. 老赵
    admin
    链接

    老赵 2009-10-12 14:30:00

    @温景良(Jason)
    再看一遍就明白了,呵呵

  4. Funeral
    *.*.*.*
    链接

    Funeral 2009-10-12 14:33:00

    这个有意思

  5. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-10-12 15:17:00

    -0- 我已经没什么好说的了 纯粹水一下吧

    唰~~~

  6. Y-Y[未注册用户]
    *.*.*.*
    链接

    Y-Y[未注册用户] 2009-10-12 15:18:00

    不知道能否举个例子来深入说明一下这样做的好处

  7. Coolin
    *.*.*.*
    链接

    Coolin 2009-10-12 15:19:00

    ……

  8. szgamer[未注册用户]
    *.*.*.*
    链接

    szgamer[未注册用户] 2009-10-12 15:20:00

    token 似乎也有包含 -- 的可能性,需追加到分析规则中

  9. 徐少侠
    *.*.*.*
    链接

    徐少侠 2009-10-12 15:22:00

    温景良(Jason):没看明白想说明是的什么东西


    个老赵的东西的确需要多看几遍

  10. 方不白
    *.*.*.*
    链接

    方不白 2009-10-12 15:23:00

    感觉像是某种URL重写的规则

  11. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-10-12 15:23:00

    吐个槽
    new List<string>
    还是
    new List<string[]>?

  12. wingoo
    *.*.*.*
    链接

    wingoo 2009-10-12 15:23:00

    逐字符解析?

  13. Duron800[未注册用户]
    *.*.*.*
    链接

    Duron800[未注册用户] 2009-10-12 15:25:00

    最后一个例子,泛型的参数笔误了吧?string[]?

  14. 老赵
    admin
    链接

    老赵 2009-10-12 15:31:00

    Y-Y:不知道能否举个例子来深入说明一下这样做的好处


    不用深入了,好处都已经写了,呵呵。

  15. 老赵
    admin
    链接

    老赵 2009-10-12 15:31:00

    @看IT新闻,上博客园新闻频道
    你的代码太乱来了,不好意思我删了啊。

  16. 老赵
    admin
    链接

    老赵 2009-10-12 15:32:00

    szgamer:token 似乎也有包含 -- 的可能性,需追加到分析规则中


    token中有-或是--没区别啊,呵呵。

  17. killkill
    *.*.*.*
    链接

    killkill 2009-10-12 15:35:00

    token 让我想起了,编译原理中的词法分析器,呵呵
    老赵的要求是什么?

  18. 老赵
    admin
    链接

    老赵 2009-10-12 15:36:00

    @韦恩卑鄙
    加油加油。

  19. 老赵
    admin
    链接

    老赵 2009-10-12 15:37:00

    @killkill
    没要求阿,解析正确就可以了,呵呵。

  20. 老达
    *.*.*.*
    链接

    老达 2009-10-12 15:42:00

    bbsmax 4.0中的后台查询,我们定义了一个搜索条件的基类,使所有继承自这个类型的搜索条件对象都可以被序列化成一个加密的字符串,用于在QueryString中传递搜索条件。

    之所以需要在QueryString中传递搜索条件,是因为搜索结果是有分页的,分页链接是普通a标签,所以需要能把搜索条件带给下一个页面,和淘宝类似。

    之所以加密,是考虑到后台使用的安全性。

    我想老赵的目的应该和我说的类似吧? :)

  21. 老赵
    admin
    链接

    老赵 2009-10-12 15:46:00

    @老达
    我的目的就是“不加密”啊,呵呵。

  22. Teddy's Knowledge Bas…
    *.*.*.*
    链接

    Teddy's Knowledge Base 2009-10-12 15:50:00

    让URL meaningful一些,自然是好的,不过如果查询条件也是从URL这样解析的那就不安全了,如果我能猜出你的column,就能任意构造查询条件了,多怕怕啊。当然,我相信老赵只是举例说明字符串解析很有用处,不是真的让大家这样玩。taobao那个address我猜顶多也就作为一个key使用,而且应该是更具条件生成的,不可能是先有URL,再用来解析的。

  23. 老赵
    admin
    链接

    老赵 2009-10-12 15:56:00

    @Teddy's Knowledge Base
    有人想猜猜其实也没有关系啦,在业务上校验一下,只执行合适的查询条件就好了,呵呵。
    taobao的话,我觉得它的url也表示了完整的信息,只不过可能进行了一定的编码吧。
    比如,这个URL其实包含了关键字“笔记本”,但是URL上实在看不出来,不过理论上读一下JS代码就知道了。

  24. David
    *.*.*.*
    链接

    David 2009-10-12 15:57:00

    感觉是:在预定的字符串中提取信息哦!:)

  25. 老赵
    admin
    链接

    老赵 2009-10-12 15:58:00

    @David
    或者说,在指定格式的字符串中提取信息。

  26. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-10-12 16:00:00

    有个问题

    'a'-b 应该是两个元素

    那么 是否 'a'a-b 算三个元素?

    闭合的''后是否必须廉洁一个-?

  27. 老赵
    admin
    链接

    老赵 2009-10-12 16:05:00

    @韦恩卑鄙
    在我看来'a'a-b属于不合法输入,呵呵。
    因为“如果token中需要包含单引号或“-”,那么这个token在表示的时候一定需要用一对单引号包裹起来”,而token之间是用-分割的。

  28. Yankee
    *.*.*.*
    链接

    Yankee 2009-10-12 16:15:00

  29. winter-cn
    *.*.*.*
    链接

    winter-cn 2009-10-12 16:17:00

            static List<List<string>> parse(string source)
            {
                Func<char, object> s1 = c => null;//token开始
                Func<char, object> s2 = c => null;//遇到-
                Func<char, object> s3 = c => null;//遇到'
                Func<char, object> s4 = c => null;//单引号外的token
                Func<char, object> s5 = c => null;//单引号内的token
    
    
                StringBuilder currentToken = new StringBuilder();
                List<string> currentTokenGroup = new List<string>();
                List<List<string>> result = new List<List<string>>();
                
                s1 = delegate(char c)//token开始
                {
                    if (c == '-')
                    {
                        //如果token中需要包含单引号或“-”,那么这个token在表示的时候一定需要用一对单引号包裹起来
                        throw new Exception("Invalid Source String");
                    }
                    if (c == '\'')
                    {
                        return s5;
                    }
                    else
                    {
                        currentToken.Append(c);
                        return s4;
                    }
    
                };
                s2 = delegate(char c)//遇到-
                {
                    if (c == '-')
                    {
                        //token Group结束
                        result.Add(currentTokenGroup);
                        currentTokenGroup = new List<string>();
                        return s1;
                    }
                    else if (c == '\'')
                    {
                        return s5;
                    }
                    else
                    {
                        currentToken.Append(c);
                        return s4;
                    }
                    
                };
                s3 = delegate(char c)//遇到'
                {
                    if (c == '\'')
                    {
                        currentToken.Append('\'');
                        return s5;
                    }
                    else if (c == '-')
                    {
                        //token结束
                        currentTokenGroup.Add(currentToken.ToString());
                        currentToken = new StringBuilder();
                        return s2;
                    }
                    else
                    {
                        throw new Exception("Invalid Source String");
                    }
                };
                s4 = delegate(char c)//单引号外的token
                {
                    if (c == '\'')
                    {
                        //如果token中需要包含单引号或“-”,那么这个token在表示的时候一定需要用一对单引号包裹起来
                        throw new Exception("Invalid Source String");
                    }
                    if (c == '-')
                    {
                        //token结束
                        currentTokenGroup.Add(currentToken.ToString());
                        currentToken = new StringBuilder();
                        return s2;
                    }
                    currentToken.Append(c);
                    return s4;
                };
                s5 = delegate(char c)//单引号内的token
                {
                    if (c == '\'')
                    {
                        return s3;
                    }
                    else
                    {
                        currentToken.Append(c);
                        return s5;
                    }
                };
                var arr = source.ToCharArray();
                Func<char, object> state = s1;
                for (int i = 0; i < arr.Length; i++)
                {
                    state = (Func<char, object>)state(arr[i]);
                }
    
                currentTokenGroup.Add(currentToken.ToString());
                result.Add(currentTokenGroup);
                return result;
            }
    

    大家都光说 不动手呢

  30. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-10-12 16:19:00

    写了一个 不过结果是顺序相反的stack 因为比起 index访问list
    我更喜欢peek() 访问最新一个节点


     var input ="cpu-3.0g--color-red-green-black--price-5000-8000--weight-'3-'--keywords-'levi''s'".ToCharArray ().ToList ();
                List<char> ContentList = new List<char>();
                bool Dashing=false  , WaitingAlterEnd =false ;
    
                Stack<List<string>> rval = new Stack<List<string>>() ;
                rval.Push(new List<string>()); 
    
                bool Altered = false ;
                input.ForEach(c =>
                {
                    if (Altered)
                    {
                        switch (c)
                        {                       
                         
                            case '-':
                                if (WaitingAlterEnd)
                                {
                                    WaitingAlterEnd = false;
                                    Altered = false;
                                    rval.Peek().Add(new string(ContentList.ToArray()));
                                    ContentList.Clear();
                                }
                                else
                                {
                                    ContentList.Add(c);
                                }
                                break;
                            case '\'':
                                if (WaitingAlterEnd)
                                {
                                    ContentList.Add(c);
                                    WaitingAlterEnd = false;
                                }
                                else 
                                {
                                    WaitingAlterEnd = true;
                                }
                                break;
                            default:
    
                                if (WaitingAlterEnd)
                                {
                                    throw new Exception("单引号错误");
                                }
                                ContentList.Add (c); 
                                
                                break;
                        } 
                    }
                    else 
                    {
                        switch (c)
                        {
                            case '-':
                                if (!Dashing)
                                {
                                    rval.Peek().Add(new string(ContentList.ToArray()));
                                    ContentList.Clear () ;
                                    Dashing = true;
                                }
                                else
                                {
                                    rval.Push(new List<string>());
                                    Dashing = false;
                                }
                                break ;
                            case '\'':
                                Altered = true;
                                break;
                            default:
                                ContentList.Add(c);
                                Dashing = false;
                                break;
                        } 
                    }
                    
                });
                rval.Peek().Add(new string(ContentList.ToArray()));
    
    



    没经过检验阿 错了随便吐我

  31. 老赵
    admin
    链接

    老赵 2009-10-12 16:21:00

    @韦恩卑鄙
    @winter-cn
    高手和普通人的一个重要区别,就在于高手是会直接动手滴。

  32. 吮指原味鸡
    *.*.*.*
    链接

    吮指原味鸡 2009-10-12 16:21:00

    想了好久,发现以自己的水平不能很简单的解决这个问题...

    小弟想用Src.Split这个方法来做:

    如果规则改为用"---"和"--"分割group和token
    用"'-'"转义token中的"-"("---"转义为"'-''-''-'")
    "'"仍转义为"''"
    并取消token外套' ' 的规定

    即将:
    cpu-3.0g--color-red-green-black--price-5000-8000--weight-'3-'--keywords-'levi''s'
    改为
    cpu--3.0g---color-red'-'green'-'black---price--5000'-'8000---weight--3'-'---keywords--levi''s

    解释时先Src.Split("---")分割group后,
    再Src.Split("--")分割token,
    再替换"'-'"为"-",
    再替换"''"为"'"
    也可以出结果

    小弟是菜鸟(刚工作一年),一直很稀换看老赵的文章(虽然很少看懂),支持老赵!

  33. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-10-12 16:30:00

    winter-cn 直接分割成5个状态的状态机了 好过分涅。。。

  34. 老赵
    admin
    链接

    老赵 2009-10-12 16:34:00

    @winter-cn
    想法简直和我如出一辙,不过,你是怎么处理'levi''s'这种情况的阿?

  35. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-10-12 16:39:00

    Jeffrey Zhao:
    @winter-cn
    想法简直和我如出一辙,不过,你是怎么处理'levi''s'这种情况的阿?


    你们两个是坏人


    印度公司待久了 我的脑子也印度了

  36. winter-cn
    *.*.*.*
    链接

    winter-cn 2009-10-12 16:46:00

    @Jeffrey Zhao
    s3的第一个if是处理这个的

  37. 老赵
    admin
    链接

    老赵 2009-10-12 16:49:00

    @winter-cn
    引号里的两个引号表示一个引号呢。
    'levi''s'表示的是levi's。

  38. winter-cn
    *.*.*.*
    链接

    winter-cn 2009-10-12 16:50:00

    韦恩卑鄙:winter-cn 直接分割成5个状态的状态机了 好过分涅。。。


    呵呵 这是传统方法了 我记得最早我最早好像是在Exceptional C++上看到的

  39. winter-cn
    *.*.*.*
    链接

    winter-cn 2009-10-12 16:51:00

    @Jeffrey Zhao
    对 s3是遇到一个引号以后的状态
    第一个if里是再遇到一个引号的意思

  40. killkill
    *.*.*.*
    链接

    killkill 2009-10-12 16:55:00

    @吮指原味鸡
    当年我做编译原理实验的时候也是这样做的,呵呵

  41. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-10-12 16:56:00

    winter-cn:
    @Jeffrey Zhao
    对 s3是遇到一个引号以后的状态
    第一个if里是再遇到一个引号的意思


    恩 我的14行也是
    既然进入 "上一个是转义后的单引号" 的状态 就只读一个单引号 或者减号就可以定案了



  42. 老赵
    admin
    链接

    老赵 2009-10-12 16:59:00

    @winter-cn
    娃哈哈,强大。

  43. 老赵
    admin
    链接

    老赵 2009-10-12 17:02:00

    @韦恩卑鄙
    看来我要换种方式做了……

  44. 亚历山大同志
    *.*.*.*
    链接

    亚历山大同志 2009-10-12 17:02:00

    把对象序列化成string再des后放url里如何?

  45. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-10-12 17:06:00

    Jeffrey Zhao:
    @韦恩卑鄙
    看来我要换种方式做了……


    我这边一个小弟作了一个 split的
    我们要走别人的路让别人无路可走

  46. Alvan
    *.*.*.*
    链接

    Alvan 2009-10-12 17:09:00

    taobao的查询url实际上不是query,而是index,与搜索引擎的index一样。
    参考文档《布尔代数和搜索引擎的索引》
    另外,可读的url对SEO基本没有什么帮助,只是可以让url的关键字部分在搜索结果中获得高亮效果,这个效果也仅对英文搜索有效。

  47. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-10-12 17:10:00

                string s = "cpu-3.0g--color-red-green-black--price-5000-8000--weight-'3-'--keywords-'levi''s'-'''-'''";
                List<string[]> biantai = new List<string[]>();
    
                string[] a = Regex.Split(s, "--");
                for (int i = 0; i < a.Length; i++)
                {
                    string[] c = a[i].Split('-');
                    List<string> c2 = new List<string>();
                    
                    int k=0;
                    for (int j = 0; j < c.Length; j++)
                    {
                        string destination="";
                        if (c[j].StartsWith("'") && c[j].EndsWith("'") && !c[j].EndsWith("''"))
                            destination = c[j].Substring(1, c[j].Length - 2).Replace("''", "'");
                        else if (c[j].StartsWith("'"))
                            destination = c[j].Substring(1, c[j].Length - 1).Replace("''", "'") + "-"+c[j++].Substring(0, c[j].Length - 1).Replace("''","'");
                        else
                            destination = c[j];
    
                        c2.Add(destination);
                    }
                    biantai.Add(c2.ToArray());
                }
    

  48. 老赵
    admin
    链接

    老赵 2009-10-12 17:16:00

    @Alvan
    就算是index,不也是query的展现形式嘛,呵呵,总之就是存放用于查询的信息阿。
    至于seo……我也不知道,我觉得seo的各种说法都是乱猜得。但是我这边有人坚持“非常有效果”,我没有反对依据,所以……不过看上去的确不错。

  49. 老赵
    admin
    链接

    老赵 2009-10-12 17:17:00

    @韦恩卑鄙
    怎么那么简单?不过看不懂了。
    我现在打算写F#的,如果你写F#了,我就换Haskell,再不行就Scala。
    我会的语言多不怕没东西写,嘿嘿。

  50. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-10-12 17:25:00

    @Jeffrey Zhao
    作为一个流氓你赢得了我的尊重。。。

  51. 老赵
    admin
    链接

    老赵 2009-10-12 17:28:00

    @韦恩卑鄙
    'hello--world'
    看到你用--拆分,我试了一下这个果然没通过……

  52. 麒麟.NET
    *.*.*.*
    链接

    麒麟.NET 2009-10-12 17:29:00

    @winter-cn
    妙哉,受教!

  53. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-10-12 17:30:00

    Jeffrey Zhao:
    @韦恩卑鄙
    'hello--world'
    看到你用--拆分,我试了一下这个果然没通过……


    不是我写的 小弟写的


    此小弟正在改bug 呵呵

  54. winter-cn
    *.*.*.*
    链接

    winter-cn 2009-10-12 17:32:00

    作为一个文盲我在此呼吁一下 貌似正则可以搞定
    本人正则水平太烂 期待高人出手

  55. zzjj296
    *.*.*.*
    链接

    zzjj296 2009-10-12 17:47:00

    有点意思.

  56. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-10-12 17:53:00

    正则的效率应该比状态机差点儿。

  57. 戏水
    *.*.*.*
    链接

    戏水 2009-10-12 20:56:00

    根据需求 查询条件更像是 键值对。 所以不同的键值对之间用非 - 符号分隔可行否? 例如
    cpu-amd,color-red 类似 这样解析起来是不是更方便一点哦

  58. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-10-12 21:04:00

    其实write-cn的方案已经很好了,但为了给我的链式处理的模板引擎鼓劲,整了个链式处理的版本:


    public static List<string[]> Parse( string text )
    {
    
      var result = new List<string[]>();
      var group = new List<string>();
      var tokenBuilder = new StringBuilder();
    
    
      bool inQuotation = false;
    
      List<Func<char, char, bool>> processors = new List<Func<char, char, bool>>();
    
    
      processors.Add( ( prev, current ) =>
        {
          if ( inQuotation )
            return false;
    
          if ( current == '-' && prev == '-' )
          {
            result.Add( group.ToArray() );
            group = new List<string>();
            return true;
          }
          else
            return false;
        }
      );
    
      processors.Add( ( prev, current ) =>
        {
          if ( inQuotation )
            return false;
    
          if ( current == '-' )
          {
            if ( tokenBuilder != null )
              group.Add( tokenBuilder.ToString() );
            tokenBuilder = new StringBuilder();
            return true;
          }
          else
            return false;
        }
      );
    
      processors.Add( ( prev, current ) =>
        {
          if ( inQuotation )
            return false;
    
          if ( current == '\'' && tokenBuilder.Length == 0 )
          {
            inQuotation = true;
            return true;
          }
          else
            return false;
        }
      );
    
      processors.Add( ( prev, current ) =>
        {
          if ( inQuotation )
          {
            if ( prev == '\'' && current == '\'' )
            {
              tokenBuilder.Append( '\'' );
              return true;
            }
          }
    
          return false;
        }
      );
    
      processors.Add( ( prev, current ) =>
        {
          if ( inQuotation )
          {
            if ( tokenBuilder.Length == 0 || tokenBuilder[tokenBuilder.Length - 1] == '\'' )
              return false;
    
            if ( prev == '\'' )
            {
              if ( current == '-' )
              {
                group.Add( tokenBuilder.ToString() );
                tokenBuilder = new StringBuilder(); ;
                inQuotation = false;
              }
              else
                throw new InvalidOperationException( "Syntax Error" );//如果单引号结束处没有紧接着-,则视为语法错误
    
              return true;
            }
          }
    
          return false;
        }
      );
    
      processors.Add( ( prev, current ) =>
        {
          if ( inQuotation )
          {
            if ( current != '\'' )
              tokenBuilder.Append( current );
            return true;
          }
    
          return false;
        }
      );
    
    
      processors.Add( ( prev, current ) =>
        {
          if ( inQuotation )
            return false;
    
          tokenBuilder.Append( current );
          return true;
        }
      );
    
    
    
      text.ToCharArray().Aggregate( ' ', ( prev, current ) =>
        {
          foreach ( var item in processors )
            if ( item( prev, current ) )
              break;
          return current;
        }
      );
    
      group.Add( tokenBuilder.ToString() );
      result.Add( group.ToArray() );
    
      return result;
    }
    

  59. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-10-12 21:21:00

    话说这个题目用正则来玩就木意思了。

  60. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-10-12 21:30:00

    用[@code=csharp]
    请将上面的@自行去掉。

  61. Leo Li
    *.*.*.*
    链接

    Leo Li 2009-10-12 21:58:00

    public List<string[]> Parse(string inputString)
    {
       string regexTokenWithEscapChar = "(?<带转义符的Token>((?<=-)'.*'(?=-)|^'.*'(?=-)|(?<=-)'.*'$|^'.*'$))";
       string regexNormalToken = "(?<NormalToken>((?<=-)[^'-]*(?=-)|[^'-]*(?=-)|(?<=-)[^'-]*$|^[^'-]*$))";
       string regexTokenDef = "(" + regexTokenWithEscapChar + "|" + regexNormalToken + ")";
       string regexTokenGroupItems = "(" + regexTokenDef + "|-)*"; 
       string regexTokenGroupDef = "(?<TokenGroup>(" +
           "^" + regexTokenGroupItems + "(?=--)" + "|" +
           "(?<=--)" + regexTokenGroupItems + "(?=--)" + "|" +
           "(?<=--)" + regexTokenGroupItems + "$" + "|" +
           "^" + regexTokenGroupItems + "$"
           + "))";
    
       List<string[]> ret = new List<string[]>();
    
       Regex regexTokenGroupMatching = new Regex(regexTokenGroupDef);
       MatchCollection tokenGroupMatches = regexTokenGroupMatching.Matches(inputString);
       foreach (Match tokenGroupMatch in tokenGroupMatches)
       {
           if (tokenGroupMatch.Success && !string.IsNullOrEmpty(tokenGroupMatch.Value))
           {
               Regex regexTokenMatching = new System.Text.RegularExpressions.Regex(regexTokenDef);
               MatchCollection tokenMatches = regexTokenMatching.Matches(tokenGroupMatch.Value);
    
               List<string> tokens = new List<string>();
               foreach (Match tokenMatch in tokenMatches)
               {
                   if (tokenMatch.Success && !string.IsNullOrEmpty(tokenMatch.Value))
                   {
                        tokens.Add(tokenMatch.Value);
                   }                     
               }
               ret.Add(tokens.ToArray());
           }
       }
       return ret;
    }
    

  62. xiongli[未注册用户]
    *.*.*.*
    链接

    xiongli[未注册用户] 2009-10-12 22:30:00

    a-'jef'ax-x

    这里的单引号算啥?

  63. 老赵
    admin
    链接

    老赵 2009-10-12 22:56:00

    xiongli:
    a-'jef'ax-x

    这里的单引号算啥?


    非法输入,呵呵。

  64. 老赵
    admin
    链接

    老赵 2009-10-12 22:56:00

    戏水:
    根据需求 查询条件更像是 键值对。 所以不同的键值对之间用非 - 符号分隔可行否? 例如
    cpu-amd,color-red 类似 这样解析起来是不是更方便一点哦


    这样就又引入一个特殊字符了,不是吗?

  65. xiongli[未注册用户]
    *.*.*.*
    链接

    xiongli[未注册用户] 2009-10-12 23:06:00

    public class TokenHelper
    {
    bool inBracket=false;
    string text;
    int i=0;
    StringBuilder currentToken=new StringBuilder();
    List<string> currentTokenGroup=new List<string>();
    List<string[]> result=new List<string[]>();

    public TokenHelper(string input)
    {
    text = string.Copy(input);
    text = '-' + text;
    Execute();
    }

  66. xiongli[未注册用户]
    *.*.*.*
    链接

    xiongli[未注册用户] 2009-10-12 23:07:00

    char escape()
    {
    if(text[i]=='\'' && i<text.Length -1)
    {
    if(text[i+1]=='\'')
    {
    i++;
    return '\'';
    }
    else
    {
    inBracket=!inBracket;
    i++;
    return escape();
    }
    }
    return text[i];
    }

  67. xiongli[未注册用户]
    *.*.*.*
    链接

    xiongli[未注册用户] 2009-10-12 23:07:00

    void end()
    {
    string str = currentToken.ToString();
    if (!string.IsNullOrEmpty(str))
    {
    currentTokenGroup.Add(str);

    }
    result.Add(currentTokenGroup.ToArray());
    }


    void Execute()
    {
    for(i=0;i<text.Length ;i++)
    {
    char c=escape();
    if(isNewToken(c))
    {
    CreateNewToken();
    }
    else
    {
    PushIntoCurrentToken(c);
    }
    }
    end();
    }

  68. xiongli[未注册用户]
    *.*.*.*
    链接

    xiongli[未注册用户] 2009-10-12 23:07:00

    void PushIntoCurrentToken(char c)
    {
    currentToken.Append(c);
    }

    void CreateNewToken()
    {
    string str=currentToken.ToString();
    if(string.IsNullOrEmpty(str))
    {
    result.Add(currentTokenGroup.ToArray());
    currentTokenGroup=new List<string>();
    }
    else
    {
    currentTokenGroup.Add(str);
    }
    currentToken=new StringBuilder();
    }

  69. xiongli[未注册用户]
    *.*.*.*
    链接

    xiongli[未注册用户] 2009-10-12 23:07:00

    bool isNewToken(char c )
    {
    if(text[i]=='-' && !inBracket)
    return true;
    else
    return false;
    }

  70. xiongli[未注册用户]
    *.*.*.*
    链接

    xiongli[未注册用户] 2009-10-12 23:11:00

    第一次写的时候
    忘记了刚开始其实不是-开头的

    第二次写的时候
    忘记了最后end()

    不训练就会退化阿...

  71. 老赵
    admin
    链接

    老赵 2009-10-12 23:19:00

    @xiongli
    熊大哥你为啥不一次贴完……要不我帮你归并一下?

  72. xiongli[未注册用户]
    *.*.*.*
    链接

    xiongli[未注册用户] 2009-10-12 23:23:00

    还是发现问题了

    应该加上

    char? escape()
    {
    if (text[i] == '\'' && i == text.Length - 1)
    {
    inBracket = !inBracket;
    return null;
    }

    以及
    void Execute()
    {
    for(i=0;i<text.Length ;i++)
    {
    object o = escape();
    char c;
    if (o != null)
    {
    c = (char)o;
    }
    else
    {
    break;
    }

  73. killkill
    *.*.*.*
    链接

    killkill 2009-10-12 23:29:00

    @Jeffrey Zhao
    真要规整一下,看了半天,没有头绪...

  74. killkill
    *.*.*.*
    链接

    killkill 2009-10-13 00:14:00

    @Jeffrey Zhao
    老赵,我基于状态机写了一个
    老赵的《趣味编程:从字符串中提取信息》的一个解决方案
    http://www.cnblogs.com/killkill/archive/2009/10/13/1582130.html

    除了正则表达式、状态机外,您应该有更优美的解决方案,什么时候show一下啊。

  75. 老赵
    admin
    链接

    老赵 2009-10-13 00:24:00

    @killkill
    没啥优美的,不过我觉得F#的做法不错,挺直观的……

  76. pk的眼泪
    *.*.*.*
    链接

    pk的眼泪 2009-10-13 11:15:00

                string str = "cpu-3.0g--color-red-green-black--price-5000-8000--weight-'3-'--keywords-'levi''s'";
                str = str.Replace("''", "'");
    
                List<string[]> exp = new List<string[]>();
                foreach (Match pair in new Regex("(?<=--|^).*?(?=--|$)").Matches(str))
                {
                    //未考虑weight-'3-'情况
                    //exp.Add(pair.Value.Split(new char[] { '-' }));
    
                    List<string> value = new List<string>();
                    foreach (Match kv in new Regex("(?<=-|^)'.*'|.*?(?=-|$)").Matches(pair.Value))
                        if (kv.Value.Trim() != "")//不知道为什么会匹配出空串,想不通,望高人指点
                            value.Add(kv.Value);
    
                    exp.Add(value.ToArray());
                }
    

  77. SZW
    *.*.*.*
    链接

    SZW 2009-10-13 12:42:00

    单引号'本身就可以被Encode,所以如果需要表示实际字符串中的单引号,使用%27就行了。

  78. 老赵
    admin
    链接

    老赵 2009-10-13 12:52:00

    @SZW
    理论上任何字符都可以被Encode,但是一旦被Encode就失去可读性了。
    而且,这个tokenizer理应不知道它会被用在URL上,所以它不能用%27这个东西,呵呵。

  79. SZW
    *.*.*.*
    链接

    SZW 2009-10-13 15:05:00

    @Jeffrey Zhao
    对,只是理论上。比如-符号我还真的没有找到可以被所有系统顺利Encoding和Decoding的代码。
    如果你一定要保证可读性又不Encoding的话,文中说到的还远远不够,还有很多需要考虑的东西,比如&符号,不管你在服务器端怎么自定义地转义,这个符号的出现已经在http层面上破坏了QueryString了,你如何保证&的可读性,又保证整个字符串不中断地被服务器读到呢?(虽然你可以用HttpMoudle对QueryString进行预处理,但是当真正需要&符号连接多个参数的时候,问题又来了……)。

  80. 老赵
    admin
    链接

    老赵 2009-10-13 15:11:00

    @SZW
    所以说是“尽可能”咯,我觉得你是不是想的太复杂了。

    在token中出现&字符的话,它就是普通字符。
    然后token要显示到url上,那么就把它UrlEncode,读的时候再Decode回来。

    例如,URL上是:aaa-'bb%26c-d'
    于是读到的text就是:aaa-'bb&c-d'

    总之,尽可能选取不会被url encode的字符作分割符,然后不要对任何字符作特殊处理。
    你会发现,这一块一切一切都很简单的,已经可以说完全满足使用要求了。

  81. SZW
    *.*.*.*
    链接

    SZW 2009-10-13 15:17:00

    @Jeffrey Zhao
    恩,“不会被UrlEncode的字符作为分割符”是公认的,没啥好议论的。
    其实这种考虑不算复杂啊,这些弹性肯定要被考虑进去的,不然以后每次拼装URL都要考虑这些东西。

    你举得例子正好是我上面要说的:既然%26可以用来表达&符号,那么用%27表达'符号不是等价的吗?都破坏了所谓的“可读性”。反过来说,如果允许%26和%27这样的符号出现,那么单引号那样的自定义的转义反而是多余了。

  82. NqIceCoffee
    *.*.*.*
    链接

    NqIceCoffee 2009-10-13 15:17:00

    呵呵,看了各位大侠写的,尤其膜拜写状态机的各位

    小弟上一个平民化的版本..呵呵

    int num = 0;
                            int l = text.Length;
    
    string text = "cpu-3.0g--color-red-green-black--price-5000-8000--weight-'3--3'--keywords-'levi''s'";
    
    
                            List<char> charList = new List<char>();
                            List<string[]> result = new List<string[]>();
                            List<string> stringList = new List<string>();
    
                            for (int i = 0; i < l; i++)
                            {
                                    if (text[i] == '\'')
                                    {
                                            num++;
                                            charList.Add(text[i]);
                                    }
                                    else if (text[i] == '-')
                                    {
                                            if (num % 2 != 0) //如果被包含在''中,则直接加入当前字符
                                                    charList.Add(text[i]);
                                            else
                                            {
                                                    stringList.Add(new string(charList.ToArray()));
                                                    charList.Clear();
    
                                                    if (text[i + 1] == '-') //如果是--则代表一个节点的结束,则添加当前的stringList
                                                    {
                                                            result.Add(stringList.ToArray());
                                                            stringList.Clear();
                                                            i++; //跳过第i+1个-的处理
                                                    }
                                            }
                                    }
                                    else
                                    {
                                            charList.Add(text[i]);
                                    }
                            }
    
                            //进行收尾工作
                            if (charList.Count > 0)
                                    stringList.Add(new string(charList.ToArray()));
    
                            if (stringList.Count > 0)
                                    result.Add(stringList.ToArray());
    

  83. 老赵
    admin
    链接

    老赵 2009-10-13 15:21:00

    @SZW
    不等价,因为处理方式不同。
    把aa-'bb''c'分割成aa和bb'c是tokenizer组件进行的,它不知道什么是url encode,不知道单引号可以被转义成%27,它不知道这个text会被用作什么。
    而知道单引号可以转义为%27的是Web组件,因为它知道这个aa-'bb''c'要被用作URL,于是它URL Enocde一下——单引号还是单引号,'bb''c'也不会变成'bb%27%27c'。
    就像我说的,&对tokenizer是普通字符,对url就是特殊字符了,让它转义去。
    tokenizer和web组件各司其职,互不干涉,互不了解。

  84. SZW
    *.*.*.*
    链接

    SZW 2009-10-13 15:25:00

    @Jeffrey Zhao
    我说的单引号是“实际字符串中的单引号”(见80楼),不是你用来包围字符串用的。

  85. SZW
    *.*.*.*
    链接

    SZW 2009-10-13 15:30:00

    @Jeffrey Zhao
    而且,如果允许“实际字符串中的单引号”Encode的话,也不需要那个用来包围特殊字符串的引号了(''')。
    比如现在的:
    jeffz-'hello'''world'
    完全可以写成:
    jeffz-hello%27world

    至于哪个层面EnCoding和DeCoding也完全不必担心,组装的时候对Value进行EnCoding,获取QueryString的时候整体DeCoding就行了。

  86. 老赵
    admin
    链接

    老赵 2009-10-13 15:45:00

    @SZW
    哎,我觉得你实际编写一下这部分逻辑就会知道这么设计的目的是什么了。
    总之就是:简化编码规则,没有任何字符被特殊化,包括引号本身。

    如果按你的说法,用'ab-c%27b'来表示引号内的引号,那么引号这个字符是不是就要根据它的位置被区别对待了呢?
    那么,负责拆分token的组件,也就是tokenizer,是不是就要知道,某个引号需要被转义成%27?

    但是,这个tokenizer为什么要有“URL Encode”这个职责呢?
    我的做法,引号就是引号,tokenizer职责非常干净,不需要了解任何外部编码规则。

  87. SZW
    *.*.*.*
    链接

    SZW 2009-10-13 15:54:00

    @Jeffrey Zhao
    我可不是凭空想象的啊,正好几年前做过一个类似的功能(而且已经实践了很久了,也升级了很多次),有点这方面的心得,确实觉得上面这个地方有点多此一举。。。

    另外还是要纠正一下你的一个想法:“用'ab-c%27b'来表示引号内的引号”我没有这个意思,因为这种情况可以说不存在。如果允许单引号Encoding的话,压根不需要外面再套单引号来表示这是一个完整的字符串(不考虑你里面有-之类的符号)。

    tokenizer的职责确实应该非常明确的,但是他处于URL这个环境中,即使tokenizer不考虑,在tokenizer接受这个参数之前,程序也是需要要考虑的。

    再举一个例子:如果你有这样一个数据,你如何保证&符号不被Encoding,又使程序可以不做额外的功正常运行呢:
    Key:jeffz
    Value:A&B

  88. straybird
    *.*.*.*
    链接

    straybird 2009-10-13 15:56:00

    我的想法很简单,定一个主游标逐个字符处理,遇到单引号(')时,特殊处理下,然后主游标跳过带引号的token继续处理。入门级思维,呵呵

            public List<List<string>> ReadFromStr(string str)
            {
                List<List<string>> result = new List<List<string>>();
                List<string> token_group = new List<string>();
                //临时容器
                string token = null;
    
                for (int i = 0; i < str.Length; i++)
                {
                    if (str[i] == '-')
                    {
                        token_group.Add(token);
                        token = null;
                        if (str[i + 1] == '-')
                        {
                            result.Add(token_group);
                            token_group = new List<string>();
                            i++;
                        }
                    } else
                    {
                        if (str[i] == '\'')
                        {
                            i += DoSpecial(str.Substring(i + 1), out token) + 1;
                        } else
                        {
                            token = token + str[i];
                        }
                    }
                }
                token_group.Add(token);
    
                result.Add(token_group);
    
                return result;
            }
    
            //遇到单引号(')时做特殊处理
            //返回引号内的字符串长度
            public int DoSpecial(string str, out string token)
            {
                token = null;
                for (int i = 0; i < str.Length - 1; i++)
                {
                    if (str[i] == '\'')
                    {
                        if (str[i + 1] == '\'')
                        {
                            token += '\'';
                            i++;
                        } else
                        {
                            return i;
                        }
                    } else
                    {
                        token += str[i];
                        if (i + 2 == str.Length && str[i + 1] == '\'')
                            return i + 1;
                    }
                }
                throw new Exception();
            }
    

  89. Funeral
    *.*.*.*
    链接

    Funeral 2009-10-13 16:01:00

    我的思路非常简单,就是"按开关"的方式,碰到第一个'打开开关,碰到第二个'关闭开关

     public static List<string[]> GetQueryList(string queryUrl)
            {
                if (string.IsNullOrEmpty(queryUrl)) throw new ArgumentException("The queryUrl is null or empty");
    
                List<string[]> list = new List<string[]>();
                //根据--分类数组
                string[] details = queryUrl.Split(new string[] { "--" }, StringSplitOptions.RemoveEmptyEntries);
    
                //引号开关,当碰到第一个'时置为true,碰到第二个'置为false
                bool quote = false;
                //两个引号之间的字符数量
                int count = 0;
    
                foreach (string detail in details)
                {
                    string tempStr = string.Empty;
                    foreach (char c in detail)
                    {
                        //如果碰到的字符为',则判断之前是否已经碰到字符'
                        //如果没碰到=>将quote置为true,如果已碰到字符'
                        //则根据两个引号之间的字符数量决定是加上字符'还是清空计数器并置quote为false
                        if (c == '\'')
                        {
                            if (quote)
                            {
                                if (count != 0)
                                {
                                    count = 0;
                                    quote = false;
                                }
                                else
                                    tempStr += "'";
                            }
                            else
                                quote = true;
                        }
                        //如果quote为true,说明之前曾碰到一个',则加上字符-,如果没有碰到则加上字符#
                        else if (c == '-')
                        {
                            if (quote)
                            {
                                tempStr += "-";
                                count++;
                            }
                            else
                                tempStr += "#";
                        }
                        //如果是非'和-的字符,直接加上,并根据之前是否碰到字符'增加计数器
                        else
                        {
                            tempStr += c.ToString();
                            if (quote) count++;
                        }    
                    }
    
                    //根据#号分隔字符串
                    string[] onlyDetail = tempStr.Split('#');
                    list.Add(onlyDetail);
                }
    
                return list;
            }
    

  90. 老赵
    admin
    链接

    老赵 2009-10-13 16:11:00

    @SZW
    我从没有说过要&字符在URL里不转义,我只是说“统一”,“简单”,“尽可能”。
    你说的我也考虑过啊,但是也要在“自洽”的前提下,看看是不是“简单”是不是?你的肯定自洽,但是我认为我的更加统一,简单,呵呵。

    Tokenizer只是一个辅助功能,不是直接用在URL上的。要用在URL上时,自然会有另一个组件来负责URL Encoding。

    至于你说'ab-c%27b'不存在——其实是存在的。我故意在里面加了一个“-”字符,为的就是迫使外面加上引号,呵呵。
    而且,我认为''比%27看上去要清楚很多,开发人员也容易记住这个规则。
    我还是这个观点:%27是什么?是URL Encoding。Tokenizer应该知道URL编码规则吗?不应该,所以不能出现%27。如果要出现%27,是不是就要负责其他URL编码?应该负责吗?不应该。
    所以,没了,Tokenizer搞得东西里不会出现“编码”相关的各种东西。它只知道我这篇文章里写的规则。

  91. 老赵
    admin
    链接

    老赵 2009-10-13 16:24:00

    博客园发评论超过一半可能失败,还要重新刷新页面,好烦阿。

  92. _龙猫
    *.*.*.*
    链接

    _龙猫 2009-10-13 16:25:00

    这个"假设所有输入都是正确的"非常暧昧.写了个半成品,大家加油

    public class UrlSplitor
    {
        const string _TOKEN_GROUP_SPLITOR = "--";
        const string _TOKEN_SPLITOR = "-";
    
        // 从text中提取出“数据"
        public static List<string[]> FetchToken(string text){
            List<string[]> tokens = new List<string[]>();
            //TODO: get token group from text
            var tokenGroups = SplitFrom(text, _TOKEN_GROUP_SPLITOR);
    
            //TODO: get token from token group
            foreach (var tokengroup in tokenGroups){
                //需加以处理,去除单引号,本人过于懒,希望看看大家的做法再说.
                tokens.Add(SplitFrom(tokengroup, _TOKEN_SPLITOR).ToArray());
            }
    
            return tokens;
        }
    
        // 从字符串中获取有效的TokenGroup
        private static List<string> SplitFrom(string text, string splitor){
            List<string> tokenGroup = new List<string>();
            string tempText = text;
            int validSplitorIndex = 0;
    
            while (!string.IsNullOrEmpty(text)){
                validSplitorIndex = GetValidSplitorIndex(text, splitor);
                if (validSplitorIndex > 0){//如果包含有效的分隔符,将该分隔符之前的内容获取
                    tokenGroup.Add(text.Remove(validSplitorIndex));
                    text = text.Substring(validSplitorIndex + splitor.Length);
                }else{//已经没有
                    tokenGroup.Add(text);
                    text = string.Empty;
                }
            }
            return tokenGroup;
        }
    
        //获取有效分隔符的Index
        private static int GetValidSplitorIndex(string text, string splitor){
            int curSplitorIndex = 0;
            int validIndex = 0;
            bool isValidIndexFound = false;
            while (text.Contains(splitor) && (!isValidIndexFound)){
                curSplitorIndex = text.IndexOf(splitor);
                if (!IsOdd(ContainCount(text.Remove(curSplitorIndex), '\'', true))){//第一个为有效Splitor
                    validIndex += curSplitorIndex;
                    isValidIndexFound = true;
                    break;
                }else{
                    text = text.Remove(curSplitorIndex, splitor.Length);//将第一个无效分隔符干掉
                    validIndex += splitor.Length;//保存被干掉的字符数目
                }
            }
            return isValidIndexFound ? validIndex : 0;
        }
        //判断是否包含有效的分隔符
        private static bool HasValidSplitor(string text, string splitor){
            return text.Contains(splitor) && !IsOdd(ContainCount(text.Remove(text.IndexOf(splitor)), '\'', true));
        }
        //判断是否奇数
        private static bool IsOdd(int p){
            return p % 2 == 1;
        }
    
        // 获取一个字符串里某字符出现的次数,网上剽窃的函数
        private static int ContainCount(string inputString, char value, bool ignoreCase){
            if (ignoreCase){
                inputString = inputString.ToUpper();
                if (Char.IsLower(value)){
                    value = Char.ToUpper(value);
                }
            }
            int count = 0;
            for (int i = 0; (i = inputString.IndexOf(value, i)) >= 0; i++){
                count++;
            }
            return count;
        }
    }
    

  93. _龙猫
    *.*.*.*
    链接

    _龙猫 2009-10-13 16:30:00

    怎么发出的代码那么乱!是标签问题么?

  94. SZW
    *.*.*.*
    链接

    SZW 2009-10-13 16:33:00

    @Jeffrey Zhao
    恩恩,Tokenizer内部自然和URL无关,更谈不上URLEnCode,只不过你整篇文章大多都是围绕URL“外貌”来说的,也举了写URL的例子。所以我们不如把焦点集中一点比较好,不然这样有意义的讨论就打折扣了,因为这确实是个很好的应用。如果非要说内部,那么即使是taobao那样的URL,在Tokenizer(假设也叫这个名字)内部我想也不会是这样的“乱码”吧。。

    另外还有一点就是,如果你的Tokenizer接受格式不完全等于URL的格式,那么中间就多了一道周折了。因为从你的表达可以看出来,Tokenizer读到的东西本来就不是给人看的,那么Tokenizer何不直接从URL解析成一个数组呢。。


    最后说说我当初实现的方法。
    第一种和这一种有点类似,思路和我上面说的大致一样:Url就是Url,保持其所有特性并进行适当Encode(除了一些符号,还有中文的Encode也是很有必要的,如果不使用ASP.NET,URL直接出现中文处理起来可就不是那么容易了);后台从URL直接生成数组。
    第二种是在第一种方法上的改进:格式如下:
    [format name]-[value1]-[value2]...
    第一个[format name]是一个参数格式的名称,利用这个format name,从后台查找对应N个Value的key的排序(可以近似理解为format name是一个枚举名称,value1,value2等分别对应了这个枚举下的各个对象的值)。这样做的好处是我可以在后台同一管理这些参数,比如可以很灵活地屏蔽掉某个参数的查询,或是将某一个参数对应到多个条件上。

  95. 老赵
    admin
    链接

    老赵 2009-10-13 16:53:00

    @SZW
    Tokenizer其实也是方面给人看和编辑的,所以要工整啊。
    Tokenizer的场景不仅仅是URL,所以我一定要把它和URL剥离。
    事实上,我URL和Token之间还要有一层转化,每一层的职责都很明确很小的。

    所以,估计是我们的焦点不同。
    你觉得Tokenizer应该接受URL格式,
    我觉得Tokenizer应该和URL没有关系。
    如果Tokenizer直接为URL服务,那自然%27也是合适的。

  96. SZW
    *.*.*.*
    链接

    SZW 2009-10-13 17:11:00

    @Jeffrey Zhao
    可能是有点,不过大致思路还是一样的。
    关键还是在你这句“URL和Token之间还要有一层转化”在上面都没提到,就不知道你打算怎么做了。。

  97. SZW
    *.*.*.*
    链接

    SZW 2009-10-13 17:14:00

    @Jeffrey Zhao
    另外对于URL,实践来下发现还是-和_表现更加接近一点,他们都是无法(至少我至今没找到没找到可以默认实现的)直接Encode和Decode的符号,这样的url即使被引用到其他系统里面进行各类url编码,都会保持一致(即使在有的系统里面被手动encoding所有的符号,这两个也不会变)。

  98. SZW
    *.*.*.*
    链接

    SZW 2009-10-13 17:16:00

    好奇怪,我看到了两个100楼……bug?

  99. www.fly020.com[未注册用户…
    *.*.*.*
    链接

    www.fly020.com[未注册用户] 2009-10-13 20:30:00

    有些逻辑可以在前端就处理好了(如拼成json格式)。如果在后端处理用正则也是比较容易处理字符串逻辑的,呵呵

  100. 老赵
    admin
    链接

    老赵 2009-10-13 21:42:00

    @www.fly020.com
    没听懂你的意思啊。

  101. skysing
    *.*.*.*
    链接

    skysing 2009-10-16 14:34:00

    以下代码,不知道算不算符合博主的问题,呵呵……


            static void Main(string[] args)
            {
                SplitFirstBlock("cpu-3.0g--color-red-green-black--price-5000-8000--weight-'3-'--keywords-'levi''s'", 0);
    
                //load
                foreach (string b in block)
                {
                    List<string> slist = new List<string>();
                    SplitSecondBlock(b, 0, ref slist);
    
                    resultList.Add(slist);
                }
    
                //write
                foreach (List<string> slist in resultList)
                {
                    StringBuilder sb = new StringBuilder();
                    foreach (string s in slist)
                    {
                        sb.Append(s + " ");
                    }
                    Console.WriteLine(sb.ToString());
                }
    
                Console.Read();
            }
    
            static List<string> block = new List<string>();
    
            static List<List<string>> resultList = new List<List<string>>();
    
            public static void SplitFirstBlock(string s, int next)
            {
                int lineIndex = -1;
                int singleQuotesIndex = -1;
                int nextSingleQuotesIndex = -1;
    
                if (s.IndexOf("--") > 0)
                {
                    lineIndex = s.IndexOf("--", next);
    
                    singleQuotesIndex = s.IndexOf("'", next);
    
                    if (singleQuotesIndex < s.Length - 1)
                    {
                        nextSingleQuotesIndex = s.IndexOf("'", singleQuotesIndex + 1);
                    }
    
                    if (lineIndex > singleQuotesIndex && lineIndex < nextSingleQuotesIndex)
                    {
                        if (nextSingleQuotesIndex < s.Length - 1)
                        {
                            SplitFirstBlock(s, nextSingleQuotesIndex + 1);
                        }
                    }
                    else if (lineIndex < singleQuotesIndex || lineIndex > nextSingleQuotesIndex || singleQuotesIndex < 0)
                    {
                        string temp = s.Substring(0, lineIndex);
                        s = s.Substring(lineIndex + 2);
    
                        block.Add(temp);
    
                        SplitFirstBlock(s, 0);
                    }
                }
                else
                {
                    block.Add(s);
                }
            }
    
            public static void SplitSecondBlock(string s, int next, ref List<string> slist)
            {
                int lineIndex = -1;
                int singleQuotesIndex = -1;
                int nextSingleQuotesIndex = -1;
    
                if (s.IndexOf("-") > 0)
                {
                    lineIndex = s.IndexOf("-", next);
    
                    singleQuotesIndex = s.IndexOf("'", next);
    
                    if (singleQuotesIndex < s.Length - 1)
                    {
                        nextSingleQuotesIndex = s.IndexOf("'", singleQuotesIndex + 1);
                    }
    
                    if (lineIndex > singleQuotesIndex && lineIndex < nextSingleQuotesIndex)
                    {
                        if (nextSingleQuotesIndex < s.Length - 1)
                        {
                            SplitSecondBlock(s, nextSingleQuotesIndex + 1, ref slist);
                        }
                        else
                        {
                            slist.Add(s.Replace("''", "'").Trim('\''));
                        }
                    }
                    else if (lineIndex < singleQuotesIndex || lineIndex > nextSingleQuotesIndex || singleQuotesIndex < 0)
                    {
                        string temp = s.Substring(0, lineIndex);
                        s = s.Substring(lineIndex + 1);
    
                        slist.Add(temp.Replace("''", "'").Trim('\''));
    
                        SplitSecondBlock(s, 0, ref slist);
                    }
                }
                else
                {
                    slist.Add(s.Replace("''", "'").Trim('\''));
                }
            }
    

  102. Elian
    *.*.*.*
    链接

    Elian 2009-10-17 15:12:00

    呵呵,小弟献丑了^^

    
     /// <summary>
            /// 拆分参数
            /// </summary>
            /// <param name="inputStr">传入的参数</param>
            /// <returns></returns>
            public List<string[]> GetParams(string inputStr)
            {
                try {
                    if (!inputStr.StartsWith("-")) {
                        inputStr = "-" + inputStr;//为的是确保每个匹配项目的序号为 1 或者 2,方便判断参数的合法性
                    }
                    string regstr = "('(([^'])|(''))+')|(([^-']|(''))+)";
                    System.Text.RegularExpressions.Regex reg = new System.Text.RegularExpressions.Regex(regstr);
                    System.Text.RegularExpressions.MatchCollection col = reg.Matches(inputStr);//利用正则匹配所有项目。
                    List<string[]> res = new List<string[]>();  //保存结果
                    List<string> tmpArr = new List<string>();
                    foreach (System.Text.RegularExpressions.Match m in col) {
                        string result = m.Value;    //取出匹配到的项目
                        int resultindex = inputStr.IndexOf(result);
                        if (resultindex == 1) {
                        } else if (resultindex == 2) {
                            string[] aGroup = tmpArr.ToArray(); //给结果赋值
                            res.Add(aGroup);
                            tmpArr.Clear();
                        } else {
                            throw new Exception("输入字符串的格式不正确!");
                        }
                        string tmpstr = result;
                        if (result.StartsWith("'") && result.EndsWith("'")) {//还要把头尾的符号给过滤了
                            tmpstr = tmpstr.Substring(1, tmpstr.Length - 2);
                        }
                        tmpstr =tmpstr.Replace("''", "'");//保存时,切记把 '' 替换为 '
                        tmpArr.Add(tmpstr);
                        inputStr = inputStr.Substring(resultindex + result.Length);
                    }
                    if (tmpArr.Count > 0) {
                        string[] aGroup = tmpArr.ToArray(); //给结果赋值
                        res.Add(aGroup);
                        tmpArr.Clear();
                    }
                    if (inputStr.Replace("-", "") != "") {
                        throw new Exception("输入字符串的格式不正确!");
                    }
                    return res;
                } catch(Exception ex) {
                    throw ex; 
                }
            }
    
    

  103. xiaowei.ji
    *.*.*.*
    链接

    xiaowei.ji 2009-10-19 11:16:00

    有意思!

  104. skysing
    *.*.*.*
    链接

    skysing 2009-10-19 11:46:00

    我晕??为什么我上个星期的回复消失了??给博主删除??

    再发一次:

    static void Main(string[] args)
            {
                SplitFirstBlock("cpu-3.0g--color-red-green-black--price-5000-8000--weight-'3-'--keywords-'levi''s'", 0);
    
                //load
                foreach (string b in block)
                {
                    List<string> slist = new List<string>();
                    SplitSecondBlock(b, 0, ref slist);
    
                    resultList.Add(slist);
                }
    
                //write
                foreach (List<string> slist in resultList)
                {
                    StringBuilder sb = new StringBuilder();
                    foreach (string s in slist)
                    {
                        sb.Append(s + " ");
                    }
                    Console.WriteLine(sb.ToString());
                }
    
                Console.Read();
            }
    
            static List<string> block = new List<string>();
    
            static List<List<string>> resultList = new List<List<string>>();
    
            public static void SplitFirstBlock(string s, int next)
            {
                int lineIndex = -1;
                int singleQuotesIndex = -1;
                int nextSingleQuotesIndex = -1;
    
                if (s.IndexOf("--") > 0)
                {
                    lineIndex = s.IndexOf("--", next);
    
                    singleQuotesIndex = s.IndexOf("'", next);
    
                    if (singleQuotesIndex < s.Length - 1)
                    {
                        nextSingleQuotesIndex = s.IndexOf("'", singleQuotesIndex + 1);
                    }
    
                    if (lineIndex > singleQuotesIndex && lineIndex < nextSingleQuotesIndex)
                    {
                        if (nextSingleQuotesIndex < s.Length - 1)
                        {
                            SplitFirstBlock(s, nextSingleQuotesIndex + 1);
                        }
                    }
                    else if (lineIndex < singleQuotesIndex || lineIndex > nextSingleQuotesIndex || singleQuotesIndex < 0)
                    {
                        string temp = s.Substring(0, lineIndex);
                        s = s.Substring(lineIndex + 2);
    
                        block.Add(temp);
    
                        SplitFirstBlock(s, 0);
                    }
                }
                else
                {
                    block.Add(s);
                }
            }
    
            public static void SplitSecondBlock(string s, int next, ref List<string> slist)
            {
                int lineIndex = -1;
                int singleQuotesIndex = -1;
                int nextSingleQuotesIndex = -1;
    
                if (s.IndexOf("-") > 0)
                {
                    lineIndex = s.IndexOf("-", next);
    
                    singleQuotesIndex = s.IndexOf("'", next);
    
                    if (singleQuotesIndex < s.Length - 1)
                    {
                        nextSingleQuotesIndex = s.IndexOf("'", singleQuotesIndex + 1);
                    }
    
                    if (lineIndex > singleQuotesIndex && lineIndex < nextSingleQuotesIndex)
                    {
                        if (nextSingleQuotesIndex < s.Length - 1)
                        {
                            SplitSecondBlock(s, nextSingleQuotesIndex + 1, ref slist);
                        }
                        else
                        {
                            slist.Add(s.Replace("''", "'").Trim('\''));
                        }
                    }
                    else if (lineIndex < singleQuotesIndex || lineIndex > nextSingleQuotesIndex || singleQuotesIndex < 0)
                    {
                        string temp = s.Substring(0, lineIndex);
                        s = s.Substring(lineIndex + 1);
    
                        slist.Add(temp.Replace("''", "'").Trim('\''));
    
                        SplitSecondBlock(s, 0, ref slist);
                    }
                }
                else
                {
                    slist.Add(s.Replace("''", "'").Trim('\''));
                }
            }
    

  105. YoungSin
    *.*.*.*
    链接

    YoungSin 2009-10-21 08:36:00

    Yankee:
    你建的规则那么工整,应该好解析吧。。

    不过我看淘宝的URL查询规则,有点莫名奇妙!http://search1.taobao.com/browse/0/n-g,geytami-g,geytami-------1------7------------------4----0--------------------g,ojsxgzlsozsv64dsnfrwkwzvgaydalbzhe4tsxi---g,whflzr5rxy-------2-------b--40--coefp-0-all-0.htm?search_multi_condition=1&ssid=s1#ListView

    标记“----”是不是太多了?????



    我想其中是否用了一些加密的方法,我现在公司的系统就是把所有request string加密,然后在目标页面再解密出来

  106. Gnie
    *.*.*.*
    链接

    Gnie 2009-10-21 11:29:00

    恩,不错,学习了~

  107. 萧萧空间
    *.*.*.*
    链接

    萧萧空间 2009-11-03 10:21:00

    字符串解析在程序经常遇到,但是没有使用这样的思路,学习了,很好。

  108. Ali Xiao
    *.*.*.*
    链接

    Ali Xiao 2010-01-27 17:38:00

    我觉得正则表达式比较适合做这种事情,只是写出来的正则以后再看的时候又不知道是什么意思了

    static Regex groupMatch = new Regex("(?:--)?(?<group>[^-]+)(?<tokens>(?:-'(?>(?:[^']|'')*)'|-[^'-]+)*)");
    static Regex tokenMatch = new Regex("-([^'-]+)|-'((?:[^']|'')*)'");
    static List<string[]> ParseOptions(string text)
    {
    	var ret = new List<string[]>();
    	foreach (Match group in groupMatch.Matches(text))
    	{
    		MatchCollection tokens = tokenMatch.Matches(group.Groups["tokens"].Value);
    		var array = new string[tokens.Count + 1];
    		int i = 0;
    		array[i++] = group.Groups["group"].Value;
    		foreach (Match token in tokens)
    		{
    			array[i++] = token.Groups[1].Success ? token.Groups[1].Value : token.Groups[2].Value.Replace("''", "'");
    		}
    		ret.Add(array);
    	}
    
    	return ret;
    }
    

  109. 默默
    218.241.130.*
    链接

    默默 2010-07-14 09:56:55

    Ali Xiao 这个正则很强大,也正如你所说,理解起来可真难啊...

  110. zoroseo2020
    103.4.30.*
    链接

    zoroseo2020 2021-09-01 20:26:59

    不是真的让大家这样玩 英国乐透。taobao那个address我猜顶多也就作为一个key使用 澳洲幸运20,而且应该是更具条件生成的,不可能是先有UR 幸运飞艇L,再用来解析的。

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我