<commit_before>/**
 * Copyright (C) 2012 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.dom4j.QName
import org.orbeon.oxf.common.{Version, OXFException}
import org.orbeon.oxf.pipeline.api.{ExternalContext, XMLReceiver, PipelineContext}
import org.orbeon.oxf.util._
import org.orbeon.oxf.xforms.XFormsConstants.XFORMS_NAMESPACE_URI
import org.orbeon.oxf.xforms.XFormsConstants.XFORMS_STRING_QNAME
import org.orbeon.oxf.xforms.control.XFormsComponentControl
import org.orbeon.oxf.xforms.control.controls.XFormsSelect1Control
import org.orbeon.oxf.xforms.processor.XFormsToSomething
import org.orbeon.oxf.xforms.processor.XFormsToSomething.Stage2CacheableState
import org.orbeon.oxf.xforms.{XFormsObject, XFormsContainingDocument}
import org.orbeon.oxf.xml.XMLConstants.XS_STRING_QNAME
import org.orbeon.oxf.xml._
import org.orbeon.saxon.om.{DocumentInfo, NodeInfo}
import org.orbeon.scaxon.XML.{Attribute ⇒ _, Text ⇒ _, _}
import xml._

/**
 *  Supported:
 *  - Simple types, adding type="…" on the xs:element
 *  - Repeats, appropriately adding min="…" max="…" on the xs:element
 *  - Section templates, using the binds defined in the app or global library
 *  - Custom namespaces, properly including namespace declarations
 *
 *  To do:
 *  - For testing, we're passing the orbeon-token from the app context, the service is defined as a page in page-flow.xml
 *
 *  Not supported:
 *  - Custom XML instance (adds lots of complexity, thinking of dropping for 4.0, and maybe foreseeable future)
 */
class XFormsToSchema extends XFormsToSomething {

    private val SchemaPath  = """/fr/([^/^.]+)/([^/^.]+)/schema""".r
    private val ComponentNS = """http://orbeon.org/oxf/xml/form-builder/component/([^/]+)/library""".r

    private implicit val Logger = new IndentedLogger(LoggerFactory.createLogger(classOf[XFormsToSchema]), "")

    case class Libraries(orbeon: Option[DocumentInfo], app: Option[DocumentInfo])

    protected def produceOutput(pipelineContext: PipelineContext,
                                outputName: String,
                                externalContext: ExternalContext,
                                indentedLogger: IndentedLogger,
                                stage2CacheableState: Stage2CacheableState,
                                containingDocument: XFormsContainingDocument,
                                xmlReceiver: XMLReceiver): Unit = {
        // This is a PE feature
        Version.instance.requirePEFeature("XForms schema generator service")

        // Read form and library
        val SchemaPath(appName, formName) = NetUtils.getExternalContext.getRequest.getRequestPath

        val formSource = FormRunner.readPublishedForm(appName, formName).get
        val libraries  = Libraries(FormRunner.readPublishedForm("orbeon", "library"), FormRunner.readPublishedForm(appName, "library"))

        // Compute root xs:element
        val rootBind   = FormRunner.findTopLevelBind(formSource).get
        val resolve    = containingDocument.getControls.getCurrentControlTree.getRoot.resolve(_: String)
        val rootXsElem = handleBind(libraries, resolve, rootBind)

        // Import XForms schema if necessary
        val schema =
            <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
                {
                    val hasXFormsNamespace = rootXsElem.descendant_or_self exists (_.scope.getPrefix(XFORMS_NAMESPACE_URI) != null)
                    if (hasXFormsNamespace)
                        <xs:import namespace="http://www.w3.org/2002/xforms"
                                   schemaLocation="http://www.w3.org/MarkUp/Forms/2007/XForms-11-Schema.xsd"/>
                }
                { rootXsElem }
            </xs:schema>

        // Send result to output
        elemToSAX(schema, xmlReceiver)
    }

    // Recursive function generating an xs:element from an xf:bind
    private def handleBind(libraries: Libraries, resolve: String ⇒ Option[XFormsObject], bind: NodeInfo): Elem = {

        case class BindInfo(elemName: String, elemType: Option[QName], repeated: Boolean, min: Option[String], max: Option[String])

        // Returns control corresponding to a bind, with a given name (e.g. *:grid)
        def findControlNodeForBind(bind: NodeInfo, controlName: String): Option[NodeInfo] = {
            val bindId: String = bind \@ "id"
            bind.rootElement \\ controlName find (_.attValue("bind") == bindId)
        }

        // Extract information from bind
        object RootBind { def unapply(bind: NodeInfo): Boolean = bind parent "*:model" nonEmpty }
        object Bind { def unapply(bind: NodeInfo): Option[BindInfo] = {
            val repeatGridNode = findControlNodeForBind(bind, "*:grid") filter (_.attValue("repeat") == "true") toList

            Some(BindInfo(
                elemName = bind \@ ("ref" || "nodeset"),
                elemType = (bind \@ "type").headOption map (_.stringValue) map bind.resolveQName filterNot Set(XS_STRING_QNAME, XFORMS_STRING_QNAME),
                repeated = repeatGridNode nonEmpty,
                min      = repeatGridNode \@ "min",
                max      = repeatGridNode \@ "max"
            ))
        }}

        // Build xs:element for this bind
        val xsElem = bind match {
            case RootBind() ⇒ <xs:element name="form"/>
            case Bind(BindInfo(elemName, elemType, repeated, min, max)) ⇒ {

                // Optional type attribute
                def attr(name: String, value: String) = Attribute(None, name, Text(value), Null)
                val typeAttr = elemType map (_.getQualifiedName) map (attr("type", _))

                // Create namespace binding for type, filtering the already declared XSD namespace
                val typeNamespaceBinding = elemType flatMap (qname ⇒
                    (qname.getNamespacePrefix, qname.getNamespaceURI) match {
                        case (XMLConstants.XSD_PREFIX, XMLConstants.XSD_URI) ⇒ None
                        case (XMLConstants.XSD_PREFIX, _) ⇒ throw new OXFException("Non-schema types with the 'xs' prefix are not supported")
                        case (prefix, uri) ⇒ Some(NamespaceBinding(prefix, uri, _: NamespaceBinding))
                    })

                // Optional min/max attributes
                def attrDefault(value: Option[String], name: String, default: String) =
                    if (repeated) Some(attr(name, value getOrElse default)) else None

                val minAttr = attrDefault(min, "minOccurs", "0")
                val maxAttr = attrDefault(max, "maxOccurs", "unbounded")

                // For controls with an itemset, generate a xs:simpleType
                val simpleTypeRestrictionElemOpt =
                    for {
                        itemset       ← resolve(elemName + "-control") collectFirst { case s: XFormsSelect1Control if s.isRelevant ⇒ s.getItemset }
                        itemsetValues = itemset.children map (_.value)
                        if itemsetValues.nonEmpty
                    } yield
                        <xs:simpleType>
                            <xs:restriction base="xs:string">
                                { itemsetValues map (value ⇒ <xs:enumeration value={value}/>) }
                            </xs:restriction>
                        </xs:simpleType>

                // Build element with optional attributes and new namespace
                val xsElem       = <xs:element name={elemName}>{ simpleTypeRestrictionElemOpt.toList }</xs:element>
                val xsElemWithNS = typeNamespaceBinding map (_(xsElem.scope)) map (s ⇒ xsElem.copy(scope = s)) getOrElse xsElem
                val attributes   = typeAttr ++ minAttr ++ maxAttr

                attributes.foldLeft(xsElemWithNS)(_ % _)
            }
        }

        // Get children of bind, or if there aren't any see if this is a component, in which case we get the binds from
        // the library.
        val (childrenBinds, newResolve) = bind \ * match {
            case Nil ⇒
                // No children for this bind, but there might be a section template associated with this bind<commit_msg>Schema generation supports attachments, fix #1336<commit_after>/**
 * Copyright (C) 2012 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.dom4j.QName
import org.orbeon.oxf.common.{Version, OXFException}
import org.orbeon.oxf.pipeline.api.{ExternalContext, XMLReceiver, PipelineContext}
import org.orbeon.oxf.util._
import org.orbeon.oxf.xforms.XFormsConstants.XFORMS_NAMESPACE_URI
import org.orbeon.oxf.xforms.XFormsConstants.XFORMS_STRING_QNAME
import org.orbeon.oxf.xforms.control.XFormsComponentControl
import org.orbeon.oxf.xforms.control.controls.XFormsSelect1Control
import org.orbeon.oxf.xforms.processor.XFormsToSomething
import org.orbeon.oxf.xforms.processor.XFormsToSomething.Stage2CacheableState
import org.orbeon.oxf.xforms.{XFormsObject, XFormsContainingDocument}
import org.orbeon.oxf.xml.XMLConstants.XS_STRING_QNAME
import org.orbeon.oxf.xml._
import org.orbeon.oxf.util.ScalaUtils._
import org.orbeon.saxon.om.{DocumentInfo, NodeInfo}
import org.orbeon.scaxon.XML.{Attribute ⇒ _, Text ⇒ _, _}
import xml._

/**
 *  Supported:
 *  - Simple types, adding type="…" on the xs:element
 *  - Repeats, appropriately adding min="…" max="…" on the xs:element
 *  - Section templates, using the binds defined in the app or global library
 *  - Custom namespaces, properly including namespace declarations
 *
 *  To do:
 *  - For testing, we're passing the orbeon-token from the app context, the service is defined as a page in page-flow.xml
 *
 *  Not supported:
 *  - Custom XML instance (adds lots of complexity, thinking of dropping for 4.0, and maybe foreseeable future)
 */
class XFormsToSchema extends XFormsToSomething {

    private val SchemaPath  = """/fr/([^/^.]+)/([^/^.]+)/schema""".r
    private val ComponentNS = """http://orbeon.org/oxf/xml/form-builder/component/([^/]+)/library""".r

    private implicit val Logger = new IndentedLogger(LoggerFactory.createLogger(classOf[XFormsToSchema]), "")

    case class Libraries(orbeon: Option[DocumentInfo], app: Option[DocumentInfo])

    protected def produceOutput(pipelineContext: PipelineContext,
                                outputName: String,
                                externalContext: ExternalContext,
                                indentedLogger: IndentedLogger,
                                stage2CacheableState: Stage2CacheableState,
                                containingDocument: XFormsContainingDocument,
                                xmlReceiver: XMLReceiver): Unit = {
        // This is a PE feature
        Version.instance.requirePEFeature("XForms schema generator service")

        // Read form and library
        val SchemaPath(appName, formName) = NetUtils.getExternalContext.getRequest.getRequestPath

        val formSource = FormRunner.readPublishedForm(appName, formName).get
        val libraries  = Libraries(FormRunner.readPublishedForm("orbeon", "library"), FormRunner.readPublishedForm(appName, "library"))

        // Compute root xs:element
        val rootBind   = FormRunner.findTopLevelBind(formSource).get
        val resolve    = containingDocument.getControls.getCurrentControlTree.getRoot.resolve(_: String)
        val rootXsElem = handleBind(libraries, resolve, rootBind)

        // Import XForms schema if necessary
        val schema =
            <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
                {
                    val hasXFormsNamespace = rootXsElem.descendant_or_self exists (_.scope.getPrefix(XFORMS_NAMESPACE_URI) != null)
                    if (hasXFormsNamespace)
                        <xs:import namespace="http://www.w3.org/2002/xforms"
                                   schemaLocation="http://www.w3.org/MarkUp/Forms/2007/XForms-11-Schema.xsd"/>
                }
                { rootXsElem }
            </xs:schema>

        // Send result to output
        elemToSAX(schema, xmlReceiver)
    }

    // Recursive function generating an xs:element from an xf:bind
    private def handleBind(libraries: Libraries, resolve: String ⇒ Option[XFormsObject], bind: NodeInfo): Elem = {

        case class BindInfo(elemName: String, elemType: Option[QName], repeated: Boolean, min: Option[String], max: Option[String])

        // Returns control corresponding to a bind, with a given name (e.g. *:grid)
        def findControlNodeForBind(bind: NodeInfo, controlName: Test): Option[NodeInfo] = {
            val bindId: String = bind \@ "id"
            bind.rootElement \\ controlName find (_.attValue("bind") == bindId)
        }

        // Extract information from bind
        object RootBind { def unapply(bind: NodeInfo): Boolean = bind parent "*:model" nonEmpty }
        object Bind { def unapply(bind: NodeInfo): Option[BindInfo] = {
            val repeatGridNode = findControlNodeForBind(bind, "*:grid") filter (_.attValue("repeat") == "true") toList

            Some(BindInfo(
                elemName = bind \@ ("ref" || "nodeset"),
                elemType = (bind \@ "type").headOption map (_.stringValue) map bind.resolveQName filterNot Set(XS_STRING_QNAME, XFORMS_STRING_QNAME),
                repeated = repeatGridNode nonEmpty,
                min      = repeatGridNode \@ "min",
                max      = repeatGridNode \@ "max"
            ))
        }}

        // Build xs:element for this bind
        val xsElem = bind match {
            case RootBind() ⇒ <xs:element name="form"/>
            case Bind(BindInfo(elemName, elemType, repeated, min, max)) ⇒ {

                // Optional type attribute
                def attr(name: String, value: String) = Attribute(None, name, Text(value), Null)

                // Create namespace binding for type, filtering the already declared XSD namespace
                val typeNamespaceBinding = elemType flatMap (qname ⇒
                    (qname.getNamespacePrefix, qname.getNamespaceURI) match {
                        case (XMLConstants.XSD_PREFIX, XMLConstants.XSD_URI) ⇒ None
                        case (XMLConstants.XSD_PREFIX, _) ⇒ throw new OXFException("Non-schema types with the 'xs' prefix are not supported")
                        case (prefix, uri) ⇒ Some(NamespaceBinding(prefix, uri, _: NamespaceBinding))
                    })

                // Optional min/max attributes
                def attrDefault(value: Option[String], name: String, default: String) =
                    if (repeated) Some(attr(name, value getOrElse default)) else None

                val minAttr = attrDefault(min, "minOccurs", "0")
                val maxAttr = attrDefault(max, "maxOccurs", "unbounded")

                // For controls with an itemset, generate a xs:simpleType
                val simpleTypeRestrictionElemOpt =
                    for {
                        itemset       ← resolve(elemName + "-control") collectFirst { case s: XFormsSelect1Control if s.isRelevant ⇒ s.getItemset }
                        itemsetValues = itemset.children map (_.value)
                        if itemsetValues.nonEmpty
                    } yield
                        <xs:simpleType>
                            <xs:restriction base="xs:string">
                                { itemsetValues map (value ⇒ <xs:enumeration value={value}/>) }
                            </xs:restriction>
                        </xs:simpleType>

                // The xf:bind is for an attachment control if it has type="xs|xf:anyURI" and the corresponding control
                // has a class 'fr-attachment'
                val isBindForAttachmentControl = {
                    val xsdOrXFormsURI = Set(XMLConstants.XSD_URI, XFORMS_NAMESPACE_URI)
                    def isBindTypeAnyURI = elemType.exists(qname ⇒ xsdOrXFormsURI(qname.getNamespaceURI) && qname.getName == "anyURI")
                    def isControlAttachment = findControlNodeForBind(bind, *) exists (_.attClasses("fr-attachment"))
                    isBindTypeAnyURI && isControlAttachment
                }

                // Value of xs:type attribute added to the xs:element, except for attachment controls, where
                // that type is defined as part of a complex type (see complexTypeForAttachment below)
                val typeAttr =
                    if (isBindForAttachmentControl) None
                    else elemType map (_.getQualifiedName) map (attr("type", _))

                val complexTypeForAttachment = isBindForAttachmentControl option
                    <xs:complexType>
                        <xs:simpleContent>
                            <xs:extension base="xs:anyURI">
                                <xs:attribute name="filename" type="xs:string"/>
                                <xs:attribute name="mediatype" type="xs:string"/>
                                <xs:attribute name="size" type="xf:integer"/>
                            </xs:extension>
                        </xs:simpleContent>
                    </xs:complexType>

                // Build element with optional attributes and new namespace
                val xsElemContent = simpleTypeRestrictionElemOpt.toList ++ complexTypeForAttachment.toList
                val xsElem        = <xs:element name={elemName}>{ xsElemContent }</xs:element>
                val xsElemWithNS  = typeNamespaceBinding map (_(xsElem.scope)) map (s ⇒ xsElem.copy(scope = s)) getOrElse xsElem
                val attributes    = typeAttr ++ minAttr ++ maxAttr

                attributes.foldLeft(xsElemWithNS)(_ % _)
            }
        }

        // Get children of bind, or if there aren't any see if this is a component, in which case we get the binds from
        // the library.
        val (childrenBinds, newResolve) = bind \ * match {
            case Nil ⇒
                // No children for this bind, but there might be a section template associated with this bind
{'fraction_non_alphanumeric': 0.09331869510664993, 'fraction_numerical': 0.0035549979088247597, 'mean_word_length': 2.283948497854077, 'pattern_counts': {'":': 0, '<': 34, '<?xml version=': 0, '>': 34, 'https://': 0, 'lorem ipsum': 0, 'www.': 8, 'xml': 16}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7245060', 'n_tokens_mistral': 5270, 'n_tokens_neox': 5022, 'n_words': 1439}