• 1. Java正则表达式入门 + HTMLParser使用详解
  • 2. 一、 Java正则表达式入门 众所周知,在程序开发中,难免会遇到需要匹配、查找、替换、判断字符串的情况发生,而这些情况有时又比较复杂,如果用纯编码方式解决,往往会浪费程序员的时间及精力。 因此,学习及使用正则表达式,便成了解决这一矛盾的主要手段。 大家都知道,正则表达式是一种可以用于模式匹配和替换的规范,一个正则表达式就是由普通的字符(例如字符a到z)以及特殊字符(元字符)组成的文字模式,它 用以描述在查找文字主体时待匹配的一个或多个字符串。正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配。 自从jdk1.4推出java.util.regex包,就为我们提供了很好的JAVA正则表达式应用平台。
  • 3. 说明转义符\a\f响铃符 = \x07换页符 = \x0C,换页符\n响铃符 = \x07,换行 (‘\u000A’)\r回车符 = \x0D,回车 (‘\u000D’)\t 制表符 = \x09,间隔 (‘\u0009’)\v垂直制表符 = \x0B\e ESC 符 = \x1B, Escape\x20使用两位十六进制表示形式,可与该编号的字符匹配\u002B使用四位十六进制表示形式,可与该编号的字符匹配 \x{20A060} 使用任意位十六进制表示形式,可与该编号的字符匹配
  • 4. 说明字符 ^$匹配输入字符串的开始位置。要匹配 "^" 字符本身,请使用 "\^"匹配输入字符串的结尾位置。要匹配 “$” 字符本身,请使用 “\$”( )标记一个子表达式的开始和结束位置。要匹配小括号,请使用 "\(" 和 "\)"[ ]用来自定义能够匹配 '多种字符' 的表达式。要匹配中括号,请使用 "\[" 和 "\]"{ }修饰匹配次数的符号。要匹配大括号,请使用 "\{" 和 "\}".匹配除了换行符(\n)以外的任意一个字符。要匹配小数点本身,请使用 "\."?修饰匹配次数为 0 次或 1 次。要匹配 "?" 字符本身,请使用 "\?"+修饰匹配次数为至少 1 次。要匹配 "+" 字符本身,请使用 "\+"*修饰匹配次数为 0 次或任意次。要匹配 "*" 字符本身,请使用"\*"|左右两边表达式之间 "或" 关系。匹配 "|" 本身,请使用 "\|"
  • 5. 说明字符集合 .\w小数点可以匹配除了换行符(\n)以外的任意一个字符可以匹配任何一个字母或者数字或者下划线,单独字符 [a-zA-Z_0-9]\W W大写,可以匹配任何一个字母或者数字或者下划线以外的字符,非单独字符 [^a-zA-Z_0-9]\s 可以匹配空格、制表符、换页符等空白字符的其中任意一个,空白符号 [\t\n\x0B\f\r]\SS大写,可以匹配任何一个空白字符以外的字符,非空白符号[^\t\n\x0B\f\r]\d可以匹配任何一个 0~9 数字字符,数字 等价于[0-9]\DD大写,可以匹配任何一个非数字字符,非数字 等价于[^0-9]字符集合 可以匹配 “多个字符” 其中任意一个字符的正则表达式。虽然是 “多个字符”,但每次只能匹配其中一个。DEELX 正则表达式中标准的字符集合有:
  • 6. 说明限定符 {n}{m, n}表达式固定重复n次,比如:"\w{2}" 相当于 "\w\w"表达式尽可能重复n次,至少重复m次:"ba{1,3}"可以匹配 "ba"或"baa"或"baaa"{m, }表达式尽可能的多匹配,至少重复m次:“\w\d{2,}”可以匹配"a12","x456"...?表达式尽可能匹配1次,也可以不匹配,相当于 {0, 1}+表达式尽可能的多匹配,至少匹配1次,相当于 {1, }*表达式尽可能的多匹配,最少可以不匹配,相当于 {0, }匹配次数限定符 使被修饰的表达式可多次重复匹配的修饰符。可使被修饰的表达式重复固定次数,也可以限定一定的重复匹配的次数范围。在限定符之后的表达式能够匹配成功的情况下,不定次数的限定符总是尽可能的多匹配。如果之后的表达式匹配失败,限定符可适当“让出”能够匹配的字符,以使整个表达式匹配成功。这种模式就叫“贪婪模式”。
  • 7. 说明限定符 {m, n}?{m, }?表达式尽量只匹配m次,最多重复n次。表达式尽量只匹配m次,最多可以匹配任意次。??表达式尽量不匹配,最多匹配1次,相当于 {0, 1}?+?表达式尽量只匹配1次,最多可匹配任意次,相当于 {1, }?*?表达式尽量不匹配,最多可匹配任意次,相当于 {0, }?“勉强模式”限定符: 在限定符之后添加问号(?),则使限定符成为“勉强模式”。勉强模式的限定符,总是尽可能少的匹配。如果之后的表达式匹配失败,勉强模式也可以尽可能少的再匹配一些,以使整个表达式匹配成功。
  • 8. 说明限定符 {m, n}+{m, }+表达式尽可能重复n次,至少重复m次。表达式尽可能的多匹配,至少重复m次。?+表达式尽可能匹配1次,也可以不匹配,相当于 {0, 1}+++表达式尽可能的多匹配,至少匹配1次,相当于 {1, }+*+表达式尽可能的多匹配,最少可以不匹配,相当于 {0, }+“占有模式”限定符: 在限定符之后添加加号(+),则使限定符成为“占有模式”。占有模式的限定符,总是尽可能多的匹配。与“贪婪模式”不同的是,即使之后的表达式匹配失败,“占有模式”也不会“让出”自己能够匹配的字符。 转义字符 \Q...\E 使用 \Q 开始,\E 结束,可使中间的标点符号失去特殊意义,将中间的字符作为普通字符。 例如: \Q(a+b)*3\E 可匹配文本 “(a+b)*3”。
  • 9. 因为正则表达式是一个很庞杂的体系,所以我仅例举些入门的概念,更多的请参阅相关书籍及自行摸索。 \b 一个单词的边界 \B 一个非单词的边界 \G 前一个匹配的结束 ^为限制开头 ^java     条件限制为以Java为开头字符 $为限制结尾 java$     条件限制为以java为结尾字符 . 条件限制除\n以外任意一个单独字符 java..     条件限制为java后除换行外任意两个字符 加入特定限制条件「[]」 [a-z]     条件限制在小写a to z范围中一个字符 [A-Z]     条件限制在大写A to Z范围中一个字符 [a-zA-Z] 条件限制在小写a to z或大写A to Z范围中一个字符
  • 10. [0-9]     条件限制在小写0 to 9范围中一个字符 [0-9a-z] 条件限制在小写0 to 9或a to z范围中一个字符 [0-9[a-z]] 条件限制在小写0 to 9或a to z范围中一个字符(交集) []中加入^后加再次限制条件「[^]」 [^a-z]     条件限制在非小写a to z范围中一个字符 [^A-Z]     条件限制在非大写A to Z范围中一个字符 [^a-zA-Z] 条件限制在非小写a to z或大写A to Z范围中一个字符 [^0-9]     条件限制在非小写0 to 9范围中一个字符 [^0-9a-z] 条件限制在非小写0 to 9或a to z范围中一个字符 [^0-9[a-z]] 条件限制在非小写0 to 9或a to z范围中一个字符(交集)在 限制条件为特定字符出现0次以上时,可以使用「*」 J*     0个以上J .*     0个以上任意字符
  • 11. J.*D     J与D之间0个以上任意字符 在限制条件为特定字符出现1次以上时,可以使用「+」 J+     1个以上J .+     1个以上任意字符 J.+D     J与D之间1个以上任意字符 在限制条件为特定字符出现有0或1次以上时,可以使用「?」 JA?     J或者JA出现 限制为连续出现指定次数字符「{a}」 J{2}     JJ J{3}     JJJ 文字a个以上,并且「{a,}」 J{3,}     JJJ,JJJJ,JJJJJ,???(3次以上J并存)
  • 12. 文字个以上,b个以下「{a,b}」 J{3,5}     JJJ或JJJJ或JJJJJ 两者取一「|」 J|A     J或A Java|Hello     Java或Hello 「()」中规定一个组合类型 比如,我查询index间的数据,可写作]+href=[\"']?([^>]*)[\"']?[^>]*>(.+?) 在使用Pattern.compile函数时,可以加入控制正则表达式的匹配行为的参数: Pattern Pattern.compile(String regex, int flag)
  • 13. flag的取值范围如下: Pattern.CANON_EQ     当且仅当两个字符的"正规分解(canonical decomposition)"都完全相同的情况下,才认定匹配。比如用了这个标志之后,表达式"a\u030A"会匹配"?"。默认情况下,不考虑"规 范相等性(canonical equivalence)"。 Pattern.CASE_INSENSITIVE(?i)     默认情况下,大小写不明感的匹配只适用于US-ASCII字符集。这个标志能让表达式忽略大小写进行匹配。要想对Unicode字符进行大小不明感的匹 配,只要将UNICODE_CASE与这个标志合起来就行了。 Pattern.COMMENTS(?x)     在这种模式下,匹配时会忽略(正则表达式里的)空格字符
  • 14. (译者注:不是指表达式里的"\\s",而是指表达式里的空格,tab,回车之类)。注释从#开始,一直到这行结束。可以通过嵌入式的标志来启用Unix行模式。 Pattern.DOTALL(?s)     在这种模式下,表达式'.'可以匹配任意字符,包括表示一行的结束符。默认情况下,表达式'.'不匹配行的结束符。 Pattern.MULTILINE (?m)     在这种模式下,'^'和'$'分别匹配一行的开始和结束。此外,'^'仍然匹配字符串的开始,'$'也匹配字符串的结束。默认情况下,这两个表达式仅仅匹配字符串的开始和结束。 Pattern.UNICODE_CASE (?u)     在这个模式下,如果你还启用了CASE_INSENSITIVE*
  • 15. 标志,那么它会对Unicode字符进行大小写不明感的匹配。默认情况下,大小写不敏感的匹配只适用于US-ASCII字符集。 Pattern.UNIX_LINES(?d)     在这个模式下,只有'\n'才被认作一行的中止,并且与'.','^',以及'$'进行匹配。 抛开空泛的概念,下面写出几个简单的Java正则用例: ◆比如,在字符串包含验证时 //查找以Java开头,任意结尾的字符串 Pattern pattern = Pattern.compile("^Java.*"); Matcher matcher = pattern.matcher("Java不是人"); boolean b= matcher.matches(); //当条件满足时,将返回true,否则返回false System.out.println(b);
  • 16. ◆以多条件分割字符串时 Pattern pattern = Pattern.compile("[, |]+"); String[] strs = pattern.split("Java Hello World Java,Hello,,World|Sun"); for (int i=0;i
  • 17. ◆文字替换(全部) Pattern pattern = Pattern.compile("正则表达式"); Matcher matcher = pattern.matcher("正则表达式 Hello World,正则表达式 Hello World"); //替换第一个符合正则的数据 System.out.println(matcher.replaceAll("Java")); ◆文字替换(置换字符) Pattern pattern = Pattern.compile(“正则表达式”); Matcher matcher = pattern.matcher(“正则表达式 Hello World,正则表达式 Hello World ”); StringBuffer sbr = new StringBuffer(); while (matcher.find()) {     matcher.appendReplacement(sbr, “Java”); }
  • 18. matcher.appendTail(sbr); System.out.println(sbr.toString()); ◆验证是否为邮箱地址 String email=“[\\w\\.\\-]+@([\\w\\-]+\\.)+[\\w\\-]+”; String str=”ceponline@yahoo.com.cn”; Pattern pattern = Pattern.compile(email,Pattern.CASE_INSENSITIVE); Matcher matcher = pattern.matcher(str); System.out.println(matcher.matches()); 比较严格的: email="^([0-9a-zA-Z]([-.\\w]*[0-9a-zA-Z])*@(([0-9a-zA-Z])+([-\\w]*[0-9a-zA-Z])*\\.)+[a-zA-Z]{2,9})$" ◆去除html标记 Pattern pattern = Pattern.compile("<.+?>", Pattern.DOTALL);
  • 19. Matcher matcher = pattern.matcher("主页"); String string = matcher.replaceAll(""); System.out.println(string); ◆查找html中对应条件字符串 Pattern pattern = Pattern.compile("href=\"(.+?)\""); Matcher matcher = pattern.matcher("主页"); if(matcher.find()) System.out.println(matcher.group(1)); ◆截取http://地址 //截取url Pattern pattern = Pattern.compile("(http://|https://){1}[\\w\\.\\-/:]+");
  • 20. Matcher matcher = pattern.matcher("dsdsdsfdf"); StringBuffer buffer = new StringBuffer(); while(matcher.find()){                  buffer.append(matcher.group());            buffer.append("\r\n");              System.out.println(buffer.toString()); } ◆替换指定{}中文字 String str = "Java目前的发展史是由{0}年-{1}年"; String[][] object={new String[]{"\\{0\\}","1995"},new String[]{"\\{1\\}","2007"}};
  • 21. System.out.println(replace(str,object)); public static String replace(final String sourceString,Object[] object) {             String temp=sourceString;                for(int i=0;i
  • 22. 读取页面链接和内容: public String newsListRegExp = "]+href=[\"']?( [^>]*)[\"']?[^>]*>(.+?)" ; newsListRegExp ="<\\s*a[^>]+href\\s*=\\'*\\s*\\\"*([^\\s|^\\\"|^>|^\\']+)[^>]*>([^<\n]*)<\\s*/\\s*a\\s*>"; public void getLinks(String content){ java.util.regex.Pattern pattern=java.util.regex.Pattern.compile(newsListRegExp, java.util.regex.Pattern.CASE_INSENSITIVE); java.util.regex.Matcher matcher=pattern.matcher(content); while(matcher.find()){ String link=matcher.group(1); String text=matcher.group(2); System.out.println(link+text); } }
  • 23. Java正则的功用还有很多,事实上只要是字符处理,就没有正则做不到的事情存在。 (当然,正则解释时较耗时间就是了!!!……)
  • 24. 二、 HTMLParser使用详解HTMLParser使用详解(1)- 初始化Parser 在研究搜索引擎的开发中,对于HTML网页的处理是核心的一个环节。网上有很多开源的代码,对于Java来说,HTMLParser是比较著名并且得到广泛应用的一个。 HTMLParser的主页是http://htmlparser.sourceforge.net/ ,最后的更新是2006年9月的1.6版。不过没关系,HTML的内容已经很久没有大的变化了,HTMLParser处理起来基本没有任何问题。
  • 25. HTMLParser的核心模块是org.htmlparser.Parser类,这个类实际完成了对于HTML页面的分析工作。这个类有下面几个构造函 数: public Parser (); public Parser (Lexer lexer, ParserFeedback fb); public Parser (URLConnection connection, ParserFeedback fb) throws ParserException; public Parser (String resource, ParserFeedback feedback) throws ParserException; public Parser (String resource) throws ParserException; public Parser (Lexer lexer); public Parser (URLConnection connection) throws ParserException; 和一个静态类 public static Parser createParser (String html, String charset);
  • 26. 对于大多数使用者来说,使用最多的是通过一个URLConnection或者一个保存有网页内容的字符串来初始化Parser,或者使用静态函数来生成一个Parser对象。 ParserFeedback的代码很简单,是针对调试和跟踪分析过程的,一般不需要改变。而使用Lexer则是一个相对比较高级的话题,放到以后再讨论吧。 这里比较有趣的一点是,如果需要设置页面的编码方式的话,不使用Lexer就只有静态函数一个方法了。 对于大多数中文页面来说,好像这是应该用得比较多的一个方法。
  • 27. HTMLParser使用详解(2)- Node内容 HTMLParser将解析过的信息保存为一个树的结构。Node是信息保存的数据类型基础。 请看Node的定义: public interface Node extends Cloneable; Node中包含的方法有几类: 对于树型结构进行遍历的函数,这些函数最容易理解: Node getParent ():取得父节点 NodeList getChildren ():取得子节点的列表
  • 28. Node getFirstChild ():取得第一个子节点 Node getLastChild ():取得最后一个子节点 Node getPreviousSibling ():取得前一个兄弟(不好意思,英文是兄弟姐妹,直译太麻烦而且不符合习惯,对不起女同胞了) Node getNextSibling ():取得下一个兄弟节点 取得Node内容的函数: String getText ():取得文本 String toPlainTextString():取得纯文本信息。 String toHtml () :取得HTML信息(原始HTML) String toHtml (boolean verbatim):取得HTML信息(原始HTML) String toString ():取得字符串信息(原始HTML) Page getPage ():取得这个Node对应的Page对象
  • 29. int getStartPosition ():取得这个Node在HTML页面中的起始位置 int getEndPosition ():取得这个Node在HTML页面中的结束位置 用于Filter过滤的函数: void collectInto (NodeList list, NodeFilter filter):基于filter的条件对于这个节点进行过滤,符合条件的节点放到list中。 用于Visitor遍历的函数: void accept (NodeVisitor visitor):对这个Node应用visitor 用于修改内容的函数,这类用得比较少: void setPage (Page page):设置这个Node对应的Page对象 void setText (String text):设置文本 void setChildren (NodeList children):设置子节点列表
  • 30. 其他函数: void doSemanticAction ():执行这个Node对应的操作(只有少数Tag有对应的操作) Object clone ():接口Clone的抽象函数。 主要注意一下,getText,toPlainTextString ,toHtml 区别 String a="
    猫扑hi

    来点

    不一样
    " ; Parser p=Parser.createParser(a, "UTF-8"); try { for(NodeIterator it= p.elements();it.hasMoreNodes();){ Node node=it.nextNode() ; System.out.println(node.getText()+"$"+node.toPlainTextString()+"$"+node.toHtml()); } } catch (ParserException e) { // TODO Auto-generated catch block e.printStackTrace(); } 输出: div id='hi'$猫扑hi$
    猫扑hi
    a class='link' href='www.baidu.com' $来点不一样$

    来点

    不一样
  • 31. 下面是用于测试的HTML文件: 白泽居-www.baizeju.com
       
                    白泽居-www.baizeju.com 白泽居-www.baizeju.com
        白泽居-www.baizeju.com
       
  • 32. toPlainTextString是把用户可以看到的内容都包含了。有趣的有两点,一是标签中的Title内容是在plainText中的,可能在标题中可见的也算可见吧。 另外就是象前面说的,HTML内容中的换行符什么的,也都成了plainText,这个逻辑上好像有点问题。另外可能大家发现toHtml,toHtml(true)和toHtml(false)的结果没什么区别。实际也是这样的,如果
  • 33. 跟踪HTMLParser的代码就可以发现,Node的子类是AbstractNode,其中实现了toHtml()的代码,直接调用toHtml(false),而AbstractNode的三个子类RemarkNode,TagNode和TextNode中,toHtml(boolean verbatim)的实现中,都没有处理verbatim参数,所以三个函数的结果是一模一样的。 如果你不需要实现你自己的什么特殊处理,简单使用toHtml就可以了。
  • 34. HTML的Node类继承关系如下图这个是从别的文章Copy的):
  • 35. AbstractNodes是Node的直接子类,也是一个抽象类。它的三个直接子类实现是RemarkNode,用于保存注释。在输出结果的toString部分中可以看到有一个"Rem (345[6,2],356[6,13]): 这是注释",就是一个RemarkNode。TextNode也很简单,就是用户可见的文字信息。 TagNode是最复杂的,包含了HTML语言中的所有标签,而且可以扩展(扩展 HTMLParser 对自定义标签的处理能力 )。TagNode包含两类,一类是简单的Tag,实际就是不能包含其他Tag的标签,只能做叶子节点。另一类是CompositeTag,就是可以包含其他Tag,是分支节点。
  • 36. 遍历了网页的内容以后,以树(森林)结构保存了结果。HTMLParser访问结果内容的方法有两种。使用Filter和使用Visitor。 (一)Filter类 顾名思义,Filter就是对于结果进行过滤,取得需要的内容。HTMLParser在org.htmlparser.filters包之内一共定义了16个不同的Filter,也可以分为几类。 HTMLParser使用详解(3)- 通过Filter访问内容
  • 37. 判断类Filter: TagNameFilter HasAttributeFilter HasChildFilter HasParentFilter HasSiblingFilter IsEqualFilter 逻辑运算Filter: AndFilter NotFilter OrFilter XorFilter
  • 38. 其他Filter: NodeClassFilter StringFilter LinkStringFilter LinkRegexFilter RegexFilter CssSelectorNodeFilter 所有的Filter类都实现了org.htmlparser.NodeFilter接口。这个接口只有一个主要函数: boolean accept (Node node); 各个子类分别实现这个函数,用于判断输入的Node是否符合这个Filter的过滤条件,如果符合,返回true,否则返回false。
  • 39. (二)判断类Filter HTMLParser使用入门(2)- Node内容 ,自己添加import部分) public static void main(String[] args) {                 try{             Parser parser = new Parser( (HttpURLConnection) (new URL("http://127.0.0.1:8080/HTMLParserTester.html")).openConnection() );   // 这里是控制测试的部分,后面的例子修改的就是这个地方。             NodeFilter filter = new TagNameFilter ("DIV");             NodeList nodes = parser.extractAllNodesThatMatch(filter);            
  • 40.   if(nodes!=null) {                 for (int i = 0; i < nodes.size(); i++) {                     Node textnode = (Node) nodes.elementAt(i);     message("getText:"+textnode.getText());                     message("==============");                 }             }                    } catch( Exception e ) {                 e.printStackTrace();         }     } 输出结果: getText:div id="top_main"                    
  • 41. ================================ getText:div id="logoindex" ================================ 可以看出文件中两个Div节点都被取出了。下面可以针对这两个DIV节点进行操作 2.2 HasChildFilter 下面让我们看看HasChildFilter。刚刚看到这个Filter的时候,我想当然地认为这个Filter返回的是有Child的Tag。直接初始化了一个 NodeFilter filter = new HasChildFilter();
  • 42. 结果调用NodeList nodes = parser.extractAllNodesThatMatch(filter);的时候HasChildFilter内部直接发生NullPointerException。读了一下HasChildFilter的代码,才发现,实际HasChildFilter是返回有符合条件的子节点的节点,需要另外一个Filter作为过滤子节点的参数。 缺省的构造函数虽然可以初始化,但是由于子节点的Filter是null,所以使用的时候发生了Exception。 从这点来看,HTMLParser的代码还有很多可以优化的的地方。呵呵。
  • 43. 修改代码: NodeFilter innerFilter = new TagNameFilter ("DIV"); NodeFilter filter = new HasChildFilter(innerFilter); NodeList nodes = parser.extractAllNodesThatMatch(filter); 输出结果: getText:body ================================ getText:div id="top_main" ================================ 可以看到,输出的是两个有DIV子Tag的Tag节点。(body有子节点DIV "top_main","top_main"有子节点"logoindex"。
  • 44. 注意HasChildFilter还有一个构造函数: public HasChildFilter (NodeFilter filter, boolean recursive) 如果recursive是false,则只对第一级子节点进行过滤。比如前面的例子,body和top_main都是在第一级的子节点里就有DIV节点,所以匹配上了。如果我们用下面的方法调用: NodeFilter filter = new HasChildFilter( innerFilter, true ); 输出结果: getText:html xmlns="http://www.w3.org/1999/xhtml" ================================ getText:body ================================ getText:div id="top_main" ================================
  • 45. 可以看到输出结果中多了一个html xmlns="http://www.w3.org/1999/xhtml",这个是整个HTML页面的节点(根节点),虽然这个节点下直接没有DIV节点,但是它的子节点body下面有DIV节点,所以它也被匹配上了。 2.3 HasAttributeFilter HasAttributeFilter有3个构造函数: public HasAttributeFilter (); public HasAttributeFilter (String attribute); public HasAttributeFilter (String attribute, String value); 这个Filter可以匹配出包含制定名字的属性,或者制定属性为指定值的节点。还是用例子说明比较容易。
  • 46. 调用方法1: NodeFilter filter = new HasAttributeFilter(); NodeList nodes = parser.extractAllNodesThatMatch(filter); 输出结果: 什么也没有输出。 调用方法2: NodeFilter filter = new HasAttributeFilter( "id" ); NodeList nodes = parser.extractAllNodesThatMatch(filter);
  • 47. 输出结果: getText:div id="top_main" ================================ getText:div id="logoindex" ================================ 调用方法3: NodeFilter filter = new HasAttributeFilter( "id", "logoindex" ); NodeList nodes = parser.extractAllNodesThatMatch(filter); 输出结果: getText:div id="logoindex" ================================ 很简单吧。呵呵
  • 48. 2.4 其他判断列Filter HasParentFilter和HasSiblingFilter的功能与HasChildFilter类似,大家自己试一下就应该了解了。 IsEqualFilter的构造函数参数是一个Node: public IsEqualFilter (Node node) {     mNode = node; } accept函数也很简单: public boolean accept (Node node)    {     return (mNode == node); } 不需要过多说明了。
  • 49. (三)逻辑运算Filter (四)其他Filter: HTMLParser使用入门(2)- Node内容 中我们已经了解了Node的不同类型,这个Filter就可以针对类型进行过滤。 测试代码:            NodeFilter filter = new NodeClassFilter(RemarkNode.class);             NodeList nodes = parser.extractAllNodesThatMatch(filter); 输出结果: getText:这是注释 ================================ 可以看到只有RemarkNode(注释)被输出了。
  • 50. 4.2 StringFilter 这个Filter用于过滤显示字符串中包含制定内容的Tag。注意是可显示的字符串,不可显示的字符串中的内容(例如注释,链接等等)不会被显示。 修改一下例子代码: 白泽居-title-www.baizeju.com
  • 51.
       
                    白泽居-字符串1-www.baizeju.com 白泽居-链接文本-www.baizeju.com    
        白泽居-字符串2-www.baizeju.com
  • 52. 测试代码:            NodeFilter filter = new StringFilter("www.baizeju.com");             NodeList nodes = parser.extractAllNodesThatMatch(filter); 输出结果: getText:白泽居-title-www.baizeju.com ================================ getText:                 白泽居-字符串1-www.baizeju.com ================================ getText:白泽居-链接文本-www.baizeju.com ================================
  • 53. getText:         白泽居-字符串2-www.baizeju.com ================================ 可以看到包含title,两个内容字符串和链接的文本字符串的Tag都被输出了,但是注释和链接Tag本身没有输出。 4.3 LinkStringFilter 这个Filter用于判断链接中是否包含某个特定的字符串,可以用来过滤出指向某个特定网站的链接。 测试代码:            NodeFilter filter = new LinkStringFilter("www.baizeju.com");             NodeList nodes = parser.extractAllNodesThatMatch(filter);
  • 54. 输出结果: getText:a href="http://www.baizeju.com" ================================ 4.4 其他几个Filter 其他几个Filter也是根据字符串对不同的域进行判断,与前面这些的区别主要就是支持正则表达式。这个不在本文的讨论范围以内,大家可以自己实验一下。 前面介绍的都是简单的Filter,只能针对某种单一类型的条件进行过滤。HTMLParser支持对于简单类型的Filter进行组合,从而实现复杂的条件。原理和一般编程语言的逻辑运算是一样的。
  • 55. 3.1 AndFilter AndFilter可以把两种Filter进行组合,只有同时满足条件的Node才会被过滤。 测试代码: NodeFilter filterID = new HasAttributeFilter( “id” ); NodeFilter filterChild = new HasChildFilter(filterA); NodeFilter filter = new AndFilter(filterID, filterChild); 输出结果: getText:div id=“logoindex” ================================
  • 56. 3.2 OrFilter 把前面的AndFilter换成OrFilter 测试代码: NodeFilter filterID = new HasAttributeFilter( "id" ); NodeFilter filterChild = new HasChildFilter(filterA); NodeFilter filter = new OrFilter(filterID, filterChild); 输出结果: getText:div id="top_main" ================================ getText:div id="logoindex" ================================
  • 57. 3.3 NotFilter 把前面的AndFilter换成NotFilter 测试代码: NodeFilter filterID = new HasAttributeFilter( "id" ); NodeFilter filterChild = new HasChildFilter(filterA); NodeFilter filter = new NotFilter(new OrFilter(filterID, filterChild)); 输出结果: getText:!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" ================================ getText:
  • 58. ================================ getText:head ================================ getText:meta http-equiv="Content-Type" content="text/html; charset=gb2312" ================================ getText:title ================================ getText:白泽居-www.baizeju.com ================================ getText:/title ================================ getText:/head
  • 59. ================================ getText: ================================ getText:html xmlns="http://www.w3.org/1999/xhtml" ================================ getText: ================================ getText:body ================================ getText: ================================ getText:        
  • 60. ================================ getText:                 ================================ getText:这是注释 ================================ getText:                 白泽居-www.baizeju.com ================================ getText:a href="http://www.baizeju.com" ================================ getText:白泽居-www.baizeju.com ================================ getText:/a
  • 61. ================================ getText:         ================================ getText:/div ================================ getText:         白泽居-www.baizeju.com ================================ getText:/div ================================ getText:
  • 62. ================================ getText:/body ================================ getText: ================================ getText:/html ================================ getText: ================================ 除了前面3.2中输出的几个Tag,其余的Tag都在这里了。
  • 63. 3.4 XorFilter 把前面的AndFilter换成NotFilter 测试代码: NodeFilter filterID = new HasAttributeFilter( "id" ); NodeFilter filterChild = new HasChildFilter(filterA); NodeFilter filter = new XorFilter(filterID, filterChild); 输出结果: getText:div id="top_main" ================================ 4.1 NodeClassFilter 这个Filter用于判断节点类型是否是某个特定的Node类型。在
  • 64. 2.1 TagNameFilter TabNameFilter是最容易理解的一个Filter,根据Tag的名字进行过滤。 下面是用于测试的HTML文件: 白泽居-www.baizeju.com< /head>
  • 65.
       
                    白泽居-www.baizeju.com 白泽居-www.baizeju.com    
        白泽居-www.baizeju.com
  • 66. HTMLParser遍历了网页的内容以后,以树(森林)结构保存了结果。HTMLParser访问结果内容的方法有两种。 使用Filter和使用Visitor。 下面介绍使用Visitor访问内容的方法。 4.1 NodeVisitor 从简单方面的理解,Filter是根据某种条件过滤取出需要的Node再进行处理。Visitor则是遍历HTMLParser使用详解(4)- 通过Visitor访问
  • 67. 内容树的每一个节点,对于符合条件的节点进行处理。实际的结果异曲同工,两种不同的方法可以达到相同的结果。 下面是一个最常见的NodeVisitro的例子。 测试代码:     public static void main(String[] args) {         try{             Parser parser = new Parser( (HttpURLConnection) (new URL("http://127.0.0.1:8080/HTMLParserTester.html")).openConnection() );
  • 68.   NodeVisitor visitor = new NodeVisitor( false, false ) {                 public void visitTag(Tag tag) {                    message("This is Tag:"+tag.getText());                 }                 public void visitStringNode (Text string)    {                      message("This is Text:"+string);                 }   public void visitRemarkNode (Remark remark) {                      message("This is Remark:"+remark.getText());                 }                 public void beginParsing () {                     message("beginParsing");                 }
  • 69. public void visitEndTag (Tag tag){                     message("visitEndTag:"+tag.getText());                 }                 public void finishedParsing () {                     message("finishedParsing");                 }             };   parser.visitAllNodesWith(visitor);         }         catch( Exception e ) {                 e.printStackTrace();         }     }
  • 70. 输出结果: beginParsing This is Tag:!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" This is Text:Txt (121[0,121],123[1,0]): \n This is Text:Txt (244[1,121],246[2,0]): \n finishedParsing 可以看到,开始遍历所以的节点以前,beginParsing先被调用,然后处理的是中间的Node,最后在结束遍历以前,finishParsing被调用。因为我设置的 recurseChildren和recurseSelf都是false,所以Visitor没有访问子节点也没有访问根节点的内容。
  • 71. 中间输出的两个\n就是我们在HTMLParser使用详解(1)- 初始化Parser 中讨论过的最高层的那两个换行。 我们先把recurseSelf设置成true,看看会发生什么。 NodeVisitor visitor = new NodeVisitor( false, true ) { 输出结果: beginParsing This is Tag:!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd This is Text:Txt (121[0,121],123[1,0]): \n This is Tag:head This is Text:Txt (244[1,121],246[2,0]): \n This is Tag:html xmlns="http://www.w3.org/1999/xhtml" finishedParsing 可以看到,HTML页面的第一层节点都被调用了。
  • 72. 我们再用下面的方法调用看看: NodeVisitor visitor = new NodeVisitor( true, false ) { 输出结果: beginParsing This is Tag:!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" This is Text:Txt (121[0,121],123[1,0]): \n This is Tag:meta http-equiv="Content-Type" content="text/html; charset=gb2312" This is Text:Txt (204[1,81],229[1,106]): 白泽居-title-www.baizeju.com visitEndTag:/title visitEndTag:/head
  • 73. This is Text:Txt (244[1,121],246[2,0]): \n This is Text:Txt (289[2,43],291[3,0]): \n This is Text:Txt (298[3,7],300[4,0]): \n This is Text:Txt (319[4,19],322[5,1]): \n\t This is Text:Txt (342[5,21],346[6,2]): \n\t\t This is Remark:这是注释 白泽居-www.baizeju.com This is Text:Txt (378[6,34],408[8,0]): \n\t\t白泽居-字符串1-www.baizeju.com\n This is Text:Txt (441[8,33],465[8,57]): 白泽居-链接文本-www.baizeju.com visitEndTag:/a This is Text:Txt (469[8,61],472[9,1]): \n\t visitEndTag:/div
  • 74. This is Text:Txt (478[9,7],507[11,0]): \n\t白泽居-字符串2-www.baizeju.com\n visitEndTag:/div This is Text:Txt (513[11,6],515[12,0]): \n visitEndTag:/body This is Text:Txt (522[12,7],524[13,0]): \n visitEndTag:/html finishedParsing 可以看到,所有的子节点都出现了,除了刚刚例子里面的两个最上层节点 This is Tag:head和This is Tag:html xmlns="http://www.w3.org/1999/xhtml"。
  • 75. 想让它们都出来,只需要 NodeVisitor visitor = new NodeVisitor( true, true ) { 输出结果: beginParsing This is Tag:!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" This is Text:Txt (121[0,121],123[1,0]): \n This is Tag:head This is Tag:meta http-equiv="Content-Type" content="text/html; charset=gb2312" This is Tag:title This is Text:Txt (204[1,81],229[1,106]): 白泽居-title-www.baizeju.com
  • 76. visitEndTag:/title visitEndTag:/head This is Text:Txt (244[1,121],246[2,0]): \n This is Tag:html xmlns="http://www.w3.org/1999/xhtml" This is Text:Txt (289[2,43],291[3,0]): \n This is Tag:body This is Text:Txt (298[3,7],300[4,0]): \n This is Tag:div id="top_main“ This is Text:Txt (319[4,19],322[5,1]): \n\t This is Tag:div id="logoindex" This is Text:Txt (342[5,21],346[6,2]): \n\t\t This is Remark:这是注释 白泽居-www.baizeju.com
  • 77. This is Text:Txt (378[6,34],408[8,0]): \n\t\t白泽居-字符串1-www.baizeju.com\n This is Tag:a href=http://www.baizeju.com This is Text:Txt (441[8,33],465[8,57]): 白泽居-链接文本-www.baizeju.com visitEndTag:/a This is Text:Txt (469[8,61],472[9,1]): \n\t visitEndTag:/div This is Text:Txt (478[9,7],507[11,0]): \n\t白泽居-字符串2-www.baizeju.com\n visitEndTag:/div This is Text:Txt (513[11,6],515[12,0]): \n visitEndTag:/body
  • 78. This is Text:Txt (522[12,7],524[13,0]): \n visitEndTag:/html finishedParsing 哈哈,这下调用清楚了,大家在需要处理的地方增加自己的代码好了。 4.2 其他Visitor HTMLParser还定义了几个其他的Visitor。HtmlPage,NodeVisitor,ObjectFindingVisitor,StringFindingVisitor,TagFindingVisitor,TextExtractingVisitor,UrlModifyingVisitor,它们都是NodeVisitor的子类,实现了一些特定的功能。
  • 79. 笔者个人的感觉是没什么用处,如果你需要什么特定的功能,还不如自己写一个,想在这些里面找到适合你需要的,化的时间可能更多。 反正大家看看代码就发现,它们每个都没几行真正有效的代码。
  • 80. Thank You !