Permalink
Browse files

Adding descendant Zipper axes.

Abusing PathCreator to reuse code, probably hurting performance.
  • Loading branch information...
1 parent b6313c3 commit 191fd38a8a3ecd615be7e8b0a3f9bfdb99421823 @ncreep committed Jan 15, 2012
@@ -2,6 +2,7 @@ package com.codecommit.antixml
package zipper
import scala.annotation.tailrec
+import PathCreator.PathVals
/**
* Adds some XPath like axes to [[com.codecommit.antixml.Zipper]] instances.
@@ -36,9 +37,30 @@ private[antixml] trait ZipperAxes { self: Zipper[Node] =>
/** Returns the preceding siblings of a node including itself. */
def precedingSiblingOrSelf = transFuncToShift(_.shiftLeft, true)
+
+ /** Returns the descendants of a node.
+ *
+ * Note: this method is not optimized, use plain zipper selection for more efficient results. */
+ def descendant = pathCreatorFuncToShift(PathCreator.allChildren)
+
+ /** Returns the descendants of a node including itself.
+ *
+ * Note: this method is not optimized, use plain zipper selection for more efficient results. */
+ def descendantOrSelf = pathCreatorFuncToShift(PathCreator.all)
+
+ /** Takes a [[PathCreator]] function and turns it into a shifting function. */
+ private def pathCreatorFuncToShift(func: Selector[Node] => Group[Node] => PathVals[Node]) = {
+ shiftHoles { parent =>
+ path =>
+ val group = Group(PathFetcher.getNode(parent)(path).toList: _*)
+ //TODO redundant calculation of path values
+ //TODO path is not efficient for this sort of operations
+ func(*)(group).map(path ++ _.path.tail) // converting path to original parent's "coordinates"
+ }
+ }
/** Takes a path transformer function and converts it to a shifting function which is applied until
- * the transformer return `None`.
+ * the transformer returns `None`.
* @param appendSource True if the initial path should be part of the result.
*/
private def transFuncToShift(func: PathTransformer => ZipperPath => Option[ZipperPath], withSource: Boolean) = {
@@ -32,7 +32,7 @@ private[antixml] trait ZipperHoleShifting extends ZipperHoleMapper { self: Zippe
* and applied to the indexed contents of the zipper.
* Assumed to produce valid paths with regard to the supplied parent.
*/
- private[antixml] def shiftHoles(shiftFunc: Group[Node] => ZipperPath => Seq[ZipperPath]): Zipper[Node] = context match {
+ private[antixml] def shiftHoles(shiftFunc: Group[Node] => ZipperPath => Traversable[ZipperPath]): Zipper[Node] = context match {
case Some(context @ Context(parent, lastUpdate, metas, additionalHoles, hiddenNodes)) => {
implicit val lexicographic = ZipperPathOrdering
@@ -20,8 +20,10 @@ class ZipperAxesSpecs extends SpecificationWithJUnit {
zipper.followingSiblingOrSelf.unselect mustEqual res
zipper.precedingSibling.unselect mustEqual res
zipper.precedingSiblingOrSelf.unselect mustEqual res
+ zipper.descendant.unselect mustEqual res
+ zipper.descendantOrSelf.unselect mustEqual res
}
-
+
"fail on broken zippers" in {
bookstore.toZipper.directParent.unselect must throwA[RuntimeException]
bookstore.toZipper.ancestor.unselect must throwA[RuntimeException]
@@ -30,47 +32,44 @@ class ZipperAxesSpecs extends SpecificationWithJUnit {
bookstore.toZipper.followingSiblingOrSelf.unselect must throwA[RuntimeException]
bookstore.toZipper.precedingSibling.unselect must throwA[RuntimeException]
bookstore.toZipper.precedingSiblingOrSelf.unselect must throwA[RuntimeException]
+ bookstore.toZipper.descendant.unselect must throwA[RuntimeException]
+ bookstore.toZipper.descendantOrSelf.unselect must throwA[RuntimeException]
}
}
"Direct parent axes" should {
"be empty for root" in {
val res = bookstore select 'bookstore directParent
- res mustEqual Group()
- res.unselect mustEqual bookstore
+ verify(res)(Group())
}
"return the direct parents of nodes" in {
val res = bookstore \\ 'author directParent;
- res mustEqual (bookstore \\ 'book)
- res.unselect mustEqual bookstore
+ verify(res)(bookstore \\ 'book)
}
}
"Ancestor axes" should {
"be empty for root" in {
val res = bookstore select 'bookstore ancestor
- res mustEqual Group()
- res.unselect mustEqual bookstore
+ verify(res)(Group())
}
"return all ancestors of nodes" in {
val res = bookstore \\ 'author ancestor;
- res mustEqual (bookstore ++ bookstore \\ 'book)
- res.unselect mustEqual bookstore
+ verify(res)(bookstore ++ bookstore \\ 'book)
}
}
"Ancestor or self axes" should {
"return self for root" in {
val res = bookstore select 'bookstore ancestorOrSelf
- res mustEqual bookstore
- res.unselect mustEqual bookstore
+ verify(res)(bookstore)
}
"return all ancestors and self of nodes" in {
@@ -79,76 +78,101 @@ class ZipperAxesSpecs extends SpecificationWithJUnit {
val res = authors ancestorOrSelf;
-
- res mustEqual (bookstore :+ books(0) :+ authors(0) :+ books(1) :+ authors(1) :+ books(2) :+ authors(2) :+ authors(3))
- res.unselect mustEqual bookstore
+ verify(res)(bookstore :+ books(0) :+ authors(0) :+ books(1) :+ authors(1) :+ books(2) :+ authors(2) :+ authors(3))
}
}
"Following sibling axes" should {
"return nothing on rightmost node" in {
val res = bookstore \\ 'author followingSibling
- res mustEqual Group((bookstore \\ 'author).apply(3))
- res.unselect mustEqual bookstore
+ verify(res)(Group((bookstore \\ 'author).apply(3)))
}
"return all following siblings of nodes" in {
val res = bookstore \\ 'title followingSibling
- res mustEqual bookstore \\ 'author
- res.unselect mustEqual bookstore
+ verify(res)(bookstore \\ 'author)
}
}
"Following sibling or self axes" should {
"return self for rightmost nodes" in {
val res = bookstore \\ 'author followingSiblingOrSelf
- res mustEqual (bookstore \\ 'author)
- res.unselect mustEqual bookstore
+ verify(res)(bookstore \\ 'author)
}
"return all following siblings and self of nodes" in {
val res = bookstore \\ 'title followingSiblingOrSelf
- res mustEqual bookstore \ 'book \ *
- res.unselect mustEqual bookstore
+ verify(res)(bookstore \ 'book \ *)
}
}
"Preceding sibling axes" should {
"return nothing on leftmost node" in {
val res = bookstore \\ 'title precedingSibling
- res mustEqual Group()
- res.unselect mustEqual bookstore
+ verify(res)(Group())
}
"return all preceding siblings of nodes" in {
val res = bookstore \\ 'author precedingSibling
- res mustEqual (bookstore \\ 'title) :+ (bookstore \\ 'author).apply(2)
- res.unselect mustEqual bookstore
+ verify(res)((bookstore \\ 'title) :+ (bookstore \\ 'author).apply(2))
}
}
"Preceding sibling or self axes" should {
"return self for leftmost nodes" in {
val res = bookstore \\ 'title precedingSiblingOrSelf
- res mustEqual (bookstore \\ 'title)
- res.unselect mustEqual bookstore
+ verify(res)(bookstore \\ 'title)
}
"return all preceding siblings and self of nodes" in {
val res = bookstore \\ 'author precedingSiblingOrSelf
- res mustEqual bookstore \ 'book \ *
- res.unselect mustEqual bookstore
+ verify(res)(bookstore \ 'book \ *)
+ }
+ }
+
+ val text = Selector({ case t: Text => t })
+
+ "Descendant axes" should {
+ "return empty for leaf nodes" in {
+ val res = bookstore \\ text descendant
+
+ verify(res)(Group())
+ }
+
+ "return all descendants of nodes" in {
+ val res = bookstore \\ 'book descendant
+
+ verify(res)(bookstore \\ 'book \\ *)
}
}
+ "Descendant or self axes" should {
+ "return self for leaf nodes" in {
+ val res = bookstore \\ text descendantOrSelf
+
+ verify(res)(bookstore \\ text)
+ }
+
+ "return all descendants and self of nodes" in {
+ val res = bookstore \\ 'book descendantOrSelf
+
+ verify(res)(bookstore \\ *)
+ }
+ }
+
+ def verify(result: Zipper[Node])(expected: Group[Node]) = {
+ result mustEqual expected
+ result.unselect mustEqual bookstore
+ }
+
def resource(filename: String) =
XML fromSource (scala.io.Source fromURL (getClass getResource ("/" + filename)))
}

0 comments on commit 191fd38

Please sign in to comment.