2012-03-27 6 views
5

Ich spiele um mit einem Spielzeug-HTML-Parser, um mir die Parsing combinators Bibliothek mit Scala vertraut machen:Scala: Parsen passende Token

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) 
    }                       
}                        

Was ich realisieren möchte ich irgendwie meine, um sicherzustellen, ist öffnende und schließende Tags passen zusammen.

Ich denke, das zu tun, ich brauche eine Art von flatMap combinator ~ Parser[A] => (A => Parser[B]) => Parser[B], so kann ich das Starttag verwenden Sie den Parser für das schliessende Tag zu konstruieren. Aber ich sehe nichts, das dieser Unterschrift in the library entspricht.

Was ist der richtige Weg, dies zu tun?

Antwort

3

Sie suchen nach dem falschen Ort. Es ist jedoch ein normaler Fehler. Sie möchten eine Methode Parser[A] => (A => Parser[B]) => Parser[B], aber Sie haben die Dokumente von Parsers, nicht Parser angesehen.

Look here.

Es gibt eine flatMap, auch bekannt als into oder >>.

4

Es gibt einen flatMap auf Parser, und auch eine äquivalente Methode namens into und ein Operator >>, die bequem Aliase mehr sein könnte (flatMap noch benötigt wird, wenn für Comprehensions -abfluss) aus. Es ist in der Tat ein guter Weg, um das zu tun, wonach Sie suchen.

Alternativ können Sie überprüfen, ob die Tags mit ^? übereinstimmen.

5

Sie können eine Methode schreiben, die einen Tag-Namen nimmt und einen Parser für eine End-Tag mit diesem Namen:

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)) 
    } 
} 

Beachten Sie, dass Sie auch node faul zu machen brauchen. Jetzt haben Sie eine schöne saubere Fehlermeldung für nicht angepasste Tags erhalten:

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

<a></b> 
    ^

Ich war ein wenig ausführlicher als notwendig aus Gründen der Klarheit. Wenn Sie Prägnanz möchten, können Sie die openTag und closeTag Methoden überspringen und element so schreiben, zum Beispiel:

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

Ich bin sicher, prägnante Versionen möglich sein würden, aber meiner Meinung nach selbst diese Richtung Unlesbarkeit Borte.