ilcali.lmxml-core_2.10.0.1.3.source-code.parsers.scala Maven / Gradle / Ivy
package lmxml
import util.parsing.{input, combinator}
import combinator._
import syntactical._
import input.CharSequenceReader
trait LmxmlParsers extends RegexParsers {
val increment: Int
type Attrs = Map[String, String]
type Nodes = Seq[ParsedNode]
type TopLevel = (Nodes => ParsedNode)
// Meaningful whitespace
override val whiteSpace = """\s+?""".r
lazy val allwp = """\s*""".r
// To be replaced by multiLine definition below
lazy val everything = """(?!\s*```)[^\n]+\n+""".r
lazy val allText = """(?!\s*~~~)[^\n]+\n+""".r
lazy val ident = """\b[A-Za-z0-9_-]+""".r
lazy val template = "[" ~> ident <~ "]"
lazy val stringLit = "\".*?\"".r ^^ { s =>
s.substring(1, s.length -1)
}
private def multiStrFormat(ls: List[String]) = ls.reduceLeft { (l, r) =>
l + whiteSpace.findPrefixOf(r).map(_ => " " + r).getOrElse(r)
}
// To be replaced by multiLine definition below
lazy val strWrapper = "```" ~> rep1(everything) <~ allwp ~ "```" ^^ (multiStrFormat)
lazy val multiLine = "~~~" ~> rep1(allText) <~ allwp ~ "~~~" ^^ (multiStrFormat)
lazy val commentNode: Parser[TopLevel] = "//" ^^ { _ => CommentNode(_) }
lazy val node: Parser[TopLevel] = ident ~ inlineParams ^^ {
case name ~ attrs => LmxmlNode(name, attrs, _)
}
// To replace ``` parser with ~~~ multiline parser
lazy val textNode: Parser[TopLevel] =
(stringLit | multiLine | strWrapper) ~ opt(unescapedAttr) ^^ {
case s ~ e => TextNode(s, e.getOrElse(false), _)
}
lazy val templateNode: Parser[TopLevel] = template ^^ {
s => TemplateLink(s, _)
}
lazy val templateDef = template ~ ":" ~ nodesAt(increment) ^^ {
case t ~ ":" ~ nodes => LinkDefinition(t, nodes)
}
lazy val unescapedAttr = "is" ~> "unescaped" ^^ { _ => true }
lazy val idAttr = "#" ~> ident ^^ (("id", _))
lazy val classAttr = "." ~> ident ^^ (("class", _))
lazy val atAttr = "@" ~> ident ~ opt(("=" | ":") ~> stringLit) ^^ {
case key ~ someValue => (key, someValue.getOrElse(key))
}
lazy val inlineAttrs = "{" ~ allwp ~> repsep(attr, "," <~ allwp) <~ allwp ~ "}"
lazy val attr = (stringLit | ident) ~ ":" ~ stringLit ^^ {
case key ~ ":" ~ value => (key, value)
}
lazy val separator = """\n*-*\n*""".r
def someAttr = idAttr | classAttr | atAttr
def inlineParams = rep(someAttr) ~ opt(inlineAttrs) ^^ {
case other ~ attrs =>
val a = flattenAttrList(other) ++ attrs.getOrElse(Nil)
Map[String, String]() ++ a
}
def topLevel = (node | textNode | templateNode | commentNode)
def lmxml = nodesAt(0) ~ separator ~ repsep(templateDef, allwp) ^^ {
case top ~ sep ~ linkDefs =>
linkDefs.foldLeft(top) { rebuild(_, _) }
}
def flattenAttrList(tups: List[(String, String)]) = {
if (tups.isEmpty) Nil else {
val unique = tups.map(_._1).distinct
unique.map { key =>
key -> tups.filter(_._1 == key).map(_._2).mkString(" ")
}
}
}
def spaces(n: Int) = """[ ]{%d}""".format(n).r
def descending(d: Int): Parser[Any] =
opt(spaces(d)) ~> topLevel ~ rep(descending(d + increment) | topLevel)
private def descend(in: List[Any]): Nodes = in match {
case (h ~ ns) :: rest =>
val ls = ns.asInstanceOf[List[_]]
val n = h.asInstanceOf[TopLevel]
Seq(n(descend(ls))) ++ descend(rest)
case Nil => Nil
}
def nodesAt(startingDepth: Int) = rep(descending(startingDepth)) ^^ {
case all => descend(all)
}
def safeParseNodes(contents: String) = {
phrase(lmxml)(new CharSequenceReader(contents)) match {
case Success(result, _) => Right(result)
case n: ParseResult[_] => Left(n)
}
}
def parseNodes(contents: String) = safeParseNodes(contents).fold({ e =>
throw new IllegalArgumentException(e.toString)
}, nodes => nodes)
def fullParse[A](contents: String)(implicit converter: Nodes => A) = {
converter(parseNodes(contents))
}
protected def rebuild(n: Nodes, link: LinkDefinition): Nodes = n match {
case (h: TemplateLink) :: rest if (h.name == link.name) =>
def rapidDescentAdder(ns: Nodes): Nodes = {
val fin = ns.lastOption
if (fin.isEmpty) {
rebuild(h.children, link)
} else {
val rest = ns.dropRight(1)
rest :+ copyNode(fin.get, rapidDescentAdder(fin.get.children))
}
}
rapidDescentAdder(link.children) ++ rebuild(rest, link)
case h :: rest =>
Seq(copyNode(h, rebuild(h.children, link))) ++ rebuild(rest, link)
case Nil => Nil
}
protected def copyNode(n: ParsedNode, nodes: Nodes) = n match {
case l: LmxmlNode => l.copy(children = nodes)
case t: TextNode => t.copy(children = nodes)
case y: TemplateLink => y.copy(children = nodes)
case _ => n
}
}
object DefaultLmxmlParser extends LmxmlParsers {
val increment = 2
}
case class PlainLmxmlParser(increment: Int) extends LmxmlParsers
© 2015 - 2025 Weber Informatics LLC | Privacy Policy