/scalate-core/src/main/scala/org/fusesource/scalate/scuery/support/CssParser.scala

http://github.com/scalate/scalate · Scala · 317 lines · 157 code · 67 blank · 93 comment · 11 complexity · 7a6e32aeec42197951e050fcd6f86e4d MD5 · raw file

  1. /**
  2. * Copyright (C) 2009-2011 the original author or authors.
  3. * See the notice.md file distributed with this work for additional
  4. * information regarding copyright ownership.
  5. *
  6. * Licensed under the Apache License, Version 2.0 (the "License");
  7. * you may not use this file except in compliance with the License.
  8. * You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. package org.fusesource.scalate.scuery.support
  19. import util.parsing.combinator.RegexParsers
  20. import util.parsing.input.{ CharSequenceReader, NoPosition, Position }
  21. import org.fusesource.scalate.TemplateException
  22. import org.fusesource.scalate.scuery._
  23. class CssScanner extends RegexParsers {
  24. override def skipWhitespace = false
  25. // ident [-]?{nmstart}{nmchar}*
  26. private def ident = (opt("-") ~ nmstart ~ rep(nmchar)) ^^ { case p ~ n ~ l => p.mkString("") + n + l.mkString("") }
  27. // name {nmchar}+
  28. private def name = rep1(nmchar)
  29. // nmstart [_a-z]|{nonascii}|{escape}
  30. private def nmstart = """[_a-zA-Z]""".r | nonascii | escape
  31. // nonascii [^\0-\177]
  32. private def nonascii = """[^\x00-\xB1]""".r
  33. // unicode \\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?
  34. private def unicode = """\\[0-9a-fA-F]{1,6}(\r\n|[ \n\r\t\f])?""".r
  35. // escape {unicode}|\\[^\n\r\f0-9a-f]
  36. private def escape = unicode | """\\[^\n\r\f0-9a-fA-F]""".r
  37. // nmchar [_a-z0-9-]|{nonascii}|{escape}
  38. private def nmchar = """[_a-zA-Z0-9-]""".r | nonascii | escape
  39. // num [0-9]+|[0-9]*\.[0-9]+
  40. private[this] val num = """[0-9]+|[0-9]*"."[0-9]+"""
  41. // string {string1}|{string2}
  42. def STRING = string1 | string2
  43. // string1 \"([^\n\r\f\\"]|\\{nl}|{nonascii}|{escape})*\"
  44. private[this] val string1 = ("\"" ~> rep("""[^\n\r\f\\"]""".r | ("\\" + nl).r | nonascii | escape) <~ "\"") ^^ { case l => l.mkString("") }
  45. // string2 \'([^\n\r\f\\']|\\{nl}|{nonascii}|{escape})*\'
  46. private[this] val string2 = ("'" ~> rep("""[^\n\r\f\']""".r | ("\\" + nl).r | nonascii | escape) <~ "'") ^^ { case l => l.mkString("") }
  47. // nl \n|\r\n|\r|\f
  48. private[this] val nl = """\n|\r\n|\r|\f"""
  49. // invalid {invalid1}|{invalid2}
  50. // invalid1 \"([^\n\r\f\\"]|\\{nl}|{nonascii}|{escape})*
  51. // invalid2 \'([^\n\r\f\\']|\\{nl}|{nonascii}|{escape})*
  52. // w [ \t\r\n\f]*
  53. val S = """\s+""".r
  54. val repS = """[\s]*""".r
  55. val rep1S = """[\s]+""".r
  56. val COMMA = ","
  57. val PLUS = """\+""".r
  58. val GREATER = """>""".r
  59. val TILDE = """~""".r
  60. val INCLUDES = "~="
  61. val DASHMATCH = "|="
  62. val PREFIXMATCH = "^="
  63. val SUFFIXMATCH = "$="
  64. val SUBSTRINGMATCH = "*="
  65. val NUMBER = num.r
  66. val INTEGER = """[0-9]""".r
  67. def IDENT = ident
  68. def HASH = ("#" ~> name)
  69. def DIMENSION = NUMBER ~ IDENT
  70. // D d|\\0{0,4}(44|64)(\r\n|[ \t\r\n\f])?
  71. def D = """d|\\0{0,4}(44|64)(\r\n|[ \t\r\n\f])?""".r
  72. // E e|\\0{0,4}(45|65)(\r\n|[ \t\r\n\f])?
  73. def E = """e|\\0{0,4}(45|65)(\r\n|[ \t\r\n\f])?""".r
  74. // N n|\\0{0,4}(4e|6e)(\r\n|[ \t\r\n\f])?|\\n
  75. def N = """n|\\0{0,4}(4e|6e)(\r\n|[ \t\r\n\f])?|\\n""".r
  76. // O o|\\0{0,4}(4f|6f)(\r\n|[ \t\r\n\f])?|\\o
  77. def O = """o|\\0{0,4}(4f|6f)(\r\n|[ \t\r\n\f])?|\\o""".r
  78. // T t|\\0{0,4}(54|74)(\r\n|[ \t\r\n\f])?|\\t
  79. def T = """t|\\0{0,4}(54|74)(\r\n|[ \t\r\n\f])?|\\t""".r
  80. // V v|\\0{0,4}(58|78)(\r\n|[ \t\r\n\f])?|\\v
  81. def V = """v|\\0{0,4}(58|78)(\r\n|[ \t\r\n\f])?|\\v""".r
  82. }
  83. /**
  84. * Parser of <a href="http://www.w3.org/TR/css3-syntax">CSS3 selectors</a>
  85. *
  86. * @version $Revision : 1.1 $
  87. */
  88. class CssParser extends CssScanner {
  89. private def phraseOrFail[T](p: Parser[T], in: String): T = {
  90. val x = phrase(p)(new CharSequenceReader(in))
  91. x match {
  92. case Success(result, _) => result
  93. case NoSuccess(message, next) => throw new InvalidCssSelectorException(message, next.pos);
  94. }
  95. }
  96. def parseSelector(in: String) = {
  97. phraseOrFail(selector, in)
  98. }
  99. // selectors_group
  100. // : selector [ COMMA S* selector ]*
  101. def selectors_group = selector ~ rep((COMMA ~ repS) ~> selector)
  102. // selector
  103. // : simple_selector_sequence [ combinator simple_selector_sequence ]*
  104. def selector = (simple_selector_sequence ~ rep(combinator_simple_selector_sequence)) ^^ {
  105. case s ~ cs =>
  106. if (cs.isEmpty) {
  107. s
  108. } else {
  109. Selector(s, cs)
  110. }
  111. }
  112. // combinator
  113. // /* combinators can be surrounded by whitespace */
  114. // : PLUS S* | GREATER S* | TILDE S* | S+
  115. def combinator_simple_selector_sequence = (((repS ~> (PLUS | GREATER | TILDE) <~ repS) | rep1S) ~ simple_selector_sequence) ^^ {
  116. case c ~ s =>
  117. c match {
  118. case ">" => ChildCombinator(s)
  119. case "+" => AdjacentSiblingdCombinator(s)
  120. case "~" => GeneralSiblingCombinator(s)
  121. case _ => DescendantCombinator(s)
  122. }
  123. }
  124. // simple_selector_sequence
  125. // : [ type_selector | universal ]
  126. // [ HASH | class | attrib | pseudo | negation ]*
  127. // | [ HASH | class | attrib | pseudo | negation ]+
  128. def simple_selector_sequence = simple_selector_sequence_1 | simple_selector_sequence_2
  129. def simple_selector_sequence_1 = (type_selector | universal) ~ rep(hash | className | attrib | negation | pseudo) ^^ {
  130. case t ~ l => Selector(t :: l)
  131. }
  132. def simple_selector_sequence_2 = rep1(hash | className | attrib | negation | pseudo) ^^ { case l => Selector(l) }
  133. // type_selector
  134. // : [ namespace_prefix ]? element_name
  135. def type_selector = (opt(namespace_prefix) ~ element_name) ^^ {
  136. case on ~ e => on match {
  137. case Some(n) => Selector(n :: e :: Nil)
  138. case _ => e
  139. }
  140. }
  141. // namespace_prefix
  142. // : [ IDENT | '*' ]? '|'
  143. def namespace_prefix = ((opt(IDENT | "*")) <~ "|") ^^ {
  144. case o => o match {
  145. case Some("*") => AnySelector
  146. case Some(prefix) => NamespacePrefixSelector(prefix)
  147. case _ => NoNamespaceSelector
  148. }
  149. }
  150. // element_name
  151. // : IDENT
  152. def element_name = (IDENT ^^ { ElementNameSelector(_) })
  153. // universal
  154. // : [ namespace_prefix ]? '*'
  155. def universal = (opt(namespace_prefix) <~ "*") ^^ {
  156. case op => op match {
  157. case Some(p) => p
  158. case _ => AnyElementSelector
  159. }
  160. }
  161. // class
  162. // : "." IDENT
  163. def className = ("." ~> IDENT) ^^ { ClassSelector(_) }
  164. def hash = HASH ^^ { h => IdSelector(h.mkString) }
  165. // attrib
  166. // : '[' S* [ namespace_prefix ]? IDENT S*
  167. // [ [ PREFIXMATCH |
  168. // SUFFIXMATCH |
  169. // SUBSTRINGMATCH |
  170. // '=' |
  171. // INCLUDES |
  172. // DASHMATCH ] S* [ IDENT | STRING ] S*
  173. // ]? ']'
  174. def attrib = (("[" ~ repS) ~> attribute_name ~ opt(attribute_value) <~ "]") ^^ {
  175. case np ~ i ~ v =>
  176. val matcher = v match {
  177. case Some(v) => v
  178. case _ => MatchesAny
  179. }
  180. np match {
  181. case Some(p) => p match {
  182. case p: NamespacePrefixSelector => NamespacedAttributeNameSelector(i, p.prefix, matcher)
  183. case _ => AttributeNameSelector(i, matcher)
  184. }
  185. case _ => AttributeNameSelector(i, matcher)
  186. }
  187. }
  188. def attribute_name = opt(namespace_prefix) ~ IDENT <~ repS
  189. def attribute_value = ((PREFIXMATCH | SUFFIXMATCH | SUBSTRINGMATCH | "=" | INCLUDES | DASHMATCH) <~ repS) ~
  190. ((IDENT | STRING) <~ repS) ^^ {
  191. case p ~ i =>
  192. p match {
  193. case PREFIXMATCH => PrefixMatch(i)
  194. case SUFFIXMATCH => SuffixMatch(i)
  195. case SUBSTRINGMATCH => SubstringMatch(i)
  196. case "=" => EqualsMatch(i)
  197. case INCLUDES => IncludesMatch(i)
  198. case DASHMATCH => DashMatch(i)
  199. }
  200. }
  201. // pseudo
  202. // /* '::' starts a pseudo-element, ':' a pseudo-class */
  203. // /* Exceptions: :first-line, :first-letter, :before and :after. */
  204. // /* Note that pseudo-elements are restricted to one per selector and */
  205. // /* occur only in the last simple_selector_sequence. */
  206. // : ':' ':'? [ IDENT | functional_pseudo ]
  207. def pseudo = (":" ~ opt(":")) ~> (functional_nth_pseudo | functional_pseudo | pseudo_ident)
  208. def pseudo_ident = IDENT ^^ { Selector.pseudoSelector(_) }
  209. // functional_pseudo
  210. // : FUNCTION S* expression ')'
  211. def functional_pseudo = (IDENT <~ ("(" ~ repS)) ~ (expression <~ ")") ^^ { case f ~ e => Selector.pseudoFunction(f) }
  212. def functional_nth_pseudo = ("nth-" ~> IDENT <~ ("(" ~ repS)) ~ (repS ~> nth <~ ")") ^^ { case f ~ e => Selector.pseudoFunction("nth-" + f, e) }
  213. // expression
  214. // /* In CSS3, the expressions are identifiers, strings, */
  215. // /* or of the form "an+b" */
  216. // : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+
  217. def expression = rep1(("+" | "-" | DIMENSION | STRING | IDENT) <~ repS)
  218. // nth
  219. // : S* [ ['-'|'+']? INTEGER? {N} [ S* ['-'|'+'] S* INTEGER ]? |
  220. // ['-'|'+']? INTEGER | {O}{D}{D} | {E}{V}{E}{N} ] S*
  221. def nth = (opt("-" | "+") ~ (opt(integer) <~ N) ~ opt((repS ~> ("-" | "+") <~ repS) ~ integer)) ^^ {
  222. case os ~ on ~ on2 =>
  223. val a = on match {
  224. case Some(n) => if (os == Some("-")) { n * -1 } else { n }
  225. case _ => 0
  226. }
  227. val b = on2 match {
  228. case Some(s ~ i) =>
  229. if (s == "-") { i * -1 } else { i }
  230. case _ => 0
  231. }
  232. NthCounter(a, b)
  233. } | (opt("-" | "+") ~ integer) ^^ {
  234. case os ~ i =>
  235. val b = if (os == Some("-")) { i * -1 } else { i }
  236. NthCounter(0, b)
  237. } | (O ~ D ~ D) ^^ {
  238. case _ => OddCounter
  239. } | (E ~ V ~ E ~ N) ^^ {
  240. case _ => EvenCounter
  241. }
  242. def integer = INTEGER ^^ { Integer.parseInt(_) }
  243. // negation
  244. // : NOT S* negation_arg S* ')'
  245. def negation = (":" ~ N ~ O ~ T ~ "(" ~ repS) ~> negation_arg <~ (repS ~ ")") ^^ { case a => NotSelector(a) }
  246. // negation_arg
  247. // : type_selector | universal | HASH | class | attrib | pseudo
  248. def negation_arg: Parser[Selector] = type_selector | universal | hash | className | attrib | pseudo
  249. }
  250. class InvalidCssSelectorException(val brief: String, val pos: Position = NoPosition) extends TemplateException(brief + " at " + pos)