2012-03-27 151 views
5

我玩弄玩具HTML解析器,以帮助自己熟悉与Scala的解析组合子库:斯卡拉:解析匹配令牌

import scala.util.parsing.combinator._ 
sealed abstract class Node                  
case class TextNode(val contents : String) extends Node          
case class Element(                   
    val tag : String,                   
    val attributes : Map[String,Option[String]],             
    val children : Seq[Node]                  
) extends Node                    

object HTML extends RegexParsers {                
    val node: Parser[Node] = text | element              
    val text: Parser[TextNode] = """[^<]+""".r ^^ TextNode          
    val label: Parser[String] = """(\w[:\w]*)""".r            
    val value : Parser[String] = """("[^"]*"|\w+)""".r         
    val attribute : Parser[(String,Option[String])] = label ~ (         
     "=" ~> value ^^ Some[String] | "" ^^ { case _ => None }       
    ) ^^ { case (k ~ v) => k -> v }               
    val element: Parser[Element] = (               
    ("<" ~> label ~ rep(whiteSpace ~> attribute) <~ ">")          
     ~ rep(node) ~                   
    ("</" ~> label <~ ">")                  
) ^^ {                      
    case (tag ~ attributes ~ children ~ close) => Element(tag, Map(attributes : _*), children) 
    }                       
}                        

什么我意识到我需要一些方法来确保我的打开和关闭标签匹配。

我认为要做到这一点,我需要某种flatMap组合器〜Parser[A] => (A => Parser[B]) => Parser[B], ,所以我可以使用开始标记来构造结束标记的解析器。但我没有看到任何匹配in the library的签名。

这样做的正确方法是什么?

回答

3

你正在看错了地方。虽然这是一个普通的错误。你想要一个方法Parser[A] => (A => Parser[B]) => Parser[B],但你看了Parsers文档,而不是Parser

Look here

还有一个flatMap,也被称为into>>

4

上有分析器一个flatMap,也等效的方法命名into并且操作者>>,这可能是更方便的别名(仍然需要flatMap在内涵当使用时)。这确实是一种有效的方式来做你想要的。

或者,您可以检查标签是否与^?匹配。

5

你可以写,需要一个标签名,并返回一个解析器使用该名称的关闭标签的方法:

object HTML extends RegexParsers {     
    lazy val node: Parser[Node] = text | element 
    val text: Parser[TextNode] = """[^<]+""".r ^^ TextNode 
    val label: Parser[String] = """(\w[:\w]*)""".r 
    val value : Parser[String] = """("[^"]*"|\w+)""".r 
    val attribute : Parser[(String, Option[String])] = label ~ (
     "=" ~> value ^^ Some[String] | "" ^^ { case _ => None } 
    ) ^^ { case (k ~ v) => k -> v } 

    val openTag: Parser[String ~ Seq[(String, Option[String])]] = 
    "<" ~> label ~ rep(whiteSpace ~> attribute) <~ ">" 

    def closeTag(name: String): Parser[String] = "</" ~> name <~ ">" 

    val element: Parser[Element] = openTag.flatMap { 
    case (tag ~ attrs) => 
     rep(node) <~ closeTag(tag) ^^ 
     (children => Element(tag, attrs.toMap, children)) 
    } 
} 

请注意,您也需要做出node懒惰。现在你会得到一个干净的错误信息,提供无与伦比的标签:

scala> HTML.parse(HTML.element, "<a></b>") 
res0: HTML.ParseResult[Element] = 
[1.6] failure: `a' expected but `b' found 

<a></b> 
    ^

我是一个有点冗长除了必要清楚起见。如果你想简洁,你可以跳过openTagcloseTag方法和写element这样的,例如:

val element = "<" ~> label ~ rep(whiteSpace ~> attribute) <~ ">" >> { 
    case (tag ~ attrs) => 
    rep(node) <~ "</" ~> tag <~ ">" ^^ 
     (children => Element(tag, attrs.toMap, children)) 
} 

我敢肯定,更简洁的版本将是可能的,但在我看来,即使这是朝不可读性边。