PageRenderTime 18ms CodeModel.GetById 2ms app.highlight 12ms RepoModel.GetById 1ms app.codeStats 1ms

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

http://github.com/scalate/scalate
Scala | 317 lines | 159 code | 66 blank | 92 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 */
 18package org.fusesource.scalate.scuery.support
 19
 20import util.parsing.combinator.RegexParsers
 21import util.parsing.input.{ CharSequenceReader, NoPosition, Position }
 22import org.fusesource.scalate.TemplateException
 23import org.fusesource.scalate.scuery._
 24
 25class CssScanner extends RegexParsers {
 26  override def skipWhitespace = false
 27
 28  //   ident     [-]?{nmstart}{nmchar}*
 29  private def ident = (opt("-") ~ nmstart ~ rep(nmchar)) ^^ { case p ~ n ~ l => p.mkString("") + n + l.mkString("") }
 30
 31  // name      {nmchar}+
 32  private def name = rep1(nmchar)
 33
 34  // nmstart   [_a-z]|{nonascii}|{escape}
 35  private def nmstart = """[_a-zA-Z]""".r | nonascii | escape
 36
 37  // nonascii  [^\0-\177]
 38  private def nonascii = """[^\x00-\xB1]""".r
 39
 40  // unicode   \\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?
 41  private def unicode = """\\[0-9a-fA-F]{1,6}(\r\n|[ \n\r\t\f])?""".r
 42
 43  // escape    {unicode}|\\[^\n\r\f0-9a-f]
 44  private def escape = unicode | """\\[^\n\r\f0-9a-fA-F]""".r
 45
 46  // nmchar    [_a-z0-9-]|{nonascii}|{escape}
 47  private def nmchar = """[_a-zA-Z0-9-]""".r | nonascii | escape
 48
 49  // num       [0-9]+|[0-9]*\.[0-9]+
 50  private[this] val num = """[0-9]+|[0-9]*"."[0-9]+"""
 51
 52  // string    {string1}|{string2}
 53  def STRING = string1 | string2
 54
 55  // string1   \"([^\n\r\f\\"]|\\{nl}|{nonascii}|{escape})*\"
 56  private[this] val string1 = ("\"" ~> rep("""[^\n\r\f\\"]""".r | ("\\" + nl).r | nonascii | escape) <~ "\"") ^^ { case l => l.mkString("") }
 57
 58  // string2   \'([^\n\r\f\\']|\\{nl}|{nonascii}|{escape})*\'
 59  private[this] val string2 = ("'" ~> rep("""[^\n\r\f\']""".r | ("\\" + nl).r | nonascii | escape) <~ "'") ^^ { case l => l.mkString("") }
 60
 61  // nl        \n|\r\n|\r|\f
 62  private[this] val nl = """\n|\r\n|\r|\f"""
 63
 64  // invalid   {invalid1}|{invalid2}
 65  // invalid1  \"([^\n\r\f\\"]|\\{nl}|{nonascii}|{escape})*
 66  // invalid2  \'([^\n\r\f\\']|\\{nl}|{nonascii}|{escape})*
 67  // w         [ \t\r\n\f]*
 68
 69  val S = """\s+""".r
 70  val repS = """[\s]*""".r
 71  val rep1S = """[\s]+""".r
 72
 73  val COMMA = ","
 74  val PLUS = """\+""".r
 75  val GREATER = """>""".r
 76  val TILDE = """~""".r
 77
 78  val INCLUDES = "~="
 79  val DASHMATCH = "|="
 80  val PREFIXMATCH = "^="
 81  val SUFFIXMATCH = "$="
 82  val SUBSTRINGMATCH = "*="
 83
 84  val NUMBER = num.r
 85  val INTEGER = """[0-9]""".r
 86
 87  def IDENT = ident
 88  def HASH = ("#" ~> name)
 89  def DIMENSION = NUMBER ~ IDENT
 90
 91  // D         d|\\0{0,4}(44|64)(\r\n|[ \t\r\n\f])?
 92  def D = """d|\\0{0,4}(44|64)(\r\n|[ \t\r\n\f])?""".r
 93
 94  // E         e|\\0{0,4}(45|65)(\r\n|[ \t\r\n\f])?
 95  def E = """e|\\0{0,4}(45|65)(\r\n|[ \t\r\n\f])?""".r
 96
 97  // N         n|\\0{0,4}(4e|6e)(\r\n|[ \t\r\n\f])?|\\n
 98  def N = """n|\\0{0,4}(4e|6e)(\r\n|[ \t\r\n\f])?|\\n""".r
 99
100  // O         o|\\0{0,4}(4f|6f)(\r\n|[ \t\r\n\f])?|\\o
101  def O = """o|\\0{0,4}(4f|6f)(\r\n|[ \t\r\n\f])?|\\o""".r
102
103  // T         t|\\0{0,4}(54|74)(\r\n|[ \t\r\n\f])?|\\t
104  def T = """t|\\0{0,4}(54|74)(\r\n|[ \t\r\n\f])?|\\t""".r
105
106  //  V         v|\\0{0,4}(58|78)(\r\n|[ \t\r\n\f])?|\\v
107  def V = """v|\\0{0,4}(58|78)(\r\n|[ \t\r\n\f])?|\\v""".r
108}
109
110/**
111 * Parser of <a href="http://www.w3.org/TR/css3-syntax">CSS3 selectors</a>
112 *
113 * @version $Revision : 1.1 $
114 */
115class CssParser extends CssScanner {
116  private def phraseOrFail[T](p: Parser[T], in: String): T = {
117    val x = phrase(p)(new CharSequenceReader(in))
118    x match {
119      case Success(result, _) => result
120      case NoSuccess(message, next) => throw new InvalidCssSelectorException(message, next.pos);
121    }
122  }
123
124  def parseSelector(in: String) = {
125    phraseOrFail(selector, in)
126  }
127
128  //  selectors_group
129  //    : selector [ COMMA S* selector ]*
130
131  def selectors_group = selector ~ rep((COMMA ~ repS) ~> selector)
132
133  //  selector
134  //    : simple_selector_sequence [ combinator simple_selector_sequence ]*
135
136  def selector = (simple_selector_sequence ~ rep(combinator_simple_selector_sequence)) ^^ {
137    case s ~ cs =>
138      if (cs.isEmpty) {
139        s
140      } else {
141        Selector(s, cs)
142      }
143  }
144
145  //  combinator
146  //    /* combinators can be surrounded by whitespace */
147  //    : PLUS S* | GREATER S* | TILDE S* | S+
148
149  def combinator_simple_selector_sequence = (((repS ~> (PLUS | GREATER | TILDE) <~ repS) | rep1S) ~ simple_selector_sequence) ^^ {
150    case c ~ s =>
151      c match {
152        case ">" => ChildCombinator(s)
153        case "+" => AdjacentSiblingdCombinator(s)
154        case "~" => GeneralSiblingCombinator(s)
155        case _ => DescendantCombinator(s)
156      }
157  }
158
159  //  simple_selector_sequence
160  //    : [ type_selector | universal ]
161  //      [ HASH | class | attrib | pseudo | negation ]*
162  //    | [ HASH | class | attrib | pseudo | negation ]+
163
164  def simple_selector_sequence = simple_selector_sequence_1 | simple_selector_sequence_2
165
166  def simple_selector_sequence_1 = (type_selector | universal) ~ rep(hash | className | attrib | negation | pseudo) ^^ {
167    case t ~ l => Selector(t :: l)
168  }
169
170  def simple_selector_sequence_2 = rep1(hash | className | attrib | negation | pseudo) ^^ { case l => Selector(l) }
171
172  //  type_selector
173  //    : [ namespace_prefix ]? element_name
174
175  def type_selector = (opt(namespace_prefix) ~ element_name) ^^ {
176    case on ~ e => on match {
177      case Some(n) => Selector(n :: e :: Nil)
178      case _ => e
179    }
180  }
181
182  //  namespace_prefix
183  //    : [ IDENT | '*' ]? '|'
184
185  def namespace_prefix = ((opt(IDENT | "*")) <~ "|") ^^ {
186    case o => o match {
187      case Some("*") => AnySelector
188      case Some(prefix) => NamespacePrefixSelector(prefix)
189      case _ => NoNamespaceSelector
190    }
191  }
192
193  //  element_name
194  //    : IDENT
195
196  def element_name = (IDENT ^^ { ElementNameSelector(_) })
197
198  //  universal
199  //    : [ namespace_prefix ]? '*'
200
201  def universal = (opt(namespace_prefix) <~ "*") ^^ {
202    case op => op match {
203      case Some(p) => p
204      case _ => AnyElementSelector
205    }
206  }
207
208  //  class
209  //    : "." IDENT
210
211  def className = ("." ~> IDENT) ^^ { ClassSelector(_) }
212
213  def hash = HASH ^^ { h => IdSelector(h.mkString) }
214
215  //  attrib
216  //    : '[' S* [ namespace_prefix ]? IDENT S*
217  //          [ [ PREFIXMATCH |
218  //              SUFFIXMATCH |
219  //              SUBSTRINGMATCH |
220  //              '=' |
221  //              INCLUDES |
222  //              DASHMATCH ] S* [ IDENT | STRING ] S*
223  //          ]? ']'
224
225  def attrib = (("[" ~ repS) ~> attribute_name ~ opt(attribute_value) <~ "]") ^^ {
226    case np ~ i ~ v =>
227      val matcher = v match {
228        case Some(v) => v
229        case _ => MatchesAny
230      }
231      np match {
232        case Some(p) => p match {
233          case p: NamespacePrefixSelector => NamespacedAttributeNameSelector(i, p.prefix, matcher)
234          case _ => AttributeNameSelector(i, matcher)
235        }
236        case _ => AttributeNameSelector(i, matcher)
237      }
238  }
239
240  def attribute_name = opt(namespace_prefix) ~ IDENT <~ repS
241
242  def attribute_value = ((PREFIXMATCH | SUFFIXMATCH | SUBSTRINGMATCH | "=" | INCLUDES | DASHMATCH) <~ repS) ~
243    ((IDENT | STRING) <~ repS) ^^ {
244      case p ~ i =>
245        p match {
246          case PREFIXMATCH => PrefixMatch(i)
247          case SUFFIXMATCH => SuffixMatch(i)
248          case SUBSTRINGMATCH => SubstringMatch(i)
249          case "=" => EqualsMatch(i)
250          case INCLUDES => IncludesMatch(i)
251          case DASHMATCH => DashMatch(i)
252        }
253    }
254
255  //  pseudo
256  //    /* '::' starts a pseudo-element, ':' a pseudo-class */
257  //    /* Exceptions: :first-line, :first-letter, :before and :after. */
258  //    /* Note that pseudo-elements are restricted to one per selector and */
259  //    /* occur only in the last simple_selector_sequence. */
260  //    : ':' ':'? [ IDENT | functional_pseudo ]
261
262  def pseudo = (":" ~ opt(":")) ~> (functional_nth_pseudo | functional_pseudo | pseudo_ident)
263
264  def pseudo_ident = IDENT ^^ { Selector.pseudoSelector(_) }
265
266  //  functional_pseudo
267  //    : FUNCTION S* expression ')'
268
269  def functional_pseudo = (IDENT <~ ("(" ~ repS)) ~ (expression <~ ")") ^^ { case f ~ e => Selector.pseudoFunction(f) }
270  def functional_nth_pseudo = ("nth-" ~> IDENT <~ ("(" ~ repS)) ~ (repS ~> nth <~ ")") ^^ { case f ~ e => Selector.pseudoFunction("nth-" + f, e) }
271
272  //  expression
273  //    /* In CSS3, the expressions are identifiers, strings, */
274  //    /* or of the form "an+b" */
275  //    : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+
276
277  def expression = rep1(("+" | "-" | DIMENSION | STRING | IDENT) <~ repS)
278
279  // nth
280  //  : S* [ ['-'|'+']? INTEGER? {N} [ S* ['-'|'+'] S* INTEGER ]? |
281  //         ['-'|'+']? INTEGER | {O}{D}{D} | {E}{V}{E}{N} ] S*
282  def nth = (opt("-" | "+") ~ (opt(integer) <~ N) ~ opt((repS ~> ("-" | "+") <~ repS) ~ integer)) ^^ {
283    case os ~ on ~ on2 =>
284      val a = on match {
285        case Some(n) => if (os == Some("-")) { n * -1 } else { n }
286        case _ => 0
287      }
288      val b = on2 match {
289        case Some(s ~ i) =>
290          if (s == "-") { i * -1 } else { i }
291        case _ => 0
292      }
293      NthCounter(a, b)
294  } | (opt("-" | "+") ~ integer) ^^ {
295    case os ~ i =>
296      val b = if (os == Some("-")) { i * -1 } else { i }
297      NthCounter(0, b)
298  } | (O ~ D ~ D) ^^ {
299    case _ => OddCounter
300  } | (E ~ V ~ E ~ N) ^^ {
301    case _ => EvenCounter
302  }
303
304  def integer = INTEGER ^^ { Integer.parseInt(_) }
305
306  //  negation
307  //    : NOT S* negation_arg S* ')'
308  def negation = (":" ~ N ~ O ~ T ~ "(" ~ repS) ~> negation_arg <~ (repS ~ ")") ^^ { case a => NotSelector(a) }
309
310  //  negation_arg
311  //    : type_selector | universal | HASH | class | attrib | pseudo
312
313  def negation_arg: Parser[Selector] = type_selector | universal | hash | className | attrib | pseudo
314
315}
316
317class InvalidCssSelectorException(val brief: String, val pos: Position = NoPosition) extends TemplateException(brief + " at " + pos)