匹配html标签及其内容

面临的问题

  • 多行匹配(大多数工具的初级用法中不包括多行匹配)
  • 自动匹配标签(还算简单,仅仅是不能区分标签是否合法)
  • 有不成对的标签

指定标签类型

先解决多行匹配的问题.
主要使用(.|\n)*?(贪婪匹配),

样本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<html class="paper"><head>
<meta name="disabled-adaptations" content="watch">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="initial-scale=1">
<base href="http://yingyu.xdf.cn/201208/9008387.html">
<title>初中英语语法大全:冠词-新东方网</title>
<style id="print">
@media print {
body {
margin: 2mm 9mm;
}
}
</style>

<p>a</p>

<style id="dynamic-article-content"></style>

<style id="extra-css"></style>

</head>

</body></html>

grep允许使用-z选项进行多行匹配

1
grep -zP '<style[^>]*>(.|\n)*?</style>' test.html

或者

1
grep -zP '<style.*?>[\s\S]*?</style>' test.html

emacs自身支持多行匹配

  1. emacs re-builder 中的表达式

    "<style[^>]*>\\(.\\|\n\\)*?</style>"

    • 这里的双转义是为了双引号的解析
    • 单转义是由于emacs自身需要对大小括号和管道符转义
  2. emacs replace-regexp 等函数中的表达式

    <style[^>]*>\(.\|^J\)*?</style>

    • 其中 ^J 使用 C-q C-j 输入

sed的两种方案

这里是匹配删除的方法.
匹配显示的方法是使用 sed -n '.../p',
与删除差别不大.

  1. 选取范围

    sed -e '/<style/,/<\/style>/d' test.html

    • 只要含有标签的行就被删除,操作不精细,无法对嵌套的tag结构进行操作
    • 操作非空行时是非贪婪的,而 操作空白行时是不稳定的
  2. 循环读取至模式空间

    sed -e ':begin; { /<\/style>/! { $! {N; b begin }; }; s/<style.*<\/style>//; };' test.html

    1. 未遇到 </style> 且不是最后一行,就不断加入模式空间
    2. 加入到模式空间的部分,可以看做一行,匹配删除

awk的方法

和sed的思想类似

  1. 匹配显示

    awk '/<style/,/style>/{print;}' test.html

  2. 匹配删除

    1. 留空行

      awk '/<style/,/style>/{next;} {print;}' test.html

    2. 不留所有空行

      awk '/<style/,/style>/{$0="";next;} {if ($0!="")print $0;}' test.html

不指定标签类型

样本

注意选取合适的范围

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<meta name="disabled-adaptations" content="watch">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="initial-scale=1">
<base href="http://yingyu.xdf.cn/201208/9008387.html">
<title>初中英语语法大全:冠词-新东方网</title>
<style id="print">
@media print {
body {
margin: 2mm 9mm;
}
}
</style>

<styla id="print">
@media print {
body {
margin: 2mm 9mm;
}
}
</styla>


<p>a</p>

<style id="dynamic-article-content"></style>

<style id="extra-css"></style>

grep仍然可以使用

同样的思想,在标签出使用分组并在结束处替换

<([a-z][a-z0-9]*)[^>]*>(.|\n)*?</\1>

<([a-z][a-z0-9]*).*?>[\s\S]*?</\1>

emcas仍然稳

  1. emacs的 re-builedr 中的式子

    1
    "<\\([[:alpha:]]+\\)[^>]*>\\(.\\|\n\\)*?</\\1>"
  2. emacs的 replace-regexp 等函数中的式子

    1
    <\([[:alpha:]]+\)[^>]*>\(.\|^J\)*?</\1>

awk

满足条件时可以使用下面的方法

  • 所有将要被匹配的标签都没有嵌套或与其他标签在同一行
    • 比如

      1
      2
      3
      <style><p>
      ...</p>
      </style>
  • 标签中不含有数字
  1. 匹配显示

    awk '/<[a-z]/,/[a-z0-9]>/{print;}' test.html

  2. 匹配删除

    1. 留空行

      awk '/<[a-z]/,/[a-z0-9]>/{next;}{print;}' test.html

    2. 不留空行

      awk '/<[a-z]/,/[a-z0-9]>/{$0="";next;} {if ($0!="")print $0;}' test.html

sed的方法

需要注意:

  • 正则表达式作为模式,不能捕获分组,\1等后向引用不能使用
  • 模式的范围控制并不精准,不能像awk一样使用
    • sed -n '/<[a-z]/,/[a-z0-9]>/p' test.html 无法过滤

不过可以使用添加到模式空间的方法
sed ':begin; { /<[a-z]>/! { $! {N; b begin }; }; s/<[a-z].*[a-z0-9]>//; };' test.html

兼顾不成对的标签

上方awk的方法兼顾了不成对的标签