已掉线,重新登录

首页 > 绿虎论坛 > 历史版块 > 编程 > PHP > 讨论/求助

标题: PHP正则表达式学习:解析虎绿林的UBB

作者: @Ta

时间: 2011-07-03

点击: 74839

《 外链:baidu.com,百度》
去掉《后面的那个空格,就成了百度
这种转换我们是怎么做到的呢?或许大家有所耳闻,我们使用的工具,名叫正则表达式。
$内容 = preg_replace(
 '!《 外链:(.*),(.*)》!uU',
 '<a href="http://\\1">\\2</a>',
 $内容);
//php认为一个UTF-8中文是三个特别的字母(因为一个UTF-8中文占三字节),所以“$内容”是一个合法的变量名。一个GB2312中文会被认为是两个特殊字母。
//“《”和“外”之间的那个空格是为了防止它被论坛UBB给匹配而留下的。
我们来分析一下这个PHP语句:
首先,它是一个赋值语句,表示把preg_replace()函数的执行结果赋予变量“$内容”
preg_replace()函数的中文名是“正则表达式替换”,它返回一个替换后的字符串。
它接受三个参数:
  第一个是替换模式,即“查找”用的正则表达式。
  第二个是替换用的字符串。
  第三个就是准备进行替换的内容啦。
preg_replace和字符串替换的函数str_replace很相似,它们有什么不同呢?相信大家看出来了,str_replace只能替换固定的字符串,比如
str_replace('免费主机', '****', $内容);
它就只能和谐掉“免费主机”这个词,我打“免费空间”是绝对不会被和谐的。
再看:
preg_replace('/免费../u', '****', $内容);
现在就糟糕啦,“免费空间”、“免费下载”,甚至“免费吗?”,只要是“免费”开头,后面跟上两个字符的内容都会变成****
于是我们得到一个结论:正则表达式的作用,就是把 某种特定格式的内容 替换成另一种格式。
“特定格式的内容”意味着这些要替换的内容是不确定的,就像上面那个例子,要替换的内容可能是“免费空间”,或者“免费主机”,或者其他任何内容,只要符合我们设定的条件,正则就能把它替换掉。
那么我们是怎么设定条件的呢?在上面的例子中,我们在“免费”后面使用了两个小数点,它们是什么意思呢?
呵呵,你猜对了。在这里,“.”不再代表它本身,而是指代任何一个字符。像这样可以代表其他字符的字符还有很多,在正则表达式的说明文档里,它们有一个更专业的名字:元字符。
『请元字符们登场』
  .   一般情况下,我表示除了换行外的任何一个字符。但是我也可以表示包括换行的任何一个字符,这个取决于你的模式设置(关于模式设置的更多内容后面再说)
  \d  我表示一个数字,从0到9。
  \D  我表示不是数字的任何一个字符。
  \w  我表示一个字母、数字或下划线。注意,我也有可能认为中文是字母哦,这个也和你的模式设置有关。
  \W  我表示的内容和\w的相反,我表示一个不是字母、数字、下划线的字符。
  \s  我表示一个空白,包括空格,换行符(\r),回车符(\n),制表符(\t, Tab键打出的那个),垂直制表符(\v)。注意哦,如果你的换行占两个字的位置(\r\n),我只能匹配其中之一哦。如果你要用我匹配换行,你得用\s\s才能匹配占两个字的换行。
  \r \n \t \v  我们的意思你已经从上面知道了吧。特别的是我们在php的双引号里面也可以用哦,如 echo "我的\n空间"; 会在“我的”和“空间”之间输出一个换行符。单引号里不能用。
  \S  我表示任何一个不是空白的字符。
  []  我们是一个组合,被称为“字符类”,我们匹配在我们之内的任何一个字符,比如 [abc] 匹配 a 、 b 或 c 三者之一。我们支持范围的表示,如 [a-zA-Z0-9] 匹配任何一个字母或数字。另外这样也可以哦 [:-@] ,它匹配 : ; < = > ? @ 这七个符号。为什么?不解释,自己去查ASCII码表。 [\dA-Z] 匹配数字和大写字母。但是注意.在我们里面就是表示它本身哦, [.,] 就表示逗号和句号。
  \  看了上面的内容,你肯定会问,我怎么匹配一个点呢,用[.]?当然可以,但是还有更简单的方法,用我来转义。\. 表示一个普通的小数点。我也可以转义自己哦,\\表示一个普通的斜杠。所以\\d 就匹配两个普通的字符 \d ,而不再是任意数字。
转义:改变意义。转义字符,就是给字符加上某个特定的符号,使一些普通字符(如d)具有特殊含义(如\d),或者使一些特殊字符(如.)失去特殊含义(如\.)。
在PHP里,转义经常发生,如输出下面这段话:
Tiger said, " I'm a phper. "
你需要这样写:
echo 'Tiger said, " I\'m a phper. "';
或者
echo "Tiger said, \" I'm a phper. \"";
如果你没有给引号正确地添加斜杠,PHP就会郑重地告诉你:悲剧了,在第n行有一个莫名其妙的关键字(T_STRING)。
它不会提到任何和引号有关的词,因为PHP会认为,在
echo 'Tom said," I'm a phper. "';
这个语句中,字符串已经在 I' 那里正常结束(如果没有后面的m a phper…,确实是到I那里就结束了)。所以,不能以常理审视语法错误提示啊。同样的,
echo $name"你好";
php会首先提示说“发现一个奇怪的双引号”,然后在括号内才提醒你可能忘了一点(.),PHP的连字符。
现在我们可以用正则表达式做一些事了,比如匹配手机号:
1[358]\d\d\d\d\d\d\d\d\d
真悲剧,要是我匹配一万个数字怎么办?一个邪恶的声音响起:写一万遍吧!喔,不!
所以,它们出场了:
『数量符』
  *  我表示任意数量个前面的字符,从0到无限大。比如, \d* 匹配0个或任意数量的数字。我的语义是:没有,或者无限多。
  ?  我表示0或1个前面的字符。比如 https?//.* 匹配http://或https://开头的网址。我的语义是:存在,或者不存在。
  +  我表示1个或更多个前面的字符。比如,http://.* 可能就匹配到一个网址开头“http://”,而 http://.+ 则不会遇到这样的问题。我的语义是:一个,或者更多。
  {}  我们又是一个组合,我们有三种语法:
{n}表示刚好n个前面的字符,比如 1[358]\d{12} 匹配手机号。
{n,}表示至少n个前面的字符。
{m,n}表示至少m个,最多n个前面的字符。如 [1-9]\d{4,9} 匹配目前的QQ号。
注意{,n}是错误的,你得写成{0,n}
现在好了,如果我要匹配手机号,只要写成
1[358]\d{9}
就可以了。
可是问题又来了:在用https?://来匹配URL开头的过程中,我们发现mtest.g.cn/?http://h.cn这样的字符串也能通过匹配。这样可就彻底悲剧了,我们是要检查网址开头是否添加了https?://啊!那么有什么方法可以使我们只匹配到指定位置的字符吗?php说:那是必须的。
『定位标记』
^ 我匹配字符串的开始位置。如果多行模式被打开,我就匹配一行的开始位置
$ 我匹配字符串的结尾位置。多行模式下匹配一行的结尾
\A 我永远匹配字符串的开始,即使在多行模式也一样
\Z 我永远匹配字符串结尾(如果以换行结尾,就匹配到结尾的换行符之前),无视多行模式
\z 永远匹配字符串节尾(包括结尾的换行),无视多行模式
\G 我表示只匹配一次。当我出现时,表示只有目标中的第一个位置会匹配成功。
\b 我匹配一个字边界。啥意思?比如“I am a     tiger!My name
is smow.”中,每两个单词之间任意数量的的非单词字符就是字边界。
\B 我和\b相反,匹配非字边界。在\b不匹配时我就匹配了。
哈,搞定。要验证一个字符串是否有合法的网址开头,只要这样:
if(preg_match('!^https?://.+!i',$内容))
 echo '网址合法';
else
 echo '请以http://或https://开头';
疑问:这里的!和!i是什么意思??
它们呢,正是preg比其他正则表达式支持更容易使用的重要体现,使用它们可以让你很方便的控制匹配时的细节。
其中,!叫做模式分隔符。preg中规定:在正则表达式字符串中出现的第一个普通标点符号(不是字母数字,也不是元字符的那些字符)为模式分隔符。同时,相同的模式分隔符也必须出现在表达式的结尾。
另外,如果表达式中间要用到模式分隔符,必须要用\转义
通常,人们使用/做为模式分隔符,但是:
/^https?:\/\/.+$/i
如果忘记转义就悲剧了。当php发现模式分隔符没有或者使用不正确时,匹配直接失败!所以还是用!吧。
在结尾的模式分隔符后面接着的,就是模式修饰符了。
『模式修饰符』
i 我表示匹配时忽略字母大小写
s 默认情况下.匹配不包含换行的任何字符。设置我可以让.也匹配换行符
m 我开启多行模式,设置我之后^和$匹配行首和行尾
U 我关闭贪婪模式。所谓贪婪模式,就是在保证整个表达式能够正确匹配的情况下,让*和+尽可能地多匹配字符。比如:
!<a>(.*)</a>!i
当遇到<a>1</a><a>2</a>的时候,得到的结果可能是悲剧性的,因为.*会把1</a><a>2都给匹配了!我们当然不能让它那样做。有两个方法实现:
1. !<a>(.*?)</a>!i 在*和+的后面加?表示此处关掉贪婪模式
2. !<a>(.*)</a>!iU 整个表达式关掉贪婪模式
当贪婪模式被关掉之后,php会在保证整个模式能匹配成功的情况下尽可能少匹配字符。所以我们不再会遇到之前的悲剧了。
如果要对某一个*或+打开贪婪模式,也是在后面加?。*和+后面的?作用是反转贪婪模式的设置。
u 我开始UTF-8模式。因为中文是多字节的,在通常情况下.只匹配到中文的其中一个字节,悲剧!而设置了我之后.就可以正常匹配UTF-8的中文单字了。?,+和{m,n}这些数量符也受到影响。缺点:开启我之后非UTF-8字符串直接匹配失败,悲剧的GB啊。
D 默认情况下$也会匹配结尾的换行。设置我可以让$无视结尾的换行符们
x 我开启表达式单行注释。
!<a>(.*)</a> #测试
!iUx
不建议使用,因为“所有空白字符都会被忽略”!
A 我规定php必须强制从字符串开始位置匹配,即使表达式里面没有^
S 我对表达式进行预处理。如果一个表达式要使用很多很多次,预处理能加快速度。
相信大家已经知道开始的!…!uU是什么意思了。
『后向引用』
preg_replace('!\\[a=(.*)\\](.*)\\[/a\\]!iU','<a href="\\1">\\2</a>',$内容);
这就是后向引用,\\1和\\2分别引用第一个和第二个括号里的内容。preg_match也可以后向引用,注意它的第三个参数。
注:写成\\是因为php要求对\进行转义。如果你遇到一个填表达式的网页输入框,就得用\1、\2等,因为这时不需要转义。
一个小技巧:?:的使用
!(?:https?://)(.*)/(.*)!iU
这个模式只会产生两个后向引用,第二个括号是\1,第三个括号是\2。第一个括号后接的?:使它在后向引用时被忽略。
php官方对正则表达式的介绍,看“模式语法”。提醒Q浏用户,遇到英文时再翻一页就是中文。

[隐藏样式|查看源码]


『回复列表(104|隐藏机器人聊天)』

1.
发言被站长屏蔽,仅管理员和作者本人可见。
(/@Ta/2011-06-14 20:51//
被站长屏蔽
)

2.
发言被站长屏蔽,仅管理员和作者本人可见。
(/@Ta/2011-06-14 21:18//
被站长屏蔽
)

3.
发言被站长屏蔽,仅管理员和作者本人可见。
(/@Ta/2011-06-14 21:40//
被站长屏蔽
)

4.
发言被站长屏蔽,仅管理员和作者本人可见。
(/@Ta/2011-06-14 21:42//
被站长屏蔽
)

5.
发言被站长屏蔽,仅管理员和作者本人可见。
(/@Ta/2011-06-14 22:22//
被站长屏蔽
)

6.
发言被站长屏蔽,仅管理员和作者本人可见。
(/@Ta/2011-06-14 23:11//
被站长屏蔽
)

7.
发言被站长屏蔽,仅管理员和作者本人可见。
(/@Ta/2011-06-15 01:21//
被站长屏蔽
)

8.
发言被站长屏蔽,仅管理员和作者本人可见。
(/@Ta/2011-06-16 07:11//
被站长屏蔽
)

9.
发言被站长屏蔽,仅管理员和作者本人可见。
(/@Ta/2011-06-16 07:12//
被站长屏蔽
)

10.
发言被站长屏蔽,仅管理员和作者本人可见。
(/@Ta/2011-06-20 23:43//
被站长屏蔽
)

11.
发言被站长屏蔽,仅管理员和作者本人可见。
(/@Ta/2011-06-20 23:44//
被站长屏蔽
)

12.
发言被站长屏蔽,仅管理员和作者本人可见。
(/@Ta/2011-06-20 23:45//
被站长屏蔽
)

13.
发言被站长屏蔽,仅管理员和作者本人可见。
(/@Ta/2011-06-23 16:35//
被站长屏蔽
)

14.
发言被站长屏蔽,仅管理员和作者本人可见。
(/@Ta/2011-06-23 18:33//
被站长屏蔽
)

15.
发言被站长屏蔽,仅管理员和作者本人可见。
(/@Ta/2011-06-23 18:35//
被站长屏蔽
)

16.
发言被站长屏蔽,仅管理员和作者本人可见。
(/@Ta/2011-07-03 10:49//
被站长屏蔽
)

17.
发言被站长屏蔽,仅管理员和作者本人可见。
(/@Ta/2011-07-03 10:57//
被站长屏蔽
)

18.
发言被站长屏蔽,仅管理员和作者本人可见。
(/@Ta/2011-07-03 10:58//
被站长屏蔽
)

19.
发言被站长屏蔽,仅管理员和作者本人可见。
(/@Ta/2011-07-03 11:37//
被站长屏蔽
)

下一页 1/6页,共104楼

回复需要登录

7月6日 02:03 星期天

本站由hu60wap6驱动

备案号: 京ICP备18041936号-1