2012-03-27 16 views
5

estoy jugando un poco con un analizador de HTML juguete, para ayudar a familiarizarme con combinadores de análisis biblioteca de Scala:Scala: el análisis de juego simbólico

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

Lo que yo estoy dando cuenta de lo que quiero es una manera de asegurarse de que mi las etiquetas de apertura y cierre coinciden.

creo que hacer eso, necesito algún tipo de flatMap combinador ~ Parser[A] => (A => Parser[B]) => Parser[B], por lo que puede utilizar la etiqueta de apertura para construir el programa de análisis de la etiqueta de cierre. Pero no veo nada que coincida con esa firma in the library.

¿Cuál es la forma correcta de hacerlo?

Respuesta

3

Estás viendo el lugar equivocado. Aunque es un error normal. Desea un método Parser[A] => (A => Parser[B]) => Parser[B], pero miró los documentos de Parsers, no Parser.

Mirar here.

Hay un flatMap, también conocido como into o >>.

4

Hay una flatMap en Analizador, y también un método equivalente nombrado into y un operador >>, que podría ser alias más convenientes (flatMap todavía se necesita cuando se utiliza en para por comprensión). De hecho, es una forma válida de hacer lo que estás buscando.

Como alternativa, puede verificar que las etiquetas coincidan con ^?.

5

Se puede escribir un método que toma un nombre de etiqueta y devuelve un analizador para una etiqueta de cierre con ese nombre:

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

Tenga en cuenta que también hay que hacer node perezoso. Ahora aparece un mensaje de error limpio agradable para las etiquetas no coincidentes:

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

<a></b> 
    ^

he sido un poco más detallado de lo necesario en aras de la claridad. Si quieres concisión puede saltarse los métodos openTag y closeTag y escribir element así, por ejemplo:

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

Estoy seguro versiones más concisos posible, pero en mi opinión, incluso Esto se inclina hacia la ilegibilidad.

Cuestiones relacionadas