<commit_before>package org.orbeon.oxf.xforms.library

import cats.syntax.option._
import org.orbeon.io.CharsetNames.Iso88591
import org.orbeon.macros.XPathFunction
import org.orbeon.oxf.util.StringUtils._
import org.orbeon.oxf.util.{XPath, XPathCache}
import org.orbeon.oxf.xforms.function.XFormsFunction
import org.orbeon.oxf.xforms.function.XFormsFunction.{currentLocale, relevantControl, resolveOrFindByStaticOrAbsoluteId}
import org.orbeon.oxf.xforms.model.{InstanceData, XFormsInstance, XFormsModel}
import org.orbeon.oxf.xml.OrbeonFunctionLibrary
import org.orbeon.saxon.expr.XPathContext
import org.orbeon.saxon.om
import org.orbeon.saxon.om.SequenceTool
import org.orbeon.xforms.XFormsId

import java.text.MessageFormat
import scala.jdk.CollectionConverters._


trait XXFormsEnvFunctions extends OrbeonFunctionLibrary {

  // NOTE: This is deprecated and just points to the event() function.
//    Fun("event", classOf[Event], op = 0, min = 1, Type.NODE_TYPE, ALLOWS_ZERO_OR_MORE,
//      Arg(STRING, EXACTLY_ONE)
//    )
//
//    Fun("cases", classOf[XXFormsCases], op = 0, min = 1, STRING, ALLOWS_ZERO_OR_MORE,
//      Arg(STRING, EXACTLY_ONE)
//    )
//
//    Fun("repeat-current", classOf[XXFormsRepeatCurrent], op = 0, min = 0, Type.NODE_TYPE, EXACTLY_ONE,
//      Arg(STRING, EXACTLY_ONE)
//    )
//
//    Fun("repeat-position", classOf[XXFormsRepeatPosition], op = 0, min = 0, INTEGER, EXACTLY_ONE,
//      Arg(STRING, EXACTLY_ONE)
//    )
//
//    Fun("context", classOf[XXFormsContext], op = 0, min = 1, Type.NODE_TYPE, ALLOWS_ZERO_OR_ONE,
//      Arg(STRING, EXACTLY_ONE)
//    )
//
//    Fun("repeat-items", classOf[XXFormsRepeatItems], op = 0, min = 0, Type.NODE_TYPE, ALLOWS_ZERO_OR_MORE,
//      Arg(STRING, EXACTLY_ONE)
//    )
//
//    // Backward compatibility, use repeat-items() instead
//    Fun("repeat-nodeset", classOf[XXFormsRepeatItems], op = 0, min = 0, Type.NODE_TYPE, ALLOWS_ZERO_OR_MORE,
//      Arg(STRING, EXACTLY_ONE)
//    )
//
//    Fun("evaluate-bind-property", classOf[XXFormsEvaluateBindProperty], op = 0, min = 2, ANY_ATOMIC, ALLOWS_ZERO_OR_ONE,
//      Arg(STRING, EXACTLY_ONE),
//      Arg(ANY_ATOMIC, EXACTLY_ONE) // QName or String
//    )
//
//    Fun("valid", classOf[XXFormsValid], op = 0, min = 0, BOOLEAN, EXACTLY_ONE,
//      Arg(Type.ITEM_TYPE, ALLOWS_ZERO_OR_MORE),
//      Arg(BOOLEAN, EXACTLY_ONE),
//      Arg(BOOLEAN, EXACTLY_ONE)
//    )
//
//    Fun("type", classOf[XXFormsType], op = 0, min = 0, QNAME, ALLOWS_ZERO_OR_MORE,
//      Arg(Type.ITEM_TYPE, ALLOWS_ZERO_OR_MORE)
//    )
//
//    Fun("custom-mip", classOf[XXFormsCustomMIP], op = 0, min = 2, STRING, ALLOWS_ZERO_OR_ONE,
//      Arg(Type.ITEM_TYPE, ALLOWS_ZERO_OR_MORE),
//      Arg(STRING, EXACTLY_ONE)
//    )
//
//    Fun("invalid-binds", classOf[XXFormsInvalidBinds], op = 0, min = 0, STRING, ALLOWS_ZERO_OR_MORE,
//      Arg(Type.NODE_TYPE, ALLOWS_ZERO_OR_MORE)
//    )
//
//    Fun("if", classOf[If], op = 0, min = 3, STRING, EXACTLY_ONE,
//      Arg(BOOLEAN, EXACTLY_ONE),
//      Arg(STRING, EXACTLY_ONE),
//      Arg(STRING, EXACTLY_ONE)
//    )
//
//    Fun("binding", classOf[XXFormsBinding], op = 0, min = 1, Type.ITEM_TYPE, ALLOWS_ZERO_OR_MORE,
//      Arg(STRING, EXACTLY_ONE)
//    )
//
//    Fun("binding-context", classOf[XXFormsBindingContext], op = 0, min = 1, Type.ITEM_TYPE, ALLOWS_ZERO_OR_ONE,
//      Arg(STRING, EXACTLY_ONE)
//    )
//
//    Fun("is-control-relevant", classOf[XXFormsIsControlRelevant], op = 0, min = 1, BOOLEAN, EXACTLY_ONE,
//      Arg(STRING, EXACTLY_ONE)
//    )
//
//    Fun("is-control-readonly", classOf[XXFormsIsControlReadonly], op = 0, min = 1, BOOLEAN, EXACTLY_ONE,
//      Arg(STRING, EXACTLY_ONE)
//    )
//
//    Fun("is-control-required", classOf[XXFormsIsControlRequired], op = 0, min = 1, BOOLEAN, EXACTLY_ONE,
//      Arg(STRING, EXACTLY_ONE)
//    )
//
//    Fun("is-control-valid", classOf[XXFormsIsControlValid], op = 0, min = 1, BOOLEAN, EXACTLY_ONE,
//      Arg(STRING, EXACTLY_ONE)
//    )
//
//    Fun("value", classOf[XXFormsValue], op = 0, min = 1, STRING, ALLOWS_ZERO_OR_MORE,
//      Arg(STRING, EXACTLY_ONE),
//      Arg(BOOLEAN, EXACTLY_ONE)
//    )
//
//    Fun("formatted-value", classOf[XXFormsFormattedValue], op = 0, min = 1, STRING, ALLOWS_ZERO_OR_MORE,
//      Arg(STRING, EXACTLY_ONE),
//      Arg(BOOLEAN, EXACTLY_ONE)
//    )
//
//    Fun("avt-value", classOf[XXFormsAVTValue], op = 0, min = 2, STRING, ALLOWS_ZERO_OR_MORE,
//      Arg(STRING, EXACTLY_ONE),
//      Arg(STRING, EXACTLY_ONE)
//    )
//

  @XPathFunction
  def componentContext()(implicit xfc: XFormsFunction.Context): Iterable[om.Item] =
    for {
      componentControl <- xfc.container.associatedControlOpt.toIterable
      bindingContext   <- Option(componentControl.bindingContext.parent).toIterable
      item             <- bindingContext.nodeset.asScala
    } yield
      item

//    Fun("component-param-value", classOf[XXFormsComponentParam], op = 0, min = 1, ANY_ATOMIC, ALLOWS_ZERO_OR_ONE,
//        Arg(STRING, EXACTLY_ONE)
//    )

  @XPathFunction
  def instance(
    instanceId : String)(implicit
    xpc        : XPathContext,
    xfc        : XFormsFunction.Context
  ): Option[om.NodeInfo] = {
    val r = instanceId.trimAllToOpt flatMap (XFormsInstance.findInAncestorScopes(xfc.container, _))
//    println(s"xxx result of `xxf:instance('$instanceId')` is ${r map (_.getDisplayName)}")
    r
  }

  //  Fun("index", classOf[XXFormsIndex], op = 0, min = 0, INTEGER, EXACTLY_ONE,
//      Arg(STRING, ALLOWS_ZERO_OR_ONE)
//    )

  @XPathFunction
  def listModels()(implicit xfc: XFormsFunction.Context): Iterator[String] =
    for {
      model       <- xfc.containingDocument.allModels
      effectiveId = model.getEffectiveId
      absoluteId  = XFormsId.effectiveIdToAbsoluteId(effectiveId)
    } yield
      absoluteId

  def listInstances(modelId: String)(implicit xpc: XPathContext, xfc: XFormsFunction.Context): Iterator[String] = {

    val modelOpt =
      resolveOrFindByStaticOrAbsoluteId(modelId) collect { case model: XFormsModel => model }

    for {
      model       <- modelOpt.iterator
      instance    <- model.instancesIterator
      effectiveId = instance.getEffectiveId
      absoluteId  = XFormsId.effectiveIdToAbsoluteId(effectiveId)
    } yield
      absoluteId
  }

  //
//    Fun("list-variables", classOf[XXFormsListVariables], op = 0, min = 1, STRING, ALLOWS_ZERO_OR_MORE,
//      Arg(STRING, EXACTLY_ONE)
//    )

  @XPathFunction
  def getVariable(modelEffectiveId: String, variableName: String)(implicit xfc: XFormsFunction.Context): Iterable[om.Item] = {
    xfc.containingDocument.getObjectByEffectiveId(modelEffectiveId) match {
      case model: XFormsModel =>
        val v = model.getTopLevelVariables(variableName)
        XPath.Logger.debug(s"zzz getVariable: $modelEffectiveId, $variableName, $v")
        v.asIterable().asScala
      case _                  => Nil
    }
  }

  //
//    Fun("itemset", classOf[XXFormsItemset], op = 0, min = 2, Type.ITEM_TYPE, ALLOWS_ZERO_OR_MORE,
//      Arg(STRING, EXACTLY_ONE),
//      Arg(STRING, EXACTLY_ONE),
//      Arg(BOOLEAN, EXACTLY_ONE)
//    )

  @XPathFunction
  def formatMessage(template: String, args: Iterable[om.Item])(implicit xpc: XPathContext): String = {
    // TODO: `MessageFormat` is not supported by Scala.js as of now (2020-12-29).
    //
    s"[TODO] $template"
//    new MessageFormat(template, currentLocale)
//      .format(args map SequenceTool.convertToJava toArray)
  }

  //    Fun("lang", classOf[XXFormsLang], op = 0, min = 0, STRING, ALLOWS_ZERO_OR_ONE)
//

  // TODO: last arg is`map(*)`
  @XPathFunction
  def r(resourceKey: String, instanceArgumentOpt: Option[String] = None, templateParams: om.Item = null): Option[String] = {

    // XXX TODO: implement
    s"""[TODO: implement `xxf:r('$resourceKey')`]""".some

//
//
//    Fun("r", classOf[XXFormsResource], op = 0, min = 1, STRING, ALLOWS_ZERO_OR_ONE,
//      Arg(STRING, EXACTLY_ONE),
//      Arg(STRING, EXACTLY_ONE),
//      Arg(BuiltInAtomicType.ANY_ATOMIC, EXACTLY_ONE)
//    )
  }

  //    Fun("resource-elements", classOf[XXFormsResourceElem], op = 0, min = 1, NODE_TYPE, ALLOWS_ZERO_OR_MORE,
//      Arg(STRING, EXACTLY_ONE),
//      Arg(STRING, EXACTLY_ONE)
//    )
//

  @XPathFunction
  def pendingUploads(): Int =
    XFormsFunction.context.containingDocument.countPendingUploads

  @XPathFunction
  def documentId()(implicit xfc: XFormsFunction.Context): String =
  xfc.containingDocument.uuid

  @XPathFunction
  def label(controlId: String)(implicit xpc: XPathContext): Option[String] =
    relevantControl(controlId) map (_.getLabel)

  @XPathFunction
  def help(controlId: String)(implicit xpc: XPathContext): Option[String] =
    relevantControl(controlId) map (_.getHelp)

  @XPathFunction
  def hint(controlId: String)(implicit xpc: XPathContext): Option[String] =
    relevantControl(controlId) map (_.getHint)

  @XPathFunction
  def alert(controlId: String)(implicit xpc: XPathContext): Option[String] =
    relevantControl(controlId) map (_.getAlert)

  @XPathFunction
  def visited(controlId: String)(implicit xpc: XPathContext): Option[Boolean] =
    relevantControl(controlId) map (_.visited)

//    Fun("focusable", classOf[XXFormsFocusable], op = 0, min = 1, BOOLEAN, ALLOWS_ZERO_OR_ONE,
//      Arg(STRING,  EXACTLY_ONE),
//      Arg(BOOLEAN, EXACTLY_ONE)
//    )
//
//    Fun("absolute-id", classOf[XXFormsAbsoluteId], op = 0, min = 1, STRING, ALLOWS_ZERO_OR_ONE,
//      Arg(STRING, EXACTLY_ONE)
//    )
//
//    Fun("client-id", classOf[XXFormsClientId], op = 0, min = 1, STRING, ALLOWS_ZERO_OR_ONE,
//      Arg(STRING, EXACTLY_ONE)
//    )<commit_msg>Add function<commit_after>package org.orbeon.oxf.xforms.library

import cats.syntax.option._
import org.orbeon.io.CharsetNames.Iso88591
import org.orbeon.macros.XPathFunction
import org.orbeon.oxf.util.StringUtils._
import org.orbeon.oxf.util.{XPath, XPathCache}
import org.orbeon.oxf.xforms.function.XFormsFunction
import org.orbeon.oxf.xforms.function.XFormsFunction.{currentLocale, relevantControl, resolveOrFindByStaticOrAbsoluteId}
import org.orbeon.oxf.xforms.model.{InstanceData, XFormsInstance, XFormsModel}
import org.orbeon.oxf.xml.OrbeonFunctionLibrary
import org.orbeon.saxon.expr.XPathContext
import org.orbeon.saxon.om
import org.orbeon.saxon.om.SequenceTool
import org.orbeon.xforms.XFormsId

import java.text.MessageFormat
import scala.jdk.CollectionConverters._


trait XXFormsEnvFunctions extends OrbeonFunctionLibrary {

  // NOTE: This is deprecated and just points to the event() function.
//    Fun("event", classOf[Event], op = 0, min = 1, Type.NODE_TYPE, ALLOWS_ZERO_OR_MORE,
//      Arg(STRING, EXACTLY_ONE)
//    )
//
//    Fun("cases", classOf[XXFormsCases], op = 0, min = 1, STRING, ALLOWS_ZERO_OR_MORE,
//      Arg(STRING, EXACTLY_ONE)
//    )
//
//    Fun("repeat-current", classOf[XXFormsRepeatCurrent], op = 0, min = 0, Type.NODE_TYPE, EXACTLY_ONE,
//      Arg(STRING, EXACTLY_ONE)
//    )
//
//    Fun("repeat-position", classOf[XXFormsRepeatPosition], op = 0, min = 0, INTEGER, EXACTLY_ONE,
//      Arg(STRING, EXACTLY_ONE)
//    )
//
//    Fun("context", classOf[XXFormsContext], op = 0, min = 1, Type.NODE_TYPE, ALLOWS_ZERO_OR_ONE,
//      Arg(STRING, EXACTLY_ONE)
//    )
//
//    Fun("repeat-items", classOf[XXFormsRepeatItems], op = 0, min = 0, Type.NODE_TYPE, ALLOWS_ZERO_OR_MORE,
//      Arg(STRING, EXACTLY_ONE)
//    )
//
//    // Backward compatibility, use repeat-items() instead
//    Fun("repeat-nodeset", classOf[XXFormsRepeatItems], op = 0, min = 0, Type.NODE_TYPE, ALLOWS_ZERO_OR_MORE,
//      Arg(STRING, EXACTLY_ONE)
//    )
//
//    Fun("evaluate-bind-property", classOf[XXFormsEvaluateBindProperty], op = 0, min = 2, ANY_ATOMIC, ALLOWS_ZERO_OR_ONE,
//      Arg(STRING, EXACTLY_ONE),
//      Arg(ANY_ATOMIC, EXACTLY_ONE) // QName or String
//    )
//
//    Fun("valid", classOf[XXFormsValid], op = 0, min = 0, BOOLEAN, EXACTLY_ONE,
//      Arg(Type.ITEM_TYPE, ALLOWS_ZERO_OR_MORE),
//      Arg(BOOLEAN, EXACTLY_ONE),
//      Arg(BOOLEAN, EXACTLY_ONE)
//    )
//
//    Fun("type", classOf[XXFormsType], op = 0, min = 0, QNAME, ALLOWS_ZERO_OR_MORE,
//      Arg(Type.ITEM_TYPE, ALLOWS_ZERO_OR_MORE)
//    )
//
//    Fun("custom-mip", classOf[XXFormsCustomMIP], op = 0, min = 2, STRING, ALLOWS_ZERO_OR_ONE,
//      Arg(Type.ITEM_TYPE, ALLOWS_ZERO_OR_MORE),
//      Arg(STRING, EXACTLY_ONE)
//    )
//
//    Fun("invalid-binds", classOf[XXFormsInvalidBinds], op = 0, min = 0, STRING, ALLOWS_ZERO_OR_MORE,
//      Arg(Type.NODE_TYPE, ALLOWS_ZERO_OR_MORE)
//    )
//
//    Fun("if", classOf[If], op = 0, min = 3, STRING, EXACTLY_ONE,
//      Arg(BOOLEAN, EXACTLY_ONE),
//      Arg(STRING, EXACTLY_ONE),
//      Arg(STRING, EXACTLY_ONE)
//    )
//
//    Fun("binding", classOf[XXFormsBinding], op = 0, min = 1, Type.ITEM_TYPE, ALLOWS_ZERO_OR_MORE,
//      Arg(STRING, EXACTLY_ONE)
//    )
//
//    Fun("binding-context", classOf[XXFormsBindingContext], op = 0, min = 1, Type.ITEM_TYPE, ALLOWS_ZERO_OR_ONE,
//      Arg(STRING, EXACTLY_ONE)
//    )
//
//    Fun("is-control-relevant", classOf[XXFormsIsControlRelevant], op = 0, min = 1, BOOLEAN, EXACTLY_ONE,
//      Arg(STRING, EXACTLY_ONE)
//    )
//
//    Fun("is-control-readonly", classOf[XXFormsIsControlReadonly], op = 0, min = 1, BOOLEAN, EXACTLY_ONE,
//      Arg(STRING, EXACTLY_ONE)
//    )
//
//    Fun("is-control-required", classOf[XXFormsIsControlRequired], op = 0, min = 1, BOOLEAN, EXACTLY_ONE,
//      Arg(STRING, EXACTLY_ONE)
//    )
//
//    Fun("is-control-valid", classOf[XXFormsIsControlValid], op = 0, min = 1, BOOLEAN, EXACTLY_ONE,
//      Arg(STRING, EXACTLY_ONE)
//    )
//
//    Fun("value", classOf[XXFormsValue], op = 0, min = 1, STRING, ALLOWS_ZERO_OR_MORE,
//      Arg(STRING, EXACTLY_ONE),
//      Arg(BOOLEAN, EXACTLY_ONE)
//    )
//
//    Fun("formatted-value", classOf[XXFormsFormattedValue], op = 0, min = 1, STRING, ALLOWS_ZERO_OR_MORE,
//      Arg(STRING, EXACTLY_ONE),
//      Arg(BOOLEAN, EXACTLY_ONE)
//    )
//
//    Fun("avt-value", classOf[XXFormsAVTValue], op = 0, min = 2, STRING, ALLOWS_ZERO_OR_MORE,
//      Arg(STRING, EXACTLY_ONE),
//      Arg(STRING, EXACTLY_ONE)
//    )
//

  @XPathFunction
  def componentContext()(implicit xfc: XFormsFunction.Context): Iterable[om.Item] =
    for {
      componentControl <- xfc.container.associatedControlOpt.toIterable
      bindingContext   <- Option(componentControl.bindingContext.parent).toIterable
      item             <- bindingContext.nodeset.asScala
    } yield
      item

//    Fun("component-param-value", classOf[XXFormsComponentParam], op = 0, min = 1, ANY_ATOMIC, ALLOWS_ZERO_OR_ONE,
//        Arg(STRING, EXACTLY_ONE)
//    )

  @XPathFunction
  def instance(
    instanceId : String)(implicit
    xpc        : XPathContext,
    xfc        : XFormsFunction.Context
  ): Option[om.NodeInfo] = {
    val r = instanceId.trimAllToOpt flatMap (XFormsInstance.findInAncestorScopes(xfc.container, _))
//    println(s"xxx result of `xxf:instance('$instanceId')` is ${r map (_.getDisplayName)}")
    r
  }

  //  Fun("index", classOf[XXFormsIndex], op = 0, min = 0, INTEGER, EXACTLY_ONE,
//      Arg(STRING, ALLOWS_ZERO_OR_ONE)
//    )

  @XPathFunction
  def listModels()(implicit xfc: XFormsFunction.Context): Iterator[String] =
    for {
      model       <- xfc.containingDocument.allModels
      effectiveId = model.getEffectiveId
      absoluteId  = XFormsId.effectiveIdToAbsoluteId(effectiveId)
    } yield
      absoluteId

  def listInstances(modelId: String)(implicit xpc: XPathContext, xfc: XFormsFunction.Context): Iterator[String] = {

    val modelOpt =
      resolveOrFindByStaticOrAbsoluteId(modelId) collect { case model: XFormsModel => model }

    for {
      model       <- modelOpt.iterator
      instance    <- model.instancesIterator
      effectiveId = instance.getEffectiveId
      absoluteId  = XFormsId.effectiveIdToAbsoluteId(effectiveId)
    } yield
      absoluteId
  }

  //
//    Fun("list-variables", classOf[XXFormsListVariables], op = 0, min = 1, STRING, ALLOWS_ZERO_OR_MORE,
//      Arg(STRING, EXACTLY_ONE)
//    )

  @XPathFunction
  def getVariable(modelEffectiveId: String, variableName: String)(implicit xfc: XFormsFunction.Context): Iterable[om.Item] = {
    xfc.containingDocument.getObjectByEffectiveId(modelEffectiveId) match {
      case model: XFormsModel =>
        val v = model.getTopLevelVariables(variableName)
        XPath.Logger.debug(s"zzz getVariable: $modelEffectiveId, $variableName, $v")
        v.asIterable().asScala
      case _                  => Nil
    }
  }

  //
//    Fun("itemset", classOf[XXFormsItemset], op = 0, min = 2, Type.ITEM_TYPE, ALLOWS_ZERO_OR_MORE,
//      Arg(STRING, EXACTLY_ONE),
//      Arg(STRING, EXACTLY_ONE),
//      Arg(BOOLEAN, EXACTLY_ONE)
//    )

  @XPathFunction
  def formatMessage(template: String, args: Iterable[om.Item])(implicit xpc: XPathContext): String = {
    // TODO: `MessageFormat` is not supported by Scala.js as of now (2020-12-29).
    //
    s"[TODO] $template"
//    new MessageFormat(template, currentLocale)
//      .format(args map SequenceTool.convertToJava toArray)
  }

  //    Fun("lang", classOf[XXFormsLang], op = 0, min = 0, STRING, ALLOWS_ZERO_OR_ONE)
//

  // TODO: last arg is`map(*)`
  @XPathFunction
  def r(resourceKey: String, instanceArgumentOpt: Option[String] = None, templateParams: om.Item = null): Option[String] = {

    // XXX TODO: implement
    s"""[TODO: implement `xxf:r('$resourceKey')`]""".some

//
//
//    Fun("r", classOf[XXFormsResource], op = 0, min = 1, STRING, ALLOWS_ZERO_OR_ONE,
//      Arg(STRING, EXACTLY_ONE),
//      Arg(STRING, EXACTLY_ONE),
//      Arg(BuiltInAtomicType.ANY_ATOMIC, EXACTLY_ONE)
//    )
  }

  //    Fun("resource-elements", classOf[XXFormsResourceElem], op = 0, min = 1, NODE_TYPE, ALLOWS_ZERO_OR_MORE,
//      Arg(STRING, EXACTLY_ONE),
//      Arg(STRING, EXACTLY_ONE)
//    )
//

  @XPathFunction
  def pendingUploads(): Int =
    XFormsFunction.context.containingDocument.countPendingUploads

  @XPathFunction
  def documentId()(implicit xfc: XFormsFunction.Context): String =
  xfc.containingDocument.uuid

  @XPathFunction
  def label(controlId: String)(implicit xpc: XPathContext): Option[String] =
    relevantControl(controlId) map (_.getLabel)

  @XPathFunction
  def help(controlId: String)(implicit xpc: XPathContext): Option[String] =
    relevantControl(controlId) map (_.getHelp)

  @XPathFunction
  def hint(controlId: String)(implicit xpc: XPathContext): Option[String] =
    relevantControl(controlId) map (_.getHint)

  @XPathFunction
  def alert(controlId: String)(implicit xpc: XPathContext): Option[String] =
    relevantControl(controlId) map (_.getAlert)

  @XPathFunction
  def visited(controlId: String)(implicit xpc: XPathContext): Option[Boolean] =
    relevantControl(controlId) map (_.visited)

//    Fun("focusable", classOf[XXFormsFocusable], op = 0, min = 1, BOOLEAN, ALLOWS_ZERO_OR_ONE,
//      Arg(STRING,  EXACTLY_ONE),
//      Arg(BOOLEAN, EXACTLY_ONE)
//    )

  @XPathFunction
  def absoluteId(staticOrAbsoluteId: String)(implicit xpc: XPathContext): Option[String] =
    resolveOrFindByStaticOrAbsoluteId(staticOrAbsoluteId) map (_.getEffectiveId) map XFormsId.effectiveIdToAbsoluteId

//
//    Fun("client-id", classOf[XXFormsClientId], op = 0, min = 1, STRING, ALLOWS_ZERO_OR_ONE,
//      Arg(STRING, EXACTLY_ONE)
//    )
{'fraction_non_alphanumeric': 0.14245910943707754, 'fraction_numerical': 0.00784273257313864, 'mean_word_length': 3.3751693002257337, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4337136', 'n_tokens_mistral': 7214, 'n_tokens_neox': 6957, 'n_words': 1255}