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