diff --git a/src/main/scala/org/orbeon/oxf/xforms/function/xxforms/XXFormsResource.scala b/src/main/scala/org/orbeon/oxf/xforms/function/xxforms/XXFormsResource.scala
index c7ec81ecfb..8b0b32831f 100644
--- a/src/main/scala/org/orbeon/oxf/xforms/function/xxforms/XXFormsResource.scala
+++ b/src/main/scala/org/orbeon/oxf/xforms/function/xxforms/XXFormsResource.scala
@@ -13,19 +13,22 @@
*/
package org.orbeon.oxf.xforms.function.xxforms
-import org.apache.commons.lang3.StringUtils
import org.orbeon.oxf.util.StringUtils._
import org.orbeon.oxf.xforms.function.{Instance, XFormsFunction}
import org.orbeon.oxf.xforms.model.XFormsInstance
import org.orbeon.saxon.`type`.Type
import org.orbeon.saxon.expr.{AxisExpression, PathMap, StringLiteral, XPathContext}
-import org.orbeon.saxon.om.{Axis, NamespaceConstant, NodeInfo, StructuredQName}
+import org.orbeon.saxon.om._
import org.orbeon.saxon.pattern.NameTest
import org.orbeon.saxon.value.StringValue
import org.orbeon.scaxon.XML._
+import scala.annotation.tailrec
+
class XXFormsResource extends XFormsFunction {
+ import XXFormsResource._
+
override def evaluateItem(xpathContext: XPathContext): StringValue = {
implicit val ctx = xpathContext
@@ -48,9 +51,9 @@ class XXFormsResource extends XFormsFunction {
resources ← findResourcesElement
requestedLang ← XXFormsLang.resolveXMLangHandleAVTs(getContainingDocument, elementAnalysis)
resourceRoot ← findResourceElementForLang(resources, requestedLang)
- leaf ← path(resourceRoot, StringUtils.replace(stringArgument(0), ".", "/"))
+ leaves ← pathFromTokens(resourceRoot, splitResourceName(stringArgument(0))).headOption
} yield
- stringToStringValue(leaf.stringValue)
+ stringToStringValue(leaves.stringValue)
resultOpt.orNull
}
@@ -64,7 +67,7 @@ class XXFormsResource extends XFormsFunction {
// function must work in either case, readonly and readwrite.
val resourcePath = arguments.head match {
case s: StringLiteral ⇒
- s.getStringValue.splitTo[List]("/.")
+ flattenResourceName(s.getStringValue) // this removes the indexes if any
case _ ⇒
pathMap.setInvalidated(true)
return null
@@ -112,3 +115,45 @@ class XXFormsResource extends XFormsFunction {
null
}
}
+
+object XXFormsResource {
+
+ private val IndexRegex = """(\d+)""".r
+
+ def splitResourceName(s: String): List[String] =
+ s.splitTo[List](".")
+
+ def flattenResourceName(s: String): List[String] =
+ splitResourceName(s) filter Name10Checker.getInstance.isValidNCName
+
+ // Hand-made simple path search
+ //
+ // - path *must* have the form `foo.bar.2.baz` (names with optional index parts)
+ // - each path element must be a NCName (non-qualified) except for indexes
+ // - as in XPath, non-qualified names mean "in no namespace"
+ //
+ // NOTE: Ideally should check if this is faster than using a pre-compiled Saxon XPath expression!
+ def pathFromTokens(context: NodeInfo, tokens: List[String]): List[NodeInfo] = {
+
+ @tailrec
+ def findChild(parents: List[NodeInfo], tokens: List[String]): List[NodeInfo] =
+ tokens match {
+ case Nil ⇒ parents
+ case token :: restTokens ⇒
+ parents match {
+ case Nil ⇒ Nil
+ case parents ⇒
+ token match {
+ case IndexRegex(index) ⇒
+ findChild(List(parents(index.toInt)), restTokens)
+ case path if Name10Checker.getInstance.isValidNCName(token) ⇒
+ findChild(parents / token toList, restTokens)
+ case _ ⇒
+ throw new IllegalArgumentException(s"invalid resource path `${tokens mkString "."}`")
+ }
+ }
+ }
+
+ findChild(List(context), tokens)
+ }
+}
\ No newline at end of file
diff --git a/src/main/scala/org/orbeon/scaxon/XML.scala b/src/main/scala/org/orbeon/scaxon/XML.scala
index af27f4dcfe..60ecb8343c 100644
--- a/src/main/scala/org/orbeon/scaxon/XML.scala
+++ b/src/main/scala/org/orbeon/scaxon/XML.scala
@@ -20,8 +20,8 @@ import org.orbeon.oxf.util.XPath
import org.orbeon.oxf.util.XPath._
import org.orbeon.oxf.util.XPathCache._
import org.orbeon.oxf.xforms.XFormsStaticStateImpl.BASIC_NAMESPACE_MAPPING
-import org.orbeon.oxf.xforms.action.XFormsAPI._
import org.orbeon.oxf.xforms.XFormsUtils
+import org.orbeon.oxf.xforms.action.XFormsAPI._
import org.orbeon.oxf.xforms.model.XFormsInstance
import org.orbeon.oxf.xml.dom4j.Dom4jUtils
import org.orbeon.oxf.xml.{NamespaceMapping, TransformerUtils, XMLParsing, XMLReceiver}
@@ -34,7 +34,6 @@ import org.orbeon.saxon.tinytree.TinyTree
import org.orbeon.saxon.value.StringValue
import org.orbeon.saxon.xqj.{SaxonXQDataFactory, StandardObjectConverter}
-import scala.annotation.tailrec
import scala.collection.JavaConverters._
import scala.collection._
import scala.xml.Elem
@@ -557,26 +556,6 @@ object XML {
}
}
- // Hand-made simple path search
- // - path *must* have the form "foo/bar/baz"
- // - each path element must be a NCName (non-qualified)
- // - as in XPath, non-qualified names mean "in no namespace"
- def path(context: NodeInfo, path: String) = {
-
- @tailrec def findChild(parent: Option[NodeInfo], tokens: List[String]): Option[NodeInfo] =
- if (tokens.isEmpty)
- parent
- else
- parent match {
- case Some(p) ⇒ findChild(p child ("" → tokens.head) headOption, tokens.tail)
- case None ⇒ None
- }
-
- val tokens = path.splitTo[List]("/")
-
- findChild(Some(context), tokens)
- }
-
// Convert a Java object to a Saxon Item using the Saxon API
val anyToItem = new StandardObjectConverter(new SaxonXQDataFactory {
def getConfiguration = XPath.GlobalConfiguration
diff --git a/src/test/resources/org/orbeon/oxf/xforms/tests-xforms-xpath-analysis.xml b/src/test/resources/org/orbeon/oxf/xforms/tests-xforms-xpath-analysis.xml
index 2fa0bf3055..e846c297a1 100644
--- a/src/test/resources/org/orbeon/oxf/xforms/tests-xforms-xpath-analysis.xml
+++ b/src/test/resources/org/orbeon/oxf/xforms/tests-xforms-xpath-analysis.xml
@@ -7311,8 +7311,8 @@
- Download
- Télécharger
+
+
@@ -7322,20 +7322,22 @@
- Hello!
- Bonjour!
+
+
-
+
+
-
+
+
@@ -7361,12 +7363,29 @@
-