coolcode转SyntaxHighlighter与Mysql正则表达式

blog的代码高亮插件原来是coolcode的,coolcode的高亮插件确实很酷,显示效果也很棒,但是占用的位子太大了。最近,我抽空改成SyntaxHighlighter。由于coolcode插件的开头标签是

<coolcode>

或者[coolcode]这样的,而SyntaxHighlighter是

[code lang="php"]

这样的(或者其他)。遂只能想办法把老的格式转化成新的格式。当然,肯定用到正则表达式了。

原来的代码高亮开头标识为

<coolcode lang="php" download="123.php" linenum="on"><coolcode lang="php" linenum="off"><coolcode lang="php">

这种类型的,
而SyntaxHighlighter的标识为

[code lang="php"]

那根据要求写的正则表达式为

<coolcode lang="[a-z]+".*?>

解释一下

[a-z]+ 匹配 php,javascript,cpp,sql,css 等,后面的.*?中的 .表示任何除了换行之外的字符,而*表示0次或者无数次,*+这些表述次数的符号后面接的?标识非贪婪模式

简单的正则表达式匹配字符串


看图,这个正则可以实现上述要求了。

但是,问题还没解决,我们还有一种情况没考虑,那就是

<coolcode

后面不一定接的就是lang="php"这样的属性啊,有可能是download,也有可能是linenum="on/off"啊,所以,我们的正则还需要改。
CFC4N把正则改为

<coolcode.*?lang="[a-z]+".*?>

截图如下

正则表达式非贪婪匹配


细心的朋友可能看出来图中匹配的红色框内多出了
<coolcode

,意思也就是说,前面的

<coolcode>

需要排除掉。如何排除呢?聪明的你肯定立刻想到.这个万能字符替换成非<>两个符号的规则,好,CFC4N立刻修改一下。
修改之后的正则为

<coolcode.*?lang="[a-z]+".*?>

果然,匹配正常了。结果见截图。

mysql与正则表达式

到这里,问题似乎解决了,可是,当初糊涂的我,把coolcode的两种开头标识都用了,那就是

<coolcode

和[coolcode,那么,看官您认为这个正则该如何改写呢?
没错,无非就是开头,结尾的标识考虑两种情况<和[,那么正则就好改了。(别忘了排除规则里的符号哦)

[<\[]coolcode[^<>\[\]]*?lang="[a-z]+"[^<>\[\]]*?[>\]]

嗯,好,我们来看下效果:

PCRE引擎php与正则表达式


很好很完美。
下面,就可以去执行了。
可是,我遇到一个很意外的事情。居然发现老的代码里包含这样的格式

[coolcode linenum=\"off\" lang=\"cpp\"]<coolcode download=\"\" lang=\"cpp\" linenum="off">

呃,问题在这里了,只是多了个转义字符\罢了,那么,改起来,也简单。也就是允许\出现0次或者一次,而标识0次或者1次的符号为?,那么我们直接在\后面加个?,也就是改成这样\?就可以了吗?
显然,不是。在正则表达式里,\也表示转义,那么,匹配\的话,也得转义一下\,则应该为\\? 这样才对。
修改后正则为

[<\[]coolcode[^<>\[\]]*?lang=\\?"[a-z]+\\?"[^<>\[\]]*?[>\]]

匹配结果见下图:

正则表达式的斜杠转义

现在,大功告成了。我们可以进行转换了。关于转换,我们可以用两种方法。

  • Mysql的REPLACE函数,单个的去替换
    <coolcode lang="php/cpp/javascript/sql/css等" download="name" linenum="on/off">

    为对应的

    [code lang="php/cpp/javascript/sql/css等"]

    ,这样操作,省的去写程序,取出,替换,再写入了,缺点是量大,手工也挺累,体力活。mysql仅仅支持正则查询,不支持正则查询的替换,我们也可以构造联合嵌套的SQL来替换正则匹配的字符串,但是无法取出php/cpp/javascrip这样的语言标记,替换为新的语言标记。也就是说,mysql不支持正则表达式的反向引用。

  • PHP读数据库,替换,再写入。PHP的preg_replace函数支持反向引用(preg_replace不支持自定义组名的反向引用),我们只好写个查询语句,查询包含coolcode标识的文章,然后再替换,当然,直接查询包含coolcode的文章可能太多,我们也可以写个MYSQL支持的POSIX正则引擎的表达式,来匹配使用coolcode标签的文章,再来替换,写入。以减少文章的操作量。当然正则表达式也会浪费很大的资源。

当然,在PHP代码的preg_replace函数使用上面的正则,进行反向引用时,需要对正则稍作修改。给lang=""中间的一个组名。正则修改为

[<\[]coolcode[^<>\[\]]*?lang=\\?"([a-z]+\\?)"[^<>\[\]]*?[>\]]

PHP的替换代码为

$contents = preg_replace('/[<|[]coolcode[^>[\]]*?lang=\\\\?"([^"]+?)\\\\?"[^>[\]]*?[>|\]]/i','[code lang="\\1"',$contents);

其中正则的i修饰符标识不区分大小写。

preg_replace的反向引用


还有,别忘记了coolcode的结束标识和[/coolcode]要替换成[/code]。
mysql里执行两句sql即可

UPDATE wp_posts SET post_content = REPLACE(post_content,'</coolcode>','[\/code]');  //注意后面多了个反斜杠,记得去掉
UPDATE wp_posts SET post_content = REPLACE(post_content,'[/coolcode]','[\/code]');  //注意后面多了个反斜杠,记得去掉

总结:
本文牵扯的正则表达式并无高级用法,都是平常很简单的用法。关于PCRE引擎正则表达式的递归(迭代),组命名,反向引用,零宽断言等,CFC4N会在以后的时间里,找合适的例子写出来。当然,这些高级用法,CFC4N在帮朋友写的正则表达式里已经用到了,大家可以看看,欢迎批评和指点。
PS:如果需要coolcode转SyntaxHighlighter的完整PHP程序,留言即可,我抽空写出来。

CFC4N小试正则表达式

朋友甲:要求根据一串字符串,反转成PHP数组,其给出的字符串为php的print_r打印出来的。我们在暂不考虑此方法是否能确保数据的准确性,以及其他意外等情况,仅根据要求写正则。
其字符串为

Array
(
    [tt] => Array
    (
        [table] => qqttcode
        [hitcode] => 1
    )

    [ww] => Array
        (
        [table] => qqwwcode
        [hitcode] =>
        )

    [pp] => Array
        (
        [table] => qqppcode
        [hitcode] => Array
            (
            [table] => qqppcode
            [hitcode] =>
            )
    )

)

CFC4N给出一下结果:

$strRge1 = '/(\[([^]]+)]\s?=>\s?)?Array[\s\S]+?\(([^()]|(?R))*\)/i';
$arrReturn = array();
if (preg_match_all($strRge1,$str,$tt1))
{
    $arrReturn = getarray($tt1[0][0]);
}
    $arrReturn2 = array();
foreach ($arrReturn as $k => $v)
{
    $arrReturn2[$k] = $v[$k];
}
print_r($arrReturn2);

function getarray ($strContents)
{
    $arrTemp = array();
    $strRge = '/\[([^]]+)]\s?=>\s?Array[\s\S]+?\(([^()]|(?R))*\)/i';
    $strReg2 = '/\[([^]]+?)]\s?=>\s?([\d\w]+)?/';
    if (preg_match_all($strRge,$strContents,$strTemp))
    {
        $num = count($strTemp[1]);
        if ($num > '1')
        {
            for ($i=0; $i<$num; $i++)
            {
                if (preg_match_all($strRge,$strTemp[0][$i],$arrTTT))
                {
                    $arrTemp[$strTemp[1][$i]] = array();
                    $arrTemp[$strTemp[1][$i]] = getarray($strTemp[0][$i]);
                }
                else
                {
                    $arrTemp[$strTemp[1][$i]] = $strTemp[0][$i];
                }
            }
        }
        else
        {

            $arrTemp[$strTemp[1][0]] = array();
            $arrTemp2 = array();
            if (preg_match_all($strReg2, $strTemp[0][0],$straa))
            {
                $num = count($straa[0]);
                for ($i=0; $i<$num-1; $i++)
                {
                    $arrTemp2[$straa[1][$i+1]] = $straa[2][$i+1];
                }
            }
            $arrTemp[$strTemp[1][0]] = $arrTemp2;
        }

    }
    return $arrTemp;
}

结果是可以用的。但是发现其只能用于固定的三层嵌套,假如N层的话,无法用这个函数了,后来,我又改造一下那个正则,改为

$strRge1 = '/\[(([^]]+)]\s?=>\s?Array[\s]+?\(([^()])+|(?R))\)+/i';

但是,并不能解决问题。。各位看官,您认为,我的误区在哪里呢?
附 第一个正则截图

更改后正则匹配截图

朋友乙:要求批量给html字符串中a标签中不包含title属性的标签添加title,而且,其title内容为<a href…>到</a>之间的文本。。
CFC4N给出答案为:

$str = '<a >ssss</a><a href="ss" >ssss</a><a title="ss" >ssss</a><a href="">ssss</a><a title="ss">ssss</a><a title="ss">ssss</a><a title="ssf">ssss</a>';
$str = preg_replace('%<a((?:(?!title="[^"]+?")[\s\S])+?)>(?:(?<!</a>)[\s\S])+?</a>%im','<a title="\\2" \\1>\\2</a>',$str);
print_r($str);

各位看官,您认为,CFC4N写的正则表达式里,哪些还可以优化呢?这个效率是不是不高??

朋友丙:要求过滤非本域名,或者非本子域名的其他域名的UBB标签链接,一旦包含,直接替换成其中间的文本,比如例子字符串如下

[url=http://www.sadas.cn]baidu[/url]

[url=www.ggasdwe.com]百度[/url]
[url=http://www.qq.com/index.php]QQ[/url]

[url=http://www.miyifun.com/index.html]其他

[/url]
[url=pc.qq.com/index.php]PC QQ[/url]

其中,字符串中不确定有几个换行等其他字符,而且,不确定url的UBB标签中的网址中是否包含http://,不确定二级域名或者三级域名

CFC4N给出的正则以及PHP代码如下

$str = '[url=http://www.sadas.cn]baidu[/url]

[url=www.ggasdwe.com]百度[/url]
[url=http://www.qq.com/index.php]QQ[/url]

[url=http://www.miyifun.com/index.html]其他

[/url]
[url=pc.qq.com/index.php]PC QQ[/url]';
print_r(preg_replace('%\[url=(http://)?(?:(?!qq\.com)[^\]])*\][\r|\r\n]*([\s\S]+?)[\r|\r\n]*\[/url\]%i','\\2',$str));

各位看官,您认为这里哪里是多余的?还可以进行哪些正则的优化来提高效率?如果没看懂,那您的疑问在哪里?

朋友丁:要求读取squid的配置文件中,起作用的行,也就是没有#开头进行注释的行
其中,squid的配置文件内字符串见附件中
squid的配置文件内容
CFC4N给出正则代码如下

preg_match_all('/^(?!#).+?$/m', file_get_contents('squid.conf'), $regs);
print_r($regs[0]);

运行截图

PHP正则匹配squid.conf文件中启用的参数

看官们,您认为,这个正则还有哪些没注意到的点?能否正确无误的匹配到朋友丁所需要的内容?您有疑问吗?

PS:以上正则,均为PCRE引擎。。其中,PHP代码的正则递归(迭代)部分,仅限于支持递归正则的引擎代码适用。。
感谢rex老大指点关于(?!)零宽断言非匹配的特性后接匹配规则可能无效的问题。