<commit_before>/**
 * Copyright (C) 2014 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 org.orbeon.dom.Document
import org.orbeon.dom.saxon.DocumentWrapper
import org.orbeon.oxf.util.StringUtils._
import org.orbeon.oxf.util.XPath
import org.orbeon.oxf.xml.TransformerUtils
import org.orbeon.oxf.xml.dom4j.Dom4jUtils
import org.orbeon.saxon.om.{DocumentInfo, Name10Checker, NodeInfo, VirtualNode}
import org.orbeon.scaxon.XML
import org.orbeon.scaxon.XML._

object DataMigration {

  import Private._

  case class PathElem(value: String) {
    require(Name10Checker.getInstance.isValidNCName(value) || PathElem.SpecialFormBuilderPaths(value))
  }

  object PathElem {
    // With Form Builder, `value` can be a `QName` and can even have a predicate.
    private val SpecialFormBuilderPaths = Set(
      "xh:head",
      "xf:model[@id = 'fr-form-model']",
      "xf:instance[@id = 'fr-form-metadata']/*"
    )
  }

  case class Migration(path: List[PathElem], iterationElem: PathElem) {
    def toJson = s"""{ "path": "${path map (_.value) mkString "/"}", "iteration-name": "${iterationElem.value}" }"""
  }

  // NOTE: We could build this with JSON objects instead
  def encodeMigrationsToJSON(migrations: Seq[Migration]): String =
    migrations map (_.toJson) mkString ("[", ",", "]")

  // Ouch, a lot of boilerplate! (Rapture JSON might provide nicer syntax)
  def decodeMigrationsFromJSON(jsonMigrationMap: String): List[Migration] = {

    import spray.json._
    val json = jsonMigrationMap.parseJson

    val migrations =
      Iterator(json) collect {
        case JsArray(migrations) ⇒
          migrations.iterator collect {
            case JsObject(fields) ⇒

              val path          = fields("path").asInstanceOf[JsString].value
              val iterationName = fields("iteration-name").asInstanceOf[JsString].value

              Migration(
                path.splitTo[List]("/") map {
                  case TrimPathElementRE(path) ⇒ PathElem(path)
                  case name                    ⇒ throw new IllegalArgumentException(s"invalid migration name: `$name`")
                },
                PathElem(iterationName)
              )
          }
      }

    migrations.flatten.to[List]
  }

  import org.orbeon.oxf.xforms.action.XFormsAPI._

  // Maybe migrate the data based on whether:
  //
  // - it is already in the format expected by the database or not
  // - the form definition contains data migration information or not
  //
  def dataMaybeMigratedToDatabaseFormat(
    app           : String,
    form          : String,
    data          : DocumentInfo,
    metadata      : Option[DocumentInfo],
    pruneMetadata : Boolean
  ): (DocumentInfo, String) = {

    val srcDataFormatVersion = FormRunnerPersistence.FormRunnerCurrentInternalDataFormatVersion
    val dstDataFormatVersion = FormRunnerPersistence.providerDataFormatVersion(app, form)

    val migratedDatabaseDataOpt =
      if (srcDataFormatVersion != dstDataFormatVersion)
        dataMaybeMigratedFrom(data, metadata, pruneMetadata)
      else
        None

    val databaseData = migratedDatabaseDataOpt getOrElse copyDocumentKeepInstanceData(data)

    // Add data format version attribute on root element
    insert(
      into       = databaseData / *,
      origin     = attributeInfo(XMLNames.DataFormatVersionQName, dstDataFormatVersion),
      doDispatch = false
    )

    databaseData → dstDataFormatVersion
  }

  //@XPathFunction
  def dataMaybeMigratedFrom(data: DocumentInfo, metadata: Option[DocumentInfo], pruneMetadata: Boolean): Option[DocumentWrapper] =
    dataMaybeMigratedFromTo(data, metadata, migrateDataFrom(_, _, pruneMetadata))

  //@XPathFunction<commit_msg>Minor: rename<commit_after>/**
 * Copyright (C) 2014 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 org.orbeon.dom.Document
import org.orbeon.dom.saxon.DocumentWrapper
import org.orbeon.oxf.util.StringUtils._
import org.orbeon.oxf.util.XPath
import org.orbeon.oxf.xml.TransformerUtils
import org.orbeon.oxf.xml.dom4j.Dom4jUtils
import org.orbeon.saxon.om.{DocumentInfo, Name10Checker, NodeInfo, VirtualNode}
import org.orbeon.scaxon.XML
import org.orbeon.scaxon.XML._

object DataMigration {

  import Private._

  case class PathElem(value: String) {
    require(Name10Checker.getInstance.isValidNCName(value) || PathElem.SpecialFormBuilderPaths(value))
  }

  object PathElem {
    // With Form Builder, `value` can be a `QName` and can even have a predicate.
    private val SpecialFormBuilderPaths = Set(
      "xh:head",
      "xf:model[@id = 'fr-form-model']",
      "xf:instance[@id = 'fr-form-metadata']/*"
    )
  }

  case class Migration(path: List[PathElem], iterationElem: PathElem) {
    def toJson = s"""{ "path": "${path map (_.value) mkString "/"}", "iteration-name": "${iterationElem.value}" }"""
  }

  // NOTE: We could build this with JSON objects instead
  def encodeMigrationsToJSON(migrations: Seq[Migration]): String =
    migrations map (_.toJson) mkString ("[", ",", "]")

  // Ouch, a lot of boilerplate! (Rapture JSON might provide nicer syntax)
  def decodeMigrationsFromJSON(jsonMigrationMap: String): List[Migration] = {

    import spray.json._
    val json = jsonMigrationMap.parseJson

    val migrations =
      Iterator(json) collect {
        case JsArray(migrations) ⇒
          migrations.iterator collect {
            case JsObject(fields) ⇒

              val path          = fields("path").asInstanceOf[JsString].value
              val iterationName = fields("iteration-name").asInstanceOf[JsString].value

              Migration(
                path.splitTo[List]("/") map {
                  case TrimPathElementRE(path) ⇒ PathElem(path)
                  case name                    ⇒ throw new IllegalArgumentException(s"invalid migration name: `$name`")
                },
                PathElem(iterationName)
              )
          }
      }

    migrations.flatten.to[List]
  }

  import org.orbeon.oxf.xforms.action.XFormsAPI._

  // Maybe migrate the data based on whether:
  //
  // - it is already in the format expected by the database or not
  // - the form definition contains data migration information or not
  //
  def dataMaybeMigratedToDatabaseFormat(
    app           : String,
    form          : String,
    data          : DocumentInfo,
    metadataOpt   : Option[DocumentInfo],
    pruneMetadata : Boolean
  ): (DocumentInfo, String) = {

    val srcDataFormatVersion = FormRunnerPersistence.FormRunnerCurrentInternalDataFormatVersion
    val dstDataFormatVersion = FormRunnerPersistence.providerDataFormatVersion(app, form)

    val migratedDatabaseDataOpt =
      if (srcDataFormatVersion != dstDataFormatVersion)
        dataMaybeMigratedFrom(data, metadataOpt, pruneMetadata)
      else
        None

    val databaseData = migratedDatabaseDataOpt getOrElse copyDocumentKeepInstanceData(data)

    // Add data format version attribute on root element
    insert(
      into       = databaseData / *,
      origin     = attributeInfo(XMLNames.DataFormatVersionQName, dstDataFormatVersion),
      doDispatch = false
    )

    databaseData → dstDataFormatVersion
  }

  //@XPathFunction
  def dataMaybeMigratedFrom(data: DocumentInfo, metadata: Option[DocumentInfo], pruneMetadata: Boolean): Option[DocumentWrapper] =
    dataMaybeMigratedFromTo(data, metadata, migrateDataFrom(_, _, pruneMetadata))

  //@XPathFunction
{'fraction_non_alphanumeric': 0.09346336932543829, 'fraction_numerical': 0.002786485545106235, 'mean_word_length': 3.139356078808265, 'pattern_counts': {'":': 4, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 4}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6346694', 'n_tokens_mistral': 2489, 'n_tokens_neox': 2431, 'n_words': 686}