<commit_before>/**
 * Copyright (C) 2013 Orbeon, Inc.
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the
 * GNU Lesser General Public License as published by the Free Software Foundation; either version
 * 2.1 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
 */
package org.orbeon.oxf.fr

import enumeratum.EnumEntry.Lowercase
import enumeratum._
import org.orbeon.oxf.externalcontext.{ExternalContext, URLRewriter}
import org.orbeon.oxf.fr.FormRunnerCommon._
import org.orbeon.oxf.fr.Names._
import org.orbeon.oxf.fr.XMLNames._
import org.orbeon.oxf.http.{Headers, HttpStatusCodeException}
import org.orbeon.oxf.properties.PropertySet
import org.orbeon.oxf.util.DateUtils
import org.orbeon.oxf.util.CoreCrossPlatformSupport.properties
import org.orbeon.oxf.util.StringUtils._
import org.orbeon.oxf.xforms.action.XFormsAPI._
import org.orbeon.oxf.xforms.model.XFormsInstance
import org.orbeon.xml.NamespaceMapping
import org.orbeon.saxon.om.NodeInfo
import org.orbeon.scaxon.Implicits._
import org.orbeon.scaxon.SimplePath._
import org.orbeon.oxf.util.PathUtils._
import org.orbeon.oxf.xforms.Loggers
import org.orbeon.oxf.xforms.action.XFormsAPI
import org.orbeon.oxf.xforms.function.XFormsFunction
import org.orbeon.oxf.xml.SaxonUtils
import org.orbeon.xforms.XFormsCrossPlatformSupport

import scala.collection.immutable
import scala.util.Try


// The standard Form Runner parameters
case class FormRunnerParams(
  app         : String,
  form        : String,
  formVersion : Int,
  document    : Option[String],
  isDraft     : Option[Boolean],
  mode        : String
) {

  def appForm: AppForm =
    AppForm(app, form)

  def appFormVersion: FormRunnerParams.AppFormVersion =
    (appForm, formVersion)
}

object FormRunnerParams {

  def apply(): FormRunnerParams =
    apply(frc.parametersInstance.get.rootElement)

  def apply(paramsRootElem: NodeInfo): FormRunnerParams = {
    FormRunnerParams(
      app         = paramsRootElem elemValue "app",
      form        = paramsRootElem elemValue "form",
      formVersion = Try(paramsRootElem elemValue "form-version" toInt) getOrElse 1, // in `test` mode, for example, `form-version` is blank
      document    = paramsRootElem elemValue "document" trimAllToOpt,
      isDraft     = paramsRootElem.elemValue("draft").trimAllToOpt.map(_ == "true"),
      mode        = paramsRootElem elemValue "mode"
    )
  }

  type AppFormVersion = (AppForm, Int)
}

case class AppForm(app: String, form: String) {
  val toList = List(app, form)
}

object AppForm {
  val FormBuilder: AppForm = AppForm("orbeon", "builder")
}

trait FormRunnerBaseOps {

  import FormRunnerBaseOps._

  val LanguageParam          = "fr-language"
  val FormVersionParam       = "form-version"

  val LiferayLanguageHeader  = "orbeon-liferay-language"

  val DefaultIterationSuffix = "-iteration"
  val TemplateContentSuffix  = "-content"
  val TemplateSuffix         = "-template"

  // Get an id based on a name
  // NOTE: The idea as of 2011-06-21 is that we support reading indiscriminately the -control, -grid
  // suffixes, whatever type of actual control they apply to. The idea is that in the end we might decide to just use
  // -control. OTOH we must have distinct ids for binds, controls and templates, so the -bind, -control and -template
  // suffixes must remain.
  //@XPathFunction
  def bindId    (controlName: String): String = controlName + "-bind"
  def gridId    (gridName: String)   : String = gridName    + "-grid"
  def sectionId (sectionName: String): String = sectionName + "-section"
  def controlId (controlName: String): String = controlName + "-control"
  //@XPathFunction
  def templateId(gridName: String)   : String = gridName    + TemplateSuffix

  def defaultIterationName(repeatName: String): String =
    repeatName + DefaultIterationSuffix

  // Find a view element by id, using the index if possible, otherwise traversing the document
  // NOTE: Searching by traversing if no index should be done directly in the selectID implementation.
  def findInViewTryIndex(staticId: String)(implicit ctx: FormRunnerDocContext): Option[NodeInfo] =
    findTryIndex(staticId, ctx.bodyElem, includeSelf = false)

  def findInBindsTryIndex(id: String)(implicit ctx: FormRunnerDocContext): Option[NodeInfo] =
    findTryIndex(id, ctx.topLevelBindElem.get, includeSelf = true)

  // NOTE: This is a rather crude way of testing the presence of the index! But we do know for now that this is
  // only called from the functions above, which search in a form's view, model, or binds, which implies the
  // existence of a form model.
  def formDefinitionHasIndex(inDoc: NodeInfo): Boolean =
    SaxonUtils.selectID(inDoc, FormModel).isDefined

  def isUnder(node: NodeInfo, under: NodeInfo, includeSelf: Boolean): Boolean =
    if (includeSelf)
      node ancestorOrSelf * contains under
    else
      node ancestor * contains under

  private def findTryIndex(id: String, under: NodeInfo, includeSelf: Boolean): Option[NodeInfo] = {

    val hasIndex = formDefinitionHasIndex(under)

    def fromSearch =
      if (includeSelf)
        under descendantOrSelf * find (_.id == id)
      else
        under descendant * find (_.id == id)

    def fromIndex =
      SaxonUtils.selectID(under, id) match {
        case elemOpt @ Some(elem) if isUnder(elem, under, includeSelf) => elemOpt
        case Some(_)                                                   => fromSearch
        case None                                                      => None
      }

    if (hasIndex)
      fromIndex
    else
      fromSearch
  }

  // Get the body element assuming the structure of an XHTML document, annotated or not, OR the structure of `xbl:xbl`.
  // NOTE: `annotate.xpl` replaces `fr:body` with `xf:group[@class = 'fb-body']`.
  def getFormRunnerBodyElem(inDoc: NodeInfo): NodeInfo =
    findFormRunnerBodyElem(inDoc).get

  private val FRGridOrLegacyRepeatTest: Test = FRGridTest || FRRepeatTest

  def findFormRunnerBodyElem(inDoc: NodeInfo): Option[NodeInfo] = {

    def parentIsNotGridOrLegacyRepeat(n: NodeInfo) =
      n parent FRGridOrLegacyRepeatTest isEmpty

    def fromGroupById = SaxonUtils.selectID(inDoc, "fb-body")
    def fromGroup     = inDoc.rootElement / "*:body" descendant XFGroupTest find (_.id == "fb-body")
    def fromFRBody    = inDoc.rootElement / "*:body" descendant FRBodyTest  find parentIsNotGridOrLegacyRepeat
    def fromTemplate  = inDoc.rootElement / XBLTemplateTest headOption

    fromGroupById orElse fromGroup orElse fromFRBody orElse fromTemplate
  }

  // Get the form model<commit_msg>Refactor: name, method<commit_after>/**
 * Copyright (C) 2013 Orbeon, Inc.
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the
 * GNU Lesser General Public License as published by the Free Software Foundation; either version
 * 2.1 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
 */
package org.orbeon.oxf.fr

import enumeratum.EnumEntry.Lowercase
import enumeratum._
import org.orbeon.oxf.externalcontext.{ExternalContext, URLRewriter}
import org.orbeon.oxf.fr.FormRunnerCommon._
import org.orbeon.oxf.fr.Names._
import org.orbeon.oxf.fr.XMLNames._
import org.orbeon.oxf.http.{Headers, HttpStatusCodeException}
import org.orbeon.oxf.properties.PropertySet
import org.orbeon.oxf.util.DateUtils
import org.orbeon.oxf.util.CoreCrossPlatformSupport.properties
import org.orbeon.oxf.util.StringUtils._
import org.orbeon.oxf.xforms.action.XFormsAPI._
import org.orbeon.oxf.xforms.model.XFormsInstance
import org.orbeon.xml.NamespaceMapping
import org.orbeon.saxon.om.NodeInfo
import org.orbeon.scaxon.Implicits._
import org.orbeon.scaxon.SimplePath._
import org.orbeon.oxf.util.PathUtils._
import org.orbeon.oxf.xforms.Loggers
import org.orbeon.oxf.xforms.action.XFormsAPI
import org.orbeon.oxf.xforms.function.XFormsFunction
import org.orbeon.oxf.xml.SaxonUtils
import org.orbeon.xforms.XFormsCrossPlatformSupport

import scala.collection.immutable
import scala.util.Try


// The standard Form Runner parameters
case class FormRunnerParams(
  app         : String,
  form        : String,
  formVersion : Int,
  document    : Option[String],
  isDraft     : Option[Boolean],
  mode        : String
) {

  def appForm: AppForm =
    AppForm(app, form)

  def appFormVersion: FormRunnerParams.AppFormVersion =
    (appForm, formVersion)
}

object FormRunnerParams {

  def apply(): FormRunnerParams =
    apply(frc.parametersInstance.get.rootElement)

  def apply(paramsRootElem: NodeInfo): FormRunnerParams = {
    FormRunnerParams(
      app         = paramsRootElem elemValue "app",
      form        = paramsRootElem elemValue "form",
      formVersion = Try(paramsRootElem elemValue "form-version" toInt) getOrElse 1, // in `test` mode, for example, `form-version` is blank
      document    = paramsRootElem elemValue "document" trimAllToOpt,
      isDraft     = paramsRootElem.elemValue("draft").trimAllToOpt.map(_ == "true"),
      mode        = paramsRootElem elemValue "mode"
    )
  }

  type AppFormVersion = (AppForm, Int)
}

case class AppForm(app: String, form: String) {
  val toList = List(app, form)
}

object AppForm {
  val FormBuilder: AppForm = AppForm("orbeon", "builder")
}

trait FormRunnerBaseOps {

  import FormRunnerBaseOps._

  val LanguageParam          = "fr-language"
  val FormVersionParam       = "form-version"

  val LiferayLanguageHeader  = "orbeon-liferay-language"

  val DefaultIterationSuffix = "-iteration"
  val TemplateContentSuffix  = "-content"
  val TemplateSuffix         = "-template"

  // Get an id based on a name
  // NOTE: The idea as of 2011-06-21 is that we support reading indiscriminately the -control, -grid
  // suffixes, whatever type of actual control they apply to. The idea is that in the end we might decide to just use
  // -control. OTOH we must have distinct ids for binds, controls and templates, so the -bind, -control and -template
  // suffixes must remain.
  //@XPathFunction
  def bindId    (controlName: String): String = controlName + "-bind"
  def gridId    (gridName: String)   : String = gridName    + "-grid"
  def sectionId (sectionName: String): String = sectionName + "-section"
  def controlId (controlName: String): String = controlName + "-control"
  //@XPathFunction
  def templateId(gridName: String)   : String = gridName    + TemplateSuffix

  def defaultIterationName(repeatName: String): String =
    repeatName + DefaultIterationSuffix

  // Find a view element by id, using the index if possible, otherwise traversing the document
  // NOTE: Searching by traversing if no index should be done directly in the selectID implementation.
  def findInViewTryIndex(staticId: String)(implicit ctx: FormRunnerDocContext): Option[NodeInfo] =
    findTryIndex(staticId, ctx.bodyElem, includeSelf = false)

  def findInBindsTryIndex(id: String)(implicit ctx: FormRunnerDocContext): Option[NodeInfo] =
    findTryIndex(id, ctx.topLevelBindElem.get, includeSelf = true)

  // NOTE: This is a rather crude way of testing the presence of the index! But we do know for now that this is
  // only called from the functions above, which search in a form's view, model, or binds, which implies the
  // existence of a form model.
  def formDefinitionHasIndex(inDoc: NodeInfo): Boolean =
    SaxonUtils.selectID(inDoc, FormModel).isDefined

  def isUnder(node: NodeInfo, under: NodeInfo, includeSelf: Boolean): Boolean =
    if (includeSelf)
      node ancestorOrSelf * contains under
    else
      node ancestor * contains under

  private def findTryIndex(id: String, under: NodeInfo, includeSelf: Boolean): Option[NodeInfo] = {

    val hasIndex = formDefinitionHasIndex(under)

    def fromSearch =
      if (includeSelf)
        under descendantOrSelf * find (_.id == id)
      else
        under descendant * find (_.id == id)

    def fromIndex =
      SaxonUtils.selectID(under, id) match {
        case elemOpt @ Some(elem) if isUnder(elem, under, includeSelf) => elemOpt
        case Some(_)                                                   => fromSearch
        case None                                                      => None
      }

    if (hasIndex)
      fromIndex
    else
      fromSearch
  }

  // Get the body element assuming the structure of an XHTML document, annotated or not, OR the structure of `xbl:xbl`.
  // NOTE: `annotate.xpl` replaces `fr:body` with `xf:group[@class = 'fb-body']`.
  def getFormRunnerBodyElem(inDoc: NodeInfo): NodeInfo =
    findFormRunnerBodyElem(inDoc).get

  private val FRGridOrLegacyRepeatTest: Test = FRGridTest || FRRepeatTest

  def findFormRunnerBodyElem(inDoc: NodeInfo): Option[NodeInfo] = {

    def parentIsNotGridOrLegacyRepeat(n: NodeInfo) =
      n parent FRGridOrLegacyRepeatTest isEmpty

    def fromFbGroupId = SaxonUtils.selectID(inDoc, "fb-body")
    def fromGroup     = inDoc.rootElement / "*:body" descendant XFGroupTest find (_.id == "fb-body")
    def fromFRBody    = inDoc.rootElement / "*:body" descendant FRBodyTest  find parentIsNotGridOrLegacyRepeat
    def fromTemplate  = inDoc.rootElement firstChildOpt XBLTemplateTest

    fromFbGroupId orElse fromGroup orElse fromFRBody orElse fromTemplate
  }

  // Get the form model
{'fraction_non_alphanumeric': 0.08679759174311927, 'fraction_numerical': 0.0021502293577981653, 'mean_word_length': 3.5111542192046556, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 4}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2771959', 'n_tokens_mistral': 4212, 'n_tokens_neox': 4097, 'n_words': 1269}