PageRenderTime 109ms CodeModel.GetById 82ms app.highlight 24ms RepoModel.GetById 1ms app.codeStats 0ms

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

http://github.com/scalate/scalate
Scala | 278 lines | 179 code | 34 blank | 65 comment | 30 complexity | a57e6d7b03b7675f5f05d10738d99b83 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 org.fusesource.scalate.scuery.Selector
 21import xml.{ Elem, Node }
 22
 23/**
 24 * Matches if the CSS class attribute contains the given class name word
 25 */
 26case class ClassSelector(className: String) extends Selector {
 27  private[this] val matcher = IncludesMatch(className)
 28
 29  def matches(node: Node, ancestors: Seq[Node]) = node match {
 30    case e: Elem =>
 31      e.attribute("class") match {
 32        case Some(nodes) => matcher.matches(nodes)
 33        case _ => false
 34      }
 35    case _ => false
 36  }
 37}
 38
 39case class IdSelector(className: String) extends Selector {
 40  def matches(node: Node, ancestors: Seq[Node]) = node match {
 41    case e: Elem =>
 42      attrEquals(e, "id", className)
 43    case _ => false
 44  }
 45}
 46
 47case class ElementNameSelector(name: String) extends Selector {
 48  def matches(node: Node, ancestors: Seq[Node]) = node match {
 49    case e: Elem =>
 50      e.label == name
 51    case _ => false
 52  }
 53}
 54
 55/**
 56 * Matches the current element if it has an attribute name
 57 */
 58case class AttributeNameSelector(name: String, matcher: Matcher) extends Selector {
 59  def matches(node: Node, ancestors: Seq[Node]) = node match {
 60    case e: Elem =>
 61      e.attribute(name) match {
 62        case Some(ns) => matcher.matches(ns)
 63        case _ => false
 64      }
 65
 66    case _ => false
 67  }
 68}
 69/**
 70 * Matches the current element if it has a namespaced attribute name
 71 */
 72case class NamespacedAttributeNameSelector(name: String, prefix: String, matcher: Matcher) extends Selector {
 73  def matches(node: Node, ancestors: Seq[Node]) = node match {
 74    case e: Elem =>
 75      val uri = e.scope.getURI(prefix)
 76      if (uri != null) {
 77        e.attribute(uri, name) match {
 78          case Some(ns) => matcher.matches(ns)
 79          case _ => false
 80        }
 81      } else {
 82        false
 83      }
 84
 85    case _ => false
 86  }
 87}
 88
 89case class NamespacePrefixSelector(prefix: String) extends Selector {
 90  def matches(node: Node, ancestors: Seq[Node]) = {
 91    // lets not compare prefixes, as we could have many prefixes mapped to the same URI
 92    // so lets compare the URI of the node to the URI of the prefix in scope on the node
 93    val boundUrl = node.scope.getURI(prefix)
 94    boundUrl != null && node.namespace == boundUrl
 95  }
 96}
 97
 98object NoNamespaceSelector extends Selector {
 99  def matches(node: Node, ancestors: Seq[Node]) = node.namespace == null
100}
101
102case class AnyElementSelector() extends Selector {
103  def matches(node: Node, ancestors: Seq[Node]) = node match {
104    case e: Elem => true
105    case _ => false
106  }
107}
108
109case class CompositeSelector(selectors: Seq[Selector]) extends Selector {
110  def matches(node: Node, ancestors: Seq[Node]) = selectors.find(!_.matches(node, ancestors)).isEmpty
111}
112
113case class ChildrenSelector(selector: Selector) extends Selector {
114  def matches(node: Node, ancestors: Seq[Node]) = ancestors match {
115    case ancestor :: xs =>
116      selector.matches(ancestor, xs)
117    case _ => false
118  }
119}
120
121case class NotSelector(selector: Selector) extends Selector {
122  def matches(node: Node, ancestors: Seq[Node]) = !selector.matches(node, ancestors)
123}
124
125object AnySelector extends Selector {
126  def matches(node: Node, ancestors: Seq[Node]) = true
127}
128
129object AnyElementSelector extends Selector {
130  def matches(node: Node, ancestors: Seq[Node]) = node match {
131    case e: Elem => true
132    case _ => false
133  }
134}
135
136// Combinators
137//-------------------------------------------------------------------------
138
139/**
140 * Represents selector: E > F
141 *
142 * See the <a href"http://www.w3.org/TR/css3-selectors/#child-combinators">description</a>
143 */
144case class ChildSelector(childSelector: Selector, ancestorSelector: Selector) extends Selector {
145  def matches(node: Node, ancestors: Seq[Node]) = {
146    !ancestors.isEmpty && childSelector.matches(node, ancestors) && ancestorSelector.matches(ancestors.head, ancestors.tail)
147  }
148}
149
150/**
151 * Represents selector: E F
152 *
153 * See the <a href"http://www.w3.org/TR/css3-selectors/#descendant-combinators">description</a>
154 */
155case class DescendantSelector(childSelector: Selector, ancestorSelector: Selector) extends Selector {
156  def matches(node: Node, ancestors: Seq[Node]) = {
157    !ancestors.isEmpty && childSelector.matches(node, ancestors) && matchAncestor(ancestors.head, ancestors.tail)
158  }
159
160  /**
161   * recursively match the ancestor selector until we have no more ancestors
162   */
163  protected def matchAncestor(node: Node, ancestors: Seq[Node]): Boolean = {
164    ancestorSelector.matches(node, ancestors) || (!ancestors.isEmpty && matchAncestor(ancestors.head, ancestors.tail))
165  }
166}
167
168/**
169 * Represents selector: E + F
170 *
171 * See the <a href"http://www.w3.org/TR/css3-selectors/#adjacent-sibling-combinators">description</a>
172 */
173case class AdjacentSiblingSelector(childSelector: Selector, ancestorSelector: Selector) extends Selector {
174  def matches(node: Node, ancestors: Seq[Node]) = {
175    if (!ancestors.isEmpty && childSelector.matches(node, ancestors)) {
176      // lets find immediate
177      // lets apply the ancestorSelector to the immediate ancestor
178
179      // find the index of node in ancestors children
180      val h = ancestors.head
181      val xs = ancestors.tail
182      val children = h.child
183      val idx = children.indexOf(node)
184      idx > 0 && ancestorSelector.matches(children(idx - 1), xs)
185    } else {
186      false
187    }
188  }
189}
190
191/**
192 * Represents selector: E ~ F
193 *
194 * See the <a href"http://www.w3.org/TR/css3-selectors/#general-sibling-combinators">description</a>
195 */
196case class GeneralSiblingSelector(childSelector: Selector, ancestorSelector: Selector) extends Selector {
197  def matches(node: Node, ancestors: Seq[Node]) = {
198    if (!ancestors.isEmpty && childSelector.matches(node, ancestors)) {
199      // lets find immediate
200      // lets apply the ancestorSelector to the immediate ancestor
201
202      // find the index of node in ancestors children
203      val h = ancestors.head
204      val xs = ancestors.tail
205
206      val children = h.child
207      val idx = children.indexOf(node)
208      idx > 0 && children.slice(0, idx).reverse.find(ancestorSelector.matches(_, xs)).isDefined
209    } else {
210      false
211    }
212  }
213}
214
215// Pseudo selectors
216//-------------------------------------------------------------------------
217
218object RootSelector extends Selector {
219  def matches(node: Node, ancestors: Seq[Node]) = node match {
220    case e: Elem =>
221      ancestors.isEmpty
222    case _ => false
223  }
224}
225
226object FirstChildSelector extends Selector {
227  def matches(node: Node, ancestors: Seq[Node]) = node match {
228    case e: Elem =>
229      ancestorChildElements(ancestors).headOption match {
230        case Some(n) => n == e
231        case _ => false
232      }
233    case _ => false
234  }
235}
236
237object LastChildSelector extends Selector {
238  def matches(node: Node, ancestors: Seq[Node]) = node match {
239    case e: Elem =>
240      ancestorChildElements(ancestors).lastOption match {
241        case Some(n) => n == e
242        case _ => false
243      }
244    case _ => false
245  }
246
247}
248
249case class NthChildSelector(counter: NthCounter) extends Selector {
250  def matches(node: Node, ancestors: Seq[Node]) = node match {
251    case e: Elem =>
252      val idx = ancestorChildElements(ancestors).indexOf(node)
253      counter.matches(idx)
254    case _ => false
255  }
256}
257
258/**
259 * Used for the <a href="http://www.w3.org/TR/css3-selectors/#nth-child-pseudo">nth</a>
260 * calculations representing an + b
261 */
262case class NthCounter(a: Int, b: Int) {
263  def matches(idx: Int): Boolean = {
264    if (idx < 0)
265      false
266    else {
267      val oneIdx = idx + 1
268      if (a == 0)
269        oneIdx == b
270      else
271        oneIdx % a == b
272    }
273
274  }
275}
276
277object OddCounter extends NthCounter(2, 1)
278object EvenCounter extends NthCounter(2, 0)