cats.xml.xpath.CursorBuilder.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cats-xml-xpath_3 Show documentation
Show all versions of cats-xml-xpath_3 Show documentation
A purely functional XML library
The newest version!
package cats.xml.xpath
import cats.parse.Parser
import cats.xml.cursor.NodeCursor
import cats.xml.cursor.NodeCursor.Root
import cats.xml.xpath.error.XPathError
import cats.xml.XmlNode
import cats.Endo
import eu.cdevreeze.xpathparser.ast.*
import eu.cdevreeze.xpathparser.common.{PrefixedName, UnprefixedName}
import eu.cdevreeze.xpathparser.parse.XPathParser
object CursorBuilder {
def fromXPath(xpathValue: String): Either[XPathError, NodeCursor] =
XPathParser.xpathExpr.parse(xpathValue) match {
case Left(value: Parser.Error) =>
Left(XPathError.ParsingError(value))
case Right((_, xpathExpr)) =>
CursorBuilder.fromXPath(xpathExpr)
}
def fromXPath(xpathExpr: XPathExpr): Either[XPathError, NodeCursor] = {
xpathExpr match {
case expr: Expr => cursorModifierFromExpr(expr).map(_.apply(Root))
}
}
// ---------------------------------------------//
private def notSupported[T](
feature: XPathElem
): Either[XPathError, T] =
Left(XPathError.NotSupportedConstruction(feature))
private def cursorModifierFromForwardStep(
step: ForwardStep
): Either[XPathError, Endo[NodeCursor]] =
step match {
case nafs @ NonAbbrevForwardStep(_, _) => notSupported(nafs)
case step: AbbrevForwardStep =>
step match {
case SimpleAbbrevForwardStep(nodeTest) => cursorModifierFromNodeTest(nodeTest)
case aaafs @ AttributeAxisAbbrevForwardStep(_) => notSupported(aaafs)
}
}
private def cursorModifierFromNodeTest(test: NodeTest): Either[XPathError, Endo[NodeCursor]] =
test match {
case test: KindTest =>
notSupported(test)
case test: NameTest =>
test match {
case snt @ SimpleNameTest(name) =>
name match {
case EQName.QName(qname) =>
qname match {
case UnprefixedName(localPart) =>
Right(NodeCursor.endo(_.down(localPart)))
case PrefixedName(_, _) => notSupported(snt)
}
case EQName.URIQualifiedName(_) => notSupported(snt)
}
case wildcard: Wildcard =>
wildcard match {
case _ @AnyWildcard => Right(NodeCursor.endo(_.downWildcard))
case pw @ PrefixWildcard(_) => notSupported(pw)
case lnw @ LocalNameWildcard(_) => notSupported(lnw)
case nw @ NamespaceWildcard(_) => notSupported(nw)
}
}
}
private def cursorModifierFromStepExpr(
stepExpr: StepExpr
): Either[XPathError, Endo[NodeCursor]] =
stepExpr match {
case postfixExpr: PostfixExpr => notSupported(postfixExpr)
case axisStep: AxisStep => cursorModifierFromAxisStep(axisStep)
}
private def cursorModifierFromAxisStep(step: AxisStep): Either[XPathError, Endo[NodeCursor]] =
step match {
case ForwardAxisStep(step, predicateList) =>
predicateList.foldLeft(cursorModifierFromForwardStep(step)) { case (res, predicate) =>
res.flatMap(f => cursorModifierFromPostfix(predicate).map(f.andThen))
}
case ras @ ReverseAxisStep(_, _) => notSupported(ras)
}
private def cursorModifierFromExpr(expr: Expr): Either[XPathError, Endo[NodeCursor]] =
expr match {
case simpleExpr: SimpleExpr =>
simpleExpr match {
case single: ExprSingle =>
single match {
case fe @ ForExpr(_, _) => notSupported(fe)
case le @ LetExpr(_, _) => notSupported(le)
case qe @ QuantifiedExpr(_, _, _) => notSupported(qe)
case ie @ IfExpr(_, _, _) => notSupported(ie)
case orExpr: OrExpr => cursorModifierFromOrExpr(orExpr)
}
}
case ce @ CompoundExpr(_, _) => notSupported(ce)
}
private def cursorModifierFromOrExpr(orExpr: OrExpr): Either[XPathError, Endo[NodeCursor]] =
orExpr match {
case simpleOrExpr: SimpleOrExpr =>
simpleOrExpr match {
case andExpr: AndExpr => cursorModifierFromAndExpr(andExpr)
}
case coe @ CompoundOrExpr(_, _) => notSupported(coe)
}
private def cursorModifierFromAndExpr(
andExpr: AndExpr
): Either[XPathError, Endo[NodeCursor]] =
andExpr match {
case simpleAndExpr: SimpleAndExpr =>
simpleAndExpr match {
case comparisonExpr: ComparisonExpr => cursorModifierFromComparisonExpr(comparisonExpr)
}
case cae @ CompoundAndExpr(_, _) => notSupported(cae)
}
private def cursorModifierFromComparisonExpr(
comparisonExpr: ComparisonExpr
): Either[XPathError, Endo[NodeCursor]] =
comparisonExpr match {
case simpleComparisonExpr: SimpleComparisonExpr =>
simpleComparisonExpr match {
case stringConcatExpr: StringConcatExpr =>
cursorModifierFromStringConcatExpr(stringConcatExpr)
}
case cce @ CompoundComparisonExpr(_, _, _) =>
notSupported(cce)
}
private def cursorModifierFromStringConcatExpr(
stringConcatExpr: StringConcatExpr
): Either[XPathError, Endo[NodeCursor]] =
stringConcatExpr match {
case simpleStringConcatExpr: SimpleStringConcatExpr =>
simpleStringConcatExpr match {
case rangeExpr: RangeExpr => cursorModifierFromRangeExpr(rangeExpr)
}
case csce @ CompoundStringConcatExpr(_, _) => notSupported(csce)
}
private def cursorModifierFromRangeExpr(
rangeExpr: RangeExpr
): Either[XPathError, Endo[NodeCursor]] =
rangeExpr match {
case simpleRangeExpr: SimpleRangeExpr =>
simpleRangeExpr match {
case additiveExpr: AdditiveExpr => cursorModifierFromAdditiveExpr(additiveExpr)
}
case cre @ CompoundRangeExpr(_, _) => notSupported(cre)
}
private def cursorModifierFromAdditiveExpr(
additiveExpr: AdditiveExpr
): Either[XPathError, Endo[NodeCursor]] =
additiveExpr match {
case simpleAdditiveExpr: SimpleAdditiveExpr =>
simpleAdditiveExpr match {
case multiplicativeExpr: MultiplicativeExpr =>
cursorModifierFromMultiplicativeExpr(multiplicativeExpr)
}
case cae @ CompoundAdditiveExpr(_, _, _) =>
notSupported(cae)
}
private def cursorModifierFromMultiplicativeExpr(
multiplicativeExpr: MultiplicativeExpr
): Either[XPathError, Endo[NodeCursor]] =
multiplicativeExpr match {
case simpleMultiplicativeExpr: SimpleMultiplicativeExpr =>
simpleMultiplicativeExpr match {
case unionExpr: UnionExpr => cursorModifierFromUnionExpr(unionExpr)
}
case cme @ CompoundMultiplicativeExpr(_, _, _) =>
notSupported(cme)
}
private def cursorModifierFromUnionExpr(
unionExpr: UnionExpr
): Either[XPathError, Endo[NodeCursor]] =
unionExpr match {
case simpleUnionExpr: SimpleUnionExpr =>
simpleUnionExpr match {
case intersectExceptExpr: IntersectExceptExpr =>
cursorModifierFromIntersectExceptExpr(intersectExceptExpr)
}
case cue @ CompoundUnionExpr(_, _) => notSupported(cue)
}
private def cursorModifierFromIntersectExceptExpr(
intersectExceptExpr: IntersectExceptExpr
): Either[XPathError, Endo[NodeCursor]] =
intersectExceptExpr match {
case simpleIntersectExceptExpr: SimpleIntersectExceptExpr =>
simpleIntersectExceptExpr match {
case instanceOfExpr: InstanceOfExpr =>
cursorModifierFromInstanceOfExpr(instanceOfExpr)
}
case ciee @ CompoundIntersectExceptExpr(_, _, _) =>
notSupported(ciee)
}
private def cursorModifierFromInstanceOfExpr(
instanceOfExpr: InstanceOfExpr
): Either[XPathError, Endo[NodeCursor]] =
instanceOfExpr match {
case simpleInstanceOfExpr: SimpleInstanceOfExpr =>
simpleInstanceOfExpr match {
case treatExpr: TreatExpr => cursorModifierFromTreatExpr(treatExpr)
}
case cioe @ CompoundInstanceOfExpr(_, _) => notSupported(cioe)
}
private def cursorModifierFromTreatExpr(
treatExpr: TreatExpr
): Either[XPathError, Endo[NodeCursor]] =
treatExpr match {
case simpleTreatExpr: SimpleTreatExpr =>
simpleTreatExpr match {
case castableExpr: CastableExpr => cursorModifierFromCastableExpr(castableExpr)
}
case cte @ CompoundTreatExpr(_, _) => notSupported(cte)
}
private def cursorModifierFromCastableExpr(
castableExpr: CastableExpr
): Either[XPathError, Endo[NodeCursor]] =
castableExpr match {
case simpleCastableExpr: SimpleCastableExpr =>
simpleCastableExpr match {
case castExpr: CastExpr => cursorModifierFromCastExpr(castExpr)
}
case cce @ CompoundCastableExpr(_, _) => notSupported(cce)
}
private def cursorModifierFromCastExpr(
castExpr: CastExpr
): Either[XPathError, Endo[NodeCursor]] =
castExpr match {
case simpleCastExpr: SimpleCastExpr =>
simpleCastExpr match {
case arrowExpr: ArrowExpr => cursorModifierFromArrowExpr(arrowExpr)
}
case cce @ CompoundCastExpr(_, _) => notSupported(cce)
}
private def cursorModifierFromArrowExpr(
arrowExpr: ArrowExpr
): Either[XPathError, Endo[NodeCursor]] =
arrowExpr match {
case simpleArrowExpr: SimpleArrowExpr =>
simpleArrowExpr match {
case unaryExpr: UnaryExpr => cursorModifierFromUnaryExpr(unaryExpr)
}
case cae @ CompoundArrowExpr(_, _) => notSupported(cae)
}
private def cursorModifierFromUnaryExpr(
unaryExpr: UnaryExpr
): Either[XPathError, Endo[NodeCursor]] =
unaryExpr match {
case simpleUnaryExpr: SimpleUnaryExpr =>
simpleUnaryExpr match {
case valueExpr: ValueExpr => cursorModifierFromValueExpr(valueExpr)
}
case cue @ CompoundUnaryExpr(_, _) => notSupported(cue)
}
private def cursorModifierFromValueExpr(
valueExpr: ValueExpr
): Either[XPathError, Endo[NodeCursor]] =
valueExpr match {
case simpleMapExpr: SimpleMapExpr =>
simpleMapExpr match {
case simpleSimpleMapExpr: SimpleSimpleMapExpr =>
simpleSimpleMapExpr match {
case pathExpr: PathExpr => cursorModifierFromPathExpr(pathExpr)
}
case csme @ CompoundSimpleMapExpr(_, _) => notSupported(csme)
}
}
private def cursorModifierFromPathExpr(
pathExpr: PathExpr
): Either[XPathError, Endo[NodeCursor]] =
pathExpr match {
case SlashOnlyPathExpr => Right(identity)
case PathExprStartingWithSingleSlash(relativePathExpr) =>
cursorModifierFromRelativePathExpr(relativePathExpr)
case peswds @ PathExprStartingWithDoubleSlash(_) => notSupported(peswds)
case relativePathExpr: RelativePathExpr =>
cursorModifierFromRelativePathExpr(relativePathExpr)
}
private def cursorModifierFromRelativePathExpr(
relativePathExpr: RelativePathExpr
): Either[XPathError, Endo[NodeCursor]] =
relativePathExpr match {
case simpleRelativePathExpr: SimpleRelativePathExpr =>
simpleRelativePathExpr match {
case stepExpr: StepExpr => cursorModifierFromStepExpr(stepExpr)
}
case crpe @ CompoundRelativePathExpr(_, StepOp.DoubleSlash, _) =>
notSupported(crpe)
case CompoundRelativePathExpr(init, StepOp.SingleSlash, lastStepExpr) =>
cursorModifierFromRelativePathExpr(init)
.flatMap(i => cursorModifierFromStepExpr(lastStepExpr).map(i.andThen))
}
private def cursorModifierFromPostfix(
postfix: Postfix
): Either[XPathError, Endo[NodeCursor]] =
postfix match {
case Predicate(IntegerLiteral(v)) =>
Right(NodeCursor.endo(_.atIndex(v.toInt)))
case Predicate(FunctionCall(EQNameEx("last"), ArgumentList(EmptySeq()))) =>
Right(NodeCursor.endo(_.last))
case Predicate(expr) =>
PredicateBuilder.fromExpr(expr).map(p => NodeCursor.endo(_.filter(p)))
case al @ ArgumentList(_) =>
notSupported(al)
case pl @ PostfixLookup(_) =>
notSupported(pl)
}
object PredicateBuilder {
import cats.syntax.apply.*
import cats.syntax.foldable.*
import cats.syntax.traverse.*
import eu.cdevreeze.xpathparser.ast.*
def fromExpr(expr: Expr): Either[XPathError, XmlNode => Boolean] =
expr match {
case expr: CompoundComparisonExpr =>
fromCompoundComparisonExpr(expr)
case expr: RelativePathExpr =>
fromRelativePathExpr(expr).map(_(_ => true))
case CompoundOrExpr(head, tail) =>
import cats.xml.xpath.utils.predicate.or.*
(fromExpr(head), tail.traverse(fromExpr(_))).mapN(_ +: _).map(_.combineAll)
case CompoundAndExpr(head, tail) =>
import cats.xml.xpath.utils.predicate.and.*
(fromExpr(head), tail.traverse(fromExpr(_))).mapN(_ +: _).map(_.combineAll)
case e => notSupported(e)
}
private def fromCompoundComparisonExpr(
expr: CompoundComparisonExpr
): Either[XPathError, XmlNode => Boolean] =
expr match {
case CompoundComparisonExpr(
cpe @ CompoundOrExact(
ForwardAxisStep(
AttributeAxisAbbrevForwardStep(SimpleNameTest(EQNameEx(name))),
EmptySeq()
)
),
GeneralComp.Eq,
StringLiteral(value)
) =>
fromRelativePathExpr(cpe)
.map(pModifier => pModifier(_.hasAllAttributes(name -> value)))
case CompoundComparisonExpr(
cpe @ CompoundOrExact(
ForwardAxisStep(SimpleAbbrevForwardStep(SimpleNameTest(_)), EmptySeq())
),
comp: GeneralComp,
IntegerLiteral(value)
) =>
fromRelativePathExpr(cpe)
.map[XmlNode => Boolean](pModifier =>
pModifier(
_.text
.flatMap(_.as[Int].toOption)
.exists(comp match {
case GeneralComp.Eq => _ == value.toInt
case GeneralComp.Ne => _ != value.toInt
case GeneralComp.Lt => _ < value.toInt
case GeneralComp.Le => _ <= value.toInt
case GeneralComp.Gt => _ > value.toInt
case GeneralComp.Ge => _ >= value.toInt
})
)
)
case CompoundComparisonExpr(
cpe @ CompoundOrExact(ForwardAxisStep(SimpleAbbrevForwardStep(TextTest), EmptySeq())),
GeneralComp.Eq,
StringLiteral(value)
) =>
fromRelativePathExpr(cpe).map(pModifier => pModifier(_.textString == value))
case e => notSupported(e)
}
private def fromRelativePathExpr(
expr: RelativePathExpr
): Either[XPathError, Endo[XmlNode => Boolean]] =
expr match {
case ForwardAxisStep(SimpleAbbrevForwardStep(SimpleNameTest(EQNameEx(name))), EmptySeq()) =>
Right(xpred(p => _.hasChild(name, p)))
case CompoundRelativePathExpr(initExpr: RelativePathExpr, StepOp.SingleSlash, lastStep) =>
// check the order
for {
init <- fromRelativePathExpr(initExpr)
last <- fromRelativePathExpr(lastStep)
} yield last.andThen(init)
case ForwardAxisStep(SimpleAbbrevForwardStep(TextTest), EmptySeq()) =>
Right(xpred(identity))
case ForwardAxisStep(AttributeAxisAbbrevForwardStep(SimpleNameTest(_)), EmptySeq()) =>
Right(xpred(identity))
case FunctionCall(
EQNameEx(fn @ ("contains" | "starts-with" | "ends-with")),
ArgumentList(
UnSeq(
ExprSingleArgument(
ForwardAxisStep(SimpleAbbrevForwardStep(TextTest), EmptySeq())
),
ExprSingleArgument(StringLiteral(value))
)
)
) =>
fn match {
case "contains" => Right(xpredConst(_.textString.contains(value)))
case "starts-with" => Right(xpredConst(_.textString.startsWith(value)))
case "ends-with" => Right(xpredConst(_.textString.endsWith(value)))
}
case FunctionCall(
EQNameEx(fn @ ("contains" | "starts-with" | "ends-with")),
ArgumentList(
UnSeq(
ExprSingleArgument(
ForwardAxisStep(
AttributeAxisAbbrevForwardStep(SimpleNameTest(EQNameEx(attrKey))),
EmptySeq()
)
),
ExprSingleArgument(StringLiteral(value))
)
)
) =>
fn match {
case "contains" =>
Right(xpredConst(_.hasAllAttributes(_.exists(attrKey)(_.contains(value)))))
case "starts-with" =>
Right(xpredConst(_.hasAllAttributes(_.exists(attrKey)(_.startsWith(value)))))
case "ends-with" =>
Right(xpredConst(_.hasAllAttributes(_.exists(attrKey)(_.endsWith(value)))))
}
case FunctionCall(
EQNameEx("not"),
ArgumentList(
UnSeq(
ExprSingleArgument(fc: FunctionCall)
)
)
) =>
fromRelativePathExpr(fc).map(_.andThen(_.andThen(res => !res)))
case e => notSupported(e)
}
@inline private def xpred(
f: Endo[XmlNode => Boolean]
): Endo[XmlNode => Boolean] =
f
@inline private def xpredConst(
f: XmlNode => Boolean
): Endo[XmlNode => Boolean] =
_ => f
private object CompoundOrExact {
def unapply(expr: RelativePathExpr): Option[StepExpr] =
expr match {
case se: StepExpr => Some(se)
case crpe: CompoundRelativePathExpr => Some(crpe.lastStepExpr)
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy