
scales.utils.collection.path.PathFunctions.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scales-xml_2.11 Show documentation
Show all versions of scales-xml_2.11 Show documentation
An alternate Scala Xml processing library
The newest version!
package scales.utils.collection.path
import scales.utils._
import collection._
import scala.reflect.ClassManifest
object PathFold {
/**
* Folds over positions within a single path, for example all given children. As such positions must be calculated.
*
* Takes the first root, returning Right(NoSingleRoot) if any of the subsequent roots don't match.
*
* folder retrieves the current path
*
* Each iteration folds the resulting tree back into the path. As this function must maintain the Path it does not expose the new path root until the result.
*
* The progress through the document is in reverse document order. This ensures that transformations can always be safely composed, e.g. a delete of a path won't stop changes below it. This, however, implies the developer must also handle any accumalation in "reverse".
*/
def foldPositions[Item <: LeftLike[Item, Tree[Item, Section, CC]], Section, CC[_], ACC](locations: Iterable[Path[Item, Section, CC]], accumulator: ACC)(folder: (ACC, Path[Item, Section, CC]) => (ACC, FoldOperation[Item, Section, CC])) (implicit seqLikeThing: SeqLikeThing[CC[_], ItemOrTree[Item, Section, CC], CC], cm : ClassManifest[(Position[Item,Section,CC], Path[Item, Section, CC])]) : Either[(ACC, Path[Item, Section, CC]), FoldError] = {
if (locations.isEmpty) return Right(NoPaths)
val sorted = sortPositions(locations, false)
val head = sorted.head
var accum = accumulator
val rootPosition = head._1
val differentRoot = sorted.exists(p => p._1.root ne rootPosition.root)
if (differentRoot)
Right(NoSingleRoot)
else {
def withPositions( opositions : Seq[Position[Item,Section,CC]] ) : Either[(ACC, Path[Item, Section, CC]), FoldError] = {
var positions = opositions
var path = head._2
while (!positions.isEmpty) {
val (accf, res) = folder(accum, path)
accum = accf
val matched = res.perform(path) //matchIt( res, path )
if (matched.isLeft) {
path = matched.left.get
positions = positions.drop(1)
if (!positions.isEmpty) {
path = moveTo(path, positions.head) // else nothing we keep path to call root
}
} else return Right(matched.right.get)
}
Left((accum, rootPath(path)))
}
// fold over positions, with the path from head, let each foldop decide what the next position sequence looks like
val positions = sorted.map(_._1).toSeq
withPositions(positions)
}
}
}
/**
* Utility functions for Paths, sorting, moving between Paths, getting to the root etc.
*/
trait Paths {
def noPath[Item <: LeftLike[Item, Tree[Item, Section, CC]], Section, CC[_]]
(implicit seqLikeThing: SeqLikeThing[CC[_], ItemOrTree[Item, Section, CC], CC]) = new Path[Item, Section, CC](Top(), Node(-1, null.asInstanceOf[ItemOrTree[Item, Section, CC]])) {
}
/**
* Returns the root path for its input, uses zipUp to ensure changes are kept
*/
def rootPath[Item <: LeftLike[Item, Tree[Item, Section, CC]], Section, CC[_]](path: Path[Item, Section, CC]): Path[Item, Section, CC] = {
var newPath = path
while (!newPath.top.isLeft)
newPath = newPath.zipUp
newPath
}
/**
* Navigates the path until the new position is reached, throws if either its a new root or the position is not reachable
*/
def moveTo[Item <: LeftLike[Item, Tree[Item, Section, CC]], Section, CC[_]](path: Path[Item, Section, CC], newPos: Position[Item, Section, CC])
(implicit seqLikeThing: SeqLikeThing[CC[_], ItemOrTree[Item, Section, CC], CC]) : Path[Item, Section, CC] = {
val root = rootPath(path)
// cheaty way, crap but quick enough
// TODO come back to this and properly move,
newPos.position.tail.foldLeft(root) { (path, pos) =>
Path(path, Node(pos, seqLikeThing.apply(path.children)(pos)))
}
}
type FoldR[Item <: LeftLike[Item, Tree[Item, Section, CC]], Section, CC[_]] = Either[Path[Item, Section, CC], FoldError]
type PathFoldR[Item <: LeftLike[Item, Tree[Item, Section, CC]], Section, CC[_]] = (Path[Item, Section, CC]) => FoldR[Item, Section, CC]
/**
* As per the non accumalating version, folds over positions within a given tree but allows for an additional accumalation.
*
* The progress through the document is in reverse document order. This ensures that transformations can always be safely composed, e.g. a delete of a path won't stop changes below it. This, however, implies the developer must also handle any accumulation in "reverse".
*/
def foldPositions[Item <: LeftLike[Item, Tree[Item, Section, CC]], Section, CC[_], ACC](locations: Iterable[Path[Item, Section, CC]], accumulator: ACC)(folder: (ACC, Path[Item, Section, CC]) => (ACC, FoldOperation[Item, Section, CC])) (implicit iseqLikeThing: SeqLikeThing[CC[_], ItemOrTree[Item, Section, CC], CC], cm : ClassManifest[(Position[Item,Section,CC], Path[Item, Section, CC])]) :
Either[(ACC, Path[Item, Section, CC]), FoldError] = PathFold.foldPositions(locations, accumulator)(folder)(iseqLikeThing, cm)
/**
* Folds over positions within a single path, for example all given children. As such positions must be calculated.
*
* Takes the first root, returning Right(NoSingleRoot) if any of the subsequent roots don't match.
*
* folder retrieves the current path
*
* Each iteration folds the resulting tree back into the path. As this function must maintain the Path it does not expose the new path root until the result.
*/
def foldPositions[Item <: LeftLike[Item, Tree[Item, Section, CC]], Section, CC[_]](locations: Iterable[Path[Item, Section, CC]])(folder: (Path[Item, Section, CC]) => FoldOperation[Item, Section, CC])(implicit iseqLikeThing: SeqLikeThing[CC[_], ItemOrTree[Item, Section, CC], CC], cm : ClassManifest[(Position[Item,Section,CC], Path[Item, Section, CC])]) : FoldR[Item, Section, CC] =
foldPositions[Item, Section, CC, Unit](locations, ())((u, p) => ((), folder(p))).
fold(x => Left(x._2), Right(_))
val NotSameRoot = 1000
/**
* When paths are not in the same root, they are compared based on the identity hash of the given roots. Of course this relies on that function
* having a decent vm implementation.
*
* @param path1
* @param path2
* @return 1 if path1 is before path2, -1 if path2 is before path1, 0 if they are the same and NotSameRoot+-1 if they are not in the same root
*/
def comparePathPositions[Item <: LeftLike[Item, Tree[Item, Section, CC]], Section, CC[_]](path1: Position[Item, Section, CC], path2: Position[Item, Section, CC]): Int = {
if (path1 eq path2) 0
else {
if (path1.root ne path2.root) {
val p1R = System.identityHashCode(path1.root)
val p2R = System.identityHashCode(path2.root)
NotSameRoot + (if (p1R < p2R) 1 else -1)
} else
compareStack(path1.position, path2.position)
}
}
/**
* Helper for comparePaths, will not evaluate position if the paths are equal
*/
def comparePathsDirect[Item <: LeftLike[Item, Tree[Item, Section, CC]], Section, CC[_]](path1: Path[Item, Section, CC], path2: Path[Item, Section, CC]): Boolean =
if (path1 eq path2)
true
else
comparePathsP((path1.position, path1), (path2.position, path2))._1 == 0
/**
* When paths are not in the same root, they are compared based on the identity hash of the given roots. Of course this relies on that function
* having a decent vm implementation. See http://www.w3.org/TR/2007/REC-xpath20-20070123/#dt-document-order, tree order must remain constant. Its
* also a pretty sensible approach for non xml trees.
*
* @param path1
* @param path2
* @return 1 if path1 is before path2, -1 if path2 is before path1, 0 if they are the same and NotSameRoot+-1 if they are not in the same root
*/
def comparePaths[Item <: LeftLike[Item, Tree[Item, Section, CC]], Section, CC[_]](path1: Path[Item, Section, CC], path2: Path[Item, Section, CC]): (Int, Position[Item, Section, CC], Position[Item, Section, CC]) =
comparePathsP((path1.position, path1), (path2.position, path2))
def comparePathsP[Item <: LeftLike[Item, Tree[Item, Section, CC]], Section, CC[_]](path1: (Position[Item, Section, CC], Path[Item, Section, CC]), path2: (Position[Item, Section, CC], Path[Item, Section, CC])): (Int, Position[Item, Section, CC], Position[Item, Section, CC]) = {
if (path1._2 eq path2._2) {
val pos = path1._1
(0, pos, pos)
} else {
val (pos1, pos2) = (path1._1, path2._1)
(comparePathPositions(pos1, pos2), pos1, pos2)
}
}
def comparePathsT[Item <: LeftLike[Item, Tree[Item, Section, CC]], Section, CC[_], T](path1: (Position[Item, Section, CC], (T, Path[Item, Section, CC])), path2: (Position[Item, Section, CC], (T, Path[Item, Section, CC]))): (Int, Position[Item, Section, CC], Position[Item, Section, CC]) = {
if (path1._2._2 eq path2._2._2) {
val pos = path1._1
(0, pos, pos)
} else {
val (pos1, pos2) = (path1._1, path2._1)
(comparePathPositions(pos1, pos2), pos1, pos2)
}
}
import scalaz.Equal
import scalaz.Equal.equal
/**
* Provides an instance of the Equal type class for positional Equality
*/
def toPositionalEqual[Item <: LeftLike[Item, Tree[Item, Section, CC]], Section, CC[_]] : Equal[Path[Item, Section, CC]] =
equal {
comparePathsDirect(_,_)
}
def top[Item <: LeftLike[Item, Tree[Item, Section, CC]], Section, CC[_]](tree: Tree[Item, Section, CC])
(implicit seqLikeThing: SeqLikeThing[CC[_], ItemOrTree[Item, Section, CC], CC]) : Path[Item, Section, CC] =
Path(Top(), Node(0, tree))
/**
* positions with tuples (T, Path)
*/
def positionsT[Item <: LeftLike[Item, Tree[Item, Section, CC]], Section, CC[_], T](paths: Iterable[(T, Path[Item, Section, CC])]): Iterable[(Position[Item, Section, CC], (T,Path[Item, Section, CC]))] =
paths.map(x => (x._2.position, x))
/**
* Obtain the positions for the paths
*/
def positions[Item <: LeftLike[Item, Tree[Item, Section, CC]], Section, CC[_]](paths: Iterable[Path[Item, Section, CC]]): Iterable[(Position[Item, Section, CC], Path[Item, Section, CC])] =
paths.map(x => (x.position, x))
/**
* sortPositions with a tuple T, Path
*/
def sortPositionsT[Item <: LeftLike[Item, Tree[Item, Section, CC]], Section, CC[X], T](paths: Iterable[(T,Path[Item, Section, CC])],
isDescending: Boolean = true)(implicit cm : ClassManifest[(Position[Item,Section,CC], (T, Path[Item, Section, CC]))]): Iterable[(Position[Item, Section, CC], (T, Path[Item, Section, CC]))] =
// Have to force them anyway
scala.util.Sorting.stableSort(positionsT(paths).toSeq, (p1: (Position[Item, Section, CC], (T,Path[Item, Section, CC])), p2: (Position[Item, Section, CC], (T,Path[Item, Section, CC]))) => {
val (res, pos1, pos2) = comparePathsT(p1, p2)
val order = (res == 1 || res == (NotSameRoot + 1))
if (isDescending) order else !order
})(cm)
/**
* Sorts according to position of each path item, descending or descending based on a depth first then rightwise order.
*/
def sortPositions[Item <: LeftLike[Item, Tree[Item, Section, CC]], Section, CC[_]](paths: Iterable[Path[Item, Section, CC]],
isDescending: Boolean = true)(implicit cm : ClassManifest[(Position[Item,Section,CC], Path[Item, Section, CC])]): Iterable[(Position[Item, Section, CC], Path[Item, Section, CC])] =
// Have to force them anyway
scala.util.Sorting.stableSort(positions(paths).toSeq, (p1: (Position[Item, Section, CC], Path[Item, Section, CC]), p2: (Position[Item, Section, CC], Path[Item, Section, CC])) => {
val (res, pos1, pos2) = comparePathsP(p1, p2)
val order = (res == 1 || res == (NotSameRoot + 1))
if (isDescending) order else !order
})(cm)
/**
* Sorts according to position of each path item, descending or descending based on a depth first then rightwise order.
*/
def sort[Item <: LeftLike[Item, Tree[Item, Section, CC]], Section, CC[_]](paths: Iterable[Path[Item, Section, CC]],
isDescending: Boolean = true)(implicit cm : ClassManifest[(Position[Item,Section,CC], Path[Item, Section, CC])]): Iterable[Path[Item, Section, CC]] = sortPositions(paths, isDescending).map(x => x._2)
/**
* sort with a tuple T, Path
*/
def sortT[Item <: LeftLike[Item, Tree[Item, Section, CC]], Section, CC[_], T](paths: Iterable[(T,Path[Item, Section, CC])],
isDescending: Boolean = true)(implicit cm : ClassManifest[(Position[Item,Section,CC], (T, Path[Item, Section, CC]))]): Iterable[(T,Path[Item, Section, CC])] = sortPositionsT(paths, isDescending).map(x => x._2)
/**
* Deepest last child
*/
def deepestLast[Item <: LeftLike[Item, Tree[Item, Section, CC]], Section, CC[_]](path : Path[Item, Section, CC]) : Path[Item, Section, CC] = {
if (path.hasChildren) {
val npath = path.lastChild.get
if (npath.isItem)
npath
else
deepestLast(npath) // keep going in
} else
path
}
/**
* gets the next preceding:: sibling equivalent in document order, unlike XPath preceding:: it does not exclude parents
*/
def preceding[Item <: LeftLike[Item, Tree[Item, Section, CC]], Section, CC[_]]( path : Path[Item, Section, CC] ) : Option[Path[Item, Section, CC]] =
if (path.hasPreviousSibling) {
val t = path.previousSibling
Some(
if (t.isItem)
t
else
deepestLast(t))
} else
if (path.top.isLeft)
None
else
preceding(path.top.getRight) // move up
/**
* gets the next following:: sibling in document order
*/
def following[Item <: LeftLike[Item, Tree[Item, Section, CC]], Section, CC[_]]( path : Path[Item, Section, CC] ) : Option[Path[Item, Section, CC]] =
if (path.hasNextSibling)
Some(path.nextSibling)
else
if (path.top.isLeft)
None
else
following(path.top.getRight) // move up
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy