这里讲的一个例子是将Text(文本)转化文HTML的正则表达式的书写过程–书中67页
Text-to-Html转换
我们写一个完成上述任务的小工具(使用Perl语言)
为了将正则表达式运用在整个文本上,而不是一行一行处理,可以使用Perl提供的功能进行转化:
undef $/; #进入“file-slurp”(文件读取)模式
$text = <>; #读入命令行中的第一个文件
print $text;
处理特殊字符
将原始文本中的特殊的字符转换为对应的HTML编码,比如'&', '<', '>'
等等在HTML中具有特殊意义的字符。需要将他们转换成'&', '<', '>'
。简单的转化如下:
$text =~ s/&/&/g; #保证基本的html字符转换后不会出错
$text =~ s/</</g;
$text =~ s/>/>/g;
分隔段落
使用html tag中表示分段的<p>
来标记段落,所以要写出找到段落的正则表达式。下面这个最先想到的写法是:
$text =~ s/^$/<p>/g;
上面的代码把空行作为段落之间的分隔。他可以匹配“行末尾紧随行开头的位置”。但是^
和$
匹配的通常不是逻辑行的开头和结尾,也就是一个逻辑行可能占多行文本。
为此,大多数支持正则表达式的语言提供了“增强的行锚点”匹配模式,在这个模式中,^
和$
会从字符串模式切换到需要的逻辑行模式。在Perl中可以使用/m
修饰符来选择此模式:
$text =~ s/^$/<p>/mg;
不过,如果在“空行”中包含空格符或其他空白字符上述正则就不能成功运作。为了处理空白字符,可以使用^。*$
,或者^[。\t\r]*$
来匹配某些系统在换行符之前的空格符、制表符或者回车符。
另外正则表达式^\s*$
也可以处理有空格、空白字符的空行,但是由于\s
本身能够匹配换行符,所以整个表达式的意义就不再是“寻找空行及只包括空白字符的行”,而是“寻找空行和只包括空白字符的行的结合”。这可以将连续的空白行进行合并,能够获得更好的体验。所以使用这个好一点。
将E-mail地址转换为超链接形式
识别出email地址,并将其转换成mailto
链接。email地址:jfriedl@oreilly.com
会被转化为
<a href="mailto:jfredl@oreilly.com">jfriedl@oreilly.com</a>
如何匹配email地址的正则形式可以写成下面这样:
$text =~ s/\b(username regex\@hostname regex)\b/<a href="mailto:$1">$1<\/a>/g;
注意上面的a
结尾标签需要对/
进行转义。
\b
包裹正则的原因是为了排除一些如"jfried@orilly.compiler"
的例子。
匹配用户名和主机名
要写正则表达式来匹配,先要清楚这个要匹配的文本的模式,主机名的一些例子如下所示:
regex.info;
www.oreilly.com
它们由点号分隔,以com, edu, info, uk
或者其他事先规定的字符序列结尾。根据这个可以先写出一个最简单的匹配email地址的办法:
\w+\@\w+(\.\w+)
用\w
来匹配用户名,以及主机名的各个部分。不过,实际应用需要考虑的更多才能完成工作。用户名可以包含点号和连字符,所以可以写成:
\w[-.\w]*
这样保证用户名以\w
开头后面部分可以包括点号和连字符。
主机名的匹配要复杂一些。有一个简单的雏形如下:
\w+(\.\w+)+
上述正则保证了有点号,但是也可能匹配Artichokee4@1.00
这样的字符串。所以还要更细心的考虑。
一个办法是给出末尾部分所有可能出现的序列,跟在\w+(\.\w+)*\.(com|edu|info)
之后。
这样就能容许开头的\w
部分,然后是可能出现的\.\w
部分,最后是我们指定的可能结尾。
实际上\w
有些不合适,因为它能匹配ASCII字母和数字,但有些流派的\w
能匹配非ASCII字母。所以要改一下得到主机名可能的一种写法如下:
[-a-z0-9]+(\.[a-z0-9]+)*\.(com|edu|info)
正则表达式除了可以用//
包裹,还可以使用其他的符号比如{}
。下面是综合起来的text转换为HTML方法:
undef $/; # 进入“文件读取”模式
$text =<>; # 读入命令行中指定的第一个文件名
$text =~ s/&/&/g; # 替换HTML基本符号
$text =~ s/</</g; #
$text =~ s/>/>/g;
$text =~ s/^\s*$/<p>/mg; #划分段落
# 转换为链接形式
$text =~ s{
\b
# 把地址保存到$1 ...
(
\w[-.\w]* # username
\@
[-a-z0-9]+(\.[a-z0-9]+)*\.(com|edu|info) #hostname
)
\b
}{<a href="mailto:$1">$1</a>}gix;
print $text; # 最后,显示HTML文本
注意只有用于划分段落的正则表达式才是用/m
修饰符。
把http URL替换为链接形式
类似的,先分析URL的模式:
http://hostname/path
其中/path
部分是可选的。于是我们得到下面的形式
$text =~ s{
\b
#将URL保存到$1中
(
http:// hostname
(
/path
)?
)
}{<a href="$1">$1</a>}gix;
主机名部分的匹配可以使用在email中分析出来的正则表达式。URL中的/path
部分可以包含各种字符,比如在先前一章中使用的:
[-a-z0-9_:@&?=+,.!/~*'%$]
它包含了除空白字符,控制字符和<>()
之外的大多数ASCII字符。
我们要对&
和@
进行转义:
$text =~ s/{
\b
# 将URL保存在$1
(
http://[-a-z0-9]+(\.[-a-z0-9]+)*(com|edu|info) \b #hostname
(
/ [-a-z0-9_:\@&?=+,.!~*'%\$]* # path不一定出现
)?
)
}{<a href="$1">$1</a>}gix;
path
后面没有\b
因为URL后面一般都是标段符号,如:
http://www.oreilly.com.catalog/regex3/
,如果末尾有\b
就不能匹配。
然后,就可以得到完整的程序
undef $/;
$text = <>;
$text =~ s/&/&/g;
$text =~ s/</</g;
$text =~ s/>/>/g;
$text =~ s/^\s*$/<p>/mg;
#将email转换为链接形式
$text =~ s{
\b
(
\w[-.\w]*
\@
[-a-z0-9]+(\.[-a-z0-9]+)*\.(com|edu|info)
)
\b
}{<a href="$1">$1</a>}gix;
#将http URL转为链接
$text =~ s{
\b
(
http:// [-a-z0-9]+(\.[-a-z0-9]+)*\.(com|edu|info) \b
(
/[-a-z0-9:_\@&?=+,.!/~*'%\$]*
)?
)
}{<a href="$1">$1</a>}gix;
print $text;
回到单词重复的问题
代码如下
$/ = ".\n";
while (<>) {
next if !s/\b([a-z]+)((?:\s|<[^>]+>)+)(\1\b)/\e[7m$1\e[m$2\e[7m$3\e[m/ig;
s/^(?:[^\e]*\n)+//mg; #删除所有的未标记的行
s/^/$ARGV: /mg; #在行首增加文件名
}
这里要做一些解释才能看懂上面的代码:
首先最外面的while循环会便利所有的文本行,
接下来的next if如果不为true,也就是第一个表达式没匹配成功,就会进行下一次循环。
\e[7m是高亮显示的开头,\e[m是高亮显示的结尾。也就是被这两个东西框柱的部分会高亮显示
接下来的一个匹配会将没有出现重复单词的行移除
最后一行在每一行的前面加上文件名
用java实现重复单词问题
import java.io.*;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
public class TwoWord
{
public static void main(String [] args)
{
Pattern regex1 = Pattern.compile(
"\\b([a-z]+)((?:\\s|\\<[^>]+\\>)+)(\\1\\b)",
Pattern.CASE_INSENSITIVE);
String replace1 = "\033[7m$1\033[m$2\033[7m$3\033[7m";
Pattern regex2 = Pattern.compile("^(?:[^\\e]*\\n)+",Pattern.MULTILINE);
Pattern regex3 = Pattern.compile("^([^\\n]+)",Pattern.MULTILINE);
//对于命令行的每个参数进行如下处理
for (int i = 0; i < args.length; i++)
{
try {
BufferedReader in = new BufferedReader(new FileReader(args[i]));
String text;
//For each paragraph of each file...
while((text = getPara(in)) != null)
{
//应用3条替换规则
text = regex1.matcher(text).replaceAll(replace1);
text = regex2.matcher(text).replaceAll("");
text = regex3.matcher(text).replaceAll(args[i] + ": $1");
//显示结果
System.out.print(text);
}
}catch(IOException e) {
System.err.println("can't read ["+args[i]+"]: " + e.getMessage());
}
}
}
//用于读入“一段”文本的子程序
static String getPara(BufferedReader in) throws java.io.IOException
{
StringBuffer buf = new StringBuffer();
String line;
while ((line = in.readLine()) != null &&
(buf.length() == 0 || line.length() !=0))
{
buf.append(line + "\n");
}
return buf.length() == 0 ? null : buf.toString();
}
}