第一幕:惊闻已沦僵尸粉,怒斩桃花正衣冠

今日晴空万里,清风徐徐,一觉醒来又是一个春日的早晨,额,咳咳,中午,,闲来无事打开久违的新浪微博,放眼望去,一千多个关注 
,即知我苦逼的身居冷宫微博账号已沦为浩瀚僵尸粉大军中的一员,虽然十天半个月才上一次,但是被他人如此玩弄是万万不能忍的,此间心情犹如皇上得知自己七年没见过的嫔妃被某个忤逆臣子轻薄!

于是乎当机立断改密码,加绑定,然后删关注。

作为一名码农,虽然仅仅是低阶的草根屌丝码农,我也知道,一千多个关注一个个点不是我的风格,在找寻批量删除关注功能无果的情况下,毅然打开新浪微博开放平台,准备操刀来一发一键删除所有关注。

 

第二幕:看完文档轻抚蛋,码农荣耀难中求

 

浏览了一下微博的SDK列表,为了我的撸妹儿800毫不犹豫的选择了WP7的SDK(已升级2.0,官方只有release的库,网上有同志共享了源码,各位朋友有需要的请自行搜索。其实SDK并不复杂,API 全是REST的,看着文档稍微包一下Http的各种操作也是一样的)。

看完文档我一阵冷汗:俺滴亲娘喂,随便找个方法参数都如此蛋疼,SDK的作者真是我亲哥啊,否则拼实体类非拼到蛋碎不可。


但是看完SDK源码瞬间觉得胯下隐隐作痛,亲哥只给我包了这么几个接口,所需要的获取所有关注的功能,怎么木有啊啊啊啊啊啊~~~~

    public enum SdkRequestType
    {
        NULL_TYPE = -1,
        FRIENDS_TIMELINE = 0,        //获取下行数据集(timeline)接口(cmdNormalMessages)
        UPLOAD_MESSAGE,             //发送微博(cmdUploadMessage)
        UPLOAD_MESSAGE_PIC,         //发送带图片微博(cmdUploadPic)
        FRIENDSHIP_CREATE,          //关注某用户(cmdFriendShip)
        FRIENDSHIP_DESDROY,         //取消关注(cmdFriendShip)
        FRIENDSHIP_SHOW,            //获取两个用户关系的详细情况(cmdFriendShip)
        AT_USERS,                   //@用户时的联想建议 (cmdAtUsers)
        USER_TIMELINE,              //获取用户发布的微博消息列表(cdmUserTimeline)
    } 

没办法了,抹了抹眼泪揉了揉蛋,自个儿加呗。

好在源码写的还是靠谱的,结构清晰一目了然,最疼的还是构造实体类,写了两行实在写不下去了,对着文档中的示例传瞅了又瞅,一拍大腿,喊道:"忍无可忍,哥今天要让你们自己变成代码!"。

 

第三幕:尼玛工欲善其事,妹的必先利其器(喵了个咪这样都让我凑够7个字了,真是才华横溢

拿出专业的态度审视文档,心想总不能为新浪微博的API接口写一个代码生成器,哪天接腾讯微博又得写一个,日后不免被人耻笑。

因此决定打盘dota,平复一下思绪,选了个warlock,边打酱油边想需求,不料 

一下子心满意足,各个dota群贴一下战绩炫耀一番之后,细细回想当年在盛大时候接别人接口时候各种从文档/代码/示例中提取实体类,还有写完代码拼文档时候的种种经历,提取出如下需求:

 

从待处理串OriginalString中通过自定义格式化串FormatString提取出所含参数列表ArgsList

再将 参数列表ArgsList 格式化到目标串TargetString中,最后替换待原串中满足格式化串FormatString的部分为目标串TargetString。

 

比较不好理解,用一个最复杂的场景举例说明(自然语言,复合链式处理):

需要将

"今天我买了4斤黄瓜,一斤2元,5斤茄子,一斤3元,6斤香蕉,一斤2.5元。(哟呵~)"

转换成

"4*2+5*3+6*2.5"

然后丢进计算器中算总价(这个可就不归我管了,嘿嘿)

 

那么

第一个转换

 OriginalString = "今天我买了4斤黄瓜,一斤2元,5斤茄子,一斤3元,6斤香蕉,一斤2.5元。 "

 FormatString = [[重量]]斤xx,一斤[[单价]]元

 分析出3组满足条件的参数列表:

 ArgsList = key: 重量, value: 4; key: 单价, value: 2

 ArgsList = key: 重量, value: 5; key: 单价, value: 3 

 ArgsList = key: 重量, value: 6; key: 单价, value: 2.5

 TargetString = [[重量]]*[[单价]]

 

 替换完成后应该是 "今天我买了4*2,5*3,6*2.5。 "

 

第二个转换

 OriginalString = "今天我买了4*2,5*3,6*2.5。 "

 FormatString = xxx[[算式1]],[[算式2]],[[算式3]]。

 分析出满足条件的参数列表:

 ArgsList = key: 算式1, value: 4*2; key: 算式2, value: 5*3; key: 算式3, value: 6*2.5

 TargetString = [[算式1]]+[[算式2]]+[[算式3]]

 

 替换完成后应该是 "4*2+5*3+6*2.5"  (转换的方式多种多样,如果事前已知只有三组,也可以一次完成转换,这里仅作为示例引出下述工具的设计思路)

 

如此看来,这个,这个,噢 ,我还没给这工具起名呢,先起个名儿再说……

……

……

………………

恩,中文名就叫做 "春风化雨未有时,独上高楼自当歌" ,翻译成英文嘛就是 Onlr.Lazytools.CodeSnippet,这名字不错。

之前说到,这个 CodeSnippet 就的需求基本就明确了,这里罗列一下:

1. 核心功能:自定义的格式串匹配和替换。

2. 支持链式处理,以解耦太过复杂的转换需求。

3. 自由组织格式串定义,增删改查,自动识别等等等等,怎么懒怎么来!


界面功能浏览


1. 自动识别加载代码段定义文件。(程序集所在目录下的所有满足XSD定义的xml)

2. 增删改查,必须的!伦家才不改XML!

3. 全局自定义快捷键操作,在剪贴板内完成转换,keep silent。(选中待处理串 -> Ctrl + c -> Ctrl + Alt + Shift + D(自定义,可修改) -> Ctrl + V -> 完成  【傲娇】)

4. 链式转换在左边列表中复选即可,根据选中先后顺序执行。

 

实现部分在这里稍微整理一下主要逻辑:

核心转换器我只想到用正则表达式来实现,但是使用正则表达式就意味着贴上了【码农Only】的标签,实非本意,暂时只能提取接口保留其他实现方式的可能性。

    public interface ISnippetConvert
    {
        /// <summary>
        
/// 获得转换器的原始代码段定义
        
/// </summary>
        
/// <returns></returns>
        Snippet GetSnippet();

        /// <summary>
        
/// str转换成预定义Snippet格式
        
/// </summary>
        
/// <param name="str"></param>
        
/// <returns></returns>
        string Convert(string str);
    }

正则表达式转换器的核心逻辑

//预处理,先将自定义的参数格式转换成标准的正则表达式,例如[[argName||regex]] 转换为正则表达式命名分组格式  (?<" + argName + ">" + regex + ") AnalizeArg完成该工作,不熟悉的读者可以参考MSDN 中关于Regex.Replace方法的文档
m_WorkedOriginalFormat = Regex.Replace(SnippetDef.originFormat, REGEX_ARGS_DEFINE, new MatchEvaluator(AnalizeArg), RegexOptions.Multiline);   
//就这样了,木有了,ConvertCore就负责将原始串替换为目标串
public override string Convert(string str)
{
    return Regex.Replace(str, m_WorkedOriginFormat, new MatchEvaluator(ConvertCore), RegexOptions.Singleline);
}
/// <summary>
/// 将待处理串中的满足格式的串进行替换操作
/// </summary>
/// <param name="m"></param>
/// <returns></returns>
string ConvertCore(Match m)
{
    try
    {
        var targetStr = SnippetDef.targetFormat;
        foreach (var arg in m_ArgsRegex)
        {
            // 提取参数值
            var value = m.Groups[arg.Key].Captures[0].Value;
            // 构造替换的目标串
            targetStr = targetStr.Replace("[[" + arg.Key + "]]", value);
        }
        return targetStr;
    }
    catch (Exception)
    {
        throw new ArgumentException("待处理串格式不正确:" + m.Value);
    }

 

核心逻辑其实非常简单,这里为了稍微平民化一点,用[[ArgumentName[||Regex]]] 的格式替换了正则表达式的(?<" +  ArgumentName + ">" + Regex + "),因此在初始化的时候要先转回来,多了一个步骤。

 

 

其他的部分都是些细枝末节的东西了,譬如需要了解.Net正则表达式相关知识,复习一下C#如何使用windows api注册全局快捷键等等,酱油码农的特质一览无遗:用到什么什么不会。无所谓,股沟呗。

 

第四幕:哟呵快刀斩乱麻,啊哈倚楼听风雨 (这绝不是江郎才……才尽)

三下五除二工具完成,赶紧来一发,(⊙o⊙)…,赶紧用一下。

就拿上面贴出来的那个接口下刀。

 

这个请求神马的,太简单了,依葫芦画瓢在SDK源码中加个请求类型和cmd实体就行。

/// <summary>
/// 参数uid与screen_name二者必选其一,且只能选其一,优先使用uid,均不填则取当前登录用户
/// </summary>
public class cmdFriends : SdkCmdBase
{
    /// <summary>
    
/// 需要查询的用户UID。
    
/// </summary>
    public string uid { getset; }
    /// <summary>
    
/// 需要查询的用户昵称。
    
/// </summary>
    public string screen_name { getset; }

    public string count { getset; }
    public string cursor { getset; }

 

关键的来了,返回的Json反序列化成实体。

首先,.Net下使用DataContractJsonSerializer可以轻松完成Json的序列化与反序列化,当然前提是,要有个实体类。刚完成的 "春风化雨未有时,独上高楼自当歌" V1.0 alpha版拿出来用呗。

定义格式串:

int类型

(?<=([\r\n]|^))\s*"[[key]]"\s*:\s*[[value||\d{1,15}]]\s*,?     

[DataMember]

public string [[key]] { get; set; } 

 

其他同理 

string类型 (?<=([\r\n]|^))\s*"[[key]]"\s*:\s*"[[value||[^\n]{0,200}]]"\s*,?

bool类型 (?<=([\r\n]|^))\s*"[[key]]"\s*:\s*[[value||true|false]]\s*,?

 

还有别忘了其他不认识的类型,例如文档中的

"geo": null, 

"annotations": [],

不知道神马玩意,直接注释掉。 记得要放在执行链的最后,因为它匹配任何类型

(?<=([\r\n]|^))\s*[[other||"\w{1,50}"\s*:[^\r\n]*(?=[\r\n]|$)]] 

 

//[[other]]

 

 

((?<=([\r\n]|^)) 这个是断言, 其他的稍微懂点正则的应该都能看得懂,这里不再细说了,.Net正则表达式相关知识 ) 

 

复选并启动转换器,最小化它吧,咔咔。


先搞Status这个实体!

 

复制Json -> 轻声说:"接下来就是见证奇迹的时刻" -> 停顿3秒 -> 拨头发:"我要的不多,掌声,5秒钟" -> "5,4,3,2,1" ->  Ctrl + Alt + Shift + D(自定义快捷键) -> 粘贴到VS中

 

呼,是的,当时就是这样。

接下来反正大家都懂了。

不管是从文档,从代码,从示例甚至从自然语言,还是转Java转C++转Python,只要499,只要499,第一个打进电话的再送金表一只……额,貌似,貌似,窜台了……,只要,你能通过正则表达式匹配出来的,"春风化雨未有时,独上高楼自当歌"都能转。

 

这里随便举几个我自己使用的例子:

Excel/SqlDbx的一列数据 转 SQL语句 (当年开发间技术客服的时候怎么没想起来写这个呢!

定义:

  <snippet name="orderIdList2SelectAllSQL_step1" comment="加逗号">
    <originFormat>[[orderid||\w{27}]]</originFormat>
    <targetFormat>[[orderid]],</targetFormat>
  </snippet>
  <snippet name="orderIdList2SelectAllSQL_step1.5" comment="去掉最后一个逗号">
    <originFormat>[[InvalidComma||,\s*$]]</originFormat>
    <targetFormat></targetFormat>
  </snippet>
  <snippet name="orderIdList2SelectAllSQL_step2" comment="塞到select 中间">
    <originFormat>[[orderIdList||^.*$]]</originFormat>
    <targetFormat>select * from [order] 
where orderid in (
[[orderIdList]]
)</targetFormat>
  </snippet>

原始数据:

 

转换结果: 

select * from [order] 
where orderid in (
KDPAY2012040513422933853694,
KDPAY2012040513441941661181,
KDPAY2012040513452083859542,
KDPAY2012040513475082286181,
KDPAY2012040513482533856395,
KDPAY2012040515061951037679,
KDPAY2012040515303436973640,
KDPAY2012040515570397912328,
KDPAY2012040515581888534356,
KDPAY2012040515591022915668,
KDPAY2012040516044938539595

文档转代码 

定义:

  <snippet name="weiboDoc2CSharp" comment="新浪微博的文档直接转为实体类">
    <originFormat>[[fieldname]]\s*\t[[type]]\s*\t[[comment||[^\n]*]](?=[\r\n]|$)</originFormat>
    <targetFormat>//[[comment]]
[DataMember]
public [[type]] [[fieldname]] { get; set; }</targetFormat>
  </snippet>
  <snippet name="boolean2bool" comment="">
    <originFormat>boolean</originFormat>
    <targetFormat>bool</targetFormat>
  </snippet>
  <snippet name="int64_2_Int64" comment="">
    <originFormat>int64</originFormat>
    <targetFormat>Int64</targetFormat>
  </snippet>

原始数据: 

 

转换结果:

        //用户UID
        [DataMember]
        public Int64 id { getset; }
        //用户昵称
        [DataMember]
        public string screen_name { getset; }
        //友好显示名称
        [DataMember]
        public string name { getset; }
        //用户所在地区ID
        [DataMember]
        public int province { getset; }
        //用户所在城市ID
        [DataMember]
        public int city { getset; }
        //用户所在地
        [DataMember]
        public string location { getset; }
        //用户描述
        [DataMember]
        public string description { getset; }
        //用户博客地址
        [DataMember]
        public string url { getset; }
        //用户头像地址
        [DataMember]
        public string profile_image_url { getset; }
        //用户的个性化域名
        [DataMember]
        public string domain { getset; }
        //性别,m:男、f:女、n:未知
        [DataMember]
        public string gender { getset; }
        //粉丝数
        [DataMember]
        public int followers_count { getset; }
        //关注数
        [DataMember]
        public int friends_count { getset; }
        //微博数
        [DataMember]
        public int statuses_count { getset; }
        //收藏数
        [DataMember]
        public int favourites_count { getset; }
        //创建时间
        [DataMember]
        public string created_at { getset; }
        //当前登录用户是否已关注该用户
        [DataMember]
        public bool following { getset; }
        //是否允许所有人给我发私信
        [DataMember]
        public bool allow_all_act_msg { getset; }
        //是否允许带有地理信息
        [DataMember]
        public bool geo_enabled { getset; }
        //是否是微博认证用户,即带V用户
        [DataMember]
        public bool verified { getset; }
        //是否允许所有人对我的微博进行评论
        [DataMember]
        public bool allow_all_comment { getset; }
        //用户大头像地址
        [DataMember]
        public string avatar_large { getset; }
        //认证原因
        [DataMember]
        public string verified_reason { getset; }
        //该用户是否关注当前登录用户
        [DataMember]
        public bool follow_me { getset; }
        //用户的在线状态,0:不在线、1:在线
        [DataMember]
        public int online_status { getset; }
        //用户的互粉数
        [DataMember]
        public int bi_followers_count { getset; }
        //用户的最近一条微博信息字段
        [DataMember]
        public object status { getset; }

还有个小插曲,就是新浪微博的测试授权每小时每用户只能请求150次,因此我的1300多个关注,删了2天才全部删掉(想起来就打开撸妹儿点一下)


最终幕:一壶浊酒喜相逢,古今多少事,都付笑谈中

 后来,

 

 

 后记

 谨以此文分享思路。代码拙劣,就不献丑了。仅供低阶码农共同讨论学习,实在不入高端人士法眼,也请见谅。

 另外,很多文本工具其实都自带了非常强悍的文本处理功能,文中提到的功能在实现上多数都是班门弄斧而已,仅为方便自己使用,效率、完备性等方面多数有欠考虑,虚心接受高人指点。

 

posted on 2012-04-17 20:01  onle  阅读(15700)  评论(13编辑  收藏  举报