Dart学习笔记(17):正则表达式实例

发表于2018-07-04 17:57 阅读(36)


目录
1 验证
2 提取
….2.1 提取中文字符串
….2.2 网络小说内容页采集(提取)
….2.3 网络小说目录页采集(分割)
3 替换
….3.1 单词首字母大写
….3.2 数字插入千分位符号(替换的延伸用法)

本文地址:http://www.cndartlang.com/778.html

对于正则表达式的应用,一般情况下包括:
验证、提取和替换(插入、删除)
验证和提取使用RegExp类的函数
替换则使用String类的函数

这里通过几个实例演示一下Dart中正则表达式的使用
以及正则表达式在处理字符串时的优势

1、验证

在程序中,我们经常会对一个字符串进行验证
如用户名、邮箱、身份证信息等
用正则表达式可以很便捷的对有效性进行判断

下面是匹配国内电话号码的例子
用到了RegExp类以及hasMatch函数
假设区号是3位,号码8位

String str = "010-88888888";
print("${new RegExp(r"^0[1-9]\d-\d{8}$").hasMatch(str)}");

^0[1-9]\d-\d{8}$

^                #行首
0                #第一位0
[1-9]            #第二位1-9
\d               #第三位数字
-                #匹配-
\d{8}            #8位数字
$                #行尾

运行结果:

true

2、提取

提取可以分为提取特定字符串
以及延伸的功能,分割

2.1 提取中文字符

首先来看个提取中文字符的例子
用到了RegExp类和迭代Match

void main() {
  String str = "Dart中文社区";
  RegExp reg = new RegExp(r"[\u4e00-\u9fa5]+");
  Iterable<Match> matches = reg.allMatches(str);

  for (Match m in matches) {
    print(m.group(0));
  }
}

运行结果:

中文社区

2.2 网络小说内容页采集(提取)

可能之前的例子实用性不高
那么现在来个网络小说采集的例子
采集数据的过程也就几行代码,不多做解释

import 'dart:io';

void main() {
  new HttpClient().getUrl(Uri.parse("http://www.biquge.la/book/32/24387.html"))
  .then((HttpClientRequest request) => request.close())
  .then((HttpClientResponse response) {
      response.transform(new SystemEncoding().decoder).listen((requestText) {
        //此时已经请求到HTML格式网页数据
        //print(requestText);

        //不区分大小写,匹配在<div>标签中的标题
        //因为匹配的的数据中有需要转义的""双引号,所以字符串没有用"r"修饰符
        //提取的是书名,定位唯一位置,因此没有使用allMatches函数
        Match match = new RegExp("booktitle\\s+=\\s+"(.*)".*readtitle\\s+=\\s+"\\s+(.*)"").firstMatch(requestText);

        if(match != null) {
          //分组1为书名,分组2为章节名
          print("书名:${match.group(1)}\n章节:${match.group(2)}");
        }
      });
  });
}

booktitle\s+=\s+”(.*)”.*readtitle\s+=\s+”\s+(.*)”

booktitle        #匹配字符串
\s+              #源码中,=赋值的时候,前后可能有空字符
=                #匹配=
\s+              #源码中,=赋值的时候,前后可能有空字符
"(.*)"           #双引号内为group(1),书名
.*               #查看源码可以知道,booktile和readtitle两个字符串之间没有换行符
readtitle        #匹配字符串
\s+              #源码中,=赋值的时候,前后可能有空字符
=                #匹配=
\s+              #源码中,=赋值的时候,前后可能有空字符
"\s+(.*)"        #双引号内为group(2),章节名

运行结果:

书名:全职高手
章节:第二章 C区47号

2.3 网络小说目录页采集(分割)

好吧!看了之前一节之后不得不承认
什么网络小说采集都只是噱头
主要还是想表达正则表达式的重要性
另外,String的功能函数还是很丰富的
分割、替换之类都可以找到

这里聊一个小插曲
本节实现的功能是采集小说的目录和地址
使用的代码我是用上一节的代码修改的
但是运行测试的时候总是出问题

审查元素分析了一下http://www.biquge.la/book/32/ 这个网页
没有什么特别的地方、、、
查看网页源代码的时候发现 第九百八十九章 的a标签并没有闭合?!

但是在Dart中请求的时候,闭合正常
不过/和a之间貌似有其他的字符,换行了

没办法,用WireShark抓包看了一下
然后我就呆了,数据包并没有问题!

Debug设置断点查看了一下
结果出乎意料,</之后就没了
看了堆栈信息,果然是空的、、、

由抓包的结果来看,应该不是网页的问题
虽然源代码中显示没有闭合标签、、、
然后突然想到:数据没接收完?

然后把处理过程改到onDone监听函数中
一切OK!汗

好吧,以上就当是一个冷笑话
(其实作用还是有的:论各种工具的重要性)

import 'dart:io';

void main() {
  String content;

  new HttpClient().getUrl(Uri.parse("http://www.biquge.la/book/32/"))
      .then((HttpClientRequest request) => request.close())
      .then((HttpClientResponse response) {
    response.transform(new SystemEncoding().decoder).listen((String requestText) {
      //接收请求到HTML格式网页数据,并保存
      content = "$content$requestText";
    }, onDone: (){
      //HTML中不支持\r\n,因此直接删掉,不会有影响
      //标签之前空格删掉,如:<dd></dd>
      content = content.replaceAll(new RegExp(r"[\r\n]|(?=\s+</?d)\s+"), "");

      //提取章节的<dd>标签信息
      content = new RegExp("<div\\s+id="list"><dl>(<dd>(?:.*)</dd>)</dl></div>").firstMatch(content).group(0);
      //提取<dd>标签中的链接和名称
      Iterable<Match> matches = new RegExp("<dd>.*?href="(.*?)">(.*?)</a></dd>").allMatches(content);

      for (Match match in matches) {
        print("地址:${match.group(1)}\t章节:${match.group(2)}");
      }
    });
  });
}

[\r\n]|(?=\s+</?d)\s+

[\r\n]                #匹配回车换行符
|
(?=\s+</?d)           #匹配"\s+<d"和"\s+</d"结构,通过HTML可以知道
                      #标签前的空白字符串删掉并没有什么影响
                      #同时,环视并不占用字符,匹配的是位置
                      #因此,环视执行完后,还是在字符串中的\s+开始位置
\s+                   #经过环视匹配后,\s+一定匹配成功,并被group(0)捕获

<div\s+id=”list”><dl>(<dd>(?:.*)</dd>)</dl></div>

<div\s+id="list"><dl>      #经过之前正则匹配后,标签之间的空格已经删除
(<dd>(?:.*)</dd>)          #group(1),捕获的内容格式为<dd>...</dd>
</dl></div>                #经过之前正则匹配后,标签之间的空格已经删除

<dd>.*?href=”(.*?)”>(.*?)</a></dd>

<dd>                        #匹配标签
.*?href="(.*?)">            #group(1),查找章节的链接地址并捕获
(.*?)</a>                   #group(2),查找<a>标签的内容,即章节的名称并捕获
</dd>                       #匹配标签

运行结果:

3、替换

3.1 单词首字母大写

替换功能主要是应用String的replaceAll、replaceAllMapped方法

String replaceAll(Pattern from, String replace)

将from匹配的字符串,也就是group(0)替换为replace

String replaceAllMapped(Pattern from, String replace(Match match))

首先匹配from,将匹配到的结果match传给replace函数,并调用
好处是可以在replace函数中,对每个分组group(0)、group(1)…进行操作处理

void main() {
  String str = "dart and flutter(sky)";
  print(str.replaceAllMapped(new RegExp(r"\b\w"), (match)=>match.group(0).toUpperCase()));
}

运行结果:

Dart And Flutter(Sky)

3.2 数字插入千分位符号(替换的延伸用法)

在String和RegExp中并没有特定的插入、删除字符串方法
但是我们可以通过正则表达式中的替换来实现
只是插入的时候,它替换的是“位置”或者字符串
同样,删除的时候,替换的是空字符串

如果是在判断条件的开始位置插入
如:句首、单词前、表达式前
那么可以方便的用String.replaceAll匹配位置来替换

void main() {
  //插入:替换的引申用法
  String str = "ice: 65";
  print(str);

  //在句首插入字符"Pr",这个时候匹配的是位置,而不是字符串
  print(str = str.replaceAll(new RegExp(r"^"), "Pr"));
  //在数字前插入字符"$"
  print(str.replaceAll(new RegExp(r"(?=\b\d+)"), "\$"));
}

运行结果:

ice: 65
Price: 65
Price: $65

但是有时候我们需要插入的位置并不在开始位置,如:
(判断条件1)(插入字符串位置)(判断条件2)
由于Dart中并不支持逆序环视
这个时候要不只判断条件2,条件1忽略
然后用环视匹配插入位置,但不推荐

要不就只能老老实实的用匹配字符串来替换了
简单的说,就是通过匹配到的分组来重新拼接字符串

这里用一个经典的例子来演示
在数字中插入千分位符号

方法有两种:

  • 一种是使用肯定顺序环视
  • 一种是不使用环视
void main() {
  //肯定顺序环视
  String text = "The population of 15269845 is growing";
  print(text.replaceAllMapped(new RegExp(r"(\d)(?=(?:\d{3})+\b)"), (match)=>"${match.group(1)},"));

  //不使用环视
  RegExp reg = new RegExp(r"(\d)((?:\d{3})+\b)");
  while(reg.hasMatch(text)) {
    text = text.replaceAllMapped(reg, (match) => "${match.group(1)},${match.group(2)}");
    print(text);
  }
}

(\d)(?=(?:\d{3})+\b)

(\d)                    #需匹配的数字group(1)
                        #replaceAllMapped的第二个参数为replace函数
                        #在replace函数中拼接千位符
(?=(?:\d{3})+\b)        #作为group(1)匹配成功的条件:一直到结尾,\d是3的倍数
                        #由于环视不占用字符,因此在匹配完一个字符后
                        #系统会依次匹配剩余的字符串,并返回Match

(\d)((?:\d{3})+\b)

(\d)                    #需匹配的数字group(1)
((?:\d{3})+\b)          #group(1)之后的字符串捕获为group(2)
                        #作为group(1)匹配成功的条件:一直到结尾,\d是3的倍数
                        #在判断的时候,指针已经指到了末尾,匹配结束
                        #因此一次只能添加一个千位符
                        #在之后的while循环中,会再次匹配
                        #一直到匹配失败,千位符插入完毕,跳出while循环

运行结果:

The population of 1,382,590,000 is growing
The population of 1,382590000 is growing
The population of 1,382,590000 is growing
The population of 1,382,590,000 is growing
The population of 1,382,590,000 is growing