blob: 51ca2ff2806d4b21feba15d18c59f0e727a7d493 [file] [log] [blame]
/* Copyright (c) 2012-2015 Tresys Technology, LLC. All rights reserved.
*
* Developed by: Tresys Technology, LLC
* http://www.tresys.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal with
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimers.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimers in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the names of Tresys Technology, nor the names of its contributors
* may be used to endorse or promote products derived from this Software
* without specific prior written permission.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
* SOFTWARE.
*/
package edu.illinois.ncsa.daffodil.dsom
import scala.xml.Node
import edu.illinois.ncsa.daffodil.schema.annotation.props.gen.Format_AnnotationMixin
import edu.illinois.ncsa.daffodil.schema.annotation.props.SeparatorSuppressionPolicyMixin
import edu.illinois.ncsa.daffodil.exceptions.Assert
import edu.illinois.ncsa.daffodil.dsom.IIUtils.IIMap
/**
* A schema document corresponds to one file usually named with an ".xsd" extension.
* The root element of a schema document is xsd:schema where xsd is the namespace for
* XML Schema.
*
* A schema document is important because it is the unit of lexical scoping of format
* properties in DFDL.
*
* Specifically, note that two schema documents may have the same target namespace, but
* different default formats scoped over their contents.
*
* When dealing with plain XML and XSD (not DFDL), the concept of Schema with a single
* target namespace is more used than the concept of schema document, which is usually
* only needed to issue diagnostic error messages ("In file foo.xsd, line 938...")
*
* Conversely, for DFDL, the concept of schema document is more used because schema documents
* are where default formats are specified, so it is very important what schema document
* a schema component was defined within.
*/
/**
* Common to both types we use for dealing with
* schema documents.
*/
trait SchemaDocumentMixin { self: SchemaComponent =>
protected final override def enclosingComponentDef: Option[SchemaComponent] = None
}
/**
* Handles everything about schema documents that has nothing to
* do with DFDL. Things like namespace, include, import, elementFormDefault
* etc.
*/
final class XMLSchemaDocument(xmlArg: Node,
schemaSetArg: SchemaSet,
/**
* ii is the include or import statement DSOM object that
* lead to this schema document being imported/included.
*/
val ii: Option[IIBase],
sfArg: Option[DFDLSchemaFile],
seenBeforeArg: IIMap,
/**
* this flag lets us import into a bootstrap 'fake' document
* even though it does not have a namespace
*/
override val isBootStrapSD: Boolean)
extends SchemaComponent(xmlArg, sfArg.getOrElse(schemaSetArg))
with SchemaDocumentMixin
with SchemaDocIncludesAndImportsMixin {
requiredEvaluations(checkUnsupportedAttributes)
final lazy val seenBefore = seenBeforeArg
final override lazy val schemaFile = sfArg
final override lazy val xmlSchemaDocument = this
/**
* Error checks on the xs:schema element itself.
*
* E.g., we don't support the xsi:schemaLocation attribute. So we issue a warning for that.
* and some other attributes as well.
*/
private def qualOrUnqual(str: String, kind: String) = {
str match {
case "unqualified" => str
case "qualified" => str
case _ => schemaDefinitionError("Unrecognized value for %s FormDefault='%s'.", kind, str)
}
}
final lazy val elementFormDefault = {
val efdAttr = (xml \ "@elementFormDefault").text
if (efdAttr == "") "unqualified"
else qualOrUnqual(efdAttr, "element")
}
final lazy val attributeFormDefault = {
val afdAttr = (xml \ "@attributeFormDefault").text
if (afdAttr == "") "unqualified"
else qualOrUnqual(afdAttr, "attribute")
}
final def checkUnsupportedAttributes = LV('checkUnsupportedAttributes) {
val hasSchemaLocation = (xml \ "@schemaLocation").text != ""
val hasBlockDefault = (xml \ "@blockDefault").text != ""
val hasFinalDefault = (xml \ "@finalDefault").text != ""
schemaDefinitionWarningUnless(!hasSchemaLocation, "schemaLocation is ignored.")
schemaDefinitionWarningUnless(!hasBlockDefault, "blockDefault is ignored")
schemaDefinitionWarningUnless(!hasFinalDefault, "finalDefault is ignored")
schemaDefinitionUnless(attributeFormDefault == "unqualified", "attributeFormDefault='qualified' is not yet implemented.")
val res = hasSchemaLocation | hasBlockDefault | hasFinalDefault
res
}.value
}
/**
* Handles only things specific to DFDL about schema documents.
*
* I.e., default format properties, named format properties, etc.
*/
final class SchemaDocument(xmlSDoc: XMLSchemaDocument)
extends AnnotatedSchemaComponent(xmlSDoc.xml, xmlSDoc)
with SchemaDocumentMixin
with Format_AnnotationMixin
with SeparatorSuppressionPolicyMixin {
override def term = Assert.usageError("not to be called on SchemaDocument")
/**
* Implements the selectivity so that if you specify a root element
* to the compiler, then only that root element (and things reached from it)
* is compiled. Otherwise all top level elements are compiled.
*/
requiredEvaluations(defaultFormat)
if (schemaSet.checkAllTopLevel) {
requiredEvaluations(globalElementDecls.map { _.forRoot() })
requiredEvaluations(defineEscapeSchemes)
requiredEvaluations(defineFormats)
requiredEvaluations(defineVariables)
// TODO: about defineVariables:
// only include these if they have default values or external values.
// Those then have to be evaluated before any processing,
// and may depend on other variables with default values or external values.
// Not these, because we'll pick these up when elements reference them.
// And we don't compile them independently of that (since they could be very
// incomplete and would lead to many errors for missing this or that.)
//
// Note: don't include these. They get checked if used.
// globalSimpleTypeDefs
// globalComplexTypeDefs
// globalGroupDefs
}
override lazy val schemaDocument = this
override lazy val schema = schemaSet.getSchema(targetNamespace).getOrElse {
Assert.invariantFailed("schema not found for schema document's namespace.")
}
// lazy val shortFormAnnotationsAreValid: Boolean = {
// val dfdlns = XMLUtils.DFDL_NAMESPACE
// val attrs = xml.attributes
//
//
// // Check that any prefixed properties in the DFDL namespace are allowed on
// // this specific annotated schema component.
//
// val dfdlAttrs = attrs.filter{ a => a.isPrefixed && a.}
// }
def nonDefaultPropertySources = Seq()
def defaultPropertySources = LV('defaultPropertySources) {
val seq = Seq(this.defaultFormatChain)
seq
}.value
protected def annotationFactory(node: Node): Option[DFDLAnnotation] = {
val res = node match {
case <dfdl:format>{ content @ _* }</dfdl:format> => new DFDLFormat(node, this)
case <dfdl:defineFormat>{ content @ _* }</dfdl:defineFormat> => new DFDLDefineFormat(node, this)
case <dfdl:defineEscapeScheme>{ content @ _* }</dfdl:defineEscapeScheme> => new DFDLDefineEscapeSchemeFactory(node, this)
case <dfdl:defineVariable>{ content @ _* }</dfdl:defineVariable> => new DFDLDefineVariable(node, this)
case _ => {
val prefix =
if (node.prefix == null || node.prefix == "") ""
else node.prefix + ":"
this.SDE("Invalid dfdl annotation found: %s", prefix + node.label)
}
}
Some(res)
}
protected def emptyFormatFactory = new DFDLFormat(newDFDLAnnotationXML("format"), this)
protected def isMyFormatAnnotation(a: DFDLAnnotation) = a.isInstanceOf[DFDLFormat]
/*
* Design note about factories for global elements, and recursive types.
*
* The point of these factories is that every local site that uses a global def/decl
* needs a copy so that the def/decl can have attributes which depend on the context
* where it is used. That is, we can't share global defs/decls because the contexts change
* their meaning.
*
* This works as is, so long as the DFDL Schema doesn't have recursion in it. Recursion would create
* an infinite tree of local sites and copies. (There's an issue: DFDL-80 in Jira about putting
* in the check to rule out recursion)
*
* But recursion would be a very cool experimental feature, potentially useful for investigations
* towards DFDL v2.0 in the future.
*
* What's cool: if these factories are changed to memoize. That is, return the exact same global def/decl
* object if they are called from the same local site, then recursion "just works". Nothing will diverge
* creating infinite structures, but furthermore, the "contextual" information will be right. That
* is to say, the first place some global structure is used is the "top" entry. It gets a copy.
* If that global ultimately has someplace that recurses back to that global structure, it has to be from some other
* local site inside it, so that's a different local site, so it will get a copy of the global. But
* that's where it ends because the next "unwind" of the recursion will be at this same local site, so
* would be returned the exact same def/decl object.
*
* Of course there are runtime/backend complexities also. Relative paths, variables with newVariableInstance
* all of which can go arbitrarily deep in the recursive case.
*/
lazy val globalElementDecls = {
val xmlelts = (xml \ "element")
val factories = xmlelts.map { new edu.illinois.ncsa.daffodil.dsom.GlobalElementDeclFactory(_, this) }
factories
}
lazy val globalSimpleTypeDefs = (xml \ "simpleType").map { new GlobalSimpleTypeDefFactory(_, this) }
lazy val globalComplexTypeDefs = (xml \ "complexType").map { new GlobalComplexTypeDefFactory(_, this) }
lazy val globalGroupDefs = (xml \ "group").map { new GlobalGroupDefFactory(_, this) }
lazy val defaultFormat = formatAnnotation.asInstanceOf[DFDLFormat]
lazy val defineFormats = annotationObjs.collect { case df: DFDLDefineFormat => df }
lazy val defineEscapeSchemes = annotationObjs.collect { case des: DFDLDefineEscapeSchemeFactory => des }
lazy val defineVariables = annotationObjs.collect { case dv: DFDLDefineVariable => dv }
/**
* by name getters for the global things that can be referenced.
*/
def getGlobalElementDecl(name: String) = {
val geds = globalElementDecls
val res = geds.find { _.name == name }
res
}
def getGlobalSimpleTypeDef(name: String) = globalSimpleTypeDefs.find { _.name == name }
def getGlobalComplexTypeDef(name: String) = globalComplexTypeDefs.find { _.name == name }
def getGlobalGroupDef(name: String) = globalGroupDefs.find { _.name == name }
def getDefineFormat(name: String) = defineFormats.find {
df =>
val dfName = df.namedQName.local
val res = dfName == name
res
}
def getDefineVariable(name: String) = {
val res = defineVariables.find { _.name == name }
res
}
def getDefaultFormat = this.defaultFormat
def getDefineEscapeScheme(name: String) = defineEscapeSchemes.find { _.name == name }
}