Permalink
Browse files

Adding a class for path transformation.

  • Loading branch information...
1 parent f6af1ad commit 199d35c1824a2a8d2a490b132794af7fee0ce826 @ncreep committed Dec 9, 2011
@@ -0,0 +1,76 @@
+package com.codecommit.antixml
+
+import scala.annotation.tailrec
+import PathTransformer._
+
+/** Transforms [[com.codecommit.antixml.ZipperPath]]s with predefined functions.
+ *
+ * The transformations rely on a source parent group from which all paths are
+ * calculated. Any paths passed to instances of this class are assumed to be valid
+ * paths in the source group.
+ *
+ * @param source The source for the transformed paths.
+ */
+private[antixml] case class PathTransformer(source: Group[Node]) {
+ //TODO this whole class is probably quite slow
+ //TODO state monad for caching?
+
+ /** Shifts the path one step upwards, if possible.
+ * @param path The path to be shifted
+ * @return An optional path which is the parent of the original path, a new path transformer
+ * with an updated cache. */
+ def shiftUp(path: ZipperPath): Option[ZipperPath] = if (path.isEmpty) None else Some(path.init)
+
+ /** Shifts the path one step to the left, if possible.
+ * @param path The path to be shifted
+ * @return An optional path which is a sibling of the original path from the left, a new path transformer
+ * with an updated cache. */
+ def shiftLeft(path: ZipperPath): Option[ZipperPath] = {
+ shiftSideways(path, -1)
+ }
+
+ /** Shifts the path one step to the right, if possible.
+ * @param path The path to be shifted
+ * @return An optional path which is a sibling of the original path from the right, a new path transformer
+ * with an updated cache. */
+ def shiftRight(path: ZipperPath): Option[ZipperPath] = {
+ shiftSideways(path, +1)
+ }
+
+ /** @return An optional node at the end of the path and updated cache. */
+ private def getNode(path: ZipperPath): Option[Node] = getNode(source, path)
+
+ @tailrec
+ private def getNode(currLevel: Group[Node], path: ZipperPath): Option[Node] = {
+ if (path.isEmpty) None
+ else if (path.size == 1) Some(currLevel(path.head))
+ else {
+ // the cast must succeed otherwise the path is invalid
+ val children = currLevel(path.head).asInstanceOf[Elem].children
+ getNode(children, path.tail)
+ }
+ }
+
+ /** Tries to shift the path sideways by the given increment. */
+ private def shiftSideways(path: ZipperPath, increment: Int): Option[ZipperPath] = {
+ assert(!path.isEmpty, "Cannot shift an empty path.")
+
+ val currLevel =
+ if (path.size == 1) source
+ else {
+ val parent = getNode(path.init) // size > 1
+ parent.get.asInstanceOf[Elem].children // must be an elem for a valid path
+ }
+
+ val end = path.size - 1
+
+ val newLoc = path(end) + increment
+
+ if (currLevel.indices.contains(newLoc)) Some(path.updated(end, newLoc))
+ else None
+ }
+}
+
+private[antixml] object PathTransformer {
+ private[antixml]type PathCache = Map[ZipperPath, Option[Node]]
+}
@@ -0,0 +1,62 @@
+package com.codecommit.antixml
+
+import org.specs2.mutable._
+import XML._
+import scala.collection.immutable.HashMap
+
+class PathTransformerSpecs extends SpecificationWithJUnit {
+
+ val x0 = fromString("<root0><a0>foo</a0><b0>baz</b0><c0/></root0>")
+ val x1 = fromString("<root1><a1>foo</a1><b1>baz</b1><c1/></root1>")
+ val x2 = fromString("<root2><a2>foo</a2><b2>baz</b2><c2/></root2>")
+
+ val group = Group(x0, x1, x2)
+
+ val empty = ZipperPath()
+ val p1 = ZipperPath(0, 1, 0)
+ val p2 = ZipperPath(1, 1)
+ val p3 = ZipperPath(2)
+ val p4 = ZipperPath(0)
+
+ val transformer = PathTransformer(group)
+
+ "shifting upwards" should {
+ "ignore empty paths" in {
+ transformer.shiftUp(empty) mustEqual None
+ }
+
+ "properly shift paths" in {
+ transformer.shiftUp(p1) mustEqual Some(ZipperPath(0, 1))
+ transformer.shiftUp(p2) mustEqual Some(ZipperPath(1))
+ transformer.shiftUp(p3) mustEqual Some(ZipperPath())
+ }
+ }
+
+ "shifting leftwards" should {
+ "fail on empty paths" in {
+ transformer.shiftLeft(empty) must throwAn[AssertionError]
+ }
+
+ "properly shift paths" in {
+ transformer.shiftLeft(p1) mustEqual None
+ transformer.shiftLeft(p2) mustEqual Some(ZipperPath(1, 0))
+ transformer.shiftLeft(p3) mustEqual Some(ZipperPath(1))
+ transformer.shiftLeft(p4) mustEqual None
+ }
+ }
+
+ "shifting rightwards" should {
+ "fail on empty paths" in {
+ transformer.shiftRight(empty) must throwAn[AssertionError]
+ }
+
+ "properly shift paths" in {
+ transformer.shiftRight(p1) mustEqual None
+ transformer.shiftRight(p2) mustEqual Some(ZipperPath(1, 2))
+ transformer.shiftRight(p3) mustEqual None
+ transformer.shiftRight(p4) mustEqual Some(ZipperPath(1))
+ }
+ }
+
+ def elem(name: String, text: String) = Elem(None, name, Attributes(), Map(), Group(Text(text)))
+}

0 comments on commit 199d35c

Please sign in to comment.