使用正则表达式解决配置文件字符串替换的思路和例子

正则表达式是非常强大的字符串处理工具,但由于晦涩难懂,唯有不断的学习和使用,才能积累经验。我使用正则表达式总是断断续续,所以水平也很初级。下面就记录这次的使用经验,备查。

下面的xml代码是一个游戏技能配置文件的简化版,其中的items是一个技能,item是该技能的一个级别的值。desc属性是该技能的介绍文本。由于介绍中包含对技能的效果的引用,而技能的效果在不同的技能级别中的值是不同的,因此这里使用定界符来标识可能会变动的值。

针对定界符,我制定的规则很简单,用花括号 {} 包含要替换的属性值即可,如 {key} ,其中key就是属性名称。对于使用数组方式提供的属性值,则使用 {key[n]} 的方式来提供,其中n是数组的索引。

程序中要做的,就是在游戏中需要技能的介绍的时候,会根据技能的级别获取到相关的的值,然后查询desc中有哪些定界符,再根据定界符获取到item中的值,最后进行替换。

这个工作虽然并不复杂,但也不是indexof能够解决的,后面就是我使用正则表达式的处理思路。对于XML的解析,我使用E4X。

 1<skill>
 2    <items id="2121" name="魔能烧烬" targetSide="2" type="special1" element="2" desc="主动技能。使魔导师能够掌握魔法精密之处,从而对目标造成{effect[0]}点的火元素伤害,同时减少目标{effect[1]}%MP上限。">
 3        <item lv="1" effect="[20,4]" targetNum="1" expended="600" cd="20000" />
 4        <item lv="2" effect="[40,6]" targetNum="1" expended="640" cd="20000" />
 5    </items>
 6    <items id="1203" name="无情杀戮" targetSide="2" type="phy" element="-1"  desc="主动技能。没有一丝怜悯的连续两次猛击目标要害,对目标造成{effect}点的物理伤害。">
 7        <item lv="1" effect="1114" targetNum="1" expended="50" cd="13000" />
 8        <item lv="2" effect="1180" targetNum="1" expended="60" cd="13000" />
 9    </items>
10</skill>

一、检测值是否是数组形式

在这里,由于值都是整数,出现的也值可能是整数数组,所以正则表达式写起来就简单许多:

1public static function isArray($str:String):Boolean
2{
3    $str = $str.replace(/\s/g, '');
4    var _reg:RegExp = /^\[(\d+,?)+\d+\]$/;
5    return _reg.test($str);
6}

先使用replace将字符串中的空格剔除,然后检测如 [int,int...] 的格式。这个正则原理如下:

  • (\d+,?)+ 匹配以逗号分隔的整型字符串,例如“123,456”或者“1”这样的格式
  • 第二个 \d+ 则确保 123, 这样的字符串不被匹配
  • 其他的就很简单,不说了

二、将字符串转成数组

先剔除空格,再剔除方括号,然后通过定界符将字符串转成数组,并转换成int存储。其中重点是字符类 [] 的运用。由于要查找的正好是方括号,而正则表达式中也是用方括号来定义字符类的,因此方括号在字符类中,必须使用转义符 \ 进行转义才能使用。

1public static function toArray($str:String, $delimiter:String=','):Array
2{
3    $str = $str.replace(/\s/g, '');
4    $str = $str.replace(/[\[\]]/g, '');
5    var __arr:Array = $str.split($delimiter); 
6    for(var i:int=0; i<__arr.length; i++)
7        __arr[i] = int(__arr[i]);
8    return __arr;
9}

三、将XML中的其他值存入一个对象

为了方便后面提取值,将XML中的所有属性存入一个对象中,这里为了演示方便,只存储了effect属性:

 1public function getSkillVO($id:String, $lv:String):Object
 2{
 3    var __itemxml:XML = _skill.items.(@id==$id).item.(@lv==$lv)[0];
 4    var __vo:Object = {};
 5    var __effect:String = __itemxml.@effect.toString();
 6    if(isArray(__effect))
 7        __vo.effect = toArray(__effect);
 8    else
 9        __vo.effect = int(__effect);
10    return __vo;
11}

四、替换

重要的是String的match方法的作用。这个方法对没有使用global标志 /g 的正则表达式,会将其中的每个组的匹配结果作为一个元素加入到匹配结果数组中。这样,就可以方便的提取到形如 {effect[0]} 的字符串中的数组索引值。

所有的分析都写在注释中了:

 1//检测字符串中是否有如{delimter}和{delimter[n]}形式的定界符,并使用对应的值进行替换
 2private function replaceDesc($str:String, $skillvo:Object):String
 3{
 4    //支持全局匹配的正则
 5    var __globalReg:RegExp = /{(\w+)(\[(\d)\])?}/g;
 6    //用于匹配每个全局匹配结果的正则
 7    var __itemReg:RegExp = /{(\w+)(\[(\d)\])?}/;
 8    //全局匹配的结果
 9    var __globalMatch:Array = $str.match(__globalReg);
10    //待替换的键名数组
11    var __keys:Array = [];
12    //待替换的值数组
13    var __values:Array = [];
14    
15    var __itemMatch:Array = null;
16    var __itemIsArray:Boolean = false;
17    var __itemKey:String = '';
18    for each(var __str:String in __globalMatch)
19    {
20        __itemMatch = __str.match(__itemReg);
21        /*按照正则表达式规则,__itemMatch应该是有4个元素的数组:
22        1 整个字符串,2 第一个括号(\w+)的内容,3 第二个括号(\[(\d)\])的内容,4 第三个括号(\d)的内容
23        因此,如果第4个元素为undefined,就说明在字符串中,没有数组的定界符*/
24        __itemIsArray =  __itemMatch[3] != undefined;
25        //第2个元素就是去掉了花括号和数组定界符(如果有)的字符串,也就是要$skillvo中的变量名
26        __itemKey = __itemMatch[1];
27        //第1个元素是整个大字符串中的要被替换的部分,包含花括号
28        __keys.push(__itemMatch[0]);
29        //如果值是数组中的元素,就是用数组中对应索引的值
30        if(__itemIsArray)
31            __values.push($skillvo[__itemKey][__itemMatch[3]]);
32        //否则,就直接使用变量的值
33        else
34            __values.push($skillvo[__itemKey]);
35    }
36    //开始替换
37    for(var j:int=0; j<__keys.length; j++)
38    {
39        $str = $str.replace(__keys[j], __values[j]);
40    }
41    return $str;
42}
43

全部代码

  1package
  2{
  3import flash.display.Sprite;
  4
  5public class SkillRegExpTest extends Sprite
  6{
  7    public function SkillRegExpTest()
  8    {
  9        trace(replaceDesc(_skill.items.(@id=="2121").@desc.toString(), getSkillVO('2121', '2')));
 10    }
 11    
 12    private var _skill:XML=
 13        <skill>
 14            <items id="2121" name="魔能烧烬" targetSide="2" type="special1" element="2" desc="主动技能。使魔导师能够掌握魔法精密之处,从而对目标造成{effect[0]}点的火元素伤害,同时减少目标{effect[1]}%MP上限。">
 15                <item lv="1" effect="[20,4]" targetNum="1" expended="600" cd="20000" />
 16                <item lv="2" effect="[40,6]" targetNum="1" expended="640" cd="20000" />
 17            </items>
 18            <items id="1203" name="无情杀戮" targetSide="2" type="phy" element="-1"  desc="主动技能。没有一丝怜悯的连续两次猛击目标要害,对目标造成{effect}点的物理伤害。">
 19                <item lv="1" effect="1114" targetNum="1" expended="50" cd="13000" />
 20                <item lv="2" effect="1180" targetNum="1" expended="60" cd="13000" />
 21            </items>
 22        </skill>
 23        
 24    /**
 25     * 检测一个字符串是否是[int,int...]的形式 
 26     */     
 27    public function isArray($str:String):Boolean
 28    {
 29        //将字符串中的空格剔除
 30        $str = $str.replace(/\s/g, '');
 31        var _reg:RegExp = /^\[(\d+,?)+\d+\]$/;
 32        return _reg.test($str);
 33    }
 34    
 35    /**
 36     * 将一个符合数组格式的字符串转换成整型数组 
 37     */     
 38    public function toArray($str:String, $delimiter:String=','):Array
 39    {
 40        $str = $str.replace(/\s/g, '');
 41        $str = $str.replace(/[\[\]]/g, '');
 42        var __arr:Array = $str.split($delimiter); 
 43        for(var i:int=0; i<__arr.length; i++)
 44        {
 45            __arr[i] = int(__arr[i]);
 46        }
 47        return __arr;
 48    }
 49    
 50    public function getSkillVO($id:String, $lv:String):Object
 51    {
 52        var __itemxml:XML = _skill.items.(@id==$id).item.(@lv==$lv)[0];
 53        var __vo:Object = {};
 54        var __effect:String = __itemxml.@effect.toString();
 55        if(isArray(__effect))
 56            __vo.effect = toArray(__effect);
 57        else
 58            __vo.effect = int(__effect);
 59        return __vo;
 60    }
 61        
 62    /**
 63     * 检测字符串中是否有如{delimter}和{delimter[n]}形式的定界符,并使用对应的值进行替换
 64     * @param $str 被替换的完整字符串
 65     * @param $skillvo 包含要替换的内容的
 66     * @return 
 67     * 
 68     */ 
 69    private function replaceDesc($str:String, $skillvo:Object):String
 70    {
 71        //支持全局匹配的正则
 72        var __globalReg:RegExp = /{(\w+)(\[(\d)\])?}/g;
 73        //用于匹配每个全局匹配结果的正则
 74        var __itemReg:RegExp = /{(\w+)(\[(\d)\])?}/;
 75        //全局匹配的结果
 76        var __globalMatch:Array = $str.match(__globalReg);
 77        //待替换的键名数组
 78        var __keys:Array = [];
 79        //待替换的值数组
 80        var __values:Array = [];
 81        
 82        var __itemMatch:Array = null;
 83        var __itemIsArray:Boolean = false;
 84        var __itemKey:String = '';
 85        for each(var __str:String in __globalMatch)
 86        {
 87            __itemMatch = __str.match(__itemReg);
 88            /*按照正则表达式规则,__itemMatch应该是有4个元素的数组:
 89            1 整个字符串,2 第一个括号(\w+)的内容,3 第二个括号(\[(\d)\])的内容,4 第三个括号(\d)的内容
 90            因此,如果第4个元素为undefined,就说明在字符串中,没有数组的定界符*/
 91            __itemIsArray =  __itemMatch[3] != undefined;
 92            //第2个元素就是去掉了花括号和数组定界符(如果有)的字符串,也就是要$skillvo中的变量名
 93            __itemKey = __itemMatch[1];
 94            //第1个元素是整个大字符串中的要被替换的部分,包含花括号
 95            __keys.push(__itemMatch[0]);
 96            //如果值是数组中的元素,就是用数组中对应索引的值
 97            if(__itemIsArray)
 98                __values.push($skillvo[__itemKey][__itemMatch[3]]);
 99            //否则,就直接使用变量的值
100            else
101                __values.push($skillvo[__itemKey]);
102        }
103        //开始替换
104        for(var j:int=0; j<__keys.length; j++)
105        {
106            $str = $str.replace(__keys[j], __values[j]);
107        }
108        return $str;
109    }
110}
111}