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

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

对于正则表达式的应用,一般情况下包括:
验证、提取和替换(插入、删除)
验证和提取使用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 class="con_top">标签中的标题
        //因为匹配的的数据中有需要转义的""双引号,所以字符串没有用"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 单词首字母大写

替换功能主要是应用StringreplaceAllreplaceAllMapped方法

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 数字插入千分位符号(替换的延伸用法)

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

如果是在判断条件的开始位置插入
如:句首、单词前、表达式前
那么可以方便的用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

 

本文出自“Dart语言中文社区”,允许转载,转载时请务必以超链接形式标明文章原始出处
本文地址:
http://www.cndartlang.com/778.html

发表评论

登录后才能评论